Skip to content

Commit c2680b8

Browse files
committed
feat: preferences option DEFAULT_MENU supports __LAST__
1 parent ade306a commit c2680b8

File tree

10 files changed

+122
-39
lines changed

10 files changed

+122
-39
lines changed

.github/workflows/checkSettingSchemasOnCommit.yaml

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@ jobs:
2727
if (key == null) {
2828
return false
2929
}
30+
if (obj.hasOwnProperty(key)) {
31+
return true
32+
}
3033
return key.split(".").every(k => {
31-
if (obj && typeof obj === "object" && Object.hasOwn(obj, k)) {
34+
if (obj && typeof obj === "object" && obj.hasOwnProperty(k)) {
3235
obj = obj[k]
3336
return true
3437
}
@@ -45,17 +48,14 @@ jobs:
4548
}
4649
4750
const loadSchemas = () => {
48-
const schemas = require("./plugin/preferences/schemas.js", "utf-8")
51+
const schemas = require("./plugin/preferences/schemas.js")
4952
Object.values(schemas).forEach(boxes => {
5053
boxes.forEach(box => box.fields = box.fields.filter(ctl => ctl.key && ctl.type !== "static" && ctl.type !== "action"))
5154
})
5255
return schemas
5356
}
5457
55-
(async () => {
56-
const schemas = loadSchemas()
57-
const settings = await loadSettings()
58-
58+
const checkExtraKeys = (schemas, settings) => {
5959
Object.entries(schemas).forEach(([fixedName, boxes]) => {
6060
const setting = settings[fixedName]
6161
boxes.forEach(box => {
@@ -66,17 +66,55 @@ jobs:
6666
})
6767
})
6868
})
69+
}
6970
71+
const checkMessingKeys = (schemas, settings) => {
72+
const isIgnored = (fixedName, key) => (
73+
fixedName === "abc" && key.startsWith("VISUAL_OPTIONS")
74+
|| (fixedName === "markdownLint") && key.startsWith("rule_config")
75+
)
76+
const flattenKeys = (obj, prefix = [], result = new Set()) => {
77+
for (const [key, val] of Object.entries(obj)) {
78+
if (Array.isArray(val)) {
79+
val.forEach(k => flattenKeys(val, [...prefix, key], result))
80+
} else if (typeof val === "object") {
81+
flattenKeys(val, [...prefix, key], result)
82+
} else {
83+
const pre = prefix.length === 0 ? "" : prefix.join(".") + "."
84+
const r = (pre + key).replace(/\.\d+/g, "")
85+
result.add(r)
86+
}
87+
}
88+
return result
89+
}
7090
Object.entries(settings).forEach(([fixedName, setting]) => {
7191
const boxes = schemas[fixedName]
72-
const keysInSchema = new Set(boxes.flatMap(box => box.fields.map(ctl => ctl.key)))
73-
keysInSchema.forEach(key => {
74-
const exist = hasNestedProperty(setting, key)
75-
if (!exist) {
76-
throw new TypeError(`schemas ${fixedName} has no such Key: ${key}`)
77-
}
92+
if (!boxes) {
93+
throw new TypeError(`schemas has no such schema: ${fixedName}`)
94+
}
95+
const keysInBoxes = boxes.flatMap(box => {
96+
return box.fields.flatMap(field => {
97+
return field.type !== "table"
98+
? field.key
99+
: field.nestedBoxes.flatMap(b => b.fields.flatMap(f => `${field.key}.${f.key}`))
100+
})
101+
})
102+
const keysObject = Object.fromEntries(keysInBoxes.map(e => [e.replace(/\.\d+/g, ""), undefined]))
103+
const newKeys = flattenKeys(setting)
104+
newKeys.forEach(key => {
105+
const exist = hasNestedProperty(keysObject, key)
106+
if (exist || isIgnored(fixedName, key)) return
107+
throw new TypeError(`schemas ${fixedName} has no such Key: ${key}`)
78108
})
79109
})
110+
}
111+
112+
(async () => {
113+
const schemas = loadSchemas()
114+
const settings = await loadSettings()
115+
checkExtraKeys(schemas, settings)
116+
checkMessingKeys(schemas, settings)
117+
console.log("schemas test passed")
80118
})()
81119
- run: |
82120
echo "./plugin/preferences/schemas.js has checked"

plugin/global/core/utils/index.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -680,11 +680,11 @@ class utils {
680680
return fileName
681681
}
682682

683-
static getStorage = (plugin) => ({
684-
set: (key, value) => localStorage.setItem(`plugin.${plugin.fixedName}.${key}`, value + ""),
685-
get: (key) => localStorage.getItem(`plugin.${plugin.fixedName}.${key}`),
686-
has: (key) => localStorage.getItem(`plugin.${plugin.fixedName}.${key}`) != null,
687-
remove: (key) => localStorage.removeItem(`plugin.${plugin.fixedName}.${key}`),
683+
static getStorage = (key) => ({
684+
set: value => localStorage.setItem(key, JSON.stringify(value)),
685+
get: () => JSON.parse(localStorage.getItem(key)),
686+
exist: () => localStorage.getItem(key) != null,
687+
remove: () => localStorage.removeItem(key),
688688
})
689689

690690
////////////////////////////// Basic file operations //////////////////////////////

plugin/global/locales/en.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"search": "Search",
5555
"readonly": "ReadOnly",
5656
"processing": "Processing. One moment please...",
57+
"lastUsed": "Last Used",
5758
"disableReminder": "Do not Show Again",
5859
"takesEffectAfterRestart": "Takes Effect After Restart",
5960
"incompatibilityWarning": "Typora version is too low, some plugins may not work.\nPlease upgrade to 0.9.98 (the last free version)",
@@ -213,6 +214,8 @@
213214
"scope.linenum": "Number of Lines",
214215
"scope.charnum": "Number of Characters",
215216
"scope.chinesenum": "Number of Chinese Characters",
217+
"scope.wordnum": "Number of Words",
218+
"scope.readminutes": "Reading minutes",
216219
"scope.imagenum": "Number of Images",
217220
"scope.imgtagnum": "Number of IMG Elements",
218221
"scope.isempty": "Empty Content",
@@ -1477,7 +1480,8 @@
14771480
"pluginName": "Block Side By Side"
14781481
},
14791482
"sortableOutline": {
1480-
"pluginName": "Sortable Outline"
1483+
"pluginName": "Sortable Outline",
1484+
"$label.auto_save_file": "Auto Save File"
14811485
},
14821486
"redirectLocalRootUrl": {
14831487
"pluginName": "Redirect Local Root Url",

plugin/global/locales/zh-CN.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"search": "查找",
5555
"readonly": "只读",
5656
"processing": "处理中,请稍等",
57+
"lastUsed": "上次使用的",
5758
"disableReminder": "不再提示",
5859
"takesEffectAfterRestart": "修改将在重启后生效",
5960
"incompatibilityWarning": "Typora 版本过低,部分插件可能失效。\n建议升级到 0.9.98 (最后一个免费版本)",
@@ -213,6 +214,8 @@
213214
"scope.linenum": "行数",
214215
"scope.charnum": "字符数",
215216
"scope.chinesenum": "中文字符数",
217+
"scope.wordnum": "单词数",
218+
"scope.readminutes": "阅读分钟数",
216219
"scope.imagenum": "图片数量",
217220
"scope.imgtagnum": "IMG 标签数量",
218221
"scope.isempty": "内容为空",
@@ -1477,7 +1480,8 @@
14771480
"pluginName": "并列显示活动块"
14781481
},
14791482
"sortableOutline": {
1480-
"pluginName": "大纲拖拽排序"
1483+
"pluginName": "大纲拖拽排序",
1484+
"$label.auto_save_file": "自动保存文件"
14811485
},
14821486
"redirectLocalRootUrl": {
14831487
"pluginName": "重定向本地资源根目录",

plugin/global/locales/zh-TW.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"search": "搜尋",
5555
"readonly": "唯讀",
5656
"processing": "處理中,請稍等",
57+
"lastUsed": "上次使用的",
5758
"disableReminder": "不再提示",
5859
"takesEffectAfterRestart": "修改將在重啟後生效",
5960
"incompatibilityWarning": "Typora 版本過低,部分插件可能失效。\n建議升級到 0.9.98 (最後一個免費版本)",
@@ -213,6 +214,8 @@
213214
"scope.linenum": "行數",
214215
"scope.charnum": "字元數",
215216
"scope.chinesenum": "中文字元數",
217+
"scope.wordnum": "單詞數",
218+
"scope.readminutes": "閱讀分鐘數",
216219
"scope.imagenum": "圖片數量",
217220
"scope.imgtagnum": "IMG 標籤數量",
218221
"scope.isempty": "內容爲空",
@@ -1477,7 +1480,8 @@
14771480
"pluginName": "並排顯示活動區塊"
14781481
},
14791482
"sortableOutline": {
1480-
"pluginName": "大綱拖拽排序"
1483+
"pluginName": "大綱拖拽排序",
1484+
"$label.auto_save_file": "自動保存文檔"
14811485
},
14821486
"redirectLocalRootUrl": {
14831487
"pluginName": "重新導向本地資源根目錄",

plugin/global/settings/settings.default.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2126,6 +2126,8 @@ NAME = ""
21262126
HOTKEY = ""
21272127

21282128
# 打开设置页面时,默认定位到哪个插件的菜单
2129+
# 填入插件 fixedName
2130+
# 支持填入__LAST__,表示最近使用的菜单
21292131
DEFAULT_MENU = "global"
21302132

21312133
# 设置页面里隐藏菜单的插件

plugin/preferences/index.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class preferencesPlugin extends BasePlugin {
2424
`
2525

2626
init = () => {
27+
this.fallbackMenu = "global"
28+
this.menuStorage = this.utils.getStorage(`${this.fixedName}.menu`)
2729
this.entities = {
2830
dialog: document.querySelector(".plugin-preferences-dialog"),
2931
menu: document.querySelector(".plugin-preferences-menu"),
@@ -133,7 +135,8 @@ class preferencesPlugin extends BasePlugin {
133135
this.utils.notification.show(this.i18n._t("global", "takesEffectAfterRestart"))
134136
}
135137
} else {
136-
await this.showDialog(this.config.DEFAULT_MENU)
138+
const menu = (this.config.DEFAULT_MENU === "__LAST__") ? this.menuStorage.get() : this.config.DEFAULT_MENU
139+
await this.showDialog(menu)
137140
this.utils.show(this.entities.dialog)
138141
}
139142
}
@@ -147,8 +150,9 @@ class preferencesPlugin extends BasePlugin {
147150
return `<div class="plugin-preferences-menu-item" data-plugin="${name}">${showName}</div>`
148151
})
149152
this.entities.menu.innerHTML = menus.join("")
150-
fixedName = plugins.hasOwnProperty(fixedName) ? fixedName : "global"
151-
await this.switchMenu(fixedName)
153+
154+
const menu = plugins.hasOwnProperty(fixedName) ? fixedName : this.fallbackMenu
155+
await this.switchMenu(menu)
152156
setTimeout(() => {
153157
const active = this.entities.menu.querySelector(".plugin-preferences-menu-item.active")
154158
active.scrollIntoView({ block: "center" })
@@ -157,7 +161,7 @@ class preferencesPlugin extends BasePlugin {
157161

158162
switchMenu = async (fixedName) => {
159163
if (this.config.HIDE_MENUS.includes(fixedName)) {
160-
fixedName = "global"
164+
fixedName = this.fallbackMenu
161165
}
162166
const schema = this.SETTING_SCHEMAS[fixedName]
163167
if (!schema) return
@@ -170,6 +174,8 @@ class preferencesPlugin extends BasePlugin {
170174
menuItem.classList.add("active")
171175
this.entities.title.textContent = menuItem.textContent
172176
$(this.entities.main).animate({ scrollTop: 0 }, 300)
177+
178+
this.menuStorage.set(fixedName)
173179
}
174180

175181
_getAllPlugins = () => {
@@ -520,7 +526,7 @@ class preferencesPlugin extends BasePlugin {
520526
},
521527
"preferences.DEFAULT_MENU": (field, data) => {
522528
if (!field.options) {
523-
field.options = this._getAllPlugins()
529+
field.options = { __LAST__: this.i18n._t("global", "lastUsed"), ...this._getAllPlugins() }
524530
}
525531
},
526532
"preferences.HIDE_MENUS": (field, data) => {

plugin/preferences/schemas.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,6 +1557,9 @@ const SETTING_SCHEMAS = {
15571557
],
15581558
sortableOutline: [
15591559
customPluginLiteBasePropBox,
1560+
UntitledBox(
1561+
Switch("auto_save_file"),
1562+
),
15601563
handleSettingsBox,
15611564
],
15621565
redirectLocalRootUrl: [

plugin/search_multi/searcher.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,29 @@ class Searcher {
253253
const { yamlObject } = splitFrontMatter(content)
254254
return yamlObject ? JSON.stringify(yamlObject) : ""
255255
},
256+
wordnum: ({ path, file, stats, content }) => {
257+
content = content.trim()
258+
if (content.length === 0) {
259+
return 0
260+
}
261+
let replaceCount = 0
262+
content = content
263+
// Replace Chinese characters with spaces
264+
.replace(/[\u3040-\uABFF\uD7A4-\uFAFF]/gi, function () {
265+
replaceCount++
266+
return " "
267+
})
268+
// Replace the letter following a single quotation mark or apostrophe with the b
269+
.replace(/[']\w+/g, "b")
270+
// Replace specific punctuation marks with spaces
271+
.replace(/(^|\s+)[(\u3000-\u303F)!-\/:-@\[-`{-~]+(\s+|$)/gm, " ")
272+
const words = content.split(/[(\u3000-\u303F)\s!-,\\:-@\[-`{-~]+/g)
273+
return words.length - 2 + replaceCount
274+
},
275+
readminutes: ({ path, file, stats, content }) => {
276+
const wordsPerMinute = File.option.wordsPerMinute || 300
277+
return QUERY.wordnum({ path, file, stats, content }) / wordsPerMinute
278+
}
256279
}
257280
const PROCESS = {
258281
size: { validate: isSize, cast: toBytes },
@@ -279,6 +302,8 @@ class Searcher {
279302
buildQualifier("atime", true, false, 1, none, PROCESS.date),
280303
buildQualifier("linenum", true, true, 2, none, PROCESS.number),
281304
buildQualifier("charnum", true, true, 2, none, PROCESS.number),
305+
buildQualifier("wordnum", true, true, 3, none, PROCESS.number),
306+
buildQualifier("readminutes", true, true, 3, none, PROCESS.number),
282307
buildQualifier("chinesenum", true, true, 2, none, PROCESS.number),
283308
buildQualifier("imagenum", true, true, 2, none, PROCESS.number),
284309
buildQualifier("imgtagnum", true, true, 2, none, PROCESS.number),

plugin/window_tab.js

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@ class windowTabBarPlugin extends BasePlugin {
2323
]
2424

2525
init = () => {
26-
this.manualSaveKey = "manualSaved"
27-
this.autoSaveKey = "autoSaved"
28-
this.storage = this.utils.getStorage(this)
26+
this.manualSaveStorage = this.utils.getStorage(`${this.fixedName}.manual`)
27+
this.autoSaveStorage = this.utils.getStorage(`${this.fixedName}.auto`)
2928

3029
this.staticActions = this.i18n.fillActions([
3130
{ act_value: "sort_tabs", act_hotkey: this.config.SORT_TABS_HOTKEY },
@@ -389,10 +388,10 @@ class windowTabBarPlugin extends BasePlugin {
389388
const reopenTabsWhenInit = () => {
390389
this.utils.eventHub.addEventListener(this.utils.eventHub.eventType.allPluginsHadInjected, () => {
391390
// Redirection is disabled when opening specific files (isDiscardableUntitled === false).
392-
this.utils.loopDetector(this.utils.isDiscardableUntitled, () => this.openSaveTabs(this.autoSaveKey, true), 50, 2000, false)
391+
this.utils.loopDetector(this.utils.isDiscardableUntitled, () => this.openSaveTabs(this.autoSaveStorage, true), 50, 2000, false)
393392

394393
setTimeout(() => {
395-
this.utils.eventHub.addEventListener(this.utils.eventHub.eventType.fileContentLoaded, () => this.saveTabs(this.autoSaveKey))
394+
this.utils.eventHub.addEventListener(this.utils.eventHub.eventType.fileContentLoaded, () => this.saveTabs(this.autoSaveStorage))
396395
}, 2000)
397396
})
398397
}
@@ -447,7 +446,7 @@ class windowTabBarPlugin extends BasePlugin {
447446
}
448447

449448
getDynamicActions = () => this.i18n.fillActions([
450-
{ act_value: "open_save_tabs", act_hidden: !this.storage.has(this.manualSaveKey) },
449+
{ act_value: "open_save_tabs", act_hidden: !this.manualSaveStorage.exist() },
451450
{ act_value: "toggle_file_ext", act_state: this.config.TRIM_FILE_EXT },
452451
{ act_value: "toggle_show_dir", act_state: this.config.SHOW_DIR_ON_DUPLICATE },
453452
{ act_value: "toggle_show_close_button", act_state: this.config.SHOW_TAB_CLOSE_BUTTON },
@@ -466,9 +465,9 @@ class windowTabBarPlugin extends BasePlugin {
466465
toggle_show_dir: () => toggleConfig("SHOW_DIR_ON_DUPLICATE"),
467466
toggle_file_ext: () => toggleConfig("TRIM_FILE_EXT"),
468467
toggle_show_close_button: () => toggleConfig("SHOW_TAB_CLOSE_BUTTON"),
469-
save_tabs: this.saveTabs,
468+
save_tabs: () => this.saveTabs(this.manualSaveStorage),
469+
open_save_tabs: () => this.openSaveTabs(this.manualSaveStorage),
470470
sort_tabs: this.sortTabs,
471-
open_save_tabs: this.openSaveTabs,
472471
toggle_tab_bar: this.forceToggleTabBar,
473472
}
474473
const func = callMap[action]
@@ -875,21 +874,19 @@ class windowTabBarPlugin extends BasePlugin {
875874

876875
openInNewWindow = idx => this.openFileNewWindow(this.tabUtil.getTabPathByIdx(idx), false)
877876

878-
saveTabs = (key = this.manualSaveKey) => {
877+
saveTabs = (storage) => {
879878
const mount_folder = this.utils.getMountFolder()
880879
const save_tabs = this.tabUtil.tabs.map((tab, idx) => ({
881880
idx: idx,
882881
path: tab.path,
883882
active: idx === this.tabUtil.activeIdx,
884883
scrollTop: tab.scrollTop,
885884
}))
886-
const result = JSON.stringify({ mount_folder, save_tabs })
887-
this.storage.set(key, result)
885+
storage.set({ mount_folder, save_tabs })
888886
}
889887

890-
openSaveTabs = (key = this.manualSaveKey, matchMountFolder = false) => {
891-
const data = this.storage.get(key)
892-
const { save_tabs, mount_folder } = JSON.parse(data || "{}")
888+
openSaveTabs = (storage, matchMountFolder = false) => {
889+
const { save_tabs, mount_folder } = storage.get() || {}
893890
if (!save_tabs || save_tabs.length === 0) return
894891
if (matchMountFolder && mount_folder !== this.utils.getMountFolder()) return
895892

0 commit comments

Comments
 (0)