go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/core/components/release_notes/common.ts (about)

     1  // Copyright 2023 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  import markdownIt from 'markdown-it';
    16  
    17  import { logging } from '@/common/tools/logging';
    18  import { defaultTarget } from '@/common/tools/markdown/plugins/default_target';
    19  
    20  export interface ReleaseNotes {
    21    /**
    22     * The version of the latest release note section.
    23     * -1 if there's no latest release note.
    24     */
    25    readonly latestVersion: number;
    26    readonly latest: string;
    27    readonly past: string;
    28  }
    29  
    30  const RELEASE_SPLITTER_RE =
    31    /(?<=^|\r\n|\r|\n)(<!-- __RELEASE__: (\d)+ -->(?:$|\r\n|\r|\n))/;
    32  
    33  export function parseReleaseNotes(releaseNote: string): ReleaseNotes {
    34    const [unreleased, latestVersionTag, latestVersion, latestContent] =
    35      releaseNote.split(RELEASE_SPLITTER_RE, 4);
    36  
    37    if (!latestVersionTag) {
    38      return {
    39        latestVersion: -1,
    40        latest: '',
    41        past: '',
    42      };
    43    }
    44  
    45    const latest = latestVersionTag + latestContent;
    46    return {
    47      latestVersion: Number(latestVersion),
    48      latest,
    49      past: releaseNote.slice(unreleased.length + latest.length),
    50    };
    51  }
    52  
    53  const md = markdownIt({ html: true, linkify: true }).use(
    54    defaultTarget,
    55    '_blank',
    56  );
    57  
    58  export function renderReleaseNotes(releaseNotes: string) {
    59    return md.render(releaseNotes);
    60  }
    61  
    62  const LAST_READ_RELEASE_VERSION_CACHE_KEY = 'last-read-release-version';
    63  
    64  /**
    65   * Get the last read version from local storage. If it's not set or the value
    66   * is invalid, return -1.
    67   */
    68  export function getLastReadVersion() {
    69    try {
    70      const ver = JSON.parse(
    71        localStorage.getItem(LAST_READ_RELEASE_VERSION_CACHE_KEY) || '-1',
    72      );
    73      return typeof ver === 'number' ? ver : -1;
    74    } catch (e) {
    75      logging.warn(e);
    76      return -1;
    77    }
    78  }
    79  
    80  /**
    81   * Update the last read version. Noop if newVer is less than or equals to the
    82   * stored last read version.
    83   */
    84  // Do not use `useLocalStorage` from react-use. Otherwise we may risk
    85  // overwriting the version set by older versions of the UI, causing the popups
    86  // to constantly show up when user switch between pages of different versions.
    87  export function bumpLastReadVersion(newVer: number) {
    88    // In case another tab has recorded a newer version, get a fresh copy from the
    89    // local storage.
    90    const prevVer = getLastReadVersion();
    91    if (newVer < prevVer) {
    92      return;
    93    }
    94  
    95    localStorage.setItem(
    96      LAST_READ_RELEASE_VERSION_CACHE_KEY,
    97      JSON.stringify(newVer),
    98    );
    99  }