Skip to content

Commit 7c38c70

Browse files
committed
Request more 5 minutes must be verified (#241)
1 parent 53ff506 commit 7c38c70

File tree

10 files changed

+302
-101
lines changed

10 files changed

+302
-101
lines changed

src/app/locale.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
/**
22
* Copyright (c) 2021 Hengyang Zhang
3-
*
3+
*
44
* This software is released under the MIT License.
55
* https://opensource.org/licenses/MIT
66
*/
77

88
import { I18nKey as _I18nKey, t as _t } from "@i18n"
9-
import { tN as _tN } from "@i18n/i18n-vue"
9+
import { tN as _tN } from "@i18n"
1010
import messages, { AppMessage } from "@i18n/message/app"
11+
import { VNode } from "vue"
1112

1213
export type I18nKey = _I18nKey<AppMessage>
1314

@@ -25,5 +26,5 @@ export function tWith(key: I18nKey, specLocale: timer.Locale, param?: any) {
2526
}
2627

2728
export function tN(key: I18nKey, param?: any) {
28-
return _tN<AppMessage>(messages, { key, param })
29+
return _tN<AppMessage, VNode>(messages, { key, param })
2930
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { t } from "@src/content-script/locale"
2+
import { LimitReason } from "../common"
3+
import { LINK_STYLE } from "../modal-style"
4+
5+
const canDelay = ({ allowDelay, type }: LimitReason) => allowDelay && (type === "DAILY" || type === "VISIT")
6+
7+
export class DelayButton {
8+
public dom: HTMLDivElement
9+
10+
constructor(reason: LimitReason, onClick: () => void) {
11+
if (!canDelay(reason)) return
12+
13+
this.dom = document.createElement('p')
14+
this.dom.style.marginTop = '100px'
15+
16+
// Only delay-allowed rules exist, can delay
17+
// @since 0.4.0
18+
const link = document.createElement('a')
19+
Object.assign(link.style || {}, LINK_STYLE)
20+
link.setAttribute('href', 'javascript:void(0)')
21+
const text = t(msg => msg.more5Minutes)
22+
link.innerText = text
23+
link.onclick = onClick
24+
this.dom.append(link)
25+
}
26+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import { VerificationPair } from "@service/limit-service/verification/common"
2+
import verificationProcessor from "@service/limit-service/verification/processor"
3+
import { LINK_STYLE } from "../modal-style"
4+
import { t as t_, locale, I18nKey, tN as tN_, I18nResultItem } from "@i18n"
5+
import { LimitMessage, verificationMessages } from "@i18n/message/app/limit"
6+
7+
const tV = (key: I18nKey<LimitMessage['verification']>, param?: any) => {
8+
return t_(verificationMessages, { key, param })
9+
}
10+
11+
const tVN = (key: I18nKey<LimitMessage['verification']>, param?: any) => {
12+
return tN_<LimitMessage['verification'], HTMLElement>(verificationMessages, { key, param })
13+
}
14+
15+
const generatePromptEle = (text: string): HTMLElement => {
16+
const b = document.createElement('b')
17+
b.innerText = text
18+
const style: Partial<CSSStyleDeclaration> = {
19+
userSelect: "none",
20+
}
21+
Object.assign(b.style || {}, style)
22+
return b
23+
}
24+
25+
const ANSWER_CANVAS_FONT_SIZE = 20
26+
27+
const generatePromptCanvas = (text: string): HTMLCanvasElement => {
28+
const ele = document.createElement('canvas')
29+
const ctx = ele.getContext("2d")
30+
const height = Math.floor(ANSWER_CANVAS_FONT_SIZE * 1.3)
31+
ele.height = height
32+
const font = LINK_STYLE.fontFamily
33+
// Set font to measure width
34+
ctx.font = font
35+
const { width } = ctx.measureText(text)
36+
ele.width = width
37+
// Need set font again after width changed
38+
ctx.font = font
39+
ctx.fillStyle = 'rgb(238, 238, 238)'
40+
ctx.fillText(text, 0, ANSWER_CANVAS_FONT_SIZE)
41+
return ele
42+
}
43+
44+
const CONFIRM_STYLE: Partial<CSSStyleDeclaration> = {
45+
width: "500px",
46+
left: "calc(50vw - 250px)",
47+
top: "200px",
48+
height: "250px",
49+
padding: "50px 20px 20px 20px",
50+
position: "absolute",
51+
textAlign: "center",
52+
background: "#111",
53+
borderRadius: "8px",
54+
}
55+
56+
const INPUT_STYLE: Partial<CSSStyleDeclaration> = {
57+
color: "#000",
58+
padding: "2px 6px",
59+
borderRadius: "2px",
60+
marginTop: "20px",
61+
width: "200px",
62+
border: "none",
63+
outline: "none",
64+
}
65+
66+
const FOOT_STYLE: Partial<CSSStyleDeclaration> = {
67+
display: "flex",
68+
marginTop: "20px",
69+
justifyContent: "center",
70+
}
71+
72+
const FOOT_BTN_STYLE: Partial<CSSStyleDeclaration> = {
73+
cursor: "pointer",
74+
padding: "5px 10px",
75+
textDecoration: "underline",
76+
}
77+
78+
const createFooterBtn = (text: string, onClick: () => void): HTMLDivElement => {
79+
const btn = document.createElement('div')
80+
btn.innerText = text
81+
Object.assign(btn.style || {}, FOOT_BTN_STYLE)
82+
btn.addEventListener('click', onClick)
83+
return btn
84+
}
85+
86+
export class DelayConfirm {
87+
public dom: HTMLDivElement
88+
private incorrectMessage: string
89+
private answerValue: string
90+
private isPassword: boolean = false
91+
private input: HTMLInputElement
92+
private visible = false
93+
private onSuccess: () => void
94+
private onError: (msg: string) => void
95+
96+
constructor(option: timer.option.DailyLimitOption) {
97+
const { limitLevel = "nothing", limitPassword, limitVerifyDifficulty } = option || {}
98+
let tips: I18nResultItem<HTMLElement>[] = []
99+
if (limitLevel === "password" && limitPassword) {
100+
this.answerValue = limitPassword
101+
this.isPassword = true
102+
tips.push(tV(msg => msg.pswInputTip))
103+
this.incorrectMessage = tV(msg => msg.incorrectPsw)
104+
} else if (limitLevel === "verification") {
105+
const pair: VerificationPair = verificationProcessor.generate(limitVerifyDifficulty, locale)
106+
const { prompt, promptParam, answer } = pair || {}
107+
this.answerValue = typeof answer === 'function' ? tV(answer) : answer
108+
this.incorrectMessage = tV(msg => msg.incorrectAnswer)
109+
if (prompt) {
110+
const promptTxt = typeof prompt === 'function'
111+
? tV(prompt, { ...promptParam, answer: this.answerValue })
112+
: prompt
113+
tips = tVN(msg => msg.inputTip, { prompt: generatePromptEle(promptTxt) })
114+
} else {
115+
const answer: HTMLElement = limitVerifyDifficulty === 'disgusting'
116+
? generatePromptCanvas(this.answerValue)
117+
: generatePromptEle(this.answerValue)
118+
tips = tVN(msg => msg.inputTip2, { answer })
119+
}
120+
} else {
121+
return
122+
}
123+
this.dom = document.createElement("div")
124+
Object.assign(this.dom.style || {}, CONFIRM_STYLE)
125+
this.dom.style.display = 'none'
126+
// tips
127+
const tipContainer = document.createElement('div')
128+
tipContainer.append(...tips)
129+
this.dom.append(tipContainer)
130+
this.initInput()
131+
this.initFooter()
132+
}
133+
134+
private initInput() {
135+
this.input = document.createElement('input')
136+
this.input.addEventListener("keypress", e => e?.key === "Enter" && this.handleConfirmClick())
137+
Object.assign(this.input.style || {}, INPUT_STYLE)
138+
this.isPassword && (this.input.type = 'password')
139+
this.dom.append(this.input)
140+
}
141+
142+
private initFooter() {
143+
const footer = document.createElement('div')
144+
Object.assign(footer.style || {}, FOOT_STYLE)
145+
const cancelBtn = createFooterBtn('Cancel', () => this.hide())
146+
const confirmBtn = createFooterBtn('Confirm', () => this.handleConfirmClick())
147+
footer.append(confirmBtn)
148+
footer.append(cancelBtn)
149+
this.dom.append(footer)
150+
}
151+
152+
private show() {
153+
if (this.visible) return
154+
this.visible = true
155+
this.dom?.style && (this.dom.style.display = "inherit")
156+
this.input?.focus?.()
157+
}
158+
159+
private hide() {
160+
if (!this.visible) return
161+
this.visible = false
162+
this.onSuccess = undefined
163+
this.onError = undefined
164+
this.dom?.style && (this.dom.style.display = "none")
165+
}
166+
167+
private handleConfirmClick() {
168+
const inputValue = this.input.value
169+
if (inputValue === this.answerValue) {
170+
this.onSuccess?.()
171+
} else {
172+
this.onError?.(this.incorrectMessage)
173+
}
174+
}
175+
176+
async doConfirm(): Promise<void> {
177+
if (!this.dom) return Promise.resolve()
178+
return new Promise<void>((resolve) => {
179+
this.onSuccess = resolve
180+
this.onError = msg => alert(msg)
181+
this.show()
182+
})
183+
}
184+
}
185+

src/content-script/limit/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,11 @@ export default async function processLimit(url: string) {
3030
const anyFail = allMatch(results, r => r.code === "fail")
3131
if (anyFail) return { code: "fail" }
3232
// Merge data of all the handlers
33-
const datas = results
33+
const items = results
3434
.filter(r => r.code === "success")
3535
.map(r => r.data)
3636
.filter(r => r !== undefined && r !== null)
37-
const data = datas.length <= 1 ? datas[0] : datas
38-
console.log("sdadsa", data)
37+
const data = items.length <= 1 ? items[0] : items
3938
return { code: "success", data }
4039
})
4140
}

src/content-script/limit/modal.ts

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { LimitReason, LimitType, MaskModal } from "./common"
44
import { sendMsg2Runtime } from "@api/chrome/runtime"
55
import optionService from "@service/option-service"
66
import { FILTER_STYLES, LINK_STYLE, MASK_STYLE } from "./modal-style"
7+
import { DelayConfirm } from "./delay/confirm"
8+
import { DelayButton } from "./delay/Button"
79

810
const TYPE_SORT: { [reason in LimitType]: number } = {
911
PERIOD: 0,
@@ -77,13 +79,15 @@ class ModalInstance implements MaskModal {
7779
delayHandlers: (() => void)[] = [
7880
() => sendMsg2Runtime('cs.moreMinutes', this.url)
7981
]
82+
options: timer.option.AllOption
8083

8184
constructor(url: string) {
8285
this.url = url
8386
this.mask = document.createElement('div')
8487
this.mask.id = "_timer_mask"
8588
this.initStyle()
8689
window?.addEventListener?.("load", () => this.refresh())
90+
optionService.getAllOption().then(val => this.options = val)
8791
}
8892

8993
addDelayHandler(handler: () => void): void {
@@ -148,9 +152,8 @@ class ModalInstance implements MaskModal {
148152
beforeCount !== afterCount && this.refresh()
149153
}
150154

151-
private showModalInner(reason: LimitReason): any {
155+
private showModalInner(reason: LimitReason): void {
152156
const url = this.url
153-
const { allowDelay, type } = reason
154157

155158
// Clear
156159
Array.from(this.mask.children).forEach(e => e.remove())
@@ -159,25 +162,13 @@ class ModalInstance implements MaskModal {
159162
this.mask.append(document.createElement("br"))
160163
this.mask.append(link2Setup(url))
161164

162-
const canDelay = (type === "DAILY" || type === "VISIT") && allowDelay
163-
164-
if (canDelay) {
165-
const delayContainer = document.createElement('p')
166-
delayContainer.style.marginTop = '100px'
167-
168-
// Only delay-allowed rules exist, can delay
169-
// @since 0.4.0
170-
const link = document.createElement('a')
171-
Object.assign(link.style || {}, LINK_STYLE)
172-
link.setAttribute('href', 'javascript:void(0)')
173-
const text = t(msg => msg.more5Minutes)
174-
link.innerText = text
175-
link.onclick = () => [
176-
this.delayHandlers?.forEach(h => h?.())
177-
]
178-
delayContainer.append(link)
179-
this.mask.append(delayContainer)
180-
}
165+
const delayConfirm = new DelayConfirm(this.options)
166+
const delayButton = new DelayButton(
167+
reason,
168+
() => delayConfirm.doConfirm().then(() => this.delayHandlers?.forEach?.(h => h?.()))
169+
)
170+
this.mask.append(delayButton.dom)
171+
this.mask.append(delayConfirm.dom)
181172

182173
document.body.append(this.mask)
183174
document.body.style.overflow = 'hidden'

src/guide/locale.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
/**
22
* Copyright (c) 2022 Hengyang Zhang
3-
*
3+
*
44
* This software is released under the MIT License.
55
* https://opensource.org/licenses/MIT
66
*/
77

8-
import { I18nKey as _I18nKey, t as _t } from "@i18n"
9-
import { tN as _tN } from "@i18n/i18n-vue"
8+
import { I18nKey as _I18nKey, t as _t, tN as _tN } from "@i18n"
109
import messages, { GuideMessage } from "@i18n/message/guide"
10+
import { VNode } from "vue"
1111

1212
export type I18nKey = _I18nKey<GuideMessage>
1313

@@ -18,5 +18,5 @@ export function t(key: I18nKey, param?: any) {
1818

1919
export function tN(key: I18nKey, param?: any) {
2020
const props = { key, param }
21-
return _tN<GuideMessage>(messages, props)
21+
return _tN<GuideMessage, VNode>(messages, props)
2222
}

src/i18n/i18n-vue.ts

Lines changed: 0 additions & 47 deletions
This file was deleted.

0 commit comments

Comments
 (0)