import { IScriptAddon } from '../../scriptAddon'; import * as ECharts from '$:/plugins/Gk0Wk/echarts/echarts.min.js'; const getFilterByDate = (date: string, subfilter: string) => `[all[tiddlers]sameday:created[${date}]][all[tiddlers]sameday:modified[${date}]] +${subfilter} +[sort[]]`; const yearDates: Map = new Map(); const dayTime = 3600 * 24 * 1000; const getData = (year: number, subfilter: string) => { if (!yearDates.has(year)) { const startDate = (ECharts as any).number .parseDate(`${year}-01-01`) .getTime(); const endDate = (ECharts as any).number .parseDate(`${year + 1}-01-01`) .getTime(); const dates: [string, string][] = []; for (let time = startDate; time < endDate; time += dayTime) { const timeFmt: string = (ECharts as any).format.formatTime( 'yyyy-MM-dd', time, ); const timeTW = timeFmt.replace(/-/g, ''); dates.push([timeFmt, timeTW]); } yearDates.set(year, dates); } let total = 0; return [ yearDates.get(year)!.map(([timeFmt, timeTW]) => { const count = $tw.wiki.filterTiddlers( getFilterByDate(timeTW, subfilter), ).length; total += count; return [timeFmt, count]; }), total, ] as [[string, number][], number]; }; const getPlatteColor = (name: string) => $tw.wiki.renderText( 'text/plain', 'text/vnd.tiddlywiki', `<$transclude tiddler={{$:/palette}} index="${name}"><$transclude tiddler="$:/palettes/Vanilla" index="${name}"><$transclude tiddler="$:/config/DefaultColourMappings/${name}"/>`, {}, ); const checkIfChinese = () => $tw.wiki.getTiddlerText('$:/language')?.includes('zh') === true; const checkIfDarkMode = () => $tw.wiki.getTiddler($tw.wiki.getTiddlerText('$:/palette')!)?.fields?.[ 'color-scheme' ] === 'dark'; const GitHubHeatMapAddon: IScriptAddon = { shouldUpdate: (_, changedTiddlers) => $tw.utils.count(changedTiddlers) > 0, onUpdate: (myChart, _state, addonAttributes) => { const year = parseInt(addonAttributes.year, 10) || new Date().getFullYear(); const subfilter = addonAttributes.subfilter || '[!is[shadow]!prefix[$:/]]'; const [data, total] = getData(year, subfilter); const tooltipFormatter = (dateValue: string, count: number) => { if (count === 0) { return checkIfChinese() ? `${(ECharts as any).format.formatTime( 'yyyy年M月d日', dateValue, )} 无条目。` : `${$tw.utils.formatDateString( $tw.utils.parseDate(dateValue.replace(/-/g, ''))!, 'MMM DDD, YYYY', )} no tiddler.`; } const p = $tw.utils.domMaker('p', { text: checkIfChinese() ? `${(ECharts as any).format.formatTime( 'yyyy年M月d日', dateValue, )} 共有 ${count} 篇:` : `${$tw.utils.formatDateString( $tw.utils.parseDate(dateValue.replace(/-/g, ''))!, 'MMM DDD, YYYY', )} ${count} tiddler${count > 1 ? 's' : ''}.`, }); const ul = $tw.utils.domMaker('ul', {}); const tiddlers = $tw.wiki.filterTiddlers( getFilterByDate(dateValue.replace(/-/g, ''), subfilter), ); const len = tiddlers.length; for (let i = 0; i < len; i++) { const tiddler = tiddlers[i]; const li = $tw.utils.domMaker('li', {}); const a = $tw.utils.domMaker('a', { text: tiddler, class: 'tc-tiddlylink tc-tiddlylink-resolves tc-popup-handle tc-popup-absolute', style: { cursor: 'pointer', }, }); // eslint-disable-next-line @typescript-eslint/no-loop-func a.addEventListener('click', () => new $tw.Story().navigateTiddler(tiddler), ); li.appendChild(a); ul.appendChild(li); } return [p, ul]; }; let lastDateValue = ''; let lastCount = 0; let cache: Element[] | string | undefined; const cachedTooltipFormatter = ({ value: [dateValue, count], }: { value: [string, number]; }) => { if (dateValue !== lastDateValue || count !== lastCount || !cache) { cache = tooltipFormatter(dateValue, count); lastDateValue = dateValue; lastCount = count; } return cache; }; myChart.setOption({ title: { top: 0, left: 'center', text: checkIfChinese() ? `今年产出 ${total} 篇文章` : `Produced ${total} tiddlers this year`, }, tooltip: { position: 'top', formatter: cachedTooltipFormatter, triggerOn: 'mousemove|click', enterable: true, hideDelay: 800, backgroundColor: getPlatteColor('page-background'), borderColor: getPlatteColor('very-muted-foreground'), }, visualMap: { type: 'piecewise', orient: 'horizontal', calculable: true, showLabel: false, right: 0, top: 175, pieces: [ // 设置分段范围 { lte: 0, color: checkIfDarkMode() ? '#161B22' : '#EBEDF0' }, { gt: 0, lte: 3, color: '#0E4429' }, { gt: 3, lte: 7, color: '#006D32' }, { gt: 7, lte: 15, color: '#26A641' }, { gt: 15, color: '#39D353' }, ], }, calendar: { top: 60, left: 0, right: 0, cellSize: 15, orient: 'horizontal', range: year, itemStyle: { borderWidth: 3, borderCap: 'round', borderJoin: 'round', borderColor: getPlatteColor('background'), }, splitLine: { show: false, }, dayLabel: { show: true, nameMap: checkIfChinese() ? 'ZH' : 'EN', }, monthLabel: { show: true, nameMap: checkIfChinese() ? 'ZH' : 'EN', }, yearLabel: { show: true, position: 'bottom', margin: 12, verticalAlign: 'top', }, }, series: { type: 'heatmap', coordinateSystem: 'calendar', calendarIndex: 0, data, }, } as any); }, }; export default GitHubHeatMapAddon;