Skip to content

Commit

Permalink
reformat passEsc
Browse files Browse the repository at this point in the history
  • Loading branch information
gdh1995 committed Jul 8, 2024
1 parent 2c22c11 commit 4e01d49
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 77 deletions.
6 changes: 6 additions & 0 deletions _locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@
"117": { "message": ".monaco-editor//textarea.monaco-mouse-cursor-text//" },
"118": { "message": "weibo.com##.expand,.collapse" },
"119": { "message": "Readonly text box is focused" },
"120": { "message":
"#read-only-cursor-text-area,.monaco-mouse-cursor-text[aria-autocomplete=none],.CodeMirror>div>textarea[readonly=''][style]"
},
"121": { "message":
"[aria-controls],[role=combobox],#kw.s_ipt,input[placeholder$$=搜索],input[type=search][name=q],.monaco-inputbox>div>textarea[style]"
},
"200": { "message": "http://www.w3.org/1999/xhtml" },
"0": { "message": "Open link in current tab" },
"2": { "message": "Open link in new tab" },
Expand Down
22 changes: 16 additions & 6 deletions background/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ export const reloadFromLegacy_ = (changed: number): void => {
}
}

const RemoveComment = (i: string) => i.startsWith("# ") ? "" : i.split("//", 1)[0].trim()

