diff --git a/package.json b/package.json index 6abcf5e7c..6c3225c6c 100644 --- a/package.json +++ b/package.json @@ -29,17 +29,17 @@ "license": "MIT", "devDependencies": { "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/preset-env": "^7.27.2", - "@crowdin/crowdin-api-client": "^1.45.0", - "@rsdoctor/rspack-plugin": "^1.1.4", - "@rspack/cli": "^1.3.15", - "@rspack/core": "^1.3.15", - "@swc/core": "^1.12.5", - "@swc/jest": "^0.2.38", + "@babel/preset-env": "^7.28.0", + "@crowdin/crowdin-api-client": "^1.46.0", + "@rsdoctor/rspack-plugin": "^1.1.8", + "@rspack/cli": "^1.4.8", + "@rspack/core": "^1.4.8", + "@swc/core": "^1.13.1", + "@swc/jest": "^0.2.39", "@types/chrome": "0.1.1", "@types/decompress": "^4.2.7", "@types/jest": "^30.0.0", - "@types/node": "^24.0.3", + "@types/node": "^24.0.15", "@types/punycode": "^2.1.4", "@vue/babel-plugin-jsx": "^1.4.0", "babel-loader": "^10.0.0", @@ -47,13 +47,13 @@ "css-loader": "^7.1.2", "decompress": "^4.2.1", "husky": "^9.1.7", - "jest": "^30.0.2", - "jest-environment-jsdom": "^30.0.2", + "jest": "^30.0.4", + "jest-environment-jsdom": "^30.0.4", "jest-junit": "^16.0.0", "postcss": "^8.5.6", "postcss-loader": "^8.1.1", "postcss-rtlcss": "^5.7.1", - "puppeteer": "^24.10.2", + "puppeteer": "^24.14.0", "sass": "^1.89.2", "sass-loader": "^16.0.5", "style-loader": "^4.0.0", @@ -64,11 +64,11 @@ "url-loader": "^4.1.1" }, "optionalDependencies": { - "web-ext": "^8.8.0" + "web-ext": "^8.9.0" }, "dependencies": { "@element-plus/icons-vue": "^2.3.1", - "@vueuse/core": "^13.4.0", + "@vueuse/core": "^13.5.0", "countup.js": "^2.9.0", "echarts": "^5.6.0", "element-plus": "2.10.4", diff --git a/src/content-script/limit/modal/components/Footer.tsx b/src/content-script/limit/modal/components/Footer.tsx index 5cf603e34..bb741df15 100644 --- a/src/content-script/limit/modal/components/Footer.tsx +++ b/src/content-script/limit/modal/components/Footer.tsx @@ -5,15 +5,15 @@ import { TAG_NAME } from "@cs/limit/element" import { t } from "@cs/locale" import { Plus, Timer } from "@element-plus/icons-vue" import optionHolder from "@service/components/option-holder" -import { meetTimeLimit } from "@util/limit" +import { meetTimeLimit } from '@util/limit' import { ElButton } from "element-plus" import { computed, defineComponent } from "vue" import { useDelayHandler, useReason, useRule } from "../context" -async function handleMore5Minutes(rule: timer.limit.Item, callback: () => void) { +async function handleMore5Minutes(rule: timer.limit.Item | null, callback: () => void) { let promise: Promise | undefined = undefined const ele = document.querySelector(TAG_NAME)?.shadowRoot?.querySelector('body') - if (await judgeVerificationRequired(rule)) { + if (rule && await judgeVerificationRequired(rule)) { const option = await optionHolder.get() promise = processVerification(option, { appendTo: ele ?? undefined }) promise ? promise.then(callback).catch(() => { }) : callback() @@ -29,16 +29,16 @@ const _default = defineComponent(() => { const { type, allowDelay, delayCount = 0 } = reason.value || {} if (!allowDelay) return false - const { time, weekly, visit, waste, weeklyWaste } = rule.value || {} + const { time, weekly, visitTime, waste, weeklyWaste } = rule.value || {} let realLimit = 0, realWaste = 0 if (type === 'DAILY') { realLimit = time ?? 0 - realWaste = waste + realWaste = waste ?? 0 } else if (type === 'WEEKLY') { realLimit = weekly ?? 0 - realWaste = weeklyWaste + realWaste = weeklyWaste ?? 0 } else if (type === 'VISIT') { - realLimit = visit + realLimit = visitTime ?? 0 realWaste = reason.value?.getVisitTime?.() ?? 0 } else { return false diff --git a/src/content-script/limit/modal/context.ts b/src/content-script/limit/modal/context.ts index 3165ec73e..5af5803ad 100644 --- a/src/content-script/limit/modal/context.ts +++ b/src/content-script/limit/modal/context.ts @@ -10,11 +10,11 @@ const DELAY_HANDLER_KEY = 'delay_handler' export const provideReason = (app: App): Ref => { const reason = ref() - app?.provide(REASON_KEY, reason) + app.provide(REASON_KEY, reason) return reason } -export const useReason = () => inject(REASON_KEY) as Ref +export const useReason = () => inject(REASON_KEY) as Ref export const provideRule = () => { const reason = useReason() @@ -33,7 +33,7 @@ export const provideRule = () => { provide(RULE_KEY, rule) } -export const useRule = () => inject(RULE_KEY) as Ref +export const useRule = () => inject(RULE_KEY) as Ref export const provideDelayHandler = (app: App, handlers: () => void) => { app?.provide(DELAY_HANDLER_KEY, handlers) diff --git a/src/content-script/limit/modal/index.ts b/src/content-script/limit/modal/index.ts index c49db0008..7bb10547d 100644 --- a/src/content-script/limit/modal/index.ts +++ b/src/content-script/limit/modal/index.ts @@ -46,8 +46,13 @@ const createHeader = () => { } class ScreenLocker { + private beforeOverflow: string | undefined + private doLock() { - if (document?.documentElement) { + const ele = document?.documentElement + this.beforeOverflow = undefined + if (ele) { + this.beforeOverflow = ele.style.overflow document.documentElement.style.setProperty('overflow', 'hidden', 'important') } } @@ -60,7 +65,7 @@ class ScreenLocker { unlock() { if (document?.documentElement) { - document.documentElement.style.overflow = '' + document.documentElement.style.overflow = this.beforeOverflow ?? '' } } } @@ -78,6 +83,7 @@ class ModalInstance implements MaskModal { screenLocker = new ScreenLocker() constructor(url: string) { + (window as any)['__modal__'] = this this.url = url } @@ -115,8 +121,11 @@ class ModalInstance implements MaskModal { } private refresh() { - const reason = this.reasons?.[0] - reason ? this.show(reason) : this.hide() + setTimeout(() => { + // update vue ref in another micro task + const reason = this.reasons?.[0] + reason ? this.show(reason) : this.hide() + }) } private async init() { diff --git a/src/pages/app/components/About/index.tsx b/src/pages/app/components/About/index.tsx index 33a49dd95..f4ab649af 100644 --- a/src/pages/app/components/About/index.tsx +++ b/src/pages/app/components/About/index.tsx @@ -1,13 +1,12 @@ -import { defineComponent } from "vue" +import { type FunctionalComponent } from "vue" import ContentContainer from "../common/ContentContainer" import Description from "./Description" -const _default = defineComponent({ - render: () => ( - - - - ) -}) +const About: FunctionalComponent = () => ( + + + +) +About.displayName = 'About' -export default _default \ No newline at end of file +export default About \ No newline at end of file diff --git a/src/pages/app/components/HelpUs/index.tsx b/src/pages/app/components/HelpUs/index.tsx index d1a713a01..157546f1c 100644 --- a/src/pages/app/components/HelpUs/index.tsx +++ b/src/pages/app/components/HelpUs/index.tsx @@ -4,33 +4,32 @@ import { Pointer } from "@element-plus/icons-vue" import Box from "@pages/components/Box" import { CROWDIN_HOMEPAGE } from "@util/constant/url" import { ElAlert, ElButton, ElCard } from "element-plus" -import { defineComponent } from "vue" +import { type FunctionalComponent } from "vue" import ContentContainer from "../common/ContentContainer" import MemberList from "./MemberList" import ProgressList from "./ProgressList" const handleJump = () => createTabAfterCurrent(CROWDIN_HOMEPAGE) -const HelpUs = defineComponent(() => { - return () => ( - - - msg.helpUs.title)}> -
  • {t(msg => msg.helpUs.alert.l1)}
  • -
  • {t(msg => msg.helpUs.alert.l2)}
  • -
  • {t(msg => msg.helpUs.alert.l3)}
  • -
  • {t(msg => msg.helpUs.alert.l4)}
  • -
    - - - {t(msg => msg.helpUs.button)} - - - - -
    -
    - ) -}) +const HelpUs: FunctionalComponent = () => ( + + + msg.helpUs.title)}> +
  • {t(msg => msg.helpUs.alert.l1)}
  • +
  • {t(msg => msg.helpUs.alert.l2)}
  • +
  • {t(msg => msg.helpUs.alert.l3)}
  • +
  • {t(msg => msg.helpUs.alert.l4)}
  • +
    + + + {t(msg => msg.helpUs.button)} + + + + +
    +
    +) +HelpUs.displayName = 'HelpUs' export default HelpUs \ No newline at end of file diff --git a/src/pages/app/components/RuleMerge/index.tsx b/src/pages/app/components/RuleMerge/index.tsx index 287ddf5d1..1f018f847 100644 --- a/src/pages/app/components/RuleMerge/index.tsx +++ b/src/pages/app/components/RuleMerge/index.tsx @@ -7,22 +7,21 @@ import Flex from "@pages/components/Flex" import { ElCard } from "element-plus" -import { defineComponent } from "vue" +import { type FunctionalComponent } from "vue" import ContentContainer from "../common/ContentContainer" import AlertInfo from "./AlertInfo" import ItemList from "./ItemList" -const RuleMerge = defineComponent(() => { - return () => ( - - - - - - - - - ) -}) +const RuleMerge: FunctionalComponent = () => ( + + + + + + + + +) +RuleMerge.displayName = 'RuleMerge' export default RuleMerge diff --git a/src/pages/app/components/Whitelist/index.tsx b/src/pages/app/components/Whitelist/index.tsx index 8c0d96d54..f12c5bd39 100644 --- a/src/pages/app/components/Whitelist/index.tsx +++ b/src/pages/app/components/Whitelist/index.tsx @@ -8,28 +8,27 @@ import { t } from "@app/locale" import Flex from "@pages/components/Flex" import { ElAlert, ElCard } from "element-plus" -import { defineComponent } from "vue" +import { type FunctionalComponent } from "vue" import ContentContainer from "../common/ContentContainer" import WhitePanel from "./WhitePanel" -const Whitelist = defineComponent(() => { - return () => ( - - - - msg.whitelist.infoAlertTitle)} - style={{ padding: "15px 25px" }} - closable={false} - > -
  • {t(msg => msg.whitelist.infoAlert0)}
  • -
  • {t(msg => msg.whitelist.infoAlert1)}
  • -
    - -
    -
    -
    - ) -}) +const Whitelist: FunctionalComponent = () => ( + + + + msg.whitelist.infoAlertTitle)} + style={{ padding: "15px 25px" }} + closable={false} + > +
  • {t(msg => msg.whitelist.infoAlert0)}
  • +
  • {t(msg => msg.whitelist.infoAlert1)}
  • +
    + +
    +
    +
    +) +Whitelist.displayName = 'Whitelist' export default Whitelist diff --git a/test-e2e/limit/common.ts b/test-e2e/limit/common.ts index 5e29d8463..e8a34a321 100644 --- a/test-e2e/limit/common.ts +++ b/test-e2e/limit/common.ts @@ -25,9 +25,9 @@ export async function createLimitRule(rule: timer.limit.Rule, page: Page) { await sleep(.1) const { time, weekly, visitTime, count, weeklyCount } = rule || {} const timeInputs = await page.$$('.el-dialog .el-date-editor input') - await fillTimeLimit(time!, timeInputs[0], page) - await fillTimeLimit(weekly!, timeInputs[1], page) - await fillTimeLimit(visitTime!, timeInputs[2], page) + await fillTimeLimit(time, timeInputs[0], page) + await fillTimeLimit(weekly, timeInputs[1], page) + await fillTimeLimit(visitTime, timeInputs[2], page) const visitInputs = await page.$$('.el-dialog .el-input-number input') await fillVisitLimit(count!, visitInputs[0], page) await fillVisitLimit(weeklyCount!, visitInputs[1], page) @@ -35,9 +35,16 @@ export async function createLimitRule(rule: timer.limit.Rule, page: Page) { // 4. Save await sleep(.3) await page.click('.el-dialog .el-button.el-button--success') + if (rule.allowDelay) { + await page.waitForSelector('.el-table__body .el-table__row td:nth-child(9) .el-switch') + await page.evaluate(async () => { + document.querySelector('.el-table__body .el-table__row td:nth-child(9) .el-switch')?.click() + }) + await sleep(.3) + } } -export async function fillTimeLimit(value: number, input: ElementHandle, page: Page): Promise { +export async function fillTimeLimit(value: number | undefined, input: ElementHandle, page: Page): Promise { value = value ?? 0 const hour = Math.floor(value / 3600) value = value - hour * 3600 @@ -45,7 +52,7 @@ export async function fillTimeLimit(value: number, input: ElementHandle { const hourSpinner = el.querySelector('.el-scrollbar:first-child .el-scrollbar__wrap') hourSpinner!.scrollTo(0, hour * 32) @@ -58,6 +65,7 @@ export async function fillTimeLimit(value: number, input: ElementHandle, page: Page) { diff --git a/test-e2e/limit/visit-limit.test.ts b/test-e2e/limit/visit-limit.test.ts new file mode 100644 index 000000000..8585cce86 --- /dev/null +++ b/test-e2e/limit/visit-limit.test.ts @@ -0,0 +1,47 @@ +import { launchBrowser, LaunchContext, MOCK_URL, sleep } from '../common/base' +import { createLimitRule } from './common' + +describe('Time limit per visit', () => { + let context: LaunchContext + + beforeEach(async () => context = await launchBrowser()) + + afterEach(() => context.close()) + + test("More 5 minutes", async () => { + const limitPage = await context.openAppPage('/behavior/limit') + const demoRule: timer.limit.Rule = { + id: 1, name: 'TEST DAILY LIMIT', + cond: [MOCK_URL], + visitTime: 1, + enabled: true, allowDelay: true, locked: false, + } + + // 1. Insert limit rule + await createLimitRule(demoRule, limitPage) + + // 2. Open test page + const testPage = await context.newPageAndWaitCsInjected(MOCK_URL) + await sleep(2) + + // 3. Modal exist and then click more 5 minutes + const clicked = await testPage.evaluate(() => { + const shadow = document.querySelector('extension-time-tracker-overlay') + if (!shadow) return false + const button = shadow.shadowRoot?.querySelector('.el-button--primary') + button?.click() + return !!button + }) + expect(clicked).toBeTruthy() + + // 4. Modal disappear + await sleep(.5) + const modalExist = await testPage.evaluate(() => { + const shadow = document.querySelector('extension-time-tracker-overlay') + if (!shadow) return false + return !!shadow.shadowRoot!.querySelector('body:not([style*="display: none"])') + }) + expect(modalExist).toBeFalsy() + + }, 10000) +}) \ No newline at end of file