UNPKG

13.4 kBSource Map (JSON)View Raw
1{"version":3,"file":"pageview.js","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","updatePageview","options","serverURL","lang","path","type","action","fetch","method","headers","body","JSON","stringify","then","resp","json","getServerURL","result","content","replace","removeEndingSplash","test","errorHandler","err","name","console","error","message","getQuery","element","dataset","getAttribute","renderVisitorCount","counts","countElements","forEach","index","innerText","toString","_exports","pageviewCount","_ref4","language","window","location","pathname","selector","update","navigator","controller","AbortController","elements","Array","from","document","querySelectorAll","filter","query","_ref","paths","signal","a","_ref2","encodeURIComponent","join","isArray","getPageview","map","catch","normalElements","elementsNeedstoBeFetched","count","length","fill","abort","bind","version"],"mappings":"4WAEO,MC8BMA,EAAuC,CAElD,eAAgB,oBCYLC,EACXC,GCwBkC,CAClCC,IAAAA,IAAAA,UAAAA,EACAC,KACAC,EAAAA,KAAAA,EACAC,KACAC,EAAAA,OAAAA,GAAAA,EAAAA,OAEAC,MAASL,GAAAA,kBAA0BC,IAAQ,CACzCK,OAAQ,OACRC,QAASV,EACTW,KAAMC,KAAKC,UAAU,CAAER,KAAMC,EAAAA,KAAAA,EAAMC,OAClCO,MAAAA,MAAMC,GAA0BA,EAAKC,QDjCnB,ECsBa,CDtBb,IAChBd,EACHI,KAAM,OACNC,OAAQ,QEHCU,EAAgBd,IAC3B,MAAMe,ECxC2BC,WAAAA,IAAAA,yDAAU,GAC3CA,OAAAA,EAAQC,QAAQ,OAAQ,GDuCTC,CCxCkBF,CDwCChB,GAElC,MCtCA,kBAAkBmB,KDsCAJ,GAAUA,EAAoBA,WAAAA,GAAQ,EEpD7CK,EAAgBC,IACV,eAAbA,EAAIC,MAAuBC,QAAQC,MAAMH,EAAII,QAAQ,ECD9CC,EAAYC,GACvBA,EAAQC,QAAQ1B,MAAQyB,EAAQE,aAAa,MCiDzCC,EAAqB,CACzBC,EACAC,KAEAA,EAAcC,SAAQ,CAACN,EAASO,KAC9BP,EAAQQ,UAAYJ,EAAOG,GAAOE,UAAU,GAC5C,EA2DsCC,EAAAC,cAxDbC,IAKVC,IAJjBxC,UACAE,EAAAA,KAAAA,EAAOuC,OAAOC,SAASC,SACvBC,SAAAA,EAAW,yBACXC,OAAAA,GAAAA,EACA5C,KAAAA,EAAO6C,UAAUN,UAAAA,EAEjB,MAAMO,EAAa,IAAIC,gBAEjBC,EAAWC,MAAMC,KAErBC,SAASC,iBAA8BT,IAGnCU,EAAU3B,IACd,MAAM4B,EAAQ7B,EAASC,GAEvB,OAAiB,OAAV4B,GAAkBrD,IAASqD,CAAK,EAGnClD,EAAS4C,GN5DUO,KAAA,IACzBxD,UAAAA,EACAC,KACAwD,EAAAA,MAAAA,EACAC,OCS+BC,GAAAH,EAAA,MAAA,CAAAI,IAAA,IAC/B5D,UACAC,EAAAA,KAAAA,EACAwD,MACAtD,EAAAA,KAAAA,EACAuD,OAEArD,GAAAA,EAAAA,OAAAA,MACKL,GAAAA,kBAA0B6D,mBAC3BJ,EAAMK,KAAK,cACHD,mBAAmB1D,EAAK2D,KAAK,cAAc7D,IACrD,CAAEyD,OACF/C,IAAAA,MAAMC,GAA6CA,EAAKC,QDnBxC,ECOa,CDPb,CAChBb,UAAAA,EACAC,KACAwD,EAAAA,MAAAA,EACAtD,KAAM,CAAC,QACPuD,OAAAA,IAGC/C,MAAMoB,GAAYmB,MAAMa,QAAQhC,GAAUA,EAAS,CAACA,IAAAA,EM+CrDiC,CAAY,CACVhE,UAAWc,EAAad,GACxByD,MAAOR,EAASgB,KAAKtC,GAAYD,EAASC,IAAYzB,IACtDD,KAAAA,EACAyD,OAAQX,EAAWW,SAElB/C,MAAMoB,GAAWD,EAAmBC,EAAQkB,KAC5CiB,MAAM9C,GAGX,GAAIyB,EAAQ,CACV,MAAMsB,EAAiBlB,EAASK,QAAQ3B,IAAa2B,EAAO3B,KACtDyC,EAA2BnB,EAASK,OAAOA,GAE5CxD,EAAe,CAClBE,UAAWc,EAAad,GACxBE,KAAAA,EACAD,KACCU,IAAAA,MAAM0D,GACPvC,EACE,IAAIoB,MAAciB,EAAeG,QAAQC,KAAKF,GAC9CF,KAKAC,EAAyBE,QACtBjE,EAAM+D,EAEd,MAGM/D,EAAM4C,GAGb,OAAOF,EAAWyB,MAAMC,KAAK1B,EAAW,EAAAV,EAAAqC,QRjHnB,QQiHmB"}
\No newline at end of file