/** @argument value may come from `LinkHints.*::characters` and `kBgCmd.toggle::value` */
export const updatePayload_ = function (shortKey: keyof SettingsNS.FrontendComplexSyncingItems, value: any
, obj?: Partial<SettingsNS.FrontendSettingCache>
Expand All @@ -233,8 +235,11 @@ export const updatePayload_ = function (shortKey: keyof SettingsNS.FrontendCompl
&& Build.OS & kBOS.MAC && (Build.OS === kBOS.MAC as number || !os_) ? kKeyLayout.ignoreCaps : 0)
break
case "d": value = value ? " D" : ""; break
case "p": case "y":
value = value.replace(":default", shortKey === "p" ? defaults_.passEsc : defaults_.ignoreReadonly)
case "p":
value = value.replace("[aria-controls],[role=combobox],#kw.s_ipt", GlobalConsts.kCssDefault) // migration
// no break;
case "y":
value = value.split("\n").map(RemoveComment).join("")
break
default: if (0) { shortKey satisfies never } break // lgtm [js/unreachable-statement]
}
Expand Down Expand Up @@ -360,10 +365,10 @@ [email protected]`
filterLinkHints: false,
grabBackFocus: false,
hideHud: false,
ignoreReadonly: "#read-only-cursor-text-area" // GitHub source file content
ignoreReadonly: GlobalConsts.kCssDefault as const
|| "#read-only-cursor-text-area" // GitHub source file content
+ ",.monaco-mouse-cursor-text[aria-autocomplete=none]" // Monaco editor >= ~0.42.0-dev-20230901
+ ',.CodeMirror>div[style]>textarea[readonly=""][style]' // Code Mirror 5
+ ",.sidebar-view-item-name>input[value][readonly]" // Netron attribute name
+ ",.CodeMirror>div>textarea[readonly=''][style]" // Code Mirror 5
,
keyLayout: kKeyLayout.Default,
keyboard: [560, 33],
Expand All @@ -378,7 +383,12 @@ [email protected]`
,\u4e0b\u4e00\u5f20,next,more,newer,>,\u203a,\u2192,\xbb,\u226b,>>",
notifyUpdate: true,
omniBlockList: "",
passEsc: "[aria-controls],[role=combobox],#kw.s_ipt", // MS Bing / Google / Baidu
passEsc: GlobalConsts.kCssDefault as const
|| "[aria-controls],[role=combobox],#kw.s_ipt" // MS Bing / Google / Baidu
+ ",input[placeholder$=\u641c\u7d22]" // some Chinese websites
+ ",input[type=search][name=q]" // Bing search result page
+ ",.monaco-inputbox>div>textarea[style]" // Monaco find input
,
preferBrowserSearch: true,
previousPatterns: "\u4e0a\u4e00\u5c01,\u4e0a\u9875,\u4e0a\u4e00\u9875,\u4e0a\u4e00\u7ae0,\u524d\u4e00\u9875\
,\u4e0a\u4e00\u5f20,prev,previous,back,older,<,\u2039,\u2190,\xab,\u226a,<<",
Expand Down
10 changes: 8 additions & 2 deletions content/insert.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
doc, keydownEvents_, safeObj, isTop, set_keydownEvents_, setupEventListener, Stop_, OnChrome, OnFirefox, weakRef_ff,
esc, onWndFocus, isEnabled_, readyState_, recordLog, weakRef_not_ff, OnEdge, getTime, abs_, fgCache, deref_,
isTY, timeout_, timeStamp_, chromeVer_
isTY, timeout_, timeStamp_, chromeVer_, VTr
} from "../lib/utils"
import {
activeEl_unsafe_, getEditableType_, getEventPath, getSelection_, frameElement_, deepActiveEl_unsafe_, blur_unsafe,
Expand Down Expand Up @@ -382,8 +382,14 @@ const testConfiguredSelector_ = <T extends "passEsc" | "ignoreReadonly">(target:
, confKey: SettingsNS.AutoSyncedNameMap[T]): boolean | 0 => {
let selector: string | [string] | 0 | void = fgCache[confKey] as string | [string] | 0
if (isTY(selector)) {
if (OnEdge) {
selector = selector.replace(<RegExpG & RegExpSearchable<0>> /:default/g
, () => VTr((confKey === "p") as boolean | BOOL as BOOL + kTip.defaultIgnoreReadonly))
}
selector = findSelectorByHost(selector, target)
fgCache[confKey] = (selector = (selector ? [selector] : 0) satisfies [string] | 0) as never
selector = selector ? (!OnEdge ? [selector.replace(":default"
, VTr((confKey === "p") as boolean | BOOL as BOOL + kTip.defaultIgnoreReadonly) as "css")] : [selector]) : 0
fgCache[confKey] = (selector satisfies [string] | 0) as never
}
return selector && testMatch(selector[0], target)
}
4 changes: 2 additions & 2 deletions lib/dom_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,14 +604,14 @@ set_findOptByHost((rules: string | object | kTip | true | MayBeSelector
const re = cond && (<RegExpOne> /[*+?^$\\(]/).test(cond) && tryCreateRegExp(cond)
path || cond && (host = Lower(loc_.host), path = host + "/" + Lower(loc_.pathname))
if (re ? re.test(matchPath ? path! : host!) : matchPath ? path!.startsWith(cond)
: !cond || host === cond || host!.endsWith("." + cond)) {
: !cond && sel || host === cond || host!.endsWith("." + cond)) {
if (!mapMode) {
return isKTip || cssCheckEl === 0 || sel && safeCall(testMatch, sel as string, _cssChecker || cssCheckEl
|| (_cssChecker = createElement_("p"))) != null ? sel as "css-selector" : void 0
}
for (const rawEntry of splitEntries_<Val | [string, Val], true>(sel, ",")) {
let ret: string | boolean | 0 = 0
if (rawEntry === "true" || rawEntry === "false") { ret = rawEntry }
if (!rawEntry || rawEntry === "true" || rawEntry === "false") { ret = rawEntry || 0 }
else {
const entry = splitEntries_(rawEntry, "//"), len = entry.length, val = len < 2 || entry[1]
const matched = len < 2 && mapMode > kNextTarget.nonCss - 1
Expand Down
13 changes: 8 additions & 5 deletions pages/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -587,9 +587,10 @@
<span data-i="95_5">If a readonly textbox gets focused, specify if to keep a current normal mode,
or otherwise to enter insert mode. </span><span data-i="106">Format: </span>
<code><pre>
host.name##.class-selector1
host2.com###id-selector,.class2
#id-on-any-host</pre></code>
host.com###id-selector,.class
#id-on-any-host,[attr=value]</pre></code>
<a data-auto-resize="ignoreReadonly" role="button" tabindex="0" data-i="autoResize">Auto resize
</a><span data-i="period">.</span>
</div></td>
</tr>
<tr>
Expand All @@ -598,9 +599,11 @@
<textarea id="passEsc" class="code min-height-4" data-model="CssSelector" data-check=""></textarea>
<div class="info" data-i="nonEmpty">Delete all to reset this option.</div>
</td>
<td class="help"><div class="help-inner" data-i="95_3">
<td class="help"><div class="help-inner"><span data-i="95_3">
In plain insert mode, specify when to pass <kbd>Escape</kbd> keydown events to pages and delay blurring.
The default value may collapse some search suggestions.
The default value may collapse some search suggestions. </span><a
data-auto-resize="passEsc" role="button" tabindex="0" data-i="autoResize">Auto resize
</a><span data-i="period">.</span>
</div></td>
</tr>
<tr>
Expand Down
117 changes: 83 additions & 34 deletions pages/options_checker.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { browser_, CurCVer_, OnChrome, OnEdge, OnFirefox, post_ } from "./async_bg"
import { browser_, OnFirefox, post_ } from "./async_bg"
import { bgSettings_, Option_, AllowedOptions, oTrans_ } from "./options_base"
import { type TextOption_, kDefaultRule } from "./options_defs"
import { CssSelectorOption_, TextOption_ } from "./options_defs"
import { kPgReq } from "../background/page_messages"

const spaceKindRe = <RegExpG & RegExpSearchable<0>> /\s/g
Expand Down Expand Up @@ -241,58 +241,107 @@ Option_.all_.keyboard.checker_ = {
}
};

const sortCssRules = (arr: string[]): string[] => {
if (!OnEdge && (!OnChrome || Build.MinCVer >= BrowserVer.MinStableSort || CurCVer_ > BrowserVer.MinStableSort - 1)) {
return arr.sort((i, j) => i[0] !== j[0] ? i > j ? -1 : 1 : 0) // ";" > ","
}
return arr.map((i, ind) => [i, ind] as const).sort((i, j) => i[0][0] !== j[0][0] ? i[0] > j[0] ? -1 : 1 : i[1] - j[1])
.map(i => i[0])
}

type CssOptions = TextOption_<"passEsc"> | TextOption_<"ignoreReadonly">
const _knownBrokenCssSelectors: Dict<1> = {}
type CssOptions = CssSelectorOption_<"passEsc"> | CssSelectorOption_<"ignoreReadonly">
let _validCssSelectors: Dict<BOOL> = {}
const isValidCssSelector = (option: CssOptions, selector: string, errors: string[]): boolean => {
selector = selector.replace(<RegExpOne> /[,;]\s*$/, "").trim()
if (selector === kDefaultRule) {
if (selector === GlobalConsts.kCssDefault) {
if (Build.NDEBUG) { return true } // EdgeHTML may have no `:default`
selector = bgSettings_.defaults_[option.field_]
selector = option.getRealDefault()
}
if (selector.includes(",")) {
return selector.split(",").map(i => isValidCssSelector(option, i, errors)).reduce((old, x) => old || x, false)
return selector.split(",").map(i => isValidCssSelector(option, i, errors)).reduce((old, x) => old && x, true)
}
if (!_knownBrokenCssSelectors[selector]) {
let valid = selector ? _validCssSelectors[selector] : 1
if (valid == null) {
try {
option.element_.querySelector(selector)
_validCssSelectors[selector] = 1
return true
} catch { /* empty */ }
_knownBrokenCssSelectors[selector] = 1
valid = _validCssSelectors[selector] = 0
}
errors.push(selector)
return false
valid || errors.push(selector)
return !!valid
}

const checkCssSelector = (opt: CssOptions, value: string): string => {
let selectorsInDefault: string | undefined
value = value.replace(<RegExpOne & RegExpSearchable<0>> /:default\([^)]*\)/, Build.NDEBUG ? kDefaultRule as never
: (s: string): string => (selectorsInDefault = s.slice(kDefaultRule.length + 1, -1), kDefaultRule))
let selectorsInDefault: string | null | undefined
if (!Build.NDEBUG) {
selectorsInDefault = (<RegExpOne> /:default\(([^)]*)\)/).exec(TextOption_.prototype.readRaw_.call(opt))?.[1]
}
interface Line { s: string, c: string }
let outputs: Line[] = []
let lastRule: Line = { s: ";", c: "" }
for (const line of value.split("\n")) {
const trimmedLine = line.trim()
if (!trimmedLine || trimmedLine.startsWith("//") || trimmedLine.startsWith("# ")) {
outputs.push({ s: "", c: trimmedLine })
continue
}
const commentSep = line.indexOf("//")
const subLines: Line[] = (commentSep > 0 ? line.slice(0, commentSep) : line).split(";").map((i, ind, arr): Line => {
i = ind ? i.trim() : i.trimRight()
return { s: ind < arr.length - 1 ? i + ";" : i, c: "" }
})
subLines[subLines.length - 1].s || subLines.pop()
commentSep > 0 && (subLines[subLines.length - 1].c = line.slice(commentSep))
const prevChar = lastRule.s.slice(-1)
if (prevChar !== ";" && subLines[0].s.includes("##")) {
lastRule.s = (prevChar === "," ? lastRule.s.slice(0, -1) : lastRule.s) + ";"
} else if (!",;".includes(prevChar) && !",;".includes(subLines[0].s[0])) {
lastRule.s += ","
}
outputs.push(...subLines)
lastRule = outputs[outputs.length - 1]
}
lastRule.s.endsWith(";") || (lastRule.s += ";")

interface Group { l: Line[], s: string, i: number }
const groups: Group[] = []
let lastGroup: Group = { l: [{ s: ";", c: "" }], s: "", i: 0 }
for (const line of outputs) {
if (lastGroup.l[lastGroup.l.length - 1].s.endsWith(";")) {
groups.push(lastGroup = { l: [line], s: "", i: 0 })
} else {
lastGroup.l.push(line)
lastGroup.s || (lastGroup.s = line.s)
}
if (!lastGroup.s && line.s) {
lastGroup.s = line.s
lastGroup.i = groups.length - outputs.length * (line.s.includes("##") ? 2 : 1)
}
}
groups.sort((a, b): number => a.i - b.i)

const errors: string[] = []
value = Build.NDEBUG && value === kDefaultRule ? bgSettings_.defaults_[opt.field_]
: sortCssRules(value.split("\n").map(i => {
i = i.trim()
return !i ? "" : i.includes("##") ? `;${i};` : isValidCssSelector(opt, Build.NDEBUG || i !== kDefaultRule
? i : selectorsInDefault || i, errors) ? `,${i},` : `\0${i}\0`
}).filter(i => !!i))
.join("").replace(<RegExpG & RegExpSearchable<0>> /\0+/g, (s) => s.length > 1 ? "," : ";")
.replace(<RegExpG> /,[,\s]+/g, ",").replace(<RegExpG> /,;[,;]*|;[,;]+/g, ";")
.replace(<RegExpOne> /^[,;]/, "").replace(<RegExpOne> /[,;]$/, "").replace(<RegExpG> / > /g, ">")
let stream: string = ""
Build.NDEBUG || selectorsInDefault && isValidCssSelector(opt, selectorsInDefault, errors)
for (const group of groups) {
for (const line of group.l) {
if (line.c) { stream += line.c + "\n"; continue }
let s = line.s.replace(<RegExpG> /\s{2,}/g, " ")
.replace(<RegExpG & RegExpSearchable<1>> /^ | ?([,>]) ?/g, (_, x) => x || " ")
s = CssSelectorOption_.WrapAndOutput_(s)
const selectors = s.slice(s.indexOf("##") + 1).replace(<RegExpG> /\n /g, "")
selectors.split(", ").forEach(i => isValidCssSelector(opt, i, errors))
s = s.replace(<RegExpG & RegExpSearchable<0>> /, | > /g, s => s.trim())
s = line.c ? s + " " + line.c + "\n" : s + "\n"
stream += s
}
}
stream = stream.trim()
if (errors.length > 0) {
errors.unshift(oTrans_("invalidCss"))
opt.showError_(errors.join("\n"), "has-error")
} else {
_validCssSelectors = {}
opt.showError_("")
}
return value
return stream
}

Option_.all_.passEsc.checker_ = { status_: 0, check_: checkCssSelector.bind(null, Option_.all_.passEsc) }
Option_.all_.ignoreReadonly.checker_ = { status_: 0, check_: checkCssSelector.bind(null, Option_.all_.ignoreReadonly) }
Option_.all_.passEsc.checker_ = { status_: 0, check_: checkCssSelector.bind(null
, Option_.all_.passEsc as CssSelectorOption_<"passEsc">) }
Option_.all_.ignoreReadonly.checker_ = { status_: 0, check_: checkCssSelector.bind(null
, Option_.all_.ignoreReadonly as CssSelectorOption_<"ignoreReadonly">) }
63 changes: 35 additions & 28 deletions pages/options_defs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
CurCVer_, OnChrome, OnFirefox, $, $$, nextTick_, post_, enableNextTick_, kReadyInfo, toggleReduceMotion_, OnEdge,
CurFFVer_, OnSafari, prevent_
CurFFVer_, OnSafari, prevent_, bTrans_
} from "./async_bg"
import type {
AllowedOptions, Checker, PossibleOptionNames, KnownOptionsDataset, OptionErrorType, ExclusionRealNode,
Expand Down Expand Up @@ -346,39 +346,46 @@ export class TextOption_<T extends TextualizedOptionNames> extends Option_<T> {

export class NonEmptyTextOption_<T extends TextOptionNames> extends TextOption_<T> {
override readValueFromElement_ (): string {
let value = super.readValueFromElement_()
if (!value) {
value = bgSettings_.defaults_[this.field_]
this.populateElement_(value, true)
if (!this.element_.value.trim()) {
this.populateElement_(bgSettings_.defaults_[this.field_], true)
}
return value
return super.readValueFromElement_()
}
}

export const kDefaultRule = ":default"

export class CssSelectorOption_ extends TextOption_<"passEsc" | "ignoreReadonly"> {
override readValueFromElement_(): string {
let value = this.readRaw_()
if (!value) {
value = kDefaultRule
this.populateElement_(value, true)
}
value = this.checker_ ? this.checker_.check_(value) as string : value
export class CssSelectorOption_<T extends "passEsc" | "ignoreReadonly"> extends NonEmptyTextOption_<T> {
override readRaw_ (): string {
const value = super.readRaw_()
return value.replace(<RegExpOne> /:default\([^)]*\)/, GlobalConsts.kCssDefault)
}
override formatValue_(value: string): string {
value = value.replace(<RegExpG & RegExpSearchable<1>> /(?:^# |\/\/)[^\n]*|([,>])/g, (full, s): string => {
return s ? s === "," ? ", " : " > " : full
})
value = value.replace(<RegExpOne & RegExpSearchable<2>> /(^|\n):default(?!\()/, (_, prefix): string => {
const val_with_default = `${GlobalConsts.kCssDefault}(${this.getRealDefault()})`
return prefix + CssSelectorOption_.WrapAndOutput_(val_with_default)
})
return value
}
override formatValue_ (value: string): string {
let default_val = bgSettings_.defaults_[this.field_]
value = value !== default_val ? value : kDefaultRule
value = value.replace(kDefaultRule + ",", `${kDefaultRule}\n`).replace("," + kDefaultRule, `\n${kDefaultRule}`)
value = value.replace(<RegExpG> /;/g, "\n")
value = value.split("\n").map(i => i.length>64 && !i.includes("##") ? i.replace(<RegExpG>/,/g, "\n") : i).join("\n")
value = value.replace(<RegExpG> /,/g, ", ")
default_val = default_val.length > 50 ? default_val.replace(<RegExpG> /,/g, ",\n ") : default_val
value = value.replace(kDefaultRule, `${kDefaultRule}(${default_val})\n`).replace(<RegExpG> /\n\n+/g, "\n")
value = value.endsWith(")\n") && value.split("\n").length >= 5 ? value.slice(0, -1) : value
value = value.replace(<RegExpG> />/g, " > ")
return value
getRealDefault(): string {
return bTrans_((this.field_ === "passEsc" ? "" + kTip.defaultPassEsc : "" + kTip.defaultIgnoreReadonly))
}
static WrapAndOutput_ (line: string): string {
const hostSep = line.indexOf("##")
let str = hostSep >= 0 ? line.slice(0, hostSep + 2) : ""
let output = ""
line = hostSep >= 0 ? line.slice(hostSep + 2) : line
line = line.replace(<RegExpSearchable<1>> /,|>/g, s => s === "," ? ", " : " > ")
for (const i of line.split(", ")) {
if (str && str.length + i.length > 62) {
output.length ? (output += "\n" + str + ",") : (output = str + ",")
str = " " + i
} else {
str = str ? str + ", " + i : i
}
}
return str ? output ? output + "\n" + str : str : output
}
}

Expand Down
1 change: 1 addition & 0 deletions typings/messages.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ declare const enum kTip {
/* 105: */ highContrast_WOB, invisibleElements, imgExt, searchResults, excludeWhenGoNext,
/* 110: */ kCommonEvents, logOmniFallback, logNotWorkOnSandboxed, logGrabFocus, prev,
/* 115: */ next, ReplacedHtmlTags, DefaultDoClickOn, DefaultClickableOnHost, readOnly,
/* 120: */ defaultIgnoreReadonly, defaultPassEsc,
INJECTED_CONTENT_END,
/* 200: */ XHTML = 200,
/** used by {@link ../Gulpfile.js} */ extendClick = 999,
Expand Down
1 change: 1 addition & 0 deletions typings/vimium_c.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,7 @@ declare const enum GlobalConsts {
ChromeHelp = "https://support.google.com/chrome/answer/157179",
EdgStorePage = "https://microsoftedge.microsoft.com/addons/detail/aibcglbfblnogfjhbcmmpobjhnomhcdo",
EdgHelp = "https://support.microsoft.com/help/4531783/microsoft-edge-keyboard-shortcuts",
kCssDefault = ":default",

kIsHighContrast = "isHC_f",
MinCancelableInBackupTimer = 50,
Expand Down

0 comments on commit 4e01d49

Please sign in to comment.