{"version":3,"file":"pageview.mjs","sources":["../src/version.ts","../src/api/utils.ts","../src/api/pageview.ts","../src/api/articleCounter.ts","../src/utils/config.ts","../src/utils/path.ts","../src/utils/error.ts","../src/utils/query.ts","../src/pageview.ts"],"sourcesContent":["declare const VERSION: string;\n\nexport const version = VERSION;\n","export interface BaseAPIOptions {\n  /**\n   * Waline 服务端地址\n   *\n   * Waline serverURL\n   */\n  serverURL: string;\n\n  /**\n   * 错误信息所使用的语言\n   *\n   * Language used in error text\n   */\n  lang: string;\n}\n\nexport interface ErrorStatusResponse {\n  /**\n   * 错误代码\n   *\n   * Error number\n   */\n  errno: number;\n\n  /**\n   * 错误消息\n   *\n   * Error msg\n   */\n  errmsg: string;\n}\n\nexport const JSON_HEADERS: Record<string, string> = {\n  // eslint-disable-next-line @typescript-eslint/naming-convention\n  'Content-Type': 'application/json',\n};\n\nexport const errorCheck = <T extends ErrorStatusResponse>(\n  data: T,\n  name = '',\n): T => {\n  if (typeof data === 'object' && data.errno)\n    throw new TypeError(`${name} failed with ${data.errno}: ${data.errmsg}`);\n\n  return data;\n};\n","import { getArticleCounter, updateArticleCounter } from './articleCounter.js';\nimport { type BaseAPIOptions } from './utils.js';\n\ninterface GetPageviewOptions extends BaseAPIOptions {\n  /**\n   * 待获取页面的 path\n   *\n   * Path of pages\n   */\n  paths: string[];\n\n  /**\n   * 取消请求的信号\n   *\n   * AbortSignal to cancel request\n   */\n  signal?: AbortSignal;\n}\n\nexport const getPageview = ({\n  serverURL,\n  lang,\n  paths,\n  signal,\n}: GetPageviewOptions): Promise<number[]> =>\n  getArticleCounter({\n    serverURL,\n    lang,\n    paths,\n    type: ['time'],\n    signal,\n  })\n    // TODO: Improve this API\n    .then((counts) => (Array.isArray(counts) ? counts : [counts])) as Promise<\n    number[]\n  >;\n\nexport interface UpdatePageviewOptions extends BaseAPIOptions {\n  /**\n   * 待更新页面的 path\n   *\n   * Path of pages\n   */\n  path: string;\n}\n\nexport const updatePageview = (\n  options: UpdatePageviewOptions,\n): Promise<number> =>\n  updateArticleCounter({\n    ...options,\n    type: 'time',\n    action: 'inc',\n  });\n","import { type BaseAPIOptions, JSON_HEADERS } from './utils.js';\n\nexport interface GetArticleCounterOptions extends BaseAPIOptions {\n  /**\n   * 待获取计数器的 path\n   *\n   * Path of counters\n   */\n  paths: string[];\n\n  /**\n   * 待获取计数器的类型\n   *\n   * Counter type to be fetched\n   */\n  type: string[];\n\n  /**\n   * 取消请求的信号\n   *\n   * AbortSignal to cancel request\n   */\n  signal?: AbortSignal;\n}\n\nexport type GetArticleCounterResponse =\n  | Record<string, number>[]\n  | Record<string, number>\n  | number[]\n  | number;\n\nexport const getArticleCounter = ({\n  serverURL,\n  lang,\n  paths,\n  type,\n  signal,\n}: GetArticleCounterOptions): Promise<GetArticleCounterResponse> =>\n  fetch(\n    `${serverURL}/article?path=${encodeURIComponent(\n      paths.join(','),\n    )}&type=${encodeURIComponent(type.join(','))}&lang=${lang}`,\n    { signal },\n  ).then((resp) => <Promise<GetArticleCounterResponse>>resp.json());\n\nexport interface UpdateArticleCounterOptions extends BaseAPIOptions {\n  /**\n   * 待更新计数器的 path\n   *\n   * Path of counter to be updated\n   */\n  path: string;\n\n  /**\n   * 待更新计数器的类型\n   *\n   * Counter type to be updated\n   */\n  type: string;\n\n  /**\n   * 更新操作\n   *\n   * Update operation\n   *\n   * @default 'inc'\n   */\n  action?: 'inc' | 'desc';\n}\n\nexport const updateArticleCounter = ({\n  serverURL,\n  lang,\n  path,\n  type,\n  action,\n}: UpdateArticleCounterOptions): Promise<number> =>\n  fetch(`${serverURL}/article?lang=${lang}`, {\n    method: 'POST',\n    headers: JSON_HEADERS,\n    body: JSON.stringify({ path, type, action }),\n  }).then((resp) => <Promise<number>>resp.json());\n","import { decodePath, isLinkHttp, removeEndingSplash } from './path.js';\nimport {\n  DEFAULT_EMOJI,\n  DEFAULT_LANG,\n  DEFAULT_LOCALES,\n  DEFAULT_REACTION,\n  defaultUploadImage,\n  defaultHighlighter,\n  defaultTeXRenderer,\n  getDefaultSearchOptions,\n  getMeta,\n} from '../config/index.js';\nimport {\n  type WalineEmojiInfo,\n  type WalineEmojiMaps,\n  type WalineLocale,\n  type WalineProps,\n} from '../typings/index.js';\n\nexport interface WalineEmojiConfig {\n  tabs: Pick<WalineEmojiInfo, 'name' | 'icon' | 'items'>[];\n  map: WalineEmojiMaps;\n}\n\nexport interface WalineConfig\n  extends Required<\n    Omit<\n      WalineProps,\n      | 'emoji'\n      | 'imageUploader'\n      | 'highlighter'\n      | 'texRenderer'\n      | 'wordLimit'\n      | 'reaction'\n      | 'search'\n    >\n  > {\n  locale: WalineLocale;\n  wordLimit: [number, number] | false;\n  reaction: string[];\n  emoji: Exclude<WalineProps['emoji'], boolean | undefined>;\n  highlighter: Exclude<WalineProps['highlighter'], true | undefined>;\n  imageUploader: Exclude<WalineProps['imageUploader'], true | undefined>;\n  texRenderer: Exclude<WalineProps['texRenderer'], true | undefined>;\n  search: Exclude<WalineProps['search'], true | undefined>;\n}\n\nexport const getServerURL = (serverURL: string): string => {\n  const result = removeEndingSplash(serverURL);\n\n  return isLinkHttp(result) ? result : `https://${result}`;\n};\n\nconst getWordLimit = (\n  wordLimit: WalineProps['wordLimit'],\n): [number, number] | false =>\n  Array.isArray(wordLimit) ? wordLimit : wordLimit ? [0, wordLimit] : false;\n\nconst fallback = <T = unknown>(\n  value: T | boolean | undefined,\n  fallback: T,\n): T | false =>\n  typeof value === 'function' ? value : value === false ? false : fallback;\n\nexport const getConfig = ({\n  serverURL,\n\n  path = location.pathname,\n  lang = typeof navigator === 'undefined' ? 'en-US' : navigator.language,\n  locale,\n  emoji = DEFAULT_EMOJI,\n  meta = ['nick', 'mail', 'link'],\n  requiredMeta = [],\n  dark = false,\n  pageSize = 10,\n  wordLimit,\n  imageUploader,\n  highlighter,\n  texRenderer,\n  copyright = true,\n  login = 'enable',\n  search,\n  reaction,\n  recaptchaV3Key = '',\n  turnstileKey = '',\n  commentSorting = 'latest',\n  ...more\n}: WalineProps): WalineConfig => ({\n  serverURL: getServerURL(serverURL),\n  path: decodePath(path),\n  locale: {\n    ...(DEFAULT_LOCALES[lang] || DEFAULT_LOCALES[DEFAULT_LANG]),\n    ...(typeof locale === 'object' ? locale : {}),\n  } as WalineLocale,\n  wordLimit: getWordLimit(wordLimit),\n  meta: getMeta(meta),\n  requiredMeta: getMeta(requiredMeta),\n  imageUploader: fallback(imageUploader, defaultUploadImage),\n  highlighter: fallback(highlighter, defaultHighlighter),\n  texRenderer: fallback(texRenderer, defaultTeXRenderer),\n  lang: Object.keys(DEFAULT_LOCALES).includes(lang) ? lang : 'en-US',\n  dark,\n  emoji: typeof emoji === 'boolean' ? (emoji ? DEFAULT_EMOJI : []) : emoji,\n  pageSize,\n  login,\n  copyright,\n  search:\n    search === false\n      ? false\n      : typeof search === 'object'\n      ? search\n      : getDefaultSearchOptions(lang),\n  recaptchaV3Key,\n  turnstileKey,\n  reaction: Array.isArray(reaction)\n    ? reaction\n    : reaction === true\n    ? DEFAULT_REACTION\n    : [],\n  commentSorting,\n  ...more,\n});\n","export const decodePath = (path: string): string => {\n  try {\n    path = decodeURI(path);\n  } catch (err) {\n    // ignore error\n  }\n\n  return path;\n};\n\nexport const removeEndingSplash = (content = ''): string =>\n  content.replace(/\\/$/u, '');\n\nexport const isLinkHttp = (link: string): boolean =>\n  /^(https?:)?\\/\\//.test(link);\n","export const errorHandler = (err: Error): void => {\n  if (err.name !== 'AbortError') console.error(err.message);\n};\n","export const getQuery = (element: HTMLElement): string | null =>\n  element.dataset.path || element.getAttribute('id');\n","import { getPageview, updatePageview } from './api/index.js';\nimport { type WalineAbort } from './typings/index.js';\nimport { errorHandler, getQuery, getServerURL } from './utils/index.js';\n\nexport interface WalinePageviewCountOptions {\n  /**\n   * Waline 服务端地址\n   *\n   * Waline server url\n   */\n  serverURL: string;\n\n  /**\n   * 浏览量 CSS 选择器\n   *\n   * Pageview CSS selector\n   *\n   * @default '.waline-pageview-count'\n   */\n  selector?: string;\n\n  /**\n   * 需要更新和获取的路径\n   *\n   * Path to be fetched and updated\n   *\n   * @default window.location.pathname\n   */\n  path?: string;\n\n  /**\n   * 是否在查询时更新 path 的浏览量\n   *\n   * Whether update pageviews when fetching path result\n   *\n   * @default true\n   */\n  update?: boolean;\n\n  /**\n   * 错误提示消息所使用的语言\n   *\n   * Language of error message\n   *\n   * @default navigator.language\n   */\n  lang?: string;\n}\n\nconst renderVisitorCount = (\n  counts: number[],\n  countElements: HTMLElement[],\n): void => {\n  countElements.forEach((element, index) => {\n    element.innerText = counts[index].toString();\n  });\n};\n\nexport const pageviewCount = ({\n  serverURL,\n  path = window.location.pathname,\n  selector = '.waline-pageview-count',\n  update = true,\n  lang = navigator.language,\n}: WalinePageviewCountOptions): WalineAbort => {\n  const controller = new AbortController();\n\n  const elements = Array.from(\n    // pageview selectors\n    document.querySelectorAll<HTMLElement>(selector),\n  );\n\n  const filter = (element: HTMLElement): boolean => {\n    const query = getQuery(element);\n\n    return query !== null && path !== query;\n  };\n\n  const fetch = (elements: HTMLElement[]): Promise<void> =>\n    getPageview({\n      serverURL: getServerURL(serverURL),\n      paths: elements.map((element) => getQuery(element) || path),\n      lang,\n      signal: controller.signal,\n    })\n      .then((counts) => renderVisitorCount(counts, elements))\n      .catch(errorHandler);\n\n  // we should update pageviews\n  if (update) {\n    const normalElements = elements.filter((element) => !filter(element));\n    const elementsNeedstoBeFetched = elements.filter(filter);\n\n    void updatePageview({\n      serverURL: getServerURL(serverURL),\n      path,\n      lang,\n    }).then((count) =>\n      renderVisitorCount(\n        new Array<number>(normalElements.length).fill(count),\n        normalElements,\n      ),\n    );\n\n    // if we should fetch count of other pages\n    if (elementsNeedstoBeFetched.length) {\n      void fetch(elementsNeedstoBeFetched);\n    }\n  }\n  // we should not update pageviews\n  else {\n    void fetch(elements);\n  }\n\n  return controller.abort.bind(controller);\n};\n"],"names":["version","JSON_HEADERS","getPageview","serverURL","lang","paths","signal","type","fetch","encodeURIComponent","join","then","resp","json","getArticleCounter","counts","Array","isArray","updatePageview","options","path","action","method","headers","body","JSON","stringify","updateArticleCounter","getServerURL","result","content","replace","removeEndingSplash","test","errorHandler","err","name","console","error","message","getQuery","element","dataset","getAttribute","renderVisitorCount","countElements","forEach","index","innerText","toString","pageviewCount","window","location","pathname","selector","update","navigator","language","controller","AbortController","elements","from","document","querySelectorAll","filter","query","map","catch","normalElements","elementsNeedstoBeFetched","count","length","fill","abort","bind"],"mappings":"AAEO,MAAMA,EAAU,SC8BVC,EAAuC,CAElD,eAAgB,oBCfLC,EAAc,EACzBC,YACAC,OACAC,QACAC,YCQ+B,GAC/BH,YACAC,OACAC,QACAE,OACAD,YAEAE,MACE,GAAGL,kBAA0BM,mBAC3BJ,EAAMK,KAAK,cACHD,mBAAmBF,EAAKG,KAAK,cAAcN,IACrD,CAAEE,WACFK,MAAMC,GAA6CA,EAAKC,SDlB1DC,CAAkB,CAChBX,YACAC,OACAC,QACAE,KAAM,CAAC,QACPD,WAGCK,MAAMI,GAAYC,MAAMC,QAAQF,GAAUA,EAAS,CAACA,KAa5CG,EACXC,GCuBkC,GAClChB,YACAC,OACAgB,OACAb,OACAc,YAEAb,MAAM,GAAGL,kBAA0BC,IAAQ,CACzCkB,OAAQ,OACRC,QAAStB,EACTuB,KAAMC,KAAKC,UAAU,CAAEN,OAAMb,OAAMc,aAClCV,MAAMC,GAA0BA,EAAKC,SDhCxCc,CAAqB,IAChBR,EACHZ,KAAM,OACNc,OAAQ,QELCO,EAAgBzB,IAC3B,MAAM0B,ECtC0B,EAACC,EAAU,KAC3CA,EAAQC,QAAQ,OAAQ,IDqCTC,CAAmB7B,GAElC,MCpCA,kBAAkB8B,KDoCAJ,GAAUA,EAAS,WAAWA,GAAQ,EElD7CK,EAAgBC,IACV,eAAbA,EAAIC,MAAuBC,QAAQC,MAAMH,EAAII,QAAQ,ECD9CC,EAAYC,GACvBA,EAAQC,QAAQtB,MAAQqB,EAAQE,aAAa,MCgDzCC,EAAqB,CACzB7B,EACA8B,KAEAA,EAAcC,SAAQ,CAACL,EAASM,KAC9BN,EAAQO,UAAYjC,EAAOgC,GAAOE,UAAU,GAC5C,EAGSC,EAAgB,EAC3B/C,YACAiB,OAAO+B,OAAOC,SAASC,SACvBC,WAAW,yBACXC,UAAS,EACTnD,OAAOoD,UAAUC,aAEjB,MAAMC,EAAa,IAAIC,gBAEjBC,EAAW5C,MAAM6C,KAErBC,SAASC,iBAA8BT,IAGnCU,EAAUvB,IACd,MAAMwB,EAAQzB,EAASC,GAEvB,OAAiB,OAAVwB,GAAkB7C,IAAS6C,CAAK,EAGnCzD,EAASoD,GACb1D,EAAY,CACVC,UAAWyB,EAAazB,GACxBE,MAAOuD,EAASM,KAAKzB,GAAYD,EAASC,IAAYrB,IACtDhB,OACAE,OAAQoD,EAAWpD,SAElBK,MAAMI,GAAW6B,EAAmB7B,EAAQ6C,KAC5CO,MAAMjC,GAGX,GAAIqB,EAAQ,CACV,MAAMa,EAAiBR,EAASI,QAAQvB,IAAauB,EAAOvB,KACtD4B,EAA2BT,EAASI,OAAOA,GAE5C9C,EAAe,CAClBf,UAAWyB,EAAazB,GACxBiB,OACAhB,SACCO,MAAM2D,GACP1B,EACE,IAAI5B,MAAcoD,EAAeG,QAAQC,KAAKF,GAC9CF,KAKAC,EAAyBE,QACtB/D,EAAM6D,EAEd,MAGM7D,EAAMoD,GAGb,OAAOF,EAAWe,MAAMC,KAAKhB,EAAW"}