UNPKG

13.1 kBSource Map (JSON)View Raw
1{"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 defaultEmoji,\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<\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 = defaultEmoji,\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: Object.keys(defaultLocales).includes(lang) ? lang : 'en-US',\n dark,\n emoji: typeof emoji === 'boolean' ? (emoji ? defaultEmoji : []) : 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 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 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":["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","navigator","language","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,QEHCO,EAAgBzB,IAC3B,MAAM0B,ECxC0B,EAACC,EAAU,KAC3CA,EAAQC,QAAQ,OAAQ,IDuCTC,CAAmB7B,GAElC,MCtCA,kBAAkB8B,KDsCAJ,GAAUA,EAAS,WAAWA,GAAQ,EEpD7CK,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,OAAOmD,UAAUC,aAEjB,MAAMC,EAAa,IAAIC,gBAEjBC,EAAW3C,MAAM4C,KAErBC,SAASC,iBAA8BT,IAGnCU,EAAUtB,IACd,MAAMuB,EAAQxB,EAASC,GAEvB,OAAiB,OAAVuB,GAAkB5C,IAAS4C,CAAK,EAGnCxD,EAASmD,GACbzD,EAAY,CACVC,UAAWyB,EAAazB,GACxBE,MAAOsD,EAASM,KAAKxB,GAAYD,EAASC,IAAYrB,IACtDhB,OACAE,OAAQmD,EAAWnD,SAElBK,MAAMI,GAAW6B,EAAmB7B,EAAQ4C,KAC5CO,MAAMhC,GAGX,GAAIoB,EAAQ,CACV,MAAMa,EAAiBR,EAASI,QAAQtB,IAAasB,EAAOtB,KACtD2B,EAA2BT,EAASI,OAAOA,GAE5C7C,EAAe,CAClBf,UAAWyB,EAAazB,GACxBiB,OACAhB,SACCO,MAAM0D,GACPzB,EACE,IAAI5B,MAAcmD,EAAeG,QAAQC,KAAKF,GAC9CF,KAKAC,EAAyBE,QACtB9D,EAAM4D,EAEd,MAGM5D,EAAMmD,GAGb,OAAOF,EAAWe,MAAMC,KAAKhB,EAAW,kBRjHnB"}
\No newline at end of file