diff --git a/package.json b/package.json index 00436600..7ad557bd 100644 --- a/package.json +++ b/package.json @@ -31,19 +31,19 @@ "@crowdin/crowdin-api-client": "^1.49.0", "@emotion/babel-plugin": "^11.13.5", "@emotion/css": "^11.13.5", - "@rsdoctor/rspack-plugin": "^1.3.11", - "@rspack/cli": "^1.6.4", - "@rspack/core": "^1.6.4", + "@rsdoctor/rspack-plugin": "^1.3.12", + "@rspack/cli": "^1.6.6", + "@rspack/core": "^1.6.6", "@swc/core": "^1.15.3", "@swc/jest": "^0.2.39", - "@types/chrome": "0.1.31", + "@types/chrome": "0.1.32", "@types/decompress": "^4.2.7", "@types/jest": "^30.0.0", "@types/node": "^24.10.1", "@types/punycode": "^2.1.4", "@vue/babel-plugin-jsx": "^2.0.1", "babel-loader": "^10.0.0", - "commitlint": "^20.1.0", + "commitlint": "^20.2.0", "css-loader": "^7.1.2", "decompress": "^4.2.1", "husky": "^9.1.7", @@ -54,7 +54,7 @@ "postcss": "^8.5.6", "postcss-loader": "^8.2.0", "postcss-rtlcss": "^5.7.1", - "puppeteer": "^24.31.0", + "puppeteer": "^24.32.0", "ts-loader": "^9.5.4", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", @@ -63,9 +63,9 @@ "dependencies": { "@element-plus/icons-vue": "^2.3.2", "echarts": "^6.0.0", - "element-plus": "2.11.8", + "element-plus": "2.12.0", "punycode": "^2.3.1", - "vue": "^3.5.24", + "vue": "^3.5.25", "vue-router": "^4.6.3" }, "engines": { diff --git a/src/api/chrome/tab.ts b/src/api/chrome/tab.ts index eb145cb0..183ca09c 100644 --- a/src/api/chrome/tab.ts +++ b/src/api/chrome/tab.ts @@ -7,7 +7,10 @@ import { handleError } from "./common" -export function getTab(id: number): Promise { +export function getTab(id: number): Promise { + if (id < 0) { + return Promise.resolve(undefined) + } return new Promise(resolve => chrome.tabs.get(id, tab => { handleError("getTab") resolve(tab) diff --git a/src/background/track-server/file-tracker.ts b/src/background/track-server/file-tracker.ts index 6efac5e5..03b8f5af 100644 --- a/src/background/track-server/file-tracker.ts +++ b/src/background/track-server/file-tracker.ts @@ -12,6 +12,7 @@ type Context = { async function convertContext(tabId: number): Promise { const tab = await getTab(tabId) + if (!tab) return null const { active, url } = tab if (!active || !url) return null const fileHost = extractFileHost(url) diff --git a/src/background/whitelist-menu-manager.ts b/src/background/whitelist-menu-manager.ts index 5a45089d..e90bcb28 100644 --- a/src/background/whitelist-menu-manager.ts +++ b/src/background/whitelist-menu-manager.ts @@ -33,7 +33,7 @@ const menuInitialOptions: ChromeContextMenuCreateProps = { async function updateContextMenuInner(param: ChromeTab | number | undefined): Promise { if (typeof param === 'number') { // If number, get the tabInfo first - const tab: ChromeTab = await getTab(currentActiveId) + const tab = await getTab(currentActiveId) tab && await updateContextMenuInner(tab) } else { const { url } = param || {} diff --git a/src/pages/side/components/Search/Cell.tsx b/src/pages/side/components/Search/Cell.tsx new file mode 100644 index 00000000..11313385 --- /dev/null +++ b/src/pages/side/components/Search/Cell.tsx @@ -0,0 +1,41 @@ +import { formatTimeYMD } from '@util/time' +import { type DateCell, useNamespace } from "element-plus" +import { computed, defineComponent } from 'vue' + +const DOT_SIZE = '3px' +const DATE_SIZE = '22px' + +const Cell = defineComponent<{ cell: DateCell, dataDates: string[] }>(props => { + const text = computed(() => { + const { renderText, text } = props.cell + return renderText ?? text + }) + const hasData = computed(() => { + const { date } = props.cell + if (!date) return false + const dateStr = formatTimeYMD(date) + return props.dataDates.includes(dateStr) + }) + const ns = useNamespace('date-table-cell') + + return () => ( +
+ {text.value} + {hasData.value && } +
+ ) +}, { props: ['cell', 'dataDates'] }) + +export default Cell diff --git a/src/pages/side/components/Search.tsx b/src/pages/side/components/Search/index.tsx similarity index 67% rename from src/pages/side/components/Search.tsx rename to src/pages/side/components/Search/index.tsx index ef503ec0..a55447ad 100644 --- a/src/pages/side/components/Search.tsx +++ b/src/pages/side/components/Search/index.tsx @@ -2,10 +2,12 @@ import { Search } from "@element-plus/icons-vue" import { css } from '@emotion/css' import { useState } from "@hooks" import Flex from "@pages/components/Flex" -import { getDatePickerIconSlots } from "@pages/element-ui/rtl" +import { getDatePickerIconSlots } from '@pages/element-ui/rtl' import { t } from "@side/locale" -import { ElDatePicker, ElInput, useNamespace } from "element-plus" -import { defineComponent, watch } from "vue" +import { type DateCell, ElDatePicker, ElInput, useNamespace } from "element-plus" +import { defineComponent, h } from "vue" +import Cell from './Cell' +import { useDatePicker } from './useDatePicker' const useCalendarStyle = () => { const inputNs = useNamespace('input') @@ -48,13 +50,12 @@ type Props = { } const _default = defineComponent(props => { - const now = Date.now() - const [query, setQuery] = useState(props.defaultQuery) - const [date, setDate] = useState(props.defaultDate) const handleSearch = () => props.onSearch?.(query.value.trim(), date.value) - - watch(date, handleSearch) + const { + date, setDate, dataDates, + disabledDate, onPanelChange, + } = useDatePicker({ onChange: handleSearch }) const [calendarCls, popoverCls] = useCalendarStyle() @@ -72,15 +73,19 @@ const _default = defineComponent(props => { }} onKeydown={kv => (kv as KeyboardEvent).code === 'Enter' && handleSearch()} /> - date.getTime() > now} - modelValue={date.value} - onUpdate:modelValue={setDate} - class={calendarCls} - popperClass={popoverCls} - v-slots={getDatePickerIconSlots()} - /> + {/* The events of date picker is not compatible with typescript, so use h() to workaround */} + {h(ElDatePicker, { + clearable: false, + disabledDate, + modelValue: date.value, + 'onUpdate:modelValue': setDate, + class: calendarCls, + popperClass: popoverCls, + onPanelChange, + }, { + ...getDatePickerIconSlots(), + default: (cell: DateCell) => , + })} ) }, { props: ['defaultDate', 'defaultQuery', 'onSearch'] }) diff --git a/src/pages/side/components/Search/useDatePicker.ts b/src/pages/side/components/Search/useDatePicker.ts new file mode 100644 index 00000000..0e255142 --- /dev/null +++ b/src/pages/side/components/Search/useDatePicker.ts @@ -0,0 +1,38 @@ +import { useRequest } from '@hooks/useRequest' +import { useState } from '@hooks/useState' +import { selectSite } from '@service/stat-service' +import { getMonthTime, MILL_PER_WEEK } from '@util/time' +import { watch } from 'vue' + +export const useDatePicker = (options: { onChange: ArgCallback }) => { + const { onChange } = options + const [date, setDate] = useState(new Date()) + + watch(date, val => onChange(val)) + + const { data: dataDates, refresh: refreshDates } = useRequest(async (dateInMonth: Date) => { + const [ms, me] = getMonthTime(dateInMonth) + const start = new Date(ms.getTime() - ms.getDay() * MILL_PER_WEEK) + const end = new Date(me.getTime() + (6 - me.getDay()) * MILL_PER_WEEK) + + const stats = await selectSite({ date: [start, end] }) + const dateSet = new Set() + stats.forEach(({ date }) => date && dateSet.add(date)) + return Array.from(dateSet) + }, { defaultValue: [], defaultParam: [new Date()] }) + + const onPanelChange = (val: Date | Date[], mode: 'year' | 'month') => { + if (mode !== 'month') return + const date = Array.isArray(val) ? val[0] : val + if (!date) return + refreshDates(date) + } + + const disabledDate = (date: Date) => date.getTime() > Date.now() + + return { + dataDates, date, setDate, + onPanelChange, + disabledDate, + } +} \ No newline at end of file