{"version":3,"file":"pageview.cjs","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';\nimport type { BaseAPIOptions } from './utils';\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 { JSON_HEADERS } from './utils';\nimport type { BaseAPIOptions } from './utils';\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 {\n  defaultLang,\n  defaultLocales,\n  defaultReaction,\n  defaultUploadImage,\n  defaultHighlighter,\n  defaultTexRenderer,\n  getDefaultSearchOptions,\n  getMeta,\n} from '../config';\n\nimport { decodePath, isLinkHttp, removeEndingSplash } from './path';\n\nimport type {\n  WalineEmojiInfo,\n  WalineEmojiMaps,\n  WalineLocale,\n  WalineProps,\n} from '../typings';\n\nexport interface WalineEmojiConfig {\n  tabs: Pick<WalineEmojiInfo, 'name' | 'icon' | 'items'>[];\n  map: WalineEmojiMaps;\n}\n\nexport interface WalineConfig\n  extends Required<Omit<WalineProps, 'wordLimit' | 'reaction'>> {\n  locale: WalineLocale;\n  wordLimit: [number, number] | false;\n  reaction: string[];\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 | false | 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 = defaultLang,\n  locale,\n  emoji = ['//unpkg.com/@waline/emojis@1.1.0/weibo'],\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  commentSorting = 'latest',\n  ...more\n}: WalineProps): WalineConfig => ({\n  serverURL: getServerURL(serverURL),\n  path: decodePath(path),\n  locale: {\n    ...(defaultLocales[lang] || defaultLocales[defaultLang]),\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,\n  dark,\n  emoji,\n  pageSize,\n  login,\n  copyright,\n  search: search ?? getDefaultSearchOptions(lang),\n  recaptchaV3Key,\n  reaction: Array.isArray(reaction)\n    ? reaction\n    : reaction === true\n    ? defaultReaction\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';\nimport { errorHandler, getQuery, getServerURL } from './utils';\n\nimport type { WalineAbort } from './typings';\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 'zh-CN'\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 = 'zh-CN',\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":["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","window","location","pathname","selector","update","controller","AbortController","elements","from","document","querySelectorAll","filter","query","map","catch","normalElements","elementsNeedstoBeFetched","count","length","fill","abort","bind"],"mappings":"aAEO,MC8BMA,EAAuC,CAElD,eAAgB,oBCfLC,EAAc,EACzBC,YACAC,OACAC,QACAC,YCS+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,SDnB1DC,CAAkB,CAChBX,YACAC,OACAC,QACAE,KAAM,CAAC,QACPD,WAGCK,MAAMI,GAAYC,MAAMC,QAAQF,GAAUA,EAAS,CAACA,KAa5CG,EACXC,GCwBkC,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,SDjCxCc,CAAqB,IAChBR,EACHZ,KAAM,OACNc,OAAQ,QEpBCO,EAAgBzB,IAC3B,MAAM0B,ECvB0B,EAACC,EAAU,KAC3CA,EAAQC,QAAQ,OAAQ,IDsBTC,CAAmB7B,GAElC,MCrBA,kBAAkB8B,KDqBAJ,GAAUA,EAAS,WAAWA,GAAQ,EEnC7CK,EAAgBC,IACV,eAAbA,EAAIC,MAAuBC,QAAQC,MAAMH,EAAII,QAAQ,ECD9CC,EAAYC,GACvBA,EAAQC,QAAQtB,MAAQqB,EAAQE,aAAa,MCiDzCC,EAAqB,CACzB7B,EACA8B,KAEAA,EAAcC,SAAQ,CAACL,EAASM,KAC9BN,EAAQO,UAAYjC,EAAOgC,GAAOE,UAAU,GAC5C,wBAGyB,EAC3B9C,YACAiB,OAAO8B,OAAOC,SAASC,SACvBC,WAAW,yBACXC,UAAS,EACTlD,OAAO,YAEP,MAAMmD,EAAa,IAAIC,gBAEjBC,EAAWzC,MAAM0C,KAErBC,SAASC,iBAA8BP,IAGnCQ,EAAUpB,IACd,MAAMqB,EAAQtB,EAASC,GAEvB,OAAiB,OAAVqB,GAAkB1C,IAAS0C,CAAK,EAGnCtD,EAASiD,GACbvD,EAAY,CACVC,UAAWyB,EAAazB,GACxBE,MAAOoD,EAASM,KAAKtB,GAAYD,EAASC,IAAYrB,IACtDhB,OACAE,OAAQiD,EAAWjD,SAElBK,MAAMI,GAAW6B,EAAmB7B,EAAQ0C,KAC5CO,MAAM9B,GAGX,GAAIoB,EAAQ,CACV,MAAMW,EAAiBR,EAASI,QAAQpB,IAAaoB,EAAOpB,KACtDyB,EAA2BT,EAASI,OAAOA,GAE5C3C,EAAe,CAClBf,UAAWyB,EAAazB,GACxBiB,OACAhB,SACCO,MAAMwD,GACPvB,EACE,IAAI5B,MAAciD,EAAeG,QAAQC,KAAKF,GAC9CF,KAKAC,EAAyBE,QACtB5D,EAAM0D,EAEd,MAGM1D,EAAMiD,GAGb,OAAOF,EAAWe,MAAMC,KAAKhB,EAAW,kBRjHnB"}