49 Commits

Author SHA1 Message Date
7b746155f7 ♻️ Refactor keybinding service 2025-12-20 16:43:04 +08:00
401eb3ab39 ⬆️ Upgrade dependencies 2025-12-17 23:19:50 +08:00
d3eba96a29 🐛 Fixed assignment issues 2025-12-17 22:55:52 +08:00
8a10b8fe0f ♻️ Refactor cursor position cache 2025-12-17 00:12:59 +08:00
8fce8bdca4 ♻️ Refactor backup service complete.
Some checks failed
CodeQL Advanced / Analyze (go) (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (c-cpp) (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
CodeQL Advanced / Analyze (rust) (push) Has been cancelled
2025-12-16 23:20:40 +08:00
1ab934cee9 Merge pull request #13 from landaiqing/alert-autofix-1
Some checks failed
CodeQL Advanced / Analyze (go) (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (c-cpp) (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
CodeQL Advanced / Analyze (rust) (push) Has been cancelled
Potential fix for code scanning alert no. 1: Workflow does not contain permissions
2025-12-16 15:20:27 +08:00
6659ac6fad 🐛 Potential fix for code scanning alert no. 1: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-12-16 15:15:36 +08:00
3a5ab1c614 Merge pull request #12 from landaiqing/alert-autofix-2
Potential fix for code scanning alert no. 2: Workflow does not contain permissions
2025-12-16 14:44:06 +08:00
1e07e1f833 🐛 Potential fix for code scanning alert no. 2: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-12-16 14:36:57 +08:00
e1e91a3683 👷 Change build mode for C/C++ in CodeQL workflow 2025-12-16 14:28:00 +08:00
c30d95a3e0 🐛 Potential fix for code scanning alert no. 3: Replacement of a substring with itself
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-12-16 14:14:52 +08:00
97f6fa843c 👷 Add CodeQL analysis workflow configuration 2025-12-16 14:01:02 +08:00
f43fc47539 Merge pull request #11 from landaiqing/dependabot/npm_and_yarn/frontend/mdast-util-to-hast-13.2.1
⬆️ Bump mdast-util-to-hast from 13.2.0 to 13.2.1 in /frontend
2025-12-16 13:48:54 +08:00
dependabot[bot]
c330de52fa ⬆️ Bump mdast-util-to-hast from 13.2.0 to 13.2.1 in /frontend
Bumps [mdast-util-to-hast](https://github.com/syntax-tree/mdast-util-to-hast) from 13.2.0 to 13.2.1.
- [Release notes](https://github.com/syntax-tree/mdast-util-to-hast/releases)
- [Commits](https://github.com/syntax-tree/mdast-util-to-hast/compare/13.2.0...13.2.1)

---
updated-dependencies:
- dependency-name: mdast-util-to-hast
  dependency-version: 13.2.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-16 05:46:59 +00:00
67d35626cb 🚧 Refactor backup service 2025-12-14 23:48:52 +08:00
cc4c2189dc 🚧 Refactor basic services 2025-12-14 02:19:50 +08:00
d16905c0a3 🐛 Fixed some known issues 2025-12-12 22:56:22 +08:00
4e611db349 Optimize minmap extension completed 2025-12-11 23:59:06 +08:00
7e9fc0ac3f Optimize minmap extension 2025-12-10 22:25:19 +08:00
ff072d1a93 Optimize minmap performance 2025-12-10 00:41:24 +08:00
a9c81c878e 🚚 2025-12-08 23:28:36 +08:00
3660d13d7d ♻️ Refactor search 2025-12-08 23:20:37 +08:00
281f53c049 Optimized markdown preview performance 2025-12-07 00:09:52 +08:00
71ca541f78 🚧 Added support for markdown preview table 2025-12-04 00:47:51 +08:00
91f4f4afac Merge branch 'markdown'
# Conflicts:
#	frontend/package-lock.json
2025-12-03 00:46:17 +08:00
fc5639d7bd 🚧 Added support for markdown preview math 2025-12-03 00:45:01 +08:00
dependabot[bot]
6668c11846 ⬆️ Bump mdast-util-to-hast from 13.2.0 to 13.2.1 in /frontend
Bumps [mdast-util-to-hast](https://github.com/syntax-tree/mdast-util-to-hast) from 13.2.0 to 13.2.1.
- [Release notes](https://github.com/syntax-tree/mdast-util-to-hast/releases)
- [Commits](https://github.com/syntax-tree/mdast-util-to-hast/compare/13.2.0...13.2.1)

---
updated-dependencies:
- dependency-name: mdast-util-to-hast
  dependency-version: 13.2.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-02 04:14:35 +00:00
17f3351cea 🚧 Added support for markdown preview footnotes 2025-12-02 00:22:22 +08:00
dd3dd4ddb2 🚧 Refactor markdown preview extension 2025-12-01 00:00:05 +08:00
60d1494d45 🚧 Refactor markdown preview extension 2025-11-30 01:09:31 +08:00
1ef5350b3f 🚧 Refactor markdown preview extension 2025-11-29 22:54:38 +08:00
3521e5787b 🚧 Refactor markdown preview extension 2025-11-29 19:24:20 +08:00
8d9bcdad7e 🚧 Refactor markdown preview extension 2025-11-28 00:38:38 +08:00
ac086db1ed ♻️ Updated markdown preview extension 2025-11-26 22:11:16 +08:00
6dff0181d2 ♻️ Refactored markdown preview extension 2025-11-24 00:10:28 +08:00
ad24d3a140 ♻️ Refactored translation extension 2025-11-23 18:45:49 +08:00
4b0f39d747 Merge branch 'master' into dev 2025-11-21 23:37:36 +08:00
096cc1da94 🎨 Optimize hyperlink extension 2025-11-21 23:35:42 +08:00
2d3200ad97 ♻️ Refactor context menu 2025-11-21 22:30:47 +08:00
4e82e2f6f7 ♻️ Refactor the Markdown preview theme application logic 2025-11-21 20:20:06 +08:00
339ed53c2e ♻️ Refactor theme module 2025-11-21 00:03:03 +08:00
fc7c162e2f ♻️ Refactor theme module 2025-11-20 23:07:12 +08:00
dependabot[bot]
24f1549730 ⬆️ Bump golang.org/x/crypto from 0.44.0 to 0.45.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.44.0 to 0.45.0.
- [Commits](https://github.com/golang/crypto/compare/v0.44.0...v0.45.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.45.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-20 03:04:41 +00:00
5584a46ca2 ♻️ Refactor theme module 2025-11-20 00:39:00 +08:00
4471441d6f ♻️ Refactor some code 2025-11-19 20:54:58 +08:00
991a89147e 🎨 Optimize code 2025-11-17 23:14:58 +08:00
a08c0d8448 🎨 Modify code block logic 2025-11-17 22:11:16 +08:00
59db8dd177 Added Monocraft font 2025-11-16 22:04:02 +08:00
29693f1baf 💄 Modify some styles 2025-11-16 21:23:59 +08:00
331 changed files with 37101 additions and 22661 deletions

View File

@@ -28,6 +28,7 @@ env:
jobs:
# 准备构建配置
prepare:
permissions: {}
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
@@ -84,6 +85,8 @@ jobs:
fi
build:
permissions:
contents: read
needs: prepare
if: ${{ fromJson(needs.prepare.outputs.matrix).include[0] != null }}
strategy:

109
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,109 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL Advanced"
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
schedule:
- cron: '29 8 * * 3'
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: actions
build-mode: none
- language: c-cpp
build-mode: none
- language: go
build-mode: autobuild
- language: javascript-typescript
build-mode: none
- language: python
build-mode: none
- language: rust
build-mode: none
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node`
# or others). This is typically only required for manual builds.
# - name: Setup runtime (example)
# uses: actions/setup-example@v1
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- name: Run manual build steps
if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{matrix.language}}"

View File

@@ -61,5 +61,3 @@ export class ServiceOptions {
return new ServiceOptions($$parsedSource as Partial<ServiceOptions>);
}
}
export type Window = any;

View File

@@ -0,0 +1,4 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export * from "./models.js";

View File

@@ -0,0 +1,17 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Create as $Create} from "@wailsio/runtime";
/**
* CancelFunc 取消订阅函数
* 调用此函数可以取消对配置的监听
*/
export type CancelFunc = any;
/**
* ObserverCallback 观察者回调函数
*/
export type ObserverCallback = any;

View File

@@ -0,0 +1,4 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export * from "./models.js";

View File

@@ -0,0 +1,370 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Create as $Create} from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as theme$0 from "./theme/models.js";
/**
* Document is the model entity for the Document schema.
*/
export class Document {
/**
* ID of the ent.
*/
"id"?: number;
/**
* UUID for cross-device sync (UUIDv7)
*/
"uuid": string;
/**
* creation time
*/
"created_at": string;
/**
* update time
*/
"updated_at": string;
/**
* deleted at
*/
"deleted_at"?: string | null;
/**
* document title
*/
"title": string;
/**
* document content
*/
"content": string;
/**
* document locked status
*/
"locked": boolean;
/** Creates a new Document instance. */
constructor($$source: Partial<Document> = {}) {
if (!("uuid" in $$source)) {
this["uuid"] = "";
}
if (!("created_at" in $$source)) {
this["created_at"] = "";
}
if (!("updated_at" in $$source)) {
this["updated_at"] = "";
}
if (!("title" in $$source)) {
this["title"] = "";
}
if (!("content" in $$source)) {
this["content"] = "";
}
if (!("locked" in $$source)) {
this["locked"] = false;
}
Object.assign(this, $$source);
}
/**
* Creates a new Document instance from a string or object.
*/
static createFrom($$source: any = {}): Document {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new Document($$parsedSource as Partial<Document>);
}
}
/**
* Extension is the model entity for the Extension schema.
*/
export class Extension {
/**
* ID of the ent.
*/
"id"?: number;
/**
* UUID for cross-device sync (UUIDv7)
*/
"uuid": string;
/**
* creation time
*/
"created_at": string;
/**
* update time
*/
"updated_at": string;
/**
* deleted at
*/
"deleted_at"?: string | null;
/**
* extension name
*/
"name": string;
/**
* extension enabled or not
*/
"enabled": boolean;
/**
* extension config
*/
"config": { [_: string]: any };
/** Creates a new Extension instance. */
constructor($$source: Partial<Extension> = {}) {
if (!("uuid" in $$source)) {
this["uuid"] = "";
}
if (!("created_at" in $$source)) {
this["created_at"] = "";
}
if (!("updated_at" in $$source)) {
this["updated_at"] = "";
}
if (!("name" in $$source)) {
this["name"] = "";
}
if (!("enabled" in $$source)) {
this["enabled"] = false;
}
if (!("config" in $$source)) {
this["config"] = {};
}
Object.assign(this, $$source);
}
/**
* Creates a new Extension instance from a string or object.
*/
static createFrom($$source: any = {}): Extension {
const $$createField7_0 = $$createType0;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("config" in $$parsedSource) {
$$parsedSource["config"] = $$createField7_0($$parsedSource["config"]);
}
return new Extension($$parsedSource as Partial<Extension>);
}
}
/**
* KeyBinding is the model entity for the KeyBinding schema.
*/
export class KeyBinding {
/**
* ID of the ent.
*/
"id"?: number;
/**
* UUID for cross-device sync (UUIDv7)
*/
"uuid": string;
/**
* creation time
*/
"created_at": string;
/**
* update time
*/
"updated_at": string;
/**
* deleted at
*/
"deleted_at"?: string | null;
/**
* command identifier
*/
"name": string;
/**
* keybinding type: standard or emacs
*/
"type": string;
/**
* universal keybinding (cross-platform)
*/
"key"?: string;
/**
* macOS specific keybinding
*/
"macos"?: string;
/**
* Windows specific keybinding
*/
"windows"?: string;
/**
* Linux specific keybinding
*/
"linux"?: string;
/**
* extension name (functional category)
*/
"extension": string;
/**
* whether this keybinding is enabled
*/
"enabled": boolean;
/**
* prevent browser default behavior
*/
"preventDefault": boolean;
/**
* keybinding scope (default: editor)
*/
"scope"?: string;
/** Creates a new KeyBinding instance. */
constructor($$source: Partial<KeyBinding> = {}) {
if (!("uuid" in $$source)) {
this["uuid"] = "";
}
if (!("created_at" in $$source)) {
this["created_at"] = "";
}
if (!("updated_at" in $$source)) {
this["updated_at"] = "";
}
if (!("name" in $$source)) {
this["name"] = "";
}
if (!("type" in $$source)) {
this["type"] = "";
}
if (!("extension" in $$source)) {
this["extension"] = "";
}
if (!("enabled" in $$source)) {
this["enabled"] = false;
}
if (!("preventDefault" in $$source)) {
this["preventDefault"] = false;
}
Object.assign(this, $$source);
}
/**
* Creates a new KeyBinding instance from a string or object.
*/
static createFrom($$source: any = {}): KeyBinding {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new KeyBinding($$parsedSource as Partial<KeyBinding>);
}
}
/**
* Theme is the model entity for the Theme schema.
*/
export class Theme {
/**
* ID of the ent.
*/
"id"?: number;
/**
* UUID for cross-device sync (UUIDv7)
*/
"uuid": string;
/**
* creation time
*/
"created_at": string;
/**
* update time
*/
"updated_at": string;
/**
* deleted at
*/
"deleted_at"?: string | null;
/**
* theme name
*/
"name": string;
/**
* theme type
*/
"type": theme$0.Type;
/**
* theme colors
*/
"colors": { [_: string]: any };
/** Creates a new Theme instance. */
constructor($$source: Partial<Theme> = {}) {
if (!("uuid" in $$source)) {
this["uuid"] = "";
}
if (!("created_at" in $$source)) {
this["created_at"] = "";
}
if (!("updated_at" in $$source)) {
this["updated_at"] = "";
}
if (!("name" in $$source)) {
this["name"] = "";
}
if (!("type" in $$source)) {
this["type"] = ("" as theme$0.Type);
}
if (!("colors" in $$source)) {
this["colors"] = {};
}
Object.assign(this, $$source);
}
/**
* Creates a new Theme instance from a string or object.
*/
static createFrom($$source: any = {}): Theme {
const $$createField7_0 = $$createType0;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("colors" in $$parsedSource) {
$$parsedSource["colors"] = $$createField7_0($$parsedSource["colors"]);
}
return new Theme($$parsedSource as Partial<Theme>);
}
}
// Private type creation functions
const $$createType0 = $Create.Map($Create.Any, $Create.Any);

View File

@@ -0,0 +1,4 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export * from "./models.js";

View File

@@ -0,0 +1,22 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Create as $Create} from "@wailsio/runtime";
/**
* Type defines the type for the "type" enum field.
*/
export enum Type {
/**
* The Go zero value for the underlying type of the enum.
*/
$zero = "",
/**
* Type values.
*/
TypeDark = "dark",
TypeLight = "light",
};

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
// This file is automatically generated. DO NOT EDIT
/**
* BackupService 提供基于Git的备份功能
* BackupService 提供基于Git的备份同步功能
* @module
*/
@@ -18,7 +18,7 @@ import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/applic
import * as models$0 from "../models/models.js";
/**
* HandleConfigChange 处理备份配置变更
* HandleConfigChange 处理配置变更
*/
export function HandleConfigChange(config: models$0.GitBackupConfig | null): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(395287784, config) as any;
@@ -34,15 +34,7 @@ export function Initialize(): Promise<void> & { cancel(): void } {
}
/**
* PushToRemote 推送本地更改到远程仓库
*/
export function PushToRemote(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(262644139) as any;
return $resultPromise;
}
/**
* Reinitialize 重新初始化备份服务,用于响应配置变更
* Reinitialize 重新初始化
*/
export function Reinitialize(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(301562543) as any;
@@ -50,7 +42,7 @@ export function Reinitialize(): Promise<void> & { cancel(): void } {
}
/**
* ServiceShutdown 服务关闭时的清理工作
* ServiceShutdown 服务关闭
*/
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(422131801) as any;
@@ -63,7 +55,7 @@ export function ServiceStartup(options: application$0.ServiceOptions): Promise<v
}
/**
* StartAutoBackup 启动自动备份定时器
* StartAutoBackup 启动自动备份
*/
export function StartAutoBackup(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(3035755449) as any;
@@ -77,3 +69,11 @@ export function StopAutoBackup(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2641894021) as any;
return $resultPromise;
}
/**
* Sync 执行完整的同步流程:导出 -> commit -> pull -> 解决冲突 -> push -> 导入
*/
export function Sync(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(3420383211) as any;
return $resultPromise;
}

View File

@@ -15,11 +15,10 @@ import {Call as $Call, Create as $Create} from "@wailsio/runtime";
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as models$0 from "../models/models.js";
import * as helper$0 from "../common/helper/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as $models from "./models.js";
import * as models$0 from "../models/models.js";
/**
* Get 获取配置项
@@ -50,7 +49,7 @@ export function MigrateConfig(): Promise<void> & { cancel(): void } {
}
/**
* ResetConfig 强制重置所有配置为默认值
* ResetConfig 重置所有配置为默认值
*/
export function ResetConfig(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(3593047389) as any;
@@ -66,7 +65,7 @@ export function ServiceShutdown(): Promise<void> & { cancel(): void } {
}
/**
* ServiceStartup initializes the service when the application starts
* ServiceStartup 服务启动时初始化
*/
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(3311949428, options) as any;
@@ -84,7 +83,7 @@ export function Set(key: string, value: any): Promise<void> & { cancel(): void }
/**
* Watch 注册配置变更监听器
*/
export function Watch(path: string, callback: $models.ObserverCallback): Promise<$models.CancelFunc> & { cancel(): void } {
export function Watch(path: string, callback: helper$0.ObserverCallback): Promise<helper$0.CancelFunc> & { cancel(): void } {
let $resultPromise = $Call.ByID(1143583035, path, callback) as any;
return $resultPromise;
}
@@ -92,7 +91,7 @@ export function Watch(path: string, callback: $models.ObserverCallback): Promise
/**
* WatchWithContext 使用 Context 注册监听器
*/
export function WatchWithContext(path: string, callback: $models.ObserverCallback): Promise<void> & { cancel(): void } {
export function WatchWithContext(path: string, callback: helper$0.ObserverCallback): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1454973098, path, callback) as any;
return $resultPromise;
}

View File

@@ -2,7 +2,7 @@
// This file is automatically generated. DO NOT EDIT
/**
* DatabaseService provides shared database functionality
* DatabaseService 数据库服务
* @module
*/
@@ -15,15 +15,7 @@ import {Call as $Call, Create as $Create} from "@wailsio/runtime";
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
/**
* RegisterModel 注册模型与表的映射关系
*/
export function RegisterModel(tableName: string, model: any): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(175397515, tableName, model) as any;
return $resultPromise;
}
/**
* ServiceShutdown shuts down the service when the application closes
* ServiceShutdown 服务关闭
*/
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(3907893632) as any;
@@ -31,7 +23,7 @@ export function ServiceShutdown(): Promise<void> & { cancel(): void } {
}
/**
* ServiceStartup initializes the service when the application starts
* ServiceStartup 服务启动
*/
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2067840771, options) as any;

View File

@@ -2,7 +2,7 @@
// This file is automatically generated. DO NOT EDIT
/**
* DocumentService provides document management functionality
* DocumentService 文档服务
* @module
*/
@@ -15,12 +15,12 @@ import {Call as $Call, Create as $Create} from "@wailsio/runtime";
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as models$0 from "../models/models.js";
import * as ent$0 from "../models/ent/models.js";
/**
* CreateDocument creates a new document and returns the created document with ID
* CreateDocument 创建文档
*/
export function CreateDocument(title: string): Promise<models$0.Document | null> & { cancel(): void } {
export function CreateDocument(title: string): Promise<ent$0.Document | null> & { cancel(): void } {
let $resultPromise = $Call.ByID(3360680842, title) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType1($result);
@@ -30,7 +30,7 @@ export function CreateDocument(title: string): Promise<models$0.Document | null>
}
/**
* DeleteDocument marks a document as deleted (default document with ID=1 cannot be deleted)
* DeleteDocument 删除文档
*/
export function DeleteDocument(id: number): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(412287269, id) as any;
@@ -38,9 +38,9 @@ export function DeleteDocument(id: number): Promise<void> & { cancel(): void } {
}
/**
* GetDocumentByID gets a document by ID
* GetDocumentByID 根据ID获取文档
*/
export function GetDocumentByID(id: number): Promise<models$0.Document | null> & { cancel(): void } {
export function GetDocumentByID(id: number): Promise<ent$0.Document | null> & { cancel(): void } {
let $resultPromise = $Call.ByID(3468193232, id) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType1($result);
@@ -50,9 +50,9 @@ export function GetDocumentByID(id: number): Promise<models$0.Document | null> &
}
/**
* ListAllDocumentsMeta lists all active (non-deleted) document metadata
* ListAllDocumentsMeta 列出所有文档
*/
export function ListAllDocumentsMeta(): Promise<(models$0.Document | null)[]> & { cancel(): void } {
export function ListAllDocumentsMeta(): Promise<(ent$0.Document | null)[]> & { cancel(): void } {
let $resultPromise = $Call.ByID(3073950297) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType2($result);
@@ -62,19 +62,7 @@ export function ListAllDocumentsMeta(): Promise<(models$0.Document | null)[]> &
}
/**
* ListDeletedDocumentsMeta lists all deleted document metadata
*/
export function ListDeletedDocumentsMeta(): Promise<(models$0.Document | null)[]> & { cancel(): void } {
let $resultPromise = $Call.ByID(490143787) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType2($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* LockDocument 锁定文档,防止删除
* LockDocument 锁定文档
*/
export function LockDocument(id: number): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1889494473, id) as any;
@@ -82,15 +70,7 @@ export function LockDocument(id: number): Promise<void> & { cancel(): void } {
}
/**
* RestoreDocument restores a deleted document
*/
export function RestoreDocument(id: number): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(784200778, id) as any;
return $resultPromise;
}
/**
* ServiceStartup initializes the service when the application starts
* ServiceStartup 服务启动
*/
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1474135487, options) as any;
@@ -106,7 +86,7 @@ export function UnlockDocument(id: number): Promise<void> & { cancel(): void } {
}
/**
* UpdateDocumentContent updates the content of a document
* UpdateDocumentContent 更新文档内容
*/
export function UpdateDocumentContent(id: number, content: string): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(3251897116, id, content) as any;
@@ -114,7 +94,7 @@ export function UpdateDocumentContent(id: number, content: string): Promise<void
}
/**
* UpdateDocumentTitle updates the title of a document
* UpdateDocumentTitle 更新文档标题
*/
export function UpdateDocumentTitle(id: number, title: string): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2045530459, id, title) as any;
@@ -122,6 +102,6 @@ export function UpdateDocumentTitle(id: number, title: string): Promise<void> &
}
// Private type creation functions
const $$createType0 = models$0.Document.createFrom;
const $$createType0 = ent$0.Document.createFrom;
const $$createType1 = $Create.Nullable($$createType0);
const $$createType2 = $Create.Array($$createType1);

View File

@@ -2,7 +2,7 @@
// This file is automatically generated. DO NOT EDIT
/**
* ExtensionService 扩展管理服务
* ExtensionService 扩展服务
* @module
*/
@@ -16,12 +16,15 @@ import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/applic
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as models$0 from "../models/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as ent$0 from "../models/ent/models.js";
/**
* GetAllExtensions 获取所有扩展配置
* GetDefaultExtensions 获取默认扩展配置(用于前端绑定生成)
*/
export function GetAllExtensions(): Promise<models$0.Extension[]> & { cancel(): void } {
let $resultPromise = $Call.ByID(3094292124) as any;
export function GetDefaultExtensions(): Promise<models$0.Extension[]> & { cancel(): void } {
let $resultPromise = $Call.ByID(4036328166) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType1($result);
}) as any;
@@ -30,23 +33,51 @@ export function GetAllExtensions(): Promise<models$0.Extension[]> & { cancel():
}
/**
* ResetAllExtensionsToDefault 重置所有扩展到默认状态
* GetExtensionByID 根据ID获取扩展
*/
export function ResetAllExtensionsToDefault(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(270611949) as any;
export function GetExtensionByID(id: number): Promise<ent$0.Extension | null> & { cancel(): void } {
let $resultPromise = $Call.ByID(1521424252, id) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType3($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* GetExtensionConfig 获取扩展配置
*/
export function GetExtensionConfig(id: number): Promise<{ [_: string]: any }> & { cancel(): void } {
let $resultPromise = $Call.ByID(1629559882, id) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType4($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* GetExtensions 获取所有扩展
*/
export function GetExtensions(): Promise<(ent$0.Extension | null)[]> & { cancel(): void } {
let $resultPromise = $Call.ByID(3179289021) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType5($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* ResetExtensionConfig 重置单个扩展到默认状态
*/
export function ResetExtensionConfig(id: number): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(3990780299, id) as any;
return $resultPromise;
}
/**
* ResetExtensionToDefault 重置扩展到默认状态
*/
export function ResetExtensionToDefault(id: models$0.ExtensionID): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(868308101, id) as any;
return $resultPromise;
}
/**
* ServiceStartup 启动时调用
* ServiceStartup 服务启动
*/
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(40324057, options) as any;
@@ -54,21 +85,33 @@ export function ServiceStartup(options: application$0.ServiceOptions): Promise<v
}
/**
* UpdateExtensionEnabled 更新扩展启用状态
* SyncExtensions 同步扩展配置
*/
export function UpdateExtensionEnabled(id: models$0.ExtensionID, enabled: boolean): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1067300094, id, enabled) as any;
export function SyncExtensions(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(167560004) as any;
return $resultPromise;
}
/**
* UpdateExtensionState 更新扩展状态
* UpdateExtensionConfig 更新扩展配置
*/
export function UpdateExtensionState(id: models$0.ExtensionID, enabled: boolean, config: models$0.ExtensionConfig): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2917946454, id, enabled, config) as any;
export function UpdateExtensionConfig(id: number, config: { [_: string]: any }): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(3184142503, id, config) as any;
return $resultPromise;
}
/**
* UpdateExtensionEnabled 更新扩展启用状态
*/
export function UpdateExtensionEnabled(id: number, enabled: boolean): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1067300094, id, enabled) as any;
return $resultPromise;
}
// Private type creation functions
const $$createType0 = models$0.Extension.createFrom;
const $$createType1 = $Create.Array($$createType0);
const $$createType2 = ent$0.Extension.createFrom;
const $$createType3 = $Create.Nullable($$createType2);
const $$createType4 = $Create.Map($Create.Any, $Create.Any);
const $$createType5 = $Create.Array($$createType3);

View File

@@ -18,12 +18,12 @@ import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/applic
import * as models$0 from "../models/models.js";
/**
* GetCurrentHotkey 获取当前热键
* GetSupportedKeys 返回系统支持的快捷键列表
*/
export function GetCurrentHotkey(): Promise<models$0.HotkeyCombo | null> & { cancel(): void } {
let $resultPromise = $Call.ByID(2572811187) as any;
export function GetSupportedKeys(): Promise<string[]> & { cancel(): void } {
let $resultPromise = $Call.ByID(1511528650) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType1($result);
return $$createType0($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
@@ -86,5 +86,4 @@ export function UpdateHotkey(enable: boolean, combo: models$0.HotkeyCombo | null
}
// Private type creation functions
const $$createType0 = models$0.HotkeyCombo.createFrom;
const $$createType1 = $Create.Nullable($$createType0);
const $$createType0 = $Create.Array($Create.Any);

View File

@@ -17,7 +17,6 @@ import * as SystemService from "./systemservice.js";
import * as TestService from "./testservice.js";
import * as ThemeService from "./themeservice.js";
import * as TranslationService from "./translationservice.js";
import * as TrayService from "./trayservice.js";
import * as WindowService from "./windowservice.js";
export {
BackupService,
@@ -36,7 +35,6 @@ export {
TestService,
ThemeService,
TranslationService,
TrayService,
WindowService
};

View File

@@ -2,7 +2,7 @@
// This file is automatically generated. DO NOT EDIT
/**
* KeyBindingService 快捷键管理服务
* KeyBindingService 快捷键服务
* @module
*/
@@ -16,12 +16,15 @@ import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/applic
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as models$0 from "../models/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as ent$0 from "../models/ent/models.js";
/**
* GetAllKeyBindings 获取所有快捷键配置
* GetDefaultKeyBindings 获取默认快捷键配置
*/
export function GetAllKeyBindings(): Promise<models$0.KeyBinding[]> & { cancel(): void } {
let $resultPromise = $Call.ByID(1633502882) as any;
export function GetDefaultKeyBindings(): Promise<models$0.KeyBinding[]> & { cancel(): void } {
let $resultPromise = $Call.ByID(3843471588) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType1($result);
}) as any;
@@ -30,13 +33,72 @@ export function GetAllKeyBindings(): Promise<models$0.KeyBinding[]> & { cancel()
}
/**
* ServiceStartup 启动时调用
* GetKeyBindingByID 根据ID获取快捷键
*/
export function GetKeyBindingByID(id: number): Promise<ent$0.KeyBinding | null> & { cancel(): void } {
let $resultPromise = $Call.ByID(1578192526, id) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType3($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* GetKeyBindings 根据类型获取快捷键
*/
export function GetKeyBindings(kbType: models$0.KeyBindingType): Promise<(ent$0.KeyBinding | null)[]> & { cancel(): void } {
let $resultPromise = $Call.ByID(4253885163, kbType) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType4($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* ResetKeyBindings 重置所有快捷键到默认值
*/
export function ResetKeyBindings(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(4251626010) as any;
return $resultPromise;
}
/**
* ServiceStartup 服务启动
*/
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2057121990, options) as any;
return $resultPromise;
}
/**
* SyncKeyBindings 同步快捷键配置
*/
export function SyncKeyBindings(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1522202638) as any;
return $resultPromise;
}
/**
* UpdateKeyBindingEnabled 更新快捷键启用状态
*/
export function UpdateKeyBindingEnabled(id: number, enabled: boolean): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(843626124, id, enabled) as any;
return $resultPromise;
}
/**
* UpdateKeyBindingKeys 更新快捷键绑定(根据操作系统自动判断更新哪个字段)
*/
export function UpdateKeyBindingKeys(id: number, key: string): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(3432755175, id, key) as any;
return $resultPromise;
}
// Private type creation functions
const $$createType0 = models$0.KeyBinding.createFrom;
const $$createType1 = $Create.Array($$createType0);
const $$createType2 = ent$0.KeyBinding.createFrom;
const $$createType3 = $Create.Nullable($$createType2);
const $$createType4 = $Create.Array($$createType3);

View File

@@ -12,12 +12,6 @@ import * as http$0 from "../../../net/http/models.js";
// @ts-ignore: Unused imports
import * as time$0 from "../../../time/models.js";
/**
* CancelFunc 取消订阅函数
* 调用此函数可以取消对配置的监听
*/
export type CancelFunc = any;
/**
* HttpRequest HTTP请求结构
*/
@@ -191,15 +185,14 @@ export class MemoryStats {
* MigrationProgress 迁移进度信息
*/
export class MigrationProgress {
"status": MigrationStatus;
/**
* 0-100
*/
"progress": number;
"error"?: string;
/** Creates a new MigrationProgress instance. */
constructor($$source: Partial<MigrationProgress> = {}) {
if (!("status" in $$source)) {
this["status"] = ("" as MigrationStatus);
}
if (!("progress" in $$source)) {
this["progress"] = 0;
}
@@ -216,20 +209,6 @@ export class MigrationProgress {
}
}
/**
* MigrationStatus 迁移状态
*/
export enum MigrationStatus {
/**
* The Go zero value for the underlying type of the enum.
*/
$zero = "",
MigrationStatusMigrating = "migrating",
MigrationStatusCompleted = "completed",
MigrationStatusFailed = "failed",
};
/**
* OSInfo 操作系统信息
*/
@@ -266,11 +245,6 @@ export class OSInfo {
}
}
/**
* ObserverCallback 观察者回调函数
*/
export type ObserverCallback = any;
/**
* SelfUpdateResult 自我更新结果
*/

View File

@@ -15,26 +15,13 @@ import {Call as $Call, Create as $Create} from "@wailsio/runtime";
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as models$0 from "../models/models.js";
import * as ent$0 from "../models/ent/models.js";
/**
* GetAllThemes 获取所有主题
* GetThemeByName 根据Key获取主题
*/
export function GetAllThemes(): Promise<(models$0.Theme | null)[]> & { cancel(): void } {
let $resultPromise = $Call.ByID(2425053076) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType2($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* GetThemeByID 根据ID或名称获取主题
* 如果 id > 0按ID查询如果 id = 0按名称查询
*/
export function GetThemeByIdOrName(id: number, ...name: string[]): Promise<models$0.Theme | null> & { cancel(): void } {
let $resultPromise = $Call.ByID(127385338, id, name) as any;
export function GetThemeByName(name: string): Promise<ent$0.Theme | null> & { cancel(): void } {
let $resultPromise = $Call.ByID(1938954770, name) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType1($result);
}) as any;
@@ -43,23 +30,15 @@ export function GetThemeByIdOrName(id: number, ...name: string[]): Promise<model
}
/**
* ResetTheme 重置主题为预设配置
* ResetTheme 删除主题
*/
export function ResetTheme(id: number, ...name: string[]): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1806334457, id, name) as any;
export function ResetTheme(key: string): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1806334457, key) as any;
return $resultPromise;
}
/**
* ServiceShutdown 服务关闭
*/
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1676749034) as any;
return $resultPromise;
}
/**
* ServiceStartup 服务启动时初始化
* ServiceStartup 服务启动
*/
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2915959937, options) as any;
@@ -67,14 +46,13 @@ export function ServiceStartup(options: application$0.ServiceOptions): Promise<v
}
/**
* UpdateTheme 更新主题
* UpdateTheme 保存或更新主题
*/
export function UpdateTheme(id: number, colors: models$0.ThemeColorConfig): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(70189749, id, colors) as any;
export function UpdateTheme(key: string, colors: { [_: string]: any }): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(70189749, key, colors) as any;
return $resultPromise;
}
// Private type creation functions
const $$createType0 = models$0.Theme.createFrom;
const $$createType0 = ent$0.Theme.createFrom;
const $$createType1 = $Create.Nullable($$createType0);
const $$createType2 = $Create.Array($$createType1);

View File

@@ -1,59 +0,0 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
/**
* TrayService 系统托盘服务
* @module
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
/**
* AutoShowHide 自动显示/隐藏主窗口
*/
export function AutoShowHide(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(4044219428) as any;
return $resultPromise;
}
/**
* HandleWindowClose 处理窗口关闭事件
*/
export function HandleWindowClose(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1824247204) as any;
return $resultPromise;
}
/**
* HandleWindowMinimize 处理窗口最小化事件
*/
export function HandleWindowMinimize(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(178686624) as any;
return $resultPromise;
}
/**
* MinimizeButtonClicked 处理标题栏最小化按钮点击
*/
export function MinimizeButtonClicked(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2477618539) as any;
return $resultPromise;
}
/**
* ShouldMinimizeToTray 检查是否应该最小化到托盘
*/
export function ShouldMinimizeToTray(): Promise<boolean> & { cancel(): void } {
let $resultPromise = $Call.ByID(3403884012) as any;
return $resultPromise;
}
/**
* ShowWindow 显示主窗口
*/
export function ShowWindow(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1315913255) as any;
return $resultPromise;
}

View File

@@ -14,18 +14,6 @@ import {Call as $Call, Create as $Create} from "@wailsio/runtime";
// @ts-ignore: Unused imports
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
/**
* GetOpenWindows 获取所有打开的文档窗口
*/
export function GetOpenWindows(): Promise<application$0.Window[]> & { cancel(): void } {
let $resultPromise = $Call.ByID(1464997251) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType0($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* IsDocumentWindowOpen 检查指定文档的窗口是否已打开
*/
@@ -57,6 +45,3 @@ export function ServiceStartup(options: application$0.ServiceOptions): Promise<v
let $resultPromise = $Call.ByID(2432987694, options) as any;
return $resultPromise;
}
// Private type creation functions
const $$createType0 = $Create.Array($Create.Any);

File diff suppressed because it is too large Load Diff

View File

@@ -22,8 +22,8 @@
"app:generate": "cd .. && wails3 generate bindings -ts"
},
"dependencies": {
"@codemirror/autocomplete": "^6.19.1",
"@codemirror/commands": "^6.10.0",
"@codemirror/autocomplete": "^6.20.0",
"@codemirror/commands": "^6.10.1",
"@codemirror/lang-angular": "^0.1.4",
"@codemirror/lang-cpp": "^6.0.3",
"@codemirror/lang-css": "^6.3.1",
@@ -34,7 +34,7 @@
"@codemirror/lang-json": "^6.0.2",
"@codemirror/lang-less": "^6.0.2",
"@codemirror/lang-lezer": "^6.0.2",
"@codemirror/lang-liquid": "^6.3.0",
"@codemirror/lang-liquid": "^6.3.1",
"@codemirror/lang-markdown": "^6.5.0",
"@codemirror/lang-php": "^6.0.2",
"@codemirror/lang-python": "^6.2.1",
@@ -50,59 +50,56 @@
"@codemirror/lint": "^6.9.2",
"@codemirror/search": "^6.5.11",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.38.6",
"@codemirror/view": "^6.39.4",
"@cospaia/prettier-plugin-clojure": "^0.0.2",
"@lezer/highlight": "^1.2.3",
"@lezer/lr": "^1.4.3",
"@mdit/plugin-katex": "^0.23.2",
"@mdit/plugin-tasklist": "^0.22.2",
"@lezer/lr": "^1.4.5",
"@prettier/plugin-xml": "^3.4.2",
"@replit/codemirror-lang-svelte": "^6.0.0",
"@toml-tools/lexer": "^1.0.0",
"@toml-tools/parser": "^1.0.0",
"@types/markdown-it": "^14.1.2",
"@types/katex": "^0.16.7",
"codemirror": "^6.0.2",
"codemirror-lang-elixir": "^4.0.0",
"colors-named": "^1.0.2",
"colors-named-hex": "^1.0.2",
"colors-named": "^1.0.4",
"colors-named-hex": "^1.0.3",
"groovy-beautify": "^0.0.17",
"highlight.js": "^11.11.1",
"hsl-matcher": "^1.2.4",
"java-parser": "^3.0.1",
"linguist-languages": "^9.1.0",
"markdown-it": "^14.1.0",
"mermaid": "^11.12.1",
"npm": "^11.6.2",
"katex": "^0.16.27",
"linguist-languages": "^9.1.11",
"marked": "^17.0.1",
"mermaid": "^11.12.2",
"php-parser": "^3.2.5",
"pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"prettier": "^3.6.2",
"sass": "^1.94.0",
"vue": "^3.5.24",
"vue-i18n": "^11.1.12",
"prettier": "^3.7.4",
"sass": "^1.97.0",
"vue": "^3.5.25",
"vue-i18n": "^11.2.2",
"vue-pick-colors": "^1.8.0",
"vue-router": "^4.6.3"
"vue-router": "^4.6.4"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@eslint/js": "^9.39.2",
"@lezer/generator": "^1.8.0",
"@types/node": "^24.9.2",
"@vitejs/plugin-vue": "^6.0.1",
"@wailsio/runtime": "latest",
"@types/node": "^25.0.3",
"@vitejs/plugin-vue": "^6.0.3",
"@wailsio/runtime": "^3.0.0-alpha.76",
"cross-env": "^10.1.0",
"eslint": "^9.39.1",
"eslint-plugin-vue": "^10.5.1",
"eslint": "^9.39.2",
"eslint-plugin-vue": "^10.6.2",
"globals": "^16.5.0",
"happy-dom": "^20.0.10",
"happy-dom": "^20.0.11",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.4",
"typescript-eslint": "^8.50.0",
"unplugin-vue-components": "^30.0.0",
"vite": "npm:rolldown-vite@latest",
"vite-plugin-node-polyfills": "^0.24.0",
"vitepress": "^2.0.0-alpha.12",
"vitest": "^4.0.8",
"vitest": "^4.0.16",
"vue-eslint-parser": "^10.2.0",
"vue-tsc": "^3.1.3"
"vue-tsc": "^3.1.8"
},
"overrides": {
"vite": "npm:rolldown-vite@latest"

View File

@@ -19,13 +19,13 @@ onBeforeMount(async () => {
// 并行初始化配置、系统信息和快捷键配置
await Promise.all([
configStore.initConfig(),
systemStore.initializeSystemInfo(),
systemStore.initSystemInfo(),
keybindingStore.loadKeyBindings(),
]);
// 初始化语言和主题
await configStore.initializeLanguage();
await themeStore.initializeTheme();
await configStore.initLanguage();
await themeStore.initTheme();
await translationStore.loadTranslators();
// 启动时检查更新

View File

@@ -1,254 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
鸿蒙字体压缩工具
使用 fonttools 库压缩 TTF 字体文件,减小文件大小
"""
import os
import sys
import subprocess
import shutil
from pathlib import Path
from typing import List, Tuple
def check_dependencies():
"""检查必要的依赖是否已安装"""
missing_packages = []
# 检查 fonttools
try:
import fontTools
except ImportError:
missing_packages.append('fonttools')
# 检查 brotli
try:
import brotli
except ImportError:
missing_packages.append('brotli')
# 检查 pyftsubset 命令是否可用
try:
result = subprocess.run(['pyftsubset', '--help'], capture_output=True, text=True)
if result.returncode != 0:
missing_packages.append('fonttools[subset]')
except FileNotFoundError:
if 'fonttools' not in missing_packages:
missing_packages.append('fonttools[subset]')
if missing_packages:
print(f"缺少必要的依赖包: {', '.join(missing_packages)}")
print("请运行以下命令安装:")
print(f"pip install {' '.join(missing_packages)}")
return False
return True
def get_file_size(file_path: str) -> int:
"""获取文件大小(字节)"""
return os.path.getsize(file_path)
def format_file_size(size_bytes: int) -> str:
"""格式化文件大小显示"""
if size_bytes < 1024:
return f"{size_bytes} B"
elif size_bytes < 1024 * 1024:
return f"{size_bytes / 1024:.2f} KB"
else:
return f"{size_bytes / (1024 * 1024):.2f} MB"
def compress_font(input_path: str, output_path: str, compression_level: str = "basic") -> bool:
"""
压缩单个字体文件
Args:
input_path: 输入字体文件路径
output_path: 输出字体文件路径
compression_level: 压缩级别 ("basic", "medium", "aggressive")
Returns:
bool: 压缩是否成功
"""
try:
# 基础压缩参数
base_args = [
"pyftsubset", input_path,
"--output-file=" + output_path,
"--flavor=woff2", # 输出为 WOFF2 格式,压缩率更高
"--with-zopfli", # 使用 Zopfli 算法进一步压缩
]
# 根据压缩级别设置不同的参数
if compression_level == "basic":
# 基础压缩:保留常用字符和功能
args = base_args + [
"--unicodes=U+0020-007F,U+00A0-00FF,U+2000-206F,U+2070-209F,U+20A0-20CF", # 基本拉丁字符、标点符号等
"--layout-features=*", # 保留所有布局特性
"--glyph-names", # 保留字形名称
"--symbol-cmap", # 保留符号映射
"--legacy-cmap", # 保留传统字符映射
"--notdef-glyph", # 保留 .notdef 字形
"--recommended-glyphs", # 保留推荐字形
"--name-IDs=*", # 保留所有名称ID
"--name-legacy", # 保留传统名称
]
elif compression_level == "medium":
# 中等压缩:移除一些不常用的功能
args = base_args + [
"--unicodes=U+0020-007F,U+00A0-00FF,U+2000-206F", # 减少字符范围
"--layout-features=kern,liga,clig", # 只保留关键布局特性
"--no-glyph-names", # 移除字形名称
"--notdef-glyph",
"--name-IDs=1,2,3,4,5,6", # 只保留基本名称ID
]
else: # aggressive
# 激进压缩:最大程度减小文件大小
args = base_args + [
"--unicodes=U+0020-007F", # 只保留基本ASCII字符
"--no-layout-features", # 移除所有布局特性
"--no-glyph-names", # 移除字形名称
"--no-symbol-cmap", # 移除符号映射
"--no-legacy-cmap", # 移除传统映射
"--notdef-glyph",
"--name-IDs=1,2", # 只保留最基本的名称
"--desubroutinize", # 去子程序化可能减小CFF字体大小
]
# 执行压缩命令
result = subprocess.run(args, capture_output=True, text=True)
if result.returncode == 0:
return True
else:
print(f"压缩失败: {result.stderr}")
return False
except Exception as e:
print(f"压缩过程中出现错误: {str(e)}")
return False
def find_font_files(directory: str) -> List[str]:
"""查找目录中的所有字体文件"""
font_extensions = ['.ttf', '.otf', '.woff', '.woff2']
font_files = []
for root, dirs, files in os.walk(directory):
for file in files:
if any(file.lower().endswith(ext) for ext in font_extensions):
font_files.append(os.path.join(root, file))
return font_files
def compress_fonts_batch(font_directory: str, compression_level: str = "basic"):
"""
批量压缩字体文件
Args:
font_directory: 字体文件目录
compression_level: 压缩级别
"""
if not os.path.exists(font_directory):
print(f"错误: 目录 {font_directory} 不存在")
return
# 查找所有字体文件
font_files = find_font_files(font_directory)
if not font_files:
print("未找到字体文件")
return
print(f"找到 {len(font_files)} 个字体文件")
print(f"压缩级别: {compression_level}")
print(f"压缩后的文件将与源文件放在同一目录,扩展名为 .woff2")
print("-" * 60)
total_original_size = 0
total_compressed_size = 0
successful_compressions = 0
for i, font_file in enumerate(font_files, 1):
print(f"[{i}/{len(font_files)}] 处理: {os.path.basename(font_file)}")
# 获取原始文件大小
original_size = get_file_size(font_file)
total_original_size += original_size
# 生成输出文件名(保持原文件名,只改变扩展名)
file_dir = os.path.dirname(font_file)
base_name = os.path.splitext(os.path.basename(font_file))[0]
output_file = os.path.join(file_dir, f"{base_name}.woff2")
# 压缩字体
if compress_font(font_file, output_file, compression_level):
if os.path.exists(output_file):
compressed_size = get_file_size(output_file)
total_compressed_size += compressed_size
successful_compressions += 1
# 计算压缩率
compression_ratio = (1 - compressed_size / original_size) * 100
print(f" ✓ 成功: {format_file_size(original_size)}{format_file_size(compressed_size)} "
f"(压缩 {compression_ratio:.1f}%)")
else:
print(f" ✗ 失败: 输出文件未生成")
else:
print(f" ✗ 失败: 压缩过程出错")
print()
# 显示总结
print("=" * 60)
print("压缩完成!")
print(f"成功压缩: {successful_compressions}/{len(font_files)} 个文件")
if successful_compressions > 0:
total_compression_ratio = (1 - total_compressed_size / total_original_size) * 100
print(f"总大小: {format_file_size(total_original_size)}{format_file_size(total_compressed_size)}")
print(f"总压缩率: {total_compression_ratio:.1f}%")
print(f"节省空间: {format_file_size(total_original_size - total_compressed_size)}")
def main():
"""主函数"""
print("鸿蒙字体压缩工具")
print("=" * 60)
# 检查依赖
if not check_dependencies():
return
# 获取当前脚本所在目录
current_dir = os.path.dirname(os.path.abspath(__file__))
# 设置默认字体目录
font_directory = current_dir
print(f"字体目录: {font_directory}")
# 让用户选择压缩级别
print("\n请选择压缩级别:")
print("1. 基础压缩 (保留大部分功能,适合网页使用)")
print("2. 中等压缩 (平衡文件大小和功能)")
print("3. 激进压缩 (最小文件大小,可能影响显示效果)")
while True:
choice = input("\n请输入选择 (1-3): ").strip()
if choice == "1":
compression_level = "basic"
break
elif choice == "2":
compression_level = "medium"
break
elif choice == "3":
compression_level = "aggressive"
break
else:
print("无效选择,请输入 1、2 或 3")
# 开始批量压缩
compress_fonts_batch(font_directory, compression_level=compression_level)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,179 @@
# 字体压缩工具使用指南
## 📖 简介
`font_compressor.py` 是一个通用的字体压缩工具,可以:
- ✅ 将 TTF、OTF、WOFF 字体文件转换为 WOFF2 格式
- ✅ 支持相对路径和绝对路径
- ✅ 自动生成 CSS 字体定义文件
- ✅ 智能识别字体字重和样式
- ✅ 批量处理整个目录(包括子目录)
## 🚀 前置要求
安装 Python 依赖包:
```bash
pip install fonttools brotli
```
## 📝 使用方法
### 基础用法
```bash
# 进入 fonts 目录
cd frontend/src/assets/fonts
# 交互式模式处理当前目录
python font_compressor.py
# 处理相对路径的 Monocraft 目录
python font_compressor.py Monocraft
# 处理相对路径并指定压缩级别
python font_compressor.py Monocraft -l basic
```
### 生成 CSS 文件
```bash
# 压缩 Monocraft 字体并生成 CSS 文件
python font_compressor.py Monocraft -l basic -c ../styles/monocraft_fonts.css
# 压缩 Hack 字体并生成 CSS
python font_compressor.py Hack -l basic -c ../styles/hack_fonts_new.css
# 压缩 OpenSans 字体并生成 CSS
python font_compressor.py OpenSans -l medium -c ../styles/opensans_fonts.css
```
### 高级用法
```bash
# 使用绝对路径
python font_compressor.py E:\Go_WorkSpace\voidraft\frontend\src\assets\fonts\Monocraft -l basic -c monocraft.css
# 不同压缩级别
python font_compressor.py Monocraft -l basic # 基础压缩,保留所有功能
python font_compressor.py Monocraft -l medium # 中等压缩,平衡大小和功能
python font_compressor.py Monocraft -l aggressive # 激进压缩,最小文件
```
## ⚙️ 命令行参数
| 参数 | 说明 | 示例 |
|------|------|------|
| `directory` | 字体目录(相对/绝对路径) | `Monocraft``/path/to/fonts` |
| `-l, --level` | 压缩级别 (basic/medium/aggressive) | `-l basic` |
| `-c, --css` | CSS 输出文件路径 | `-c monocraft.css` |
| `--version` | 显示版本信息 | `--version` |
| `-h, --help` | 显示帮助信息 | `-h` |
## 📊 压缩级别说明
### basic基础 - 推荐
- 保留大部分字体功能
- 适合网页使用
- 压缩率约 30-40%
### medium中等
- 移除一些不常用的功能
- 平衡文件大小和功能
- 压缩率约 40-50%
### aggressive激进
- 最大程度减小文件大小
- 可能影响高级排版功能
- 压缩率约 50-60%
## 📁 输出结果
### 字体文件
压缩后的 `.woff2` 文件会保存在原文件相同的目录下,例如:
- `Monocraft/ttf/Monocraft-Bold.ttf``Monocraft/ttf/Monocraft-Bold.woff2`
- `Hack/hack-regular.ttf``Hack/hack-regular.woff2`
### CSS 文件
生成的 CSS 文件会包含:
- 自动识别的字体家族名称
- 正确的字重和样式设置
- 使用相对路径的字体引用
- 按字重排序的 `@font-face` 定义
生成的 CSS 示例:
```css
/* 自动生成的字体文件 */
/* 由 font_compressor.py 生成 */
/* Monocraft 字体家族 */
/* Monocraft Light */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/ttf/Monocraft-Light.woff2') format('woff2');
font-weight: 300;
font-style: normal;
font-display: swap;
}
/* Monocraft Bold */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/ttf/Monocraft-Bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
```
## 🎯 实际使用示例
### 示例 1: 压缩 Monocraft 字体
```bash
cd frontend/src/assets/fonts
python font_compressor.py Monocraft -l basic -c ../styles/monocraft_fonts.css
```
这将:
1. 扫描 `Monocraft/ttf``Monocraft/otf` 目录
2. 将所有字体文件转换为 WOFF2
3.`frontend/src/assets/styles/monocraft_fonts.css` 生成 CSS 文件
### 示例 2: 批量处理多个字体目录
```bash
cd frontend/src/assets/fonts
# 压缩 Monocraft
python font_compressor.py Monocraft -l basic -c ../styles/monocraft_fonts.css
# 压缩 OpenSans
python font_compressor.py OpenSans -l basic -c ../styles/opensans_fonts.css
# 压缩 Hack已有 CSS只需生成新版本对比
python font_compressor.py Hack -l basic -c ../styles/hack_fonts_new.css
```
## 🔍 字体信息自动识别
工具会自动从文件名识别:
- **字重**Thin(100), Light(300), Regular(400), Medium(500), SemiBold(600), Bold(700), Black(900)
- **样式**normal, italic
- **字体家族**:自动去除字重和样式后缀
支持的命名格式:
- `FontName-Bold.ttf`
- `FontName_Bold.otf`
- `FontName-BoldItalic.ttf`
- `FontName_SemiBold_Italic.woff`
## 📞 获取帮助
```bash
python font_compressor.py --help
```

View File

@@ -0,0 +1,494 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
通用字体压缩工具
使用 fonttools 库将字体文件转换为 WOFF2 格式,减小文件大小
支持 TTF、OTF、WOFF 等格式的字体文件
"""
import os
import sys
import subprocess
import shutil
import argparse
import re
from pathlib import Path
from typing import List, Tuple, Dict, Optional
def check_dependencies():
"""检查必要的依赖是否已安装"""
missing_packages = []
# 检查 fonttools
try:
import fontTools
except ImportError:
missing_packages.append('fonttools')
# 检查 brotli
try:
import brotli
except ImportError:
missing_packages.append('brotli')
# 检查 pyftsubset 命令是否可用
try:
result = subprocess.run(['pyftsubset', '--help'], capture_output=True, text=True)
if result.returncode != 0:
missing_packages.append('fonttools[subset]')
except FileNotFoundError:
if 'fonttools' not in missing_packages:
missing_packages.append('fonttools[subset]')
if missing_packages:
print(f"缺少必要的依赖包: {', '.join(missing_packages)}")
print("请运行以下命令安装:")
print(f"pip install {' '.join(missing_packages)}")
return False
return True
def get_file_size(file_path: str) -> int:
"""获取文件大小(字节)"""
return os.path.getsize(file_path)
def format_file_size(size_bytes: int) -> str:
"""格式化文件大小显示"""
if size_bytes < 1024:
return f"{size_bytes} B"
elif size_bytes < 1024 * 1024:
return f"{size_bytes / 1024:.2f} KB"
else:
return f"{size_bytes / (1024 * 1024):.2f} MB"
def compress_font(input_path: str, output_path: str, compression_level: str = "basic") -> bool:
"""
压缩单个字体文件
Args:
input_path: 输入字体文件路径
output_path: 输出字体文件路径
compression_level: 压缩级别 ("basic", "medium", "aggressive")
Returns:
bool: 压缩是否成功
"""
try:
# 基础压缩参数
base_args = [
"pyftsubset", input_path,
"--output-file=" + output_path,
"--flavor=woff2", # 输出为 WOFF2 格式,压缩率更高
"--with-zopfli", # 使用 Zopfli 算法进一步压缩
]
# 根据压缩级别设置不同的参数
if compression_level == "basic":
# 基础压缩:保留常用字符和功能
args = base_args + [
"--unicodes=U+0020-007F,U+00A0-00FF,U+2000-206F,U+2070-209F,U+20A0-20CF", # 基本拉丁字符、标点符号等
"--layout-features=*", # 保留所有布局特性
"--glyph-names", # 保留字形名称
"--symbol-cmap", # 保留符号映射
"--legacy-cmap", # 保留传统字符映射
"--notdef-glyph", # 保留 .notdef 字形
"--recommended-glyphs", # 保留推荐字形
"--name-IDs=*", # 保留所有名称ID
"--name-legacy", # 保留传统名称
]
elif compression_level == "medium":
# 中等压缩:移除一些不常用的功能
args = base_args + [
"--unicodes=U+0020-007F,U+00A0-00FF,U+2000-206F", # 减少字符范围
"--layout-features=kern,liga,clig", # 只保留关键布局特性
"--no-glyph-names", # 移除字形名称
"--notdef-glyph",
"--name-IDs=1,2,3,4,5,6", # 只保留基本名称ID
]
else: # aggressive
# 激进压缩:最大程度减小文件大小
args = base_args + [
"--unicodes=U+0020-007F", # 只保留基本ASCII字符
"--no-layout-features", # 移除所有布局特性
"--no-glyph-names", # 移除字形名称
"--no-symbol-cmap", # 移除符号映射
"--no-legacy-cmap", # 移除传统映射
"--notdef-glyph",
"--name-IDs=1,2", # 只保留最基本的名称
"--desubroutinize", # 去子程序化可能减小CFF字体大小
]
# 执行压缩命令
result = subprocess.run(args, capture_output=True, text=True)
if result.returncode == 0:
return True
else:
print(f"压缩失败: {result.stderr}")
return False
except Exception as e:
print(f"压缩过程中出现错误: {str(e)}")
return False
def find_font_files(directory: str, exclude_woff2: bool = False) -> List[str]:
"""查找目录中的所有字体文件"""
if exclude_woff2:
font_extensions = ['.ttf', '.otf', '.woff']
else:
font_extensions = ['.ttf', '.otf', '.woff', '.woff2']
font_files = []
for root, dirs, files in os.walk(directory):
for file in files:
if any(file.lower().endswith(ext) for ext in font_extensions):
font_files.append(os.path.join(root, file))
return font_files
def parse_font_info(filename: str) -> Dict[str, any]:
"""
从字体文件名解析字体信息(字重、样式等)
Args:
filename: 字体文件名(不含路径)
Returns:
包含字体信息的字典
"""
# 移除扩展名
name_without_ext = os.path.splitext(filename)[0]
# 字重映射
weight_mapping = {
'thin': (100, 'Thin'),
'extralight': (200, 'ExtraLight'),
'light': (300, 'Light'),
'regular': (400, 'Regular'),
'normal': (400, 'Regular'),
'medium': (500, 'Medium'),
'semibold': (600, 'SemiBold'),
'bold': (700, 'Bold'),
'extrabold': (800, 'ExtraBold'),
'black': (900, 'Black'),
'heavy': (900, 'Heavy'),
}
# 默认值
font_weight = 400
font_style = 'normal'
weight_name = 'Regular'
# 检查是否为斜体
if re.search(r'italic', name_without_ext, re.IGNORECASE):
font_style = 'italic'
# 检查字重
name_lower = name_without_ext.lower()
for weight_key, (weight_value, weight_label) in weight_mapping.items():
if weight_key in name_lower:
font_weight = weight_value
weight_name = weight_label
break
# 提取字体家族名称(移除字重和样式后缀)
family_name = name_without_ext
for weight_key, (_, weight_label) in weight_mapping.items():
family_name = re.sub(r'[-_]?' + weight_label, '', family_name, flags=re.IGNORECASE)
family_name = re.sub(r'[-_]?italic', '', family_name, flags=re.IGNORECASE)
family_name = family_name.strip('-_')
return {
'family': family_name,
'weight': font_weight,
'style': font_style,
'weight_name': weight_name,
'full_name': name_without_ext
}
def generate_css(font_files: List[str], output_css_path: str, css_base_path: str):
"""
生成CSS字体文件
Args:
font_files: 字体文件路径列表woff2文件
output_css_path: 输出CSS文件路径
css_base_path: CSS文件相对于字体文件的基础路径
"""
# 按字体家族分组
font_groups: Dict[str, List[Dict]] = {}
for font_file in font_files:
if not font_file.endswith('.woff2'):
continue
filename = os.path.basename(font_file)
font_info = parse_font_info(filename)
# 计算相对路径
font_dir = os.path.dirname(font_file)
css_dir = os.path.dirname(output_css_path)
try:
# 计算从CSS文件到字体文件的相对路径
rel_path = os.path.relpath(font_file, css_dir)
# 统一使用正斜杠适用于Web
rel_path = rel_path.replace('\\', '/')
except ValueError:
# 如果在不同驱动器上,使用绝对路径
rel_path = font_file.replace('\\', '/')
font_info['path'] = rel_path
family = font_info['family']
if family not in font_groups:
font_groups[family] = []
font_groups[family].append(font_info)
# 生成CSS内容
css_lines = ['/* 自动生成的字体文件 */', '/* 由 font_compressor.py 生成 */', '']
for family, fonts in sorted(font_groups.items()):
css_lines.append(f'/* {family} 字体家族 */')
css_lines.append('')
# 按字重排序
fonts.sort(key=lambda x: (x['weight'], x['style']))
for font in fonts:
css_lines.append(f"/* {family} {font['weight_name']}{' Italic' if font['style'] == 'italic' else ''} */")
css_lines.append('@font-face {')
css_lines.append(f" font-family: '{family}';")
css_lines.append(f" src: url('{font['path']}') format('woff2');")
css_lines.append(f" font-weight: {font['weight']};")
css_lines.append(f" font-style: {font['style']};")
css_lines.append(' font-display: swap;')
css_lines.append('}')
css_lines.append('')
# 写入CSS文件
with open(output_css_path, 'w', encoding='utf-8') as f:
f.write('\n'.join(css_lines))
print(f"[OK] CSS文件已生成: {output_css_path}")
print(f" 包含 {sum(len(fonts) for fonts in font_groups.values())} 个字体定义")
print(f" 字体家族: {', '.join(sorted(font_groups.keys()))}")
def compress_fonts_batch(font_directory: str, compression_level: str = "basic") -> List[str]:
"""
批量压缩字体文件
Args:
font_directory: 字体文件目录
compression_level: 压缩级别
Returns:
生成的woff2文件路径列表
"""
if not os.path.exists(font_directory):
print(f"错误: 目录 {font_directory} 不存在")
return []
# 查找所有字体文件排除已经是woff2的
font_files = find_font_files(font_directory, exclude_woff2=True)
if not font_files:
print("未找到字体文件")
return []
print(f"找到 {len(font_files)} 个字体文件")
print(f"压缩级别: {compression_level}")
print(f"压缩后的文件将与源文件放在同一目录,扩展名为 .woff2")
print("-" * 60)
total_original_size = 0
total_compressed_size = 0
successful_compressions = 0
generated_woff2_files = []
for i, font_file in enumerate(font_files, 1):
print(f"[{i}/{len(font_files)}] 处理: {os.path.basename(font_file)}")
# 获取原始文件大小
original_size = get_file_size(font_file)
total_original_size += original_size
# 生成输出文件名(保持原文件名,只改变扩展名)
file_dir = os.path.dirname(font_file)
base_name = os.path.splitext(os.path.basename(font_file))[0]
output_file = os.path.join(file_dir, f"{base_name}.woff2")
# 压缩字体
if compress_font(font_file, output_file, compression_level):
if os.path.exists(output_file):
compressed_size = get_file_size(output_file)
total_compressed_size += compressed_size
successful_compressions += 1
generated_woff2_files.append(output_file)
# 计算压缩率
compression_ratio = (1 - compressed_size / original_size) * 100
print(f" [OK] 成功: {format_file_size(original_size)} -> {format_file_size(compressed_size)} "
f"(压缩 {compression_ratio:.1f}%)")
else:
print(f" [失败] 输出文件未生成")
else:
print(f" [失败] 压缩过程出错")
print()
# 显示总结
print("=" * 60)
print("压缩完成!")
print(f"成功压缩: {successful_compressions}/{len(font_files)} 个文件")
if successful_compressions > 0:
total_compression_ratio = (1 - total_compressed_size / total_original_size) * 100
print(f"总大小: {format_file_size(total_original_size)}{format_file_size(total_compressed_size)}")
print(f"总压缩率: {total_compression_ratio:.1f}%")
print(f"节省空间: {format_file_size(total_original_size - total_compressed_size)}")
return generated_woff2_files
def main():
"""主函数"""
# 解析命令行参数
parser = argparse.ArgumentParser(
description='通用字体压缩工具 - 将字体文件转换为 WOFF2 格式并生成CSS',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
使用示例:
%(prog)s # 交互式模式,处理当前目录
%(prog)s Monocraft # 处理相对路径目录
%(prog)s Monocraft -l basic # 使用基础压缩级别
%(prog)s Monocraft -l basic -c monocraft.css # 压缩并生成CSS文件
%(prog)s /path/to/fonts -l medium -c fonts.css # 使用绝对路径
压缩级别说明:
basic - 基础压缩:保留大部分功能,适合网页使用
medium - 中等压缩:平衡文件大小和功能
aggressive - 激进压缩:最小文件大小,可能影响显示效果
CSS生成说明:
使用 -c/--css 选项生成CSS文件自动使用相对路径引用字体文件
'''
)
parser.add_argument(
'directory',
nargs='?',
default=None,
help='字体文件目录路径(支持相对/绝对路径,默认为当前脚本所在目录)'
)
parser.add_argument(
'-l', '--level',
choices=['basic', 'medium', 'aggressive'],
default=None,
help='压缩级别basic基础、medium中等、aggressive激进'
)
parser.add_argument(
'-c', '--css',
default=None,
help='生成CSS文件路径相对于脚本位置或绝对路径'
)
parser.add_argument(
'--version',
action='version',
version='%(prog)s 2.0'
)
args = parser.parse_args()
print("=" * 60)
print("通用字体压缩工具 v2.0")
print("=" * 60)
# 检查依赖
if not check_dependencies():
return
# 获取脚本所在目录
script_dir = os.path.dirname(os.path.abspath(__file__))
# 确定字体目录
if args.directory:
# 支持相对路径和绝对路径
if os.path.isabs(args.directory):
font_directory = args.directory
else:
font_directory = os.path.join(script_dir, args.directory)
font_directory = os.path.abspath(font_directory)
else:
# 默认使用当前脚本所在目录
font_directory = script_dir
# 检查目录是否存在
if not os.path.exists(font_directory):
print(f"\n错误: 目录不存在: {font_directory}")
sys.exit(1)
print(f"\n字体目录: {font_directory}")
# 确定压缩级别
compression_level = args.level
if compression_level is None:
# 交互式选择压缩级别
print("\n请选择压缩级别:")
print("1. 基础压缩 (保留大部分功能,适合网页使用)")
print("2. 中等压缩 (平衡文件大小和功能)")
print("3. 激进压缩 (最小文件大小,可能影响显示效果)")
while True:
choice = input("\n请输入选择 (1-3): ").strip()
if choice == "1":
compression_level = "basic"
break
elif choice == "2":
compression_level = "medium"
break
elif choice == "3":
compression_level = "aggressive"
break
else:
print("无效选择,请输入 1、2 或 3")
# 开始批量压缩
print()
generated_files = compress_fonts_batch(font_directory, compression_level=compression_level)
# 生成CSS文件
if args.css and generated_files:
print()
print("=" * 60)
print("生成CSS文件...")
print("=" * 60)
# 确定CSS输出路径
if os.path.isabs(args.css):
css_path = args.css
else:
css_path = os.path.join(script_dir, args.css)
css_path = os.path.abspath(css_path)
# 确保输出目录存在
css_dir = os.path.dirname(css_path)
if css_dir and not os.path.exists(css_dir):
os.makedirs(css_dir)
# 生成CSS
generate_css(generated_files, css_path, script_dir)
elif args.css and not generated_files:
print("\n警告: 没有成功生成WOFF2文件跳过CSS生成")
print()
print("=" * 60)
print("全部完成!")
print("=" * 60)
if __name__ == "__main__":
main()

View File

@@ -1,7 +1,9 @@
/* 导入所有CSS文件 */
@import 'normalize.css';
@import 'variables.css';
@import "harmony_fonts.css";
@import 'scrollbar.css';
@import 'hack_fonts.css';
@import 'opensans_fonts.css';
@import 'opensans_fonts.css';
@import "monocraft_fonts.css";
@import 'variables.css';
@import 'scrollbar.css';
@import 'styles.css';

View File

@@ -0,0 +1,202 @@
/* 自动生成的字体文件 */
/* 由 font_compressor.py 生成 */
/* Monocraft 字体家族 */
/* Monocraft ExtraLight Italic */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/otf/Monocraft-ExtraLight-Italic.woff2') format('woff2');
font-weight: 200;
font-style: italic;
font-display: swap;
}
/* Monocraft ExtraLight Italic */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/ttf/Monocraft-ExtraLight-Italic.woff2') format('woff2');
font-weight: 200;
font-style: italic;
font-display: swap;
}
/* Monocraft ExtraLight */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/otf/Monocraft-ExtraLight.woff2') format('woff2');
font-weight: 200;
font-style: normal;
font-display: swap;
}
/* Monocraft ExtraLight */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/ttf/Monocraft-ExtraLight.woff2') format('woff2');
font-weight: 200;
font-style: normal;
font-display: swap;
}
/* Monocraft Light Italic */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/otf/Monocraft-Light-Italic.woff2') format('woff2');
font-weight: 300;
font-style: italic;
font-display: swap;
}
/* Monocraft Light Italic */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/ttf/Monocraft-Light-Italic.woff2') format('woff2');
font-weight: 300;
font-style: italic;
font-display: swap;
}
/* Monocraft Light */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/otf/Monocraft-Light.woff2') format('woff2');
font-weight: 300;
font-style: normal;
font-display: swap;
}
/* Monocraft Light */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/ttf/Monocraft-Light.woff2') format('woff2');
font-weight: 300;
font-style: normal;
font-display: swap;
}
/* Monocraft Regular Italic */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/otf/Monocraft-Italic.woff2') format('woff2');
font-weight: 400;
font-style: italic;
font-display: swap;
}
/* Monocraft Regular Italic */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/ttf/Monocraft-Italic.woff2') format('woff2');
font-weight: 400;
font-style: italic;
font-display: swap;
}
/* Monocraft SemiBold Italic */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/otf/Monocraft-SemiBold-Italic.woff2') format('woff2');
font-weight: 600;
font-style: italic;
font-display: swap;
}
/* Monocraft SemiBold Italic */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/ttf/Monocraft-SemiBold-Italic.woff2') format('woff2');
font-weight: 600;
font-style: italic;
font-display: swap;
}
/* Monocraft SemiBold */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/otf/Monocraft-SemiBold.woff2') format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
}
/* Monocraft SemiBold */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/ttf/Monocraft-SemiBold.woff2') format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
}
/* Monocraft Bold Italic */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/otf/Monocraft-Bold-Italic.woff2') format('woff2');
font-weight: 700;
font-style: italic;
font-display: swap;
}
/* Monocraft Bold Italic */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/ttf/Monocraft-Bold-Italic.woff2') format('woff2');
font-weight: 700;
font-style: italic;
font-display: swap;
}
/* Monocraft Bold */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/otf/Monocraft-Bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
/* Monocraft Bold */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/ttf/Monocraft-Bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
/* Monocraft Black Italic */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/otf/Monocraft-Black-Italic.woff2') format('woff2');
font-weight: 900;
font-style: italic;
font-display: swap;
}
/* Monocraft Black Italic */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/ttf/Monocraft-Black-Italic.woff2') format('woff2');
font-weight: 900;
font-style: italic;
font-display: swap;
}
/* Monocraft Black */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/otf/Monocraft-Black.woff2') format('woff2');
font-weight: 900;
font-style: normal;
font-display: swap;
}
/* Monocraft Black */
@font-face {
font-family: 'Monocraft';
src: url('../fonts/Monocraft/ttf/Monocraft-Black.woff2') format('woff2');
font-weight: 900;
font-style: normal;
font-display: swap;
}

View File

@@ -0,0 +1,3 @@
body {
background-color: var(--bg-primary);
}

View File

@@ -1,255 +1,266 @@
:root {
/* 编辑器区域 */
--text-primary: #9BB586; /* 内容区域字体颜色 */
/* 深色主题颜色变量 */
--dark-toolbar-bg: #2d2d2d;
--dark-toolbar-border: #404040;
--dark-toolbar-text: #ffffff;
--dark-toolbar-text-secondary: #cccccc;
--dark-toolbar-button-hover: #404040;
--dark-tab-active-line: linear-gradient(90deg, #007acc 0%, #0099ff 100%);
--dark-bg-secondary: #0E1217;
--dark-text-secondary: #a0aec0;
--dark-text-muted: #666;
--dark-border-color: #2d3748;
--dark-settings-bg: #2a2a2a;
--dark-settings-card-bg: #333333;
--dark-settings-text: #ffffff;
--dark-settings-text-secondary: #cccccc;
--dark-settings-border: #444444;
--dark-settings-input-bg: #3a3a3a;
--dark-settings-input-border: #555555;
--dark-settings-hover: #404040;
--dark-scrollbar-track: #2a2a2a;
--dark-scrollbar-thumb: #555555;
--dark-scrollbar-thumb-hover: #666666;
--dark-selection-bg: rgba(181, 206, 168, 0.1);
--dark-selection-text: #b5cea8;
--dark-danger-color: #ff6b6b;
--dark-bg-primary: #1a1a1a;
--dark-bg-hover: #2a2a2a;
--dark-loading-bg-gradient: radial-gradient(#222922, #000500);
--dark-loading-color: #fff;
--dark-loading-glow: 0 0 10px rgba(50, 255, 50, 0.5), 0 0 5px rgba(100, 255, 100, 0.5);
--dark-loading-done-color: #6f6;
--dark-loading-overlay: linear-gradient(transparent 0%, rgba(10, 16, 10, 0.5) 50%);
/* 浅色主题颜色变量 */
--light-toolbar-bg: #f8f9fa;
--light-toolbar-border: #e9ecef;
--light-toolbar-text: #212529;
--light-toolbar-text-secondary: #495057;
--light-toolbar-button-hover: #e9ecef;
--light-tab-active-line: linear-gradient(90deg, #0066cc 0%, #0088ff 100%);
--light-bg-secondary: #f7fef7;
--light-text-secondary: #374151;
--light-text-muted: #6b7280;
--light-border-color: #e5e7eb;
--light-settings-bg: #ffffff;
--light-settings-card-bg: #f8f9fa;
--light-settings-text: #212529;
--light-settings-text-secondary: #6c757d;
--light-settings-border: #dee2e6;
--light-settings-input-bg: #ffffff;
--light-settings-input-border: #ced4da;
--light-settings-hover: #e9ecef;
--light-scrollbar-track: #f1f3f4;
--light-scrollbar-thumb: #c1c1c1;
--light-scrollbar-thumb-hover: #a8a8a8;
--light-selection-bg: rgba(59, 130, 246, 0.15);
--light-selection-text: #2563eb;
--light-danger-color: #dc3545;
--light-bg-primary: #ffffff;
--light-bg-hover: #f1f3f4;
--light-loading-bg-gradient: radial-gradient(#f0f6f0, #e5efe5);
--light-loading-color: #1a3c1a;
--light-loading-glow: 0 0 10px rgba(0, 160, 0, 0.3), 0 0 5px rgba(0, 120, 0, 0.2);
--light-loading-done-color: #008800;
--light-loading-overlay: linear-gradient(transparent 0%, rgba(220, 240, 220, 0.5) 50%);
/* 默认使用深色主题 */
--toolbar-bg: var(--dark-toolbar-bg);
--toolbar-border: var(--dark-toolbar-border);
--toolbar-text: var(--dark-toolbar-text);
--toolbar-text-secondary: var(--dark-toolbar-text-secondary);
--toolbar-button-hover: var(--dark-toolbar-button-hover);
--toolbar-separator: var(--dark-toolbar-button-hover);
--tab-active-line: var(--dark-tab-active-line);
--bg-secondary: var(--dark-bg-secondary);
--text-secondary: var(--dark-text-secondary);
--text-muted: var(--dark-text-muted);
--border-color: var(--dark-border-color);
--settings-bg: var(--dark-settings-bg);
--settings-card-bg: var(--dark-settings-card-bg);
--settings-text: var(--dark-settings-text);
--settings-text-secondary: var(--dark-settings-text-secondary);
--settings-border: var(--dark-settings-border);
--settings-input-bg: var(--dark-settings-input-bg);
--settings-input-border: var(--dark-settings-input-border);
--settings-hover: var(--dark-settings-hover);
--scrollbar-track: var(--dark-scrollbar-track);
--scrollbar-thumb: var(--dark-scrollbar-thumb);
--scrollbar-thumb-hover: var(--dark-scrollbar-thumb-hover);
--selection-bg: var(--dark-selection-bg);
--selection-text: var(--dark-selection-text);
--text-danger: var(--dark-danger-color);
--bg-primary: var(--dark-bg-primary);
--bg-hover: var(--dark-bg-hover);
--voidraft-bg-gradient: var(--dark-loading-bg-gradient);
--voidraft-loading-color: var(--dark-loading-color);
--voidraft-loading-glow: var(--dark-loading-glow);
--voidraft-loading-done-color: var(--dark-loading-done-color);
--voidraft-loading-overlay: var(--dark-loading-overlay);
--voidraft-mono-font: "HarmonyOS Sans Mono", monospace;
color-scheme: light dark;
--voidraft-font-mono: "HarmonyOS", SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
}
/* 监听系统深色主题 */
@media (prefers-color-scheme: dark) {
:root[data-theme="auto"] {
--toolbar-bg: var(--dark-toolbar-bg);
--toolbar-border: var(--dark-toolbar-border);
--toolbar-text: var(--dark-toolbar-text);
--toolbar-text-secondary: var(--dark-toolbar-text-secondary);
--toolbar-button-hover: var(--dark-toolbar-button-hover);
--toolbar-separator: var(--dark-toolbar-button-hover);
--tab-active-line: var(--dark-tab-active-line);
--bg-secondary: var(--dark-bg-secondary);
--text-secondary: var(--dark-text-secondary);
--text-muted: var(--dark-text-muted);
--border-color: var(--dark-border-color);
--settings-bg: var(--dark-settings-bg);
--settings-card-bg: var(--dark-settings-card-bg);
--settings-text: var(--dark-settings-text);
--settings-text-secondary: var(--dark-settings-text-secondary);
--settings-border: var(--dark-settings-border);
--settings-input-bg: var(--dark-settings-input-bg);
--settings-input-border: var(--dark-settings-input-border);
--settings-hover: var(--dark-settings-hover);
--scrollbar-track: var(--dark-scrollbar-track);
--scrollbar-thumb: var(--dark-scrollbar-thumb);
--scrollbar-thumb-hover: var(--dark-scrollbar-thumb-hover);
--selection-bg: var(--dark-selection-bg);
--selection-text: var(--dark-selection-text);
--text-danger: var(--dark-danger-color);
--bg-primary: var(--dark-bg-primary);
--bg-hover: var(--dark-bg-hover);
--voidraft-bg-gradient: var(--dark-loading-bg-gradient);
--voidraft-loading-color: var(--dark-loading-color);
--voidraft-loading-glow: var(--dark-loading-glow);
--voidraft-loading-done-color: var(--dark-loading-done-color);
--voidraft-loading-overlay: var(--dark-loading-overlay);
}
/* 默认/暗色主题 */
:root,
:root[data-theme="dark"],
:root[data-theme="auto"] {
color-scheme: dark;
--text-primary: #ffffff;
--toolbar-bg: #2d2d2d;
--toolbar-border: #404040;
--toolbar-text: #ffffff;
--toolbar-text-secondary: #cccccc;
--toolbar-button-hover: #404040;
--toolbar-separator: #404040;
--tab-active-line: linear-gradient(90deg, #007acc 0%, #0099ff 100%);
--bg-secondary: #0e1217;
--bg-primary: #1a1a1a;
--bg-hover: #2a2a2a;
--text-secondary: #a0aec0;
--text-muted: #666666;
--text-danger: #ff6b6b;
--border-color: #2d3748;
--settings-bg: #2a2a2a;
--settings-card-bg: #333333;
--settings-text: #ffffff;
--settings-text-secondary: #cccccc;
--settings-border: #444444;
--settings-input-bg: #3a3a3a;
--settings-input-border: #555555;
--settings-hover: #404040;
--scrollbar-track: #2a2a2a;
--scrollbar-thumb: #555555;
--scrollbar-thumb-hover: #666666;
--selection-bg: rgba(181, 206, 168, 0.1);
--selection-text: #b5cea8;
--voidraft-bg-gradient: radial-gradient(#222922, #000500);
--voidraft-loading-color: #ffffff;
--voidraft-loading-glow: 0 0 10px rgba(50, 255, 50, 0.5), 0 0 5px rgba(100, 255, 100, 0.5);
--voidraft-loading-done-color: #66ff66;
--voidraft-loading-overlay: linear-gradient(transparent 0%, rgba(10, 16, 10, 0.5) 50%);
/* Markdown 代码块样式 - 暗色主题 */
--cm-codeblock-bg: rgba(46, 51, 69, 0.8);
--cm-codeblock-radius: 0.4rem;
/* Markdown 内联代码样式 */
--cm-inline-code-bg: oklch(28% 0.02 255);
/* Markdown 上标/下标样式 */
--cm-superscript-color: inherit;
--cm-subscript-color: inherit;
/* Markdown 高亮样式 */
--cm-highlight-background: rgba(250, 204, 21, 0.35);
/* Markdown 表格样式 - 暗色主题 */
--cm-table-bg: rgba(35, 40, 52, 0.5);
--cm-table-header-bg: rgba(46, 51, 69, 0.7);
--cm-table-border: rgba(75, 85, 99, 0.35);
--cm-table-row-hover: rgba(55, 62, 78, 0.5);
/* Search Panel - Dark Theme */
--search-panel-bg: #252526;
--search-panel-text: #cccccc;
--search-panel-border: #454545;
--search-input-bg: #3c3c3c;
--search-input-text: #cccccc;
--search-input-border: #3c3c3c;
--search-focus-border: #0078d4;
--search-btn-hover: rgba(255, 255, 255, 0.1);
--search-btn-active-bg: rgba(0, 120, 212, 0.4);
--search-btn-active-text: #ffffff;
--search-error-border: #f14c4c;
--search-error-bg: #5a1d1d;
/* Search Match Highlight - Dark Theme (VSCode style) */
--search-match-bg: rgba(250, 220, 81, 0.85);
--search-match-selected-bg: rgba(81, 175, 255, 0.5);
--search-match-selected-border: #74b0f4;
}
/* 监听系统浅色主题 */
/* 色主题 */
:root[data-theme="light"] {
color-scheme: light;
--text-primary: #000000;
--toolbar-bg: #f8f9fa;
--toolbar-border: #e9ecef;
--toolbar-text: #212529;
--toolbar-text-secondary: #495057;
--toolbar-button-hover: #e9ecef;
--toolbar-separator: #e9ecef;
--tab-active-line: linear-gradient(90deg, #0066cc 0%, #0088ff 100%);
--bg-secondary: #f7fef7;
--bg-primary: #ffffff;
--bg-hover: #f1f3f4;
--text-secondary: #374151;
--text-muted: #6b7280;
--text-danger: #dc3545;
--border-color: #e5e7eb;
--settings-bg: #ffffff;
--settings-card-bg: #f8f9fa;
--settings-text: #212529;
--settings-text-secondary: #6c757d;
--settings-border: #dee2e6;
--settings-input-bg: #ffffff;
--settings-input-border: #ced4da;
--settings-hover: #e9ecef;
--scrollbar-track: #f1f3f4;
--scrollbar-thumb: #c1c1c1;
--scrollbar-thumb-hover: #a8a8a8;
--selection-bg: rgba(59, 130, 246, 0.15);
--selection-text: #2563eb;
--voidraft-bg-gradient: radial-gradient(#f0f6f0, #e5efe5);
--voidraft-loading-color: #1a3c1a;
--voidraft-loading-glow: 0 0 10px rgba(0, 160, 0, 0.3), 0 0 5px rgba(0, 120, 0, 0.2);
--voidraft-loading-done-color: #008800;
--voidraft-loading-overlay: linear-gradient(transparent 0%, rgba(220, 240, 220, 0.5) 50%);
/* Markdown 代码块样式 - 亮色主题 */
--cm-codeblock-bg: #f3f3f3;
--cm-codeblock-radius: 0.4rem;
/* Markdown 内联代码样式 */
--cm-inline-code-bg: oklch(92.9% 0.013 255.508);
/* Markdown 上标/下标样式 */
--cm-superscript-color: inherit;
--cm-subscript-color: inherit;
/* Markdown 高亮样式 */
--cm-highlight-background: rgba(253, 224, 71, 0.45);
/* Markdown 表格样式 - 亮色主题 */
--cm-table-bg: oklch(97.5% 0.006 255);
--cm-table-header-bg: oklch(94% 0.01 255);
--cm-table-border: oklch(88% 0.008 255);
--cm-table-row-hover: oklch(95% 0.008 255);
/* Search Panel - Light Theme */
--search-panel-bg: #f3f3f3;
--search-panel-text: #616161;
--search-panel-border: #c8c8c8;
--search-input-bg: #ffffff;
--search-input-text: #616161;
--search-input-border: #cecece;
--search-focus-border: #0078d4;
--search-btn-hover: rgba(0, 0, 0, 0.1);
--search-btn-active-bg: rgba(0, 120, 212, 0.2);
--search-btn-active-text: #0078d4;
--search-error-border: #e51400;
--search-error-bg: #fdeceb;
/* Search Match Highlight - Light Theme (VSCode style) */
--search-match-bg: rgba(250, 220, 81, 0.85);
--search-match-selected-bg: rgba(38, 143, 255, 0.3);
--search-match-selected-border: #268fff;
}
/* 跟随系统的浅色偏好 */
@media (prefers-color-scheme: light) {
:root[data-theme="auto"] {
--toolbar-bg: var(--light-toolbar-bg);
--toolbar-border: var(--light-toolbar-border);
--toolbar-text: var(--light-toolbar-text);
--toolbar-text-secondary: var(--light-toolbar-text-secondary);
--toolbar-button-hover: var(--light-toolbar-button-hover);
--toolbar-separator: var(--light-toolbar-button-hover);
--tab-active-line: var(--light-tab-active-line);
--bg-secondary: var(--light-bg-secondary);
--text-secondary: var(--light-text-secondary);
--text-muted: var(--light-text-muted);
--border-color: var(--light-border-color);
--settings-bg: var(--light-settings-bg);
--settings-card-bg: var(--light-settings-card-bg);
--settings-text: var(--light-settings-text);
--settings-text-secondary: var(--light-settings-text-secondary);
--settings-border: var(--light-settings-border);
--settings-input-bg: var(--light-settings-input-bg);
--settings-input-border: var(--light-settings-input-border);
--settings-hover: var(--light-settings-hover);
--scrollbar-track: var(--light-scrollbar-track);
--scrollbar-thumb: var(--light-scrollbar-thumb);
--scrollbar-thumb-hover: var(--light-scrollbar-thumb-hover);
--selection-bg: var(--light-selection-bg);
--selection-text: var(--light-selection-text);
--text-danger: var(--light-danger-color);
--bg-primary: var(--light-bg-primary);
--bg-hover: var(--light-bg-hover);
--voidraft-bg-gradient: var(--light-loading-bg-gradient);
--voidraft-loading-color: var(--light-loading-color);
--voidraft-loading-glow: var(--light-loading-glow);
--voidraft-loading-done-color: var(--light-loading-done-color);
--voidraft-loading-overlay: var(--light-loading-overlay);
color-scheme: light;
--text-primary: #000000;
--toolbar-bg: #f8f9fa;
--toolbar-border: #e9ecef;
--toolbar-text: #212529;
--toolbar-text-secondary: #495057;
--toolbar-button-hover: #e9ecef;
--toolbar-separator: #e9ecef;
--tab-active-line: linear-gradient(90deg, #0066cc 0%, #0088ff 100%);
--bg-secondary: #f7fef7;
--bg-primary: #ffffff;
--bg-hover: #f1f3f4;
--text-secondary: #374151;
--text-muted: #6b7280;
--text-danger: #dc3545;
--border-color: #e5e7eb;
--settings-bg: #ffffff;
--settings-card-bg: #f8f9fa;
--settings-text: #212529;
--settings-text-secondary: #6c757d;
--settings-border: #dee2e6;
--settings-input-bg: #ffffff;
--settings-input-border: #ced4da;
--settings-hover: #e9ecef;
--scrollbar-track: #f1f3f4;
--scrollbar-thumb: #c1c1c1;
--scrollbar-thumb-hover: #a8a8a8;
--selection-bg: rgba(59, 130, 246, 0.15);
--selection-text: #2563eb;
--voidraft-bg-gradient: radial-gradient(#f0f6f0, #e5efe5);
--voidraft-loading-color: #1a3c1a;
--voidraft-loading-glow: 0 0 10px rgba(0, 160, 0, 0.3), 0 0 5px rgba(0, 120, 0, 0.2);
--voidraft-loading-done-color: #008800;
--voidraft-loading-overlay: linear-gradient(transparent 0%, rgba(220, 240, 220, 0.5) 50%);
/* Markdown 代码块样式 - 亮色主题 */
--cm-codeblock-bg: oklch(92.9% 0.013 255.508);
--cm-codeblock-radius: 0.4rem;
/* Markdown 内联代码样式 */
--cm-inline-code-bg: oklch(92.9% 0.013 255.508);
/* Markdown 上标/下标样式 */
--cm-superscript-color: inherit;
--cm-subscript-color: inherit;
/* Markdown 高亮样式 */
--cm-highlight-background: rgba(253, 224, 71, 0.45);
/* Markdown 表格样式 - 亮色主题 */
--cm-table-bg: oklch(97.5% 0.006 255);
--cm-table-header-bg: oklch(94% 0.01 255);
--cm-table-border: oklch(88% 0.008 255);
--cm-table-row-hover: oklch(95% 0.008 255);
/* Search Panel - Light Theme (auto) */
--search-panel-bg: #f3f3f3;
--search-panel-text: #616161;
--search-panel-border: #c8c8c8;
--search-input-bg: #ffffff;
--search-input-text: #616161;
--search-input-border: #cecece;
--search-focus-border: #0078d4;
--search-btn-hover: rgba(0, 0, 0, 0.1);
--search-btn-active-bg: rgba(0, 120, 212, 0.2);
--search-btn-active-text: #0078d4;
--search-error-border: #e51400;
--search-error-bg: #fdeceb;
/* Search Match Highlight - Light Theme auto (VSCode style) */
--search-match-bg: rgba(250, 220, 81, 0.85);
--search-match-selected-bg: rgba(38, 143, 255, 0.3);
--search-match-selected-border: #268fff;
}
}
/* 手动选择浅色主题 */
:root[data-theme="light"] {
--toolbar-bg: var(--light-toolbar-bg);
--toolbar-border: var(--light-toolbar-border);
--toolbar-text: var(--light-toolbar-text);
--toolbar-text-secondary: var(--light-toolbar-text-secondary);
--toolbar-button-hover: var(--light-toolbar-button-hover);
--toolbar-separator: var(--light-toolbar-button-hover);
--tab-active-line: var(--light-tab-active-line);
--bg-secondary: var(--light-bg-secondary);
--text-secondary: var(--light-text-secondary);
--text-muted: var(--light-text-muted);
--border-color: var(--light-border-color);
--settings-bg: var(--light-settings-bg);
--settings-card-bg: var(--light-settings-card-bg);
--settings-text: var(--light-settings-text);
--settings-text-secondary: var(--light-settings-text-secondary);
--settings-border: var(--light-settings-border);
--settings-input-bg: var(--light-settings-input-bg);
--settings-input-border: var(--light-settings-input-border);
--settings-hover: var(--light-settings-hover);
--scrollbar-track: var(--light-scrollbar-track);
--scrollbar-thumb: var(--light-scrollbar-thumb);
--scrollbar-thumb-hover: var(--light-scrollbar-thumb-hover);
--selection-bg: var(--light-selection-bg);
--selection-text: var(--light-selection-text);
--text-danger: var(--light-danger-color);
--bg-primary: var(--light-bg-primary);
--bg-hover: var(--light-bg-hover);
--voidraft-bg-gradient: var(--light-loading-bg-gradient);
--voidraft-loading-color: var(--light-loading-color);
--voidraft-loading-glow: var(--light-loading-glow);
--voidraft-loading-done-color: var(--light-loading-done-color);
--voidraft-loading-overlay: var(--light-loading-overlay);
}
/* 手动选择深色主题 */
:root[data-theme="dark"] {
--toolbar-bg: var(--dark-toolbar-bg);
--toolbar-border: var(--dark-toolbar-border);
--toolbar-text: var(--dark-toolbar-text);
--toolbar-text-secondary: var(--dark-toolbar-text-secondary);
--toolbar-button-hover: var(--dark-toolbar-button-hover);
--toolbar-separator: var(--dark-toolbar-button-hover);
--tab-active-line: var(--dark-tab-active-line);
--bg-secondary: var(--dark-bg-secondary);
--text-secondary: var(--dark-text-secondary);
--text-muted: var(--dark-text-muted);
--border-color: var(--dark-border-color);
--settings-bg: var(--dark-settings-bg);
--settings-card-bg: var(--dark-settings-card-bg);
--settings-text: var(--dark-settings-text);
--settings-text-secondary: var(--dark-settings-text-secondary);
--settings-border: var(--dark-settings-border);
--settings-input-bg: var(--dark-settings-input-bg);
--settings-input-border: var(--dark-settings-input-border);
--settings-hover: var(--dark-settings-hover);
--scrollbar-track: var(--dark-scrollbar-track);
--scrollbar-thumb: var(--dark-scrollbar-thumb);
--scrollbar-thumb-hover: var(--dark-scrollbar-thumb-hover);
--selection-bg: var(--dark-selection-bg);
--selection-text: var(--dark-selection-text);
--text-danger: var(--dark-danger-color);
--bg-primary: var(--dark-bg-primary);
--bg-hover: var(--dark-bg-hover);
--voidraft-bg-gradient: var(--dark-loading-bg-gradient);
--voidraft-loading-color: var(--dark-loading-color);
--voidraft-loading-glow: var(--dark-loading-glow);
--voidraft-loading-done-color: var(--dark-loading-done-color);
--voidraft-loading-overlay: var(--dark-loading-overlay);
}

View File

@@ -1,43 +1,20 @@
import {
AppConfig,
AppearanceConfig,
EditingConfig,
GeneralConfig,
AuthMethod,
KeyBindingType,
LanguageType,
SystemThemeType,
TabType,
UpdatesConfig,
UpdateSourceType,
GitBackupConfig,
AuthMethod
UpdateSourceType
} from '@/../bindings/voidraft/internal/models/models';
import {FONT_OPTIONS} from './fonts';
// 配置键映射和限制的类型定义
export type GeneralConfigKeyMap = {
readonly [K in keyof GeneralConfig]: string;
};
export type EditingConfigKeyMap = {
readonly [K in keyof EditingConfig]: string;
};
export type AppearanceConfigKeyMap = {
readonly [K in keyof AppearanceConfig]: string;
};
export type UpdatesConfigKeyMap = {
readonly [K in keyof UpdatesConfig]: string;
};
export type BackupConfigKeyMap = {
readonly [K in keyof GitBackupConfig]: string;
};
export type NumberConfigKey = 'fontSize' | 'tabSize' | 'lineHeight';
export type ConfigSection = 'general' | 'editing' | 'appearance' | 'updates' | 'backup';
// 配置键映射
export const GENERAL_CONFIG_KEY_MAP: GeneralConfigKeyMap = {
// 统一配置键映射(平级展开)
export const CONFIG_KEY_MAP = {
// general
alwaysOnTop: 'general.alwaysOnTop',
dataPath: 'general.dataPath',
enableSystemTray: 'general.enableSystemTray',
@@ -47,9 +24,7 @@ export const GENERAL_CONFIG_KEY_MAP: GeneralConfigKeyMap = {
enableWindowSnap: 'general.enableWindowSnap',
enableLoadingAnimation: 'general.enableLoadingAnimation',
enableTabs: 'general.enableTabs',
} as const;
export const EDITING_CONFIG_KEY_MAP: EditingConfigKeyMap = {
// editing
fontSize: 'editing.fontSize',
fontFamily: 'editing.fontFamily',
fontWeight: 'editing.fontWeight',
@@ -57,16 +32,13 @@ export const EDITING_CONFIG_KEY_MAP: EditingConfigKeyMap = {
enableTabIndent: 'editing.enableTabIndent',
tabSize: 'editing.tabSize',
tabType: 'editing.tabType',
autoSaveDelay: 'editing.autoSaveDelay'
} as const;
export const APPEARANCE_CONFIG_KEY_MAP: AppearanceConfigKeyMap = {
keymapMode: 'editing.keymapMode',
autoSaveDelay: 'editing.autoSaveDelay',
// appearance
language: 'appearance.language',
systemTheme: 'appearance.systemTheme',
currentTheme: 'appearance.currentTheme'
} as const;
export const UPDATES_CONFIG_KEY_MAP: UpdatesConfigKeyMap = {
currentTheme: 'appearance.currentTheme',
// updates
version: 'updates.version',
autoUpdate: 'updates.autoUpdate',
primarySource: 'updates.primarySource',
@@ -74,10 +46,8 @@ export const UPDATES_CONFIG_KEY_MAP: UpdatesConfigKeyMap = {
backupBeforeUpdate: 'updates.backupBeforeUpdate',
updateTimeout: 'updates.updateTimeout',
github: 'updates.github',
gitea: 'updates.gitea'
} as const;
export const BACKUP_CONFIG_KEY_MAP: BackupConfigKeyMap = {
gitea: 'updates.gitea',
// backup
enabled: 'backup.enabled',
repo_url: 'backup.repo_url',
auth_method: 'backup.auth_method',
@@ -90,6 +60,8 @@ export const BACKUP_CONFIG_KEY_MAP: BackupConfigKeyMap = {
auto_backup: 'backup.auto_backup',
} as const;
export type ConfigKey = keyof typeof CONFIG_KEY_MAP;
// 配置限制
export const CONFIG_LIMITS = {
fontSize: {min: 12, max: 28, default: 13},
@@ -125,11 +97,12 @@ export const DEFAULT_CONFIG: AppConfig = {
enableTabIndent: true,
tabSize: CONFIG_LIMITS.tabSize.default,
tabType: CONFIG_LIMITS.tabType.default,
keymapMode: KeyBindingType.Standard,
autoSaveDelay: 5000
},
appearance: {
language: LanguageType.LangZhCN,
systemTheme: SystemThemeType.SystemThemeAuto,
systemTheme: SystemThemeType.SystemThemeDark,
currentTheme: 'default-dark'
},
updates: {

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,10 @@ export const FONT_OPTIONS = [
label: 'Open Sans',
value: '"Open Sans"'
},
{
label: 'Monocraft',
value: 'Monocraft'
},
// Common system fonts
{
label: 'Arial',
@@ -46,7 +50,7 @@ export const FONT_OPTIONS = [
label: 'System UI',
value: 'system-ui'
},
// Chinese fonts
{
label: 'Microsoft YaHei',
@@ -56,7 +60,7 @@ export const FONT_OPTIONS = [
label: 'PingFang SC',
value: '"PingFang SC"'
},
// Popular programming fonts
{
label: 'JetBrains Mono',

View File

@@ -1,49 +0,0 @@
/**
* 默认翻译配置
*/
export const DEFAULT_TRANSLATION_CONFIG = {
minSelectionLength: 2,
maxTranslationLength: 5000,
} as const;
/**
* 翻译相关的错误消息
*/
export const TRANSLATION_ERRORS = {
NO_TEXT: 'no text to translate',
TRANSLATION_FAILED: 'translation failed',
} as const;
/**
* 翻译结果接口
*/
export interface TranslationResult {
translatedText: string;
error?: string;
}
/**
* 语言信息接口
*/
export interface LanguageInfo {
Code: string; // 语言代码
Name: string; // 语言名称
}
/**
* 翻译器扩展配置
*/
export interface TranslatorConfig {
/** 最小选择字符数才显示翻译按钮 */
minSelectionLength: number;
/** 最大翻译字符数 */
maxTranslationLength: number;
}
/**
* 翻译图标SVG
*/
export const TRANSLATION_ICON_SVG = `
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24">
<path d="M599.68 485.056h-8l30.592 164.672c20.352-7.04 38.72-17.344 54.912-31.104a271.36 271.36 0 0 1-40.704-64.64l32.256-4.032c8.896 17.664 19.072 33.28 30.592 46.72 23.872-27.968 42.24-65.152 55.04-111.744l-154.688 0.128z m121.92 133.76c18.368 15.36 39.36 26.56 62.848 33.472l14.784 4.416-8.64 30.336-14.72-4.352a205.696 205.696 0 0 1-76.48-41.728c-20.672 17.92-44.928 31.552-71.232 40.064l20.736 110.912H519.424l-9.984 72.512h385.152c18.112 0 32.704-14.144 32.704-31.616V295.424a32.128 32.128 0 0 0-32.704-31.552H550.528l35.2 189.696h79.424v-31.552h61.44v31.552h102.4v31.616h-42.688c-14.272 55.488-35.712 100.096-64.64 133.568zM479.36 791.68H193.472c-36.224 0-65.472-28.288-65.472-63.168V191.168C128 156.16 157.312 128 193.472 128h327.68l20.544 104.32h352.832c36.224 0 65.472 28.224 65.472 63.104v537.408c0 34.944-29.312 63.168-65.472 63.168H468.608l10.688-104.32zM337.472 548.352v-33.28H272.768v-48.896h60.16V433.28h-60.16v-41.728h64.704v-32.896h-102.4v189.632h102.4z m158.272 0V453.76c0-17.216-4.032-30.272-12.16-39.488-8.192-9.152-20.288-13.696-36.032-13.696a55.04 55.04 0 0 0-24.768 5.376 39.04 39.04 0 0 0-17.088 15.936h-1.984l-5.056-18.56h-28.352V548.48h37.12V480c0-17.088 2.304-29.376 6.912-36.736 4.608-7.424 12.16-11.072 22.528-11.072 7.616 0 13.248 2.56 16.64 7.872 3.52 5.248 5.312 13.056 5.312 23.488v84.736h36.928z" fill="currentColor"></path>
</svg>`;

View File

@@ -1,159 +0,0 @@
// Enclose abbreviations in <abbr> tags
//
import MarkdownIt, {StateBlock, StateCore, Token} from 'markdown-it';
/**
* 环境接口,包含缩写定义
*/
interface AbbrEnv {
abbreviations?: { [key: string]: string };
}
/**
* markdown-it-abbr 插件
* 用于支持缩写语法
*/
export default function abbr_plugin(md: MarkdownIt): void {
const escapeRE = md.utils.escapeRE;
const arrayReplaceAt = md.utils.arrayReplaceAt;
// ASCII characters in Cc, Sc, Sm, Sk categories we should terminate on;
// you can check character classes here:
// http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
const OTHER_CHARS = ' \r\n$+<=>^`|~';
const UNICODE_PUNCT_RE = md.utils.lib.ucmicro.P.source;
const UNICODE_SPACE_RE = md.utils.lib.ucmicro.Z.source;
function abbr_def(state: StateBlock, startLine: number, endLine: number, silent: boolean): boolean {
let labelEnd: number;
let pos = state.bMarks[startLine] + state.tShift[startLine];
const max = state.eMarks[startLine];
if (pos + 2 >= max) { return false; }
if (state.src.charCodeAt(pos++) !== 0x2A/* * */) { return false; }
if (state.src.charCodeAt(pos++) !== 0x5B/* [ */) { return false; }
const labelStart = pos;
for (; pos < max; pos++) {
const ch = state.src.charCodeAt(pos);
if (ch === 0x5B /* [ */) {
return false;
} else if (ch === 0x5D /* ] */) {
labelEnd = pos;
break;
} else if (ch === 0x5C /* \ */) {
pos++;
}
}
if (labelEnd! < 0 || state.src.charCodeAt(labelEnd! + 1) !== 0x3A/* : */) {
return false;
}
if (silent) { return true; }
const label = state.src.slice(labelStart, labelEnd!).replace(/\\(.)/g, '$1');
const title = state.src.slice(labelEnd! + 2, max).trim();
if (label.length === 0) { return false; }
if (title.length === 0) { return false; }
const env = state.env as AbbrEnv;
if (!env.abbreviations) { env.abbreviations = {}; }
// prepend ':' to avoid conflict with Object.prototype members
if (typeof env.abbreviations[':' + label] === 'undefined') {
env.abbreviations[':' + label] = title;
}
state.line = startLine + 1;
return true;
}
function abbr_replace(state: StateCore): void {
const blockTokens = state.tokens;
const env = state.env as AbbrEnv;
if (!env.abbreviations) { return; }
const regSimple = new RegExp('(?:' +
Object.keys(env.abbreviations).map(function (x: string) {
return x.substr(1);
}).sort(function (a: string, b: string) {
return b.length - a.length;
}).map(escapeRE).join('|') +
')');
const regText = '(^|' + UNICODE_PUNCT_RE + '|' + UNICODE_SPACE_RE +
'|[' + OTHER_CHARS.split('').map(escapeRE).join('') + '])' +
'(' + Object.keys(env.abbreviations).map(function (x: string) {
return x.substr(1);
}).sort(function (a: string, b: string) {
return b.length - a.length;
}).map(escapeRE).join('|') + ')' +
'($|' + UNICODE_PUNCT_RE + '|' + UNICODE_SPACE_RE +
'|[' + OTHER_CHARS.split('').map(escapeRE).join('') + '])'
const reg = new RegExp(regText, 'g');
for (let j = 0, l = blockTokens.length; j < l; j++) {
if (blockTokens[j].type !== 'inline') { continue; }
let tokens = blockTokens[j].children!;
// We scan from the end, to keep position when new tags added.
for (let i = tokens.length - 1; i >= 0; i--) {
const currentToken = tokens[i];
if (currentToken.type !== 'text') { continue; }
let pos = 0;
const text = currentToken.content;
reg.lastIndex = 0;
const nodes: Token[] = [];
// fast regexp run to determine whether there are any abbreviated words
// in the current token
if (!regSimple.test(text)) { continue; }
let m: RegExpExecArray | null;
while ((m = reg.exec(text))) {
if (m.index > 0 || m[1].length > 0) {
const token = new state.Token('text', '', 0);
token.content = text.slice(pos, m.index + m[1].length);
nodes.push(token);
}
const token_o = new state.Token('abbr_open', 'abbr', 1);
token_o.attrs = [['title', env.abbreviations[':' + m[2]]]];
nodes.push(token_o);
const token_t = new state.Token('text', '', 0);
token_t.content = m[2];
nodes.push(token_t);
const token_c = new state.Token('abbr_close', 'abbr', -1);
nodes.push(token_c);
reg.lastIndex -= m[3].length;
pos = reg.lastIndex;
}
if (!nodes.length) { continue; }
if (pos < text.length) {
const token = new state.Token('text', '', 0);
token.content = text.slice(pos);
nodes.push(token);
}
// replace current node
blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, nodes);
}
}
}
md.block.ruler.before('reference', 'abbr_def', abbr_def, { alt: ['paragraph', 'reference'] });
md.core.ruler.after('linkify', 'abbr_replace', abbr_replace);
}

View File

@@ -1,209 +0,0 @@
// Process definition lists
//
import MarkdownIt, { StateBlock, Token } from 'markdown-it';
/**
* markdown-it-deflist 插件
* 用于支持定义列表语法
*/
export default function deflist_plugin(md: MarkdownIt): void {
const isSpace = md.utils.isSpace;
// Search `[:~][\n ]`, returns next pos after marker on success
// or -1 on fail.
function skipMarker(state: StateBlock, line: number): number {
let start = state.bMarks[line] + state.tShift[line];
const max = state.eMarks[line];
if (start >= max) { return -1; }
// Check bullet
const marker = state.src.charCodeAt(start++);
if (marker !== 0x7E/* ~ */ && marker !== 0x3A/* : */) { return -1; }
const pos = state.skipSpaces(start);
// require space after ":"
if (start === pos) { return -1; }
// no empty definitions, e.g. " : "
if (pos >= max) { return -1; }
return start;
}
function markTightParagraphs(state: StateBlock, idx: number): void {
const level = state.level + 2;
for (let i = idx + 2, l = state.tokens.length - 2; i < l; i++) {
if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') {
state.tokens[i + 2].hidden = true;
state.tokens[i].hidden = true;
i += 2;
}
}
}
function deflist(state: StateBlock, startLine: number, endLine: number, silent: boolean): boolean {
if (silent) {
// quirk: validation mode validates a dd block only, not a whole deflist
if (state.ddIndent < 0) { return false; }
return skipMarker(state, startLine) >= 0;
}
let nextLine = startLine + 1;
if (nextLine >= endLine) { return false; }
if (state.isEmpty(nextLine)) {
nextLine++;
if (nextLine >= endLine) { return false; }
}
if (state.sCount[nextLine] < state.blkIndent) { return false; }
let contentStart = skipMarker(state, nextLine);
if (contentStart < 0) { return false; }
// Start list
const listTokIdx = state.tokens.length;
let tight = true;
const token_dl_o: Token = state.push('dl_open', 'dl', 1);
const listLines: [number, number] = [startLine, 0];
token_dl_o.map = listLines;
//
// Iterate list items
//
let dtLine = startLine;
let ddLine = nextLine;
// One definition list can contain multiple DTs,
// and one DT can be followed by multiple DDs.
//
// Thus, there is two loops here, and label is
// needed to break out of the second one
//
/* eslint no-labels:0,block-scoped-var:0 */
OUTER:
for (;;) {
let prevEmptyEnd = false;
const token_dt_o: Token = state.push('dt_open', 'dt', 1);
token_dt_o.map = [dtLine, dtLine];
const token_i: Token = state.push('inline', '', 0);
token_i.map = [dtLine, dtLine];
token_i.content = state.getLines(dtLine, dtLine + 1, state.blkIndent, false).trim();
token_i.children = [];
state.push('dt_close', 'dt', -1);
for (;;) {
const token_dd_o: Token = state.push('dd_open', 'dd', 1);
const itemLines: [number, number] = [nextLine, 0];
token_dd_o.map = itemLines;
let pos = contentStart;
const max = state.eMarks[ddLine];
let offset = state.sCount[ddLine] + contentStart - (state.bMarks[ddLine] + state.tShift[ddLine]);
while (pos < max) {
const ch = state.src.charCodeAt(pos);
if (isSpace(ch)) {
if (ch === 0x09) {
offset += 4 - offset % 4;
} else {
offset++;
}
} else {
break;
}
pos++;
}
contentStart = pos;
const oldTight = state.tight;
const oldDDIndent = state.ddIndent;
const oldIndent = state.blkIndent;
const oldTShift = state.tShift[ddLine];
const oldSCount = state.sCount[ddLine];
const oldParentType = state.parentType;
state.blkIndent = state.ddIndent = state.sCount[ddLine] + 2;
state.tShift[ddLine] = contentStart - state.bMarks[ddLine];
state.sCount[ddLine] = offset;
state.tight = true;
state.parentType = 'deflist' as any;
state.md.block.tokenize(state, ddLine, endLine);
// If any of list item is tight, mark list as tight
if (!state.tight || prevEmptyEnd) {
tight = false;
}
// Item become loose if finish with empty line,
// but we should filter last element, because it means list finish
prevEmptyEnd = (state.line - ddLine) > 1 && state.isEmpty(state.line - 1);
state.tShift[ddLine] = oldTShift;
state.sCount[ddLine] = oldSCount;
state.tight = oldTight;
state.parentType = oldParentType;
state.blkIndent = oldIndent;
state.ddIndent = oldDDIndent;
state.push('dd_close', 'dd', -1);
itemLines[1] = nextLine = state.line;
if (nextLine >= endLine) { break OUTER; }
if (state.sCount[nextLine] < state.blkIndent) { break OUTER; }
contentStart = skipMarker(state, nextLine);
if (contentStart < 0) { break; }
ddLine = nextLine;
// go to the next loop iteration:
// insert DD tag and repeat checking
}
if (nextLine >= endLine) { break; }
dtLine = nextLine;
if (state.isEmpty(dtLine)) { break; }
if (state.sCount[dtLine] < state.blkIndent) { break; }
ddLine = dtLine + 1;
if (ddLine >= endLine) { break; }
if (state.isEmpty(ddLine)) { ddLine++; }
if (ddLine >= endLine) { break; }
if (state.sCount[ddLine] < state.blkIndent) { break; }
contentStart = skipMarker(state, ddLine);
if (contentStart < 0) { break; }
// go to the next loop iteration:
// insert DT and DD tags and repeat checking
}
// Finilize list
state.push('dl_close', 'dl', -1);
listLines[1] = nextLine;
state.line = nextLine;
// mark paragraphs tight if needed
if (tight) {
markTightParagraphs(state, listTokIdx);
}
return true;
}
md.block.ruler.before('paragraph', 'deflist', deflist, { alt: ['paragraph', 'reference', 'blockquote'] });
}

View File

@@ -1,4 +0,0 @@
export { default as bare } from './lib/bare';
export { default as light } from './lib/light';
export { default as full } from './lib/full';

View File

@@ -1,26 +0,0 @@
import MarkdownIt from 'markdown-it';
import emoji_html from './render';
import emoji_replace from './replace';
import normalize_opts, { EmojiOptions } from './normalize_opts';
/**
* Bare emoji 插件(不包含预定义的 emoji 数据)
*/
export default function emoji_plugin(md: MarkdownIt, options?: Partial<EmojiOptions>): void {
const defaults: EmojiOptions = {
defs: {},
shortcuts: {},
enabled: []
};
const opts = normalize_opts(md.utils.assign({}, defaults, options || {}) as EmojiOptions);
md.renderer.rules.emoji = emoji_html;
md.core.ruler.after(
'linkify',
'emoji',
emoji_replace(md, opts.defs, opts.shortcuts, opts.scanRE, opts.replaceRE)
);
}

View File

@@ -1,158 +0,0 @@
// Generated, don't edit
import { EmojiDefs } from '../normalize_opts';
const emojies: EmojiDefs = {
"grinning": "😀",
"smiley": "😃",
"smile": "😄",
"grin": "😁",
"laughing": "😆",
"satisfied": "😆",
"sweat_smile": "😅",
"joy": "😂",
"wink": "😉",
"blush": "😊",
"innocent": "😇",
"heart_eyes": "😍",
"kissing_heart": "😘",
"kissing": "😗",
"kissing_closed_eyes": "😚",
"kissing_smiling_eyes": "😙",
"yum": "😋",
"stuck_out_tongue": "😛",
"stuck_out_tongue_winking_eye": "😜",
"stuck_out_tongue_closed_eyes": "😝",
"neutral_face": "😐",
"expressionless": "😑",
"no_mouth": "😶",
"smirk": "😏",
"unamused": "😒",
"relieved": "😌",
"pensive": "😔",
"sleepy": "😪",
"sleeping": "😴",
"mask": "😷",
"dizzy_face": "😵",
"sunglasses": "😎",
"confused": "😕",
"worried": "😟",
"open_mouth": "😮",
"hushed": "😯",
"astonished": "😲",
"flushed": "😳",
"frowning": "😦",
"anguished": "😧",
"fearful": "😨",
"cold_sweat": "😰",
"disappointed_relieved": "😥",
"cry": "😢",
"sob": "😭",
"scream": "😱",
"confounded": "😖",
"persevere": "😣",
"disappointed": "😞",
"sweat": "😓",
"weary": "😩",
"tired_face": "😫",
"rage": "😡",
"pout": "😡",
"angry": "😠",
"smiling_imp": "😈",
"smiley_cat": "😺",
"smile_cat": "😸",
"joy_cat": "😹",
"heart_eyes_cat": "😻",
"smirk_cat": "😼",
"kissing_cat": "😽",
"scream_cat": "🙀",
"crying_cat_face": "😿",
"pouting_cat": "😾",
"heart": "❤️",
"hand": "✋",
"raised_hand": "✋",
"v": "✌️",
"point_up": "☝️",
"fist_raised": "✊",
"fist": "✊",
"monkey_face": "🐵",
"cat": "🐱",
"cow": "🐮",
"mouse": "🐭",
"coffee": "☕",
"hotsprings": "♨️",
"anchor": "⚓",
"airplane": "✈️",
"hourglass": "⌛",
"watch": "⌚",
"sunny": "☀️",
"star": "⭐",
"cloud": "☁️",
"umbrella": "☔",
"zap": "⚡",
"snowflake": "❄️",
"sparkles": "✨",
"black_joker": "🃏",
"mahjong": "🀄",
"phone": "☎️",
"telephone": "☎️",
"envelope": "✉️",
"pencil2": "✏️",
"black_nib": "✒️",
"scissors": "✂️",
"wheelchair": "♿",
"warning": "⚠️",
"aries": "♈",
"taurus": "♉",
"gemini": "♊",
"cancer": "♋",
"leo": "♌",
"virgo": "♍",
"libra": "♎",
"scorpius": "♏",
"sagittarius": "♐",
"capricorn": "♑",
"aquarius": "♒",
"pisces": "♓",
"heavy_multiplication_x": "✖️",
"heavy_plus_sign": "",
"heavy_minus_sign": "",
"heavy_division_sign": "➗",
"bangbang": "‼️",
"interrobang": "⁉️",
"question": "❓",
"grey_question": "❔",
"grey_exclamation": "❕",
"exclamation": "❗",
"heavy_exclamation_mark": "❗",
"wavy_dash": "〰️",
"recycle": "♻️",
"white_check_mark": "✅",
"ballot_box_with_check": "☑️",
"heavy_check_mark": "✔️",
"x": "❌",
"negative_squared_cross_mark": "❎",
"curly_loop": "➰",
"loop": "➿",
"part_alternation_mark": "〽️",
"eight_spoked_asterisk": "✳️",
"eight_pointed_black_star": "✴️",
"sparkle": "❇️",
"copyright": "©️",
"registered": "®️",
"tm": "™️",
"information_source": "",
"m": "Ⓜ️",
"black_circle": "⚫",
"white_circle": "⚪",
"black_large_square": "⬛",
"white_large_square": "⬜",
"black_medium_square": "◼️",
"white_medium_square": "◻️",
"black_medium_small_square": "◾",
"white_medium_small_square": "◽",
"black_small_square": "▪️",
"white_small_square": "▫️"
};
export default emojies;

View File

@@ -1,45 +0,0 @@
// Emoticons -> Emoji mapping.
//
// (!) Some patterns skipped, to avoid collisions
// without increase matcher complicity. Than can change in future.
//
// Places to look for more emoticons info:
//
// - http://en.wikipedia.org/wiki/List_of_emoticons#Western
// - https://github.com/wooorm/emoticon/blob/master/Support.md
// - http://factoryjoe.com/projects/emoticons/
//
import { EmojiShortcuts } from '../normalize_opts';
const shortcuts: EmojiShortcuts = {
angry: ['>:(', '>:-('],
blush: [':")', ':-")'],
broken_heart: ['</3', '<\\3'],
// :\ and :-\ not used because of conflict with markdown escaping
confused: [':/', ':-/'], // twemoji shows question
cry: [":'(", ":'-(", ':,(', ':,-('],
frowning: [':(', ':-('],
heart: ['<3'],
imp: [']:(', ']:-('],
innocent: ['o:)', 'O:)', 'o:-)', 'O:-)', '0:)', '0:-)'],
joy: [":')", ":'-)", ':,)', ':,-)', ":'D", ":'-D", ':,D', ':,-D'],
kissing: [':*', ':-*'],
laughing: ['x-)', 'X-)'],
neutral_face: [':|', ':-|'],
open_mouth: [':o', ':-o', ':O', ':-O'],
rage: [':@', ':-@'],
smile: [':D', ':-D'],
smiley: [':)', ':-)'],
smiling_imp: [']:)', ']:-)'],
sob: [":,'(", ":,'-(", ';(', ';-('],
stuck_out_tongue: [':P', ':-P'],
sunglasses: ['8-)', 'B-)'],
sweat: [',:(', ',:-('],
sweat_smile: [',:)', ',:-)'],
unamused: [':s', ':-S', ':z', ':-Z', ':$', ':-$'],
wink: [';)', ';-)']
};
export default shortcuts;

View File

@@ -1,21 +0,0 @@
import MarkdownIt from 'markdown-it';
import emojies_defs from './data/full';
import emojies_shortcuts from './data/shortcuts';
import bare_emoji_plugin from './bare';
import { EmojiOptions } from './normalize_opts';
/**
* Full emoji 插件(包含完整的 emoji 数据)
*/
export default function emoji_plugin(md: MarkdownIt, options?: Partial<EmojiOptions>): void {
const defaults: EmojiOptions = {
defs: emojies_defs,
shortcuts: emojies_shortcuts,
enabled: []
};
const opts = md.utils.assign({}, defaults, options || {}) as EmojiOptions;
bare_emoji_plugin(md, opts);
}

View File

@@ -1,21 +0,0 @@
import MarkdownIt from 'markdown-it';
import emojies_defs from './data/light';
import emojies_shortcuts from './data/shortcuts';
import bare_emoji_plugin from './bare';
import { EmojiOptions } from './normalize_opts';
/**
* Light emoji 插件(包含常用的 emoji 数据)
*/
export default function emoji_plugin(md: MarkdownIt, options?: Partial<EmojiOptions>): void {
const defaults: EmojiOptions = {
defs: emojies_defs,
shortcuts: emojies_shortcuts,
enabled: []
};
const opts = md.utils.assign({}, defaults, options || {}) as EmojiOptions;
bare_emoji_plugin(md, opts);
}

View File

@@ -1,95 +0,0 @@
/**
* Emoji 定义类型
*/
export interface EmojiDefs {
[key: string]: string;
}
/**
* Emoji 快捷方式类型
*/
export interface EmojiShortcuts {
[key: string]: string | string[];
}
/**
* 输入选项接口
*/
export interface EmojiOptions {
defs: EmojiDefs;
shortcuts: EmojiShortcuts;
enabled: string[];
}
/**
* 标准化后的选项接口
*/
export interface NormalizedEmojiOptions {
defs: EmojiDefs;
shortcuts: { [key: string]: string };
scanRE: RegExp;
replaceRE: RegExp;
}
/**
* 转义正则表达式特殊字符
*/
function quoteRE(str: string): string {
return str.replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&');
}
/**
* 将输入选项转换为更可用的格式并编译搜索正则表达式
*/
export default function normalize_opts(options: EmojiOptions): NormalizedEmojiOptions {
let emojies = options.defs;
// Filter emojies by whitelist, if needed
if (options.enabled.length) {
emojies = Object.keys(emojies).reduce((acc: EmojiDefs, key: string) => {
if (options.enabled.indexOf(key) >= 0) acc[key] = emojies[key];
return acc;
}, {});
}
// Flatten shortcuts to simple object: { alias: emoji_name }
const shortcuts = Object.keys(options.shortcuts).reduce((acc: { [key: string]: string }, key: string) => {
// Skip aliases for filtered emojies, to reduce regexp
if (!emojies[key]) return acc;
if (Array.isArray(options.shortcuts[key])) {
(options.shortcuts[key] as string[]).forEach((alias: string) => { acc[alias] = key; });
return acc;
}
acc[options.shortcuts[key] as string] = key;
return acc;
}, {});
const keys = Object.keys(emojies);
let names: string;
// If no definitions are given, return empty regex to avoid replacements with 'undefined'.
if (keys.length === 0) {
names = '^$';
} else {
// Compile regexp
names = keys
.map((name: string) => { return `:${name}:`; })
.concat(Object.keys(shortcuts))
.sort()
.reverse()
.map((name: string) => { return quoteRE(name); })
.join('|');
}
const scanRE = RegExp(names);
const replaceRE = RegExp(names, 'g');
return {
defs: emojies,
shortcuts,
scanRE,
replaceRE
};
}

View File

@@ -1,9 +0,0 @@
import { Token } from 'markdown-it';
/**
* Emoji 渲染函数
*/
export default function emoji_html(tokens: Token[], idx: number): string {
return tokens[idx].content;
}

View File

@@ -1,97 +0,0 @@
import MarkdownIt, { StateCore, Token } from 'markdown-it';
import { EmojiDefs } from './normalize_opts';
/**
* Emoji 和快捷方式替换逻辑
*
* 注意:理论上,在内联链中解析 :smile: 并只留下快捷方式会更快。
* 但是,谁在乎呢...
*/
export default function create_rule(
md: MarkdownIt,
emojies: EmojiDefs,
shortcuts: { [key: string]: string },
scanRE: RegExp,
replaceRE: RegExp
) {
const arrayReplaceAt = md.utils.arrayReplaceAt;
const ucm = md.utils.lib.ucmicro;
const has = md.utils.has;
const ZPCc = new RegExp([ucm.Z.source, ucm.P.source, ucm.Cc.source].join('|'));
function splitTextToken(text: string, level: number, TokenConstructor: any): Token[] {
let last_pos = 0;
const nodes: Token[] = [];
text.replace(replaceRE, function (match: string, offset: number, src: string): string {
let emoji_name: string;
// Validate emoji name
if (has(shortcuts, match)) {
// replace shortcut with full name
emoji_name = shortcuts[match];
// Don't allow letters before any shortcut (as in no ":/" in http://)
if (offset > 0 && !ZPCc.test(src[offset - 1])) return '';
// Don't allow letters after any shortcut
if (offset + match.length < src.length && !ZPCc.test(src[offset + match.length])) {
return '';
}
} else {
emoji_name = match.slice(1, -1);
}
// Add new tokens to pending list
if (offset > last_pos) {
const token = new TokenConstructor('text', '', 0);
token.content = text.slice(last_pos, offset);
nodes.push(token);
}
const token = new TokenConstructor('emoji', '', 0);
token.markup = emoji_name;
token.content = emojies[emoji_name];
nodes.push(token);
last_pos = offset + match.length;
return '';
});
if (last_pos < text.length) {
const token = new TokenConstructor('text', '', 0);
token.content = text.slice(last_pos);
nodes.push(token);
}
return nodes;
}
return function emoji_replace(state: StateCore): void {
let token: Token;
const blockTokens = state.tokens;
let autolinkLevel = 0;
for (let j = 0, l = blockTokens.length; j < l; j++) {
if (blockTokens[j].type !== 'inline') { continue; }
let tokens = blockTokens[j].children!;
// We scan from the end, to keep position when new tags added.
// Use reversed logic in links start/end match
for (let i = tokens.length - 1; i >= 0; i--) {
token = tokens[i];
if (token.type === 'link_open' || token.type === 'link_close') {
if (token.info === 'auto') { autolinkLevel -= token.nesting; }
}
if (token.type === 'text' && autolinkLevel === 0 && scanRE.test(token.content)) {
// replace current node
blockTokens[j].children = tokens = arrayReplaceAt(
tokens, i, splitTextToken(token.content, token.level, state.Token)
);
}
}
}
};
}

View File

@@ -1,390 +0,0 @@
import MarkdownIt, {Renderer, StateBlock, StateCore, StateInline, Token} from 'markdown-it';
/**
* 脚注元数据接口
*/
interface FootnoteMeta {
id: number;
subId: number;
label: string;
}
/**
* 脚注列表项接口
*/
interface FootnoteItem {
label?: string;
content?: string;
tokens?: Token[];
count: number;
}
/**
* 环境接口
*/
interface FootnoteEnv {
footnotes?: {
refs?: { [key: string]: number };
list?: FootnoteItem[];
};
docId?: string;
}
/// /////////////////////////////////////////////////////////////////////////////
// Renderer partials
function render_footnote_anchor_name(tokens: Token[], idx: number, options: any, env: FootnoteEnv): string {
const n = Number(tokens[idx].meta.id + 1).toString();
let prefix = '';
if (typeof env.docId === 'string') prefix = `-${env.docId}-`;
return prefix + n;
}
function render_footnote_caption(tokens: Token[], idx: number): string {
let n = Number(tokens[idx].meta.id + 1).toString();
if (tokens[idx].meta.subId > 0) n += `:${tokens[idx].meta.subId}`;
return `[${n}]`;
}
function render_footnote_ref(tokens: Token[], idx: number, options: any, env: FootnoteEnv, slf: Renderer): string {
const id = slf.rules.footnote_anchor_name!(tokens, idx, options, env, slf);
const caption = slf.rules.footnote_caption!(tokens, idx, options, env, slf);
let refid = id;
if (tokens[idx].meta.subId > 0) refid += `:${tokens[idx].meta.subId}`;
return `<sup class="footnote-ref"><a href="#fn${id}" id="fnref${refid}">${caption}</a></sup>`;
}
function render_footnote_block_open(tokens: Token[], idx: number, options: any): string {
return (options.xhtmlOut ? '<hr class="footnotes-sep" />\n' : '<hr class="footnotes-sep">\n') +
'<section class="footnotes">\n' +
'<ol class="footnotes-list">\n';
}
function render_footnote_block_close(): string {
return '</ol>\n</section>\n';
}
function render_footnote_open(tokens: Token[], idx: number, options: any, env: FootnoteEnv, slf: Renderer): string {
let id = slf.rules.footnote_anchor_name!(tokens, idx, options, env, slf);
if (tokens[idx].meta.subId > 0) id += `:${tokens[idx].meta.subId}`;
return `<li id="fn${id}" class="footnote-item">`;
}
function render_footnote_close(): string {
return '</li>\n';
}
function render_footnote_anchor(tokens: Token[], idx: number, options: any, env: FootnoteEnv, slf: Renderer): string {
let id = slf.rules.footnote_anchor_name!(tokens, idx, options, env, slf);
if (tokens[idx].meta.subId > 0) id += `:${tokens[idx].meta.subId}`;
/* ↩ with escape code to prevent display as Apple Emoji on iOS */
return ` <a href="#fnref${id}" class="footnote-backref">\u21a9\uFE0E</a>`;
}
/**
* markdown-it-footnote 插件
* 用于支持脚注语法
*/
export default function footnote_plugin(md: MarkdownIt): void {
const parseLinkLabel = md.helpers.parseLinkLabel;
const isSpace = md.utils.isSpace;
md.renderer.rules.footnote_ref = render_footnote_ref;
md.renderer.rules.footnote_block_open = render_footnote_block_open;
md.renderer.rules.footnote_block_close = render_footnote_block_close;
md.renderer.rules.footnote_open = render_footnote_open;
md.renderer.rules.footnote_close = render_footnote_close;
md.renderer.rules.footnote_anchor = render_footnote_anchor;
// helpers (only used in other rules, no tokens are attached to those)
md.renderer.rules.footnote_caption = render_footnote_caption;
md.renderer.rules.footnote_anchor_name = render_footnote_anchor_name;
// Process footnote block definition
function footnote_def(state: StateBlock, startLine: number, endLine: number, silent: boolean): boolean {
const start = state.bMarks[startLine] + state.tShift[startLine];
const max = state.eMarks[startLine];
// line should be at least 5 chars - "[^x]:"
if (start + 4 > max) return false;
if (state.src.charCodeAt(start) !== 0x5B/* [ */) return false;
if (state.src.charCodeAt(start + 1) !== 0x5E/* ^ */) return false;
let pos: number;
for (pos = start + 2; pos < max; pos++) {
if (state.src.charCodeAt(pos) === 0x20) return false;
if (state.src.charCodeAt(pos) === 0x5D /* ] */) {
break;
}
}
if (pos === start + 2) return false; // no empty footnote labels
if (pos + 1 >= max || state.src.charCodeAt(++pos) !== 0x3A /* : */) return false;
if (silent) return true;
pos++;
const env = state.env as FootnoteEnv;
if (!env.footnotes) env.footnotes = {};
if (!env.footnotes.refs) env.footnotes.refs = {};
const label = state.src.slice(start + 2, pos - 2);
env.footnotes.refs[`:${label}`] = -1;
const token_fref_o = new state.Token('footnote_reference_open', '', 1);
token_fref_o.meta = { label };
token_fref_o.level = state.level++;
state.tokens.push(token_fref_o);
const oldBMark = state.bMarks[startLine];
const oldTShift = state.tShift[startLine];
const oldSCount = state.sCount[startLine];
const oldParentType = state.parentType;
const posAfterColon = pos;
const initial = state.sCount[startLine] + pos - (state.bMarks[startLine] + state.tShift[startLine]);
let offset = initial;
while (pos < max) {
const ch = state.src.charCodeAt(pos);
if (isSpace(ch)) {
if (ch === 0x09) {
offset += 4 - offset % 4;
} else {
offset++;
}
} else {
break;
}
pos++;
}
state.tShift[startLine] = pos - posAfterColon;
state.sCount[startLine] = offset - initial;
state.bMarks[startLine] = posAfterColon;
state.blkIndent += 4;
state.parentType = 'footnote' as any;
if (state.sCount[startLine] < state.blkIndent) {
state.sCount[startLine] += state.blkIndent;
}
state.md.block.tokenize(state, startLine, endLine);
state.parentType = oldParentType;
state.blkIndent -= 4;
state.tShift[startLine] = oldTShift;
state.sCount[startLine] = oldSCount;
state.bMarks[startLine] = oldBMark;
const token_fref_c = new state.Token('footnote_reference_close', '', -1);
token_fref_c.level = --state.level;
state.tokens.push(token_fref_c);
return true;
}
// Process inline footnotes (^[...])
function footnote_inline(state: StateInline, silent: boolean): boolean {
const max = state.posMax;
const start = state.pos;
if (start + 2 >= max) return false;
if (state.src.charCodeAt(start) !== 0x5E/* ^ */) return false;
if (state.src.charCodeAt(start + 1) !== 0x5B/* [ */) return false;
const labelStart = start + 2;
const labelEnd = parseLinkLabel(state, start + 1);
// parser failed to find ']', so it's not a valid note
if (labelEnd < 0) return false;
// We found the end of the link, and know for a fact it's a valid link;
// so all that's left to do is to call tokenizer.
//
if (!silent) {
const env = state.env as FootnoteEnv;
if (!env.footnotes) env.footnotes = {};
if (!env.footnotes.list) env.footnotes.list = [];
const footnoteId = env.footnotes.list.length;
const tokens: Token[] = [];
state.md.inline.parse(
state.src.slice(labelStart, labelEnd),
state.md,
state.env,
tokens
);
const token = state.push('footnote_ref', '', 0);
token.meta = { id: footnoteId };
env.footnotes.list[footnoteId] = {
content: state.src.slice(labelStart, labelEnd),
tokens,
count: 0
};
}
state.pos = labelEnd + 1;
state.posMax = max;
return true;
}
// Process footnote references ([^...])
function footnote_ref(state: StateInline, silent: boolean): boolean {
const max = state.posMax;
const start = state.pos;
// should be at least 4 chars - "[^x]"
if (start + 3 > max) return false;
const env = state.env as FootnoteEnv;
if (!env.footnotes || !env.footnotes.refs) return false;
if (state.src.charCodeAt(start) !== 0x5B/* [ */) return false;
if (state.src.charCodeAt(start + 1) !== 0x5E/* ^ */) return false;
let pos: number;
for (pos = start + 2; pos < max; pos++) {
if (state.src.charCodeAt(pos) === 0x20) return false;
if (state.src.charCodeAt(pos) === 0x0A) return false;
if (state.src.charCodeAt(pos) === 0x5D /* ] */) {
break;
}
}
if (pos === start + 2) return false; // no empty footnote labels
if (pos >= max) return false;
pos++;
const label = state.src.slice(start + 2, pos - 1);
if (typeof env.footnotes.refs[`:${label}`] === 'undefined') return false;
if (!silent) {
if (!env.footnotes.list) env.footnotes.list = [];
let footnoteId: number;
if (env.footnotes.refs[`:${label}`] < 0) {
footnoteId = env.footnotes.list.length;
env.footnotes.list[footnoteId] = { label, count: 0 };
env.footnotes.refs[`:${label}`] = footnoteId;
} else {
footnoteId = env.footnotes.refs[`:${label}`];
}
const footnoteSubId = env.footnotes.list[footnoteId].count;
env.footnotes.list[footnoteId].count++;
const token = state.push('footnote_ref', '', 0);
token.meta = { id: footnoteId, subId: footnoteSubId, label };
}
state.pos = pos;
state.posMax = max;
return true;
}
// Glue footnote tokens to end of token stream
function footnote_tail(state: StateCore): void {
let tokens: Token[] | null = null;
let current: Token[];
let currentLabel: string;
let insideRef = false;
const refTokens: { [key: string]: Token[] } = {};
const env = state.env as FootnoteEnv;
if (!env.footnotes) { return; }
state.tokens = state.tokens.filter(function (tok) {
if (tok.type === 'footnote_reference_open') {
insideRef = true;
current = [];
currentLabel = tok.meta.label;
return false;
}
if (tok.type === 'footnote_reference_close') {
insideRef = false;
// prepend ':' to avoid conflict with Object.prototype members
refTokens[':' + currentLabel] = current;
return false;
}
if (insideRef) { current.push(tok); }
return !insideRef;
});
if (!env.footnotes.list) { return; }
const list = env.footnotes.list;
state.tokens.push(new state.Token('footnote_block_open', '', 1));
for (let i = 0, l = list.length; i < l; i++) {
const token_fo = new state.Token('footnote_open', '', 1);
token_fo.meta = { id: i, label: list[i].label };
state.tokens.push(token_fo);
if (list[i].tokens) {
tokens = [];
const token_po = new state.Token('paragraph_open', 'p', 1);
token_po.block = true;
tokens.push(token_po);
const token_i = new state.Token('inline', '', 0);
token_i.children = list[i].tokens || null;
token_i.content = list[i].content || '';
tokens.push(token_i);
const token_pc = new state.Token('paragraph_close', 'p', -1);
token_pc.block = true;
tokens.push(token_pc);
} else if (list[i].label) {
tokens = refTokens[`:${list[i].label}`] || null;
}
if (tokens) state.tokens = state.tokens.concat(tokens);
let lastParagraph: Token | null;
if (state.tokens[state.tokens.length - 1].type === 'paragraph_close') {
lastParagraph = state.tokens.pop()!;
} else {
lastParagraph = null;
}
const t = list[i].count > 0 ? list[i].count : 1;
for (let j = 0; j < t; j++) {
const token_a = new state.Token('footnote_anchor', '', 0);
token_a.meta = { id: i, subId: j, label: list[i].label };
state.tokens.push(token_a);
}
if (lastParagraph) {
state.tokens.push(lastParagraph);
}
state.tokens.push(new state.Token('footnote_close', '', -1));
}
state.tokens.push(new state.Token('footnote_block_close', '', -1));
}
md.block.ruler.before('reference', 'footnote_def', footnote_def, { alt: ['paragraph', 'reference'] });
md.inline.ruler.after('image', 'footnote_inline', footnote_inline);
md.inline.ruler.after('footnote_inline', 'footnote_ref', footnote_ref);
md.core.ruler.after('inline', 'footnote_tail', footnote_tail);
}

View File

@@ -1,160 +0,0 @@
import MarkdownIt, { StateInline, Token } from 'markdown-it';
/**
* 分隔符接口定义
*/
interface Delimiter {
marker: number;
length: number;
jump: number;
token: number;
end: number;
open: boolean;
close: boolean;
}
/**
* 扫描结果接口定义
*/
interface ScanResult {
can_open: boolean;
can_close: boolean;
length: number;
}
/**
* Token 元数据接口定义
*/
interface TokenMeta {
delimiters?: Delimiter[];
}
/**
* markdown-it-ins 插件
* 用于支持插入文本语法 ++text++
*/
export default function ins_plugin(md: MarkdownIt): void {
// Insert each marker as a separate text token, and add it to delimiter list
//
function tokenize(state: StateInline, silent: boolean): boolean {
const start = state.pos;
const marker = state.src.charCodeAt(start);
if (silent) { return false; }
if (marker !== 0x2B/* + */) { return false; }
const scanned = state.scanDelims(state.pos, true) as ScanResult;
let len = scanned.length;
const ch = String.fromCharCode(marker);
if (len < 2) { return false; }
if (len % 2) {
const token: Token = state.push('text', '', 0);
token.content = ch;
len--;
}
for (let i = 0; i < len; i += 2) {
const token: Token = state.push('text', '', 0);
token.content = ch + ch;
if (!scanned.can_open && !scanned.can_close) { continue; }
state.delimiters.push({
marker,
length: 0, // disable "rule of 3" length checks meant for emphasis
jump: i / 2, // 1 delimiter = 2 characters
token: state.tokens.length - 1,
end: -1,
open: scanned.can_open,
close: scanned.can_close
} as Delimiter);
}
state.pos += scanned.length;
return true;
}
// Walk through delimiter list and replace text tokens with tags
//
function postProcess(state: StateInline, delimiters: Delimiter[]): void {
let token: Token;
const loneMarkers: number[] = [];
const max = delimiters.length;
for (let i = 0; i < max; i++) {
const startDelim = delimiters[i];
if (startDelim.marker !== 0x2B/* + */) {
continue;
}
if (startDelim.end === -1) {
continue;
}
const endDelim = delimiters[startDelim.end];
token = state.tokens[startDelim.token];
token.type = 'ins_open';
token.tag = 'ins';
token.nesting = 1;
token.markup = '++';
token.content = '';
token = state.tokens[endDelim.token];
token.type = 'ins_close';
token.tag = 'ins';
token.nesting = -1;
token.markup = '++';
token.content = '';
if (state.tokens[endDelim.token - 1].type === 'text' &&
state.tokens[endDelim.token - 1].content === '+') {
loneMarkers.push(endDelim.token - 1);
}
}
// If a marker sequence has an odd number of characters, it's splitted
// like this: `~~~~~` -> `~` + `~~` + `~~`, leaving one marker at the
// start of the sequence.
//
// So, we have to move all those markers after subsequent s_close tags.
//
while (loneMarkers.length) {
const i = loneMarkers.pop()!;
let j = i + 1;
while (j < state.tokens.length && state.tokens[j].type === 'ins_close') {
j++;
}
j--;
if (i !== j) {
token = state.tokens[j];
state.tokens[j] = state.tokens[i];
state.tokens[i] = token;
}
}
}
md.inline.ruler.before('emphasis', 'ins', tokenize);
md.inline.ruler2.before('emphasis', 'ins', function (state: StateInline): boolean {
const tokens_meta = state.tokens_meta as TokenMeta[];
const max = (state.tokens_meta || []).length;
postProcess(state, state.delimiters as Delimiter[]);
for (let curr = 0; curr < max; curr++) {
if (tokens_meta[curr] && tokens_meta[curr].delimiters) {
postProcess(state, tokens_meta[curr].delimiters!);
}
}
return true;
});
}

View File

@@ -1,160 +0,0 @@
import MarkdownIt, {StateInline, Token} from 'markdown-it';
/**
* 分隔符接口定义
*/
interface Delimiter {
marker: number;
length: number;
jump: number;
token: number;
end: number;
open: boolean;
close: boolean;
}
/**
* 扫描结果接口定义
*/
interface ScanResult {
can_open: boolean;
can_close: boolean;
length: number;
}
/**
* Token 元数据接口定义
*/
interface TokenMeta {
delimiters?: Delimiter[];
}
/**
* markdown-it-mark 插件
* 用于支持 ==标记文本== 语法
*/
export default function markPlugin(md: MarkdownIt): void {
// Insert each marker as a separate text token, and add it to delimiter list
//
function tokenize(state: StateInline, silent: boolean): boolean {
const start = state.pos;
const marker = state.src.charCodeAt(start);
if (silent) { return false; }
if (marker !== 0x3D/* = */) { return false; }
const scanned = state.scanDelims(state.pos, true) as ScanResult;
let len = scanned.length;
const ch = String.fromCharCode(marker);
if (len < 2) { return false; }
if (len % 2) {
const token: Token = state.push('text', '', 0);
token.content = ch;
len--;
}
for (let i = 0; i < len; i += 2) {
const token: Token = state.push('text', '', 0);
token.content = ch + ch;
if (!scanned.can_open && !scanned.can_close) { continue; }
state.delimiters.push({
marker,
length: 0, // disable "rule of 3" length checks meant for emphasis
jump: i / 2, // 1 delimiter = 2 characters
token: state.tokens.length - 1,
end: -1,
open: scanned.can_open,
close: scanned.can_close
} as Delimiter);
}
state.pos += scanned.length;
return true;
}
// Walk through delimiter list and replace text tokens with tags
//
function postProcess(state: StateInline, delimiters: Delimiter[]): void {
const loneMarkers: number[] = [];
const max = delimiters.length;
for (let i = 0; i < max; i++) {
const startDelim = delimiters[i];
if (startDelim.marker !== 0x3D/* = */) {
continue;
}
if (startDelim.end === -1) {
continue;
}
const endDelim = delimiters[startDelim.end];
const token_o = state.tokens[startDelim.token];
token_o.type = 'mark_open';
token_o.tag = 'mark';
token_o.nesting = 1;
token_o.markup = '==';
token_o.content = '';
const token_c = state.tokens[endDelim.token];
token_c.type = 'mark_close';
token_c.tag = 'mark';
token_c.nesting = -1;
token_c.markup = '==';
token_c.content = '';
if (state.tokens[endDelim.token - 1].type === 'text' &&
state.tokens[endDelim.token - 1].content === '=') {
loneMarkers.push(endDelim.token - 1);
}
}
// If a marker sequence has an odd number of characters, it's splitted
// like this: `~~~~~` -> `~` + `~~` + `~~`, leaving one marker at the
// start of the sequence.
//
// So, we have to move all those markers after subsequent s_close tags.
//
while (loneMarkers.length) {
const i = loneMarkers.pop()!;
let j = i + 1;
while (j < state.tokens.length && state.tokens[j].type === 'mark_close') {
j++;
}
j--;
if (i !== j) {
const token = state.tokens[j];
state.tokens[j] = state.tokens[i];
state.tokens[i] = token;
}
}
}
md.inline.ruler.before('emphasis', 'mark', tokenize);
md.inline.ruler2.before('emphasis', 'mark', function (state: StateInline): boolean {
let curr: number;
const tokens_meta = state.tokens_meta as TokenMeta[];
const max = (state.tokens_meta || []).length;
postProcess(state, state.delimiters as Delimiter[]);
for (curr = 0; curr < max; curr++) {
if (tokens_meta[curr] && tokens_meta[curr].delimiters) {
postProcess(state, tokens_meta[curr].delimiters!);
}
}
return true;
});
}

View File

@@ -1,106 +0,0 @@
import mermaid from "mermaid";
import {genUid, hashCode, sleep} from "./utils";
const mermaidCache = new Map<string, HTMLElement>();
// 缓存计数器,用于清除缓存
const mermaidCacheCount = new Map<string, number>();
let count = 0;
let countTmo = setTimeout(() => undefined, 0);
const addCount = () => {
clearTimeout(countTmo);
countTmo = setTimeout(() => {
count++;
clearCache();
}, 500);
};
const clearCache = () => {
for (const key of mermaidCacheCount.keys()) {
const value = mermaidCacheCount.get(key)!;
if (value + 3 < count) {
mermaidCache.delete(key);
mermaidCacheCount.delete(key);
}
}
};
/**
* 渲染 mermaid
* @param code mermaid 代码
* @param targetId 目标 id
* @param count 计数器
*/
const renderMermaid = async (code: string, targetId: string, count: number) => {
let limit = 100;
while (limit-- > 0) {
const container = document.getElementById(targetId);
if (!container) {
await sleep(100);
continue;
}
try {
const {svg} = await mermaid.render("mermaid-svg-" + genUid(), code, container);
container.innerHTML = svg;
mermaidCache.set(targetId, container);
mermaidCacheCount.set(targetId, count);
} catch (e) {
}
break;
}
};
export interface MermaidItOptions {
theme?: "default" | "dark" | "forest" | "neutral" | "base";
}
/**
* 更新 mermaid 主题
*/
export const updateMermaidTheme = (theme: "default" | "dark" | "forest" | "neutral" | "base") => {
mermaid.initialize({
startOnLoad: false,
theme: theme
});
// 清空缓存,强制重新渲染
mermaidCache.clear();
mermaidCacheCount.clear();
};
/**
* mermaid 插件
* @param md markdown-it
* @param options 配置选项
* @constructor MermaidIt
*/
export const MermaidIt = function (md: any, options?: MermaidItOptions): void {
const theme = options?.theme || "default";
mermaid.initialize({
startOnLoad: false,
theme: theme
});
const defaultRenderer = md.renderer.rules.fence.bind(md.renderer.rules);
md.renderer.rules.fence = (tokens: any, idx: any, options: any, env: any, self: any) => {
addCount();
const token = tokens[idx];
const info = token.info.trim();
if (info === "mermaid") {
const containerId = "mermaid-container-" + hashCode(token.content);
const container = document.createElement("div");
container.id = containerId;
if (mermaidCache.has(containerId)) {
container.innerHTML = mermaidCache.get(containerId)!.innerHTML;
mermaidCacheCount.set(containerId, count);
} else {
renderMermaid(token.content, containerId, count).then();
}
return container.outerHTML;
}
// 使用默认的渲染规则
return defaultRenderer(tokens, idx, options, env, self);
};
};

View File

@@ -1,49 +0,0 @@
import { v4 as uuidv4 } from "uuid";
/**
* uuid 生成函数
* @param split 分隔符
*/
export const genUid = (split = "") => {
return uuidv4().split("-").join(split);
};
/**
* 一个简易的sleep函数
*/
export const sleep = async (ms: number) => {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
};
/**
* 计算字符串的hash值
* 返回一个数字
* @param str
*/
export const hashCode = (str: string) => {
let hash = 0;
if (str.length === 0) return hash;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
};
/**
* 一个简易的阻塞函数
*/
export const awaitFor = async (cb: () => boolean, timeout = 0, errText = "超时暂停阻塞") => {
const start = Date.now();
while (true) {
if (cb()) return true;
if (timeout && Date.now() - start > timeout) {
console.error("阻塞超时: " + errText);
return false;
}
await sleep(100);
}
};

View File

@@ -1,66 +0,0 @@
// Process ~subscript~
import MarkdownIt, { StateInline, Token } from 'markdown-it';
// same as UNESCAPE_MD_RE plus a space
const UNESCAPE_RE = /\\([ \\!"#$%&'()*+,./:;<=>?@[\]^_`{|}~-])/g;
function subscript(state: StateInline, silent: boolean): boolean {
const max = state.posMax;
const start = state.pos;
if (state.src.charCodeAt(start) !== 0x7E/* ~ */) { return false; }
if (silent) { return false; } // don't run any pairs in validation mode
if (start + 2 >= max) { return false; }
state.pos = start + 1;
let found = false;
while (state.pos < max) {
if (state.src.charCodeAt(state.pos) === 0x7E/* ~ */) {
found = true;
break;
}
state.md.inline.skipToken(state);
}
if (!found || start + 1 === state.pos) {
state.pos = start;
return false;
}
const content = state.src.slice(start + 1, state.pos);
// don't allow unescaped spaces/newlines inside
if (content.match(/(^|[^\\])(\\\\)*\s/)) {
state.pos = start;
return false;
}
// found!
state.posMax = state.pos;
state.pos = start + 1;
// Earlier we checked !silent, but this implementation does not need it
const token_so: Token = state.push('sub_open', 'sub', 1);
token_so.markup = '~';
const token_t: Token = state.push('text', '', 0);
token_t.content = content.replace(UNESCAPE_RE, '$1');
const token_sc: Token = state.push('sub_close', 'sub', -1);
token_sc.markup = '~';
state.pos = state.posMax + 1;
state.posMax = max;
return true;
}
/**
* markdown-it-sub 插件
* 用于支持下标语法 ~text~
*/
export default function sub_plugin(md: MarkdownIt): void {
md.inline.ruler.after('emphasis', 'sub', subscript);
}

View File

@@ -1,66 +0,0 @@
// Process ^superscript^
import MarkdownIt, { StateInline, Token } from 'markdown-it';
// same as UNESCAPE_MD_RE plus a space
const UNESCAPE_RE = /\\([ \\!"#$%&'()*+,./:;<=>?@[\]^_`{|}~-])/g;
function superscript(state: StateInline, silent: boolean): boolean {
const max = state.posMax;
const start = state.pos;
if (state.src.charCodeAt(start) !== 0x5E/* ^ */) { return false; }
if (silent) { return false; } // don't run any pairs in validation mode
if (start + 2 >= max) { return false; }
state.pos = start + 1;
let found = false;
while (state.pos < max) {
if (state.src.charCodeAt(state.pos) === 0x5E/* ^ */) {
found = true;
break;
}
state.md.inline.skipToken(state);
}
if (!found || start + 1 === state.pos) {
state.pos = start;
return false;
}
const content = state.src.slice(start + 1, state.pos);
// don't allow unescaped spaces/newlines inside
if (content.match(/(^|[^\\])(\\\\)*\s/)) {
state.pos = start;
return false;
}
// found!
state.posMax = state.pos;
state.pos = start + 1;
// Earlier we checked !silent, but this implementation does not need it
const token_so: Token = state.push('sup_open', 'sup', 1);
token_so.markup = '^';
const token_t: Token = state.push('text', '', 0);
token_t.content = content.replace(UNESCAPE_RE, '$1');
const token_sc: Token = state.push('sup_close', 'sup', -1);
token_sc.markup = '^';
state.pos = state.posMax + 1;
state.posMax = max;
return true;
}
/**
* markdown-it-sup 插件
* 用于支持上标语法 ^text^
*/
export default function sup_plugin(md: MarkdownIt): void {
md.inline.ruler.after('emphasis', 'sup', superscript);
}

View File

@@ -0,0 +1,65 @@
/**
* Formatter utility functions
*/
export interface DateTimeFormatOptions {
locale?: string;
includeTime?: boolean;
hour12?: boolean;
}
/**
* Format date time string to localized format
* @param dateString - ISO date string or null
* @param options - Formatting options
* @returns Formatted date string or error message
*/
export const formatDateTime = (
dateString: string | null,
options: DateTimeFormatOptions = {}
): string => {
const {
locale = 'en-US',
includeTime = true,
hour12 = false
} = options;
if (!dateString) {
return 'Unknown time';
}
try {
const date = new Date(dateString);
if (isNaN(date.getTime())) {
return 'Invalid date';
}
const formatOptions: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: '2-digit',
day: '2-digit',
};
if (includeTime) {
formatOptions.hour = '2-digit';
formatOptions.minute = '2-digit';
formatOptions.hour12 = hour12;
}
return date.toLocaleString(locale, formatOptions);
} catch {
return 'Time error';
}
};
/**
* Truncate string with ellipsis
* @param str - String to truncate
* @param maxLength - Maximum length before truncation
* @returns Truncated string with ellipsis if needed
*/
export const truncateString = (str: string, maxLength: number): string => {
if (!str) return '';
return str.length > maxLength ? str.substring(0, maxLength) + '...' : str;
};

Some files were not shown because too many files have changed in this diff Show More