UNPKG

basb-cli

Version:
312 lines (296 loc) 9.94 kB
import { header, navigator, footer, htmlLang, inlineDarkmodeSwitcherScript, } from "./snippets.js" import { config } from "../utils/loadConfig.js" import { formatEchartsDate, insertDataIntoMap } from "../scripts/count/utils.js" import languageSelector from "../utils/languageSelector.js" /** @import {ArticleMetadata} from "../../types/ArticleMetadata.d.ts" */ const now = Date.now() const oneYear = 365 * 24 * 60 * 60 * 1000 function isWithinLastYear(timestamp) { return now - oneYear <= timestamp } function classifyDataByDay(metadataList) { const resultMap = new Map() for (const {date, count} of metadataList) { if (!isWithinLastYear(date)) { continue; } insertDataIntoMap(resultMap, [formatEchartsDate(date), count]) } return Array.from(resultMap.entries()) } function classifyDataByMonth(metadataList) { /** @type {(d: Date) => string} */ const yearMonthFormater = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}` const now = new Date() /** @type {Map<string, {countData: number, wordsData: number}>} */ const monthCountMap = new Map(Array.from({ length: 12 }, (_, i) => { const d = new Date(now.getFullYear(), now.getMonth() - i, 1) return [yearMonthFormater(d), {countData: 0, wordsData: 0}] })) for (const data of metadataList) { const d = new Date(data.date) const key = yearMonthFormater(d) const current = monthCountMap.get(key) if (current === undefined) continue monthCountMap.set(key, { countData: current.countData + 1, wordsData: current.wordsData + data.count, }) } return { months: Array.from(monthCountMap.keys()).reverse(), data: Array.from(monthCountMap.values()).reverse(), } } function classifyDataByYear(metadataList) { const yearsMap = new Map() for (const { date, count } of metadataList) { const year = new Date(date).getFullYear() insertDataIntoMap(yearsMap, [year, count]) } return { years: Array.from(yearsMap.keys()).reverse(), data: Array.from(yearsMap.values()).reverse(), } } function classifyDataByCatalog(metadataList) { const historyCatalogMap = new Map() const annualCatalogMap = new Map() for (const { date, catalog, count } of metadataList) { const kvPair = [catalog, count] insertDataIntoMap(historyCatalogMap, kvPair) if (isWithinLastYear(date)) { insertDataIntoMap(annualCatalogMap, kvPair) } } const historyData = [] const annualData = [] for (const [key, value] of historyCatalogMap) { historyData.push({ name: key, value }) } for (const [key, value] of annualCatalogMap) { annualData.push({ name: key, value }) } return { historyData, annualData } } function injectedScriptGenerator(lastYearData, multiMonthData, multiYearData, multiCatalogData) { const startDate = formatEchartsDate(now - oneYear) const endDate = formatEchartsDate(now) const paddingWidth = 4 const lastYearOption = { width: 1280, height: 280, title: { top: 20, left: "center", text: languageSelector("年度字数统计", "Word Count in the Last Year"), }, visualMap: { show: false, type: "piecewise", pieces: [ { min: 0, max: 300 }, { min: 300, max: 600 }, { min: 600, max: 1500 }, { min: 1500, max: 3000 }, { min: 3000, max: 6000 }, { min: 6000 }, ] }, calendar: { top: 80, left: 36, right: 30, cellSize: 24, range: [startDate, endDate], yearLabel: { show: false } }, series: { type: "heatmap", coordinateSystem: "calendar", data: lastYearData, } } const pastMonthsOption = { title: { top: 30, left: "center", text: languageSelector("过去 12 个月字数统计", "Word Counts for the Last 12 Months") }, xAxis: { type: "category", data: multiMonthData.months, }, yAxis: [ { type: "value", position: "left" , }, { type: "value", position: "right", }, ], grid: { top: 80, left: 75, }, dataZoom: { type: "slider", startValue: 8, endValue: 11, zoomLock: true, }, series: [ { data: multiMonthData.data.map(item => item.wordsData), type: "bar", }, { data: multiMonthData.data.map(item => item.countData), yAxisIndex: 1, type: "line", }, ], }; const pastYearsOption = { title: { top: 30, left: "center", text: languageSelector("历年字数统计", "Word Counts for the Past Years") }, xAxis: { type: "category", data: multiYearData.years, }, yAxis: { type: "value" }, grid: { top: 80, left: 75, }, dataZoom: { type: "slider", startValue: 0, endValue: 5, zoomLock: true, }, series: { data: multiYearData.data, type: "bar", }, } const catalogsOption = { title: [ { text: languageSelector("各分类年度字数统计", "Word Counts of Catalogs\nfor the Last Year"), left: "2%", top: "10%", textAlign: "left", }, { text: languageSelector("各分类历年字数统计", "Word Counts of Catalogs\nfor the Past Years"), left: "96%", bottom: "10%", textAlign: "right", }, ], series: [ { type: "pie", left: "42%", right: paddingWidth, top: paddingWidth, bottom: "40%", data: multiCatalogData.annualData, }, { type: "pie", left: paddingWidth, right: "42%", top: "40%", bottom: paddingWidth, data: multiCatalogData.historyData, } ], } const injectedScript = `\ <script type="module"> import echartsImporter from "../dist/echarts.min.js" document.querySelector("#last-year").__ChartOptions__ = ${JSON.stringify(lastYearOption)} document.querySelector("#past-months").__ChartOptions__ = ${JSON.stringify(pastMonthsOption)} document.querySelector("#past-years").__ChartOptions__ = ${JSON.stringify(pastYearsOption)} document.querySelector("#catalogs").__ChartOptions__ = ${JSON.stringify(catalogsOption)} echartsImporter() </script>` return injectedScript } function bodyContentGenerator(startDate, articleCount, totalCount) { const resultContent = `\ <h1>${languageSelector("统计信息", "Statistics")}</h1> <p>${languageSelector( `自 <b>${startDate}</b> 以来,你共发布了文章 <b>${articleCount}</b> 篇,总字数 <b>${totalCount}</b> 字。`, `Since <b>${startDate}</b>, you have published <b>${articleCount}</b> articles, with a total word count of <b>${totalCount}</b>.`, )}</p> <p>${languageSelector( "下面是你在过去一年中每一天输出的字数:", "The chart following shows the word count you outputed in the last year:" )}</p> <div class="media-container"> <div class="echarts-container" id="last-year"></div> </div> <p>${languageSelector( "下面是你在过去一年中每月输出的字数和文章数:", "The chart following shows the word count and article count you outputed in the past 12 months:" )}</p> <div class="media-container"> <div class="echarts-container" id="past-months"></div> </div> <p>${languageSelector( "下面是你在过去几年中每一年输出的字数:", "The chart following shows the word count you outputed in the past few years:" )}</p> <div class="media-container"> <div class="echarts-container" id="past-years"></div> </div> <p>${languageSelector( "下面是你在各个分类下输出的字数:", "The chart following shows the word count you outputed in the different catalogs:" )}</p> <div class="media-container"> <div class="echarts-container" id="catalogs"></div> </div>` return resultContent } /** * @param {number} startTime * @param {ArticleMetadata[]} metadataList * @param {number} totalWordCount * @returns {string} */ export default function (startTime, metadataList, totalWordCount) { const lastYearData = classifyDataByDay(metadataList) const multiMonthData = classifyDataByMonth(metadataList) const multiYearData = classifyDataByYear(metadataList) const multiCatalogData = classifyDataByCatalog(metadataList) const startDate = new Intl.DateTimeFormat().format(new Date(startTime)) const injectedScript = injectedScriptGenerator(lastYearData, multiMonthData, multiYearData, multiCatalogData) const bodyContent = bodyContentGenerator(startDate, metadataList.length, totalWordCount) const template = `\ <!DOCTYPE html> <html lang="${htmlLang}"> <head> ${header( (config.title ?? "MarkdownBlog") + languageSelector("站点统计", "Statistics"), config.description, "../") } </head> <body> ${inlineDarkmodeSwitcherScript()} ${navigator("../")} <article> ${bodyContent} </article> ${footer()} ${injectedScript} </body> </html>` return template }