From e10104cd2099cc4840e393985c06e4bf0bf8a6bd Mon Sep 17 00:00:00 2001
From: Novout
Date: Wed, 10 Jun 2026 18:55:36 -0300
Subject: [PATCH 01/30] chore: added claude.md
---
CLAUDE.md | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 113 insertions(+)
create mode 100644 CLAUDE.md
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 00000000..74e9cd57
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,113 @@
+# betterwrite — CLAUDE.md
+
+## Project Overview
+
+**Better Write** is a 100% client-side creative word processor built as a Vue 3 + TypeScript monorepo. There is no backend — all data lives in the browser (IndexedDB). Exports (PDF, DOCX, HTML, TXT, EPUB) are generated entirely in the browser. The project is deployed as a PWA at betterwrite.io.
+
+## Monorepo Structure
+
+Managed with **pnpm workspaces** + **Lerna** + **Nx** (task caching).
+
+```
+packages/
+ app/ # Main Vue 3 SPA — the entry point for everything
+ better-write-types/ # Shared TypeScript types — check here first before defining new types
+ contenteditable-ast/ # Custom AST for the entity-model text editor
+ client-storage/ # IndexedDB abstraction layer
+ extension/ # .bw file format (ZIP + data.json)
+ plugin-*/ # Feature plugins (characters, theme, shortcuts, voice, etc.)
+ plugin-exporter-*/ # Export generators (pdf, docx, html, txt, epub)
+ plugin-importer/ # File import handling
+ google-fonts-api/ # Google Fonts integration
+ color-converter/ # Color utility
+ image-converter/ # Image processing
+ languages/ # i18n translation files
+```
+
+## Key Architecture Concepts
+
+### Entity Model
+The document is not stored as raw HTML. Content is a tree of **entities** (paragraphs, headings, images, page breaks, etc.), each with a `type`, `raw` text, and optional visual overrides. See `packages/better-write-types` and `docs/ENTITY_MODEL.md`.
+
+### Plugin System
+Plugins receive three injection points: `emitter` (mitt event bus), `stores` (Pinia), and `hooks` (lifecycle). All plugins initialize in `packages/app/src/App.vue`. When adding a feature, check whether it belongs in an existing plugin or as a new `plugin-*` package. See `docs/PLUGIN_SYSTEM.md`.
+
+### State Management
+Pinia stores live in `packages/app/src/store/`. There are ~14 stores. Prefer composables in `packages/app/src/use/` over direct store access in components.
+
+### Export Pipeline
+Each export format is a separate `plugin-exporter-*` package. Generators receive the entity tree and produce browser-downloadable output. See `docs/GENERATOR_FLOW.md`.
+
+### .bw Extension Format
+Project files saved as `.bw` are a ZIP archive containing `data.json`. Handled by `packages/extension/`. See `docs/PROJECT_FLOW.md`.
+
+## Development Setup
+
+```bash
+# Install dependencies
+pnpm install
+
+# Start dev server (port 3000)
+pnpm dev
+
+# Build all packages
+pnpm build
+
+# Run tests
+pnpm test
+
+# Format code
+pnpm lint
+```
+
+Requires a `.env.local` file in `packages/app/` — copy from `.env.example`.
+
+Node 16+ and pnpm 8+ required.
+
+## Coding Conventions
+
+- **TypeScript strict mode** — no `any` without justification.
+- **No semicolons**, **single quotes**, **2-space indent** (Prettier enforced).
+- **Conventional Commits** required: `feat:`, `fix:`, `chore:`, `refactor:`, `docs:`, `ci:`.
+- **Vue 3 Composition API** — use `
diff --git a/packages/app/src/components/page/about/AboutInfo.vue b/packages/app/src/components/page/about/AboutInfo.vue
index 08d7063c..0054ce15 100644
--- a/packages/app/src/components/page/about/AboutInfo.vue
+++ b/packages/app/src/components/page/about/AboutInfo.vue
@@ -70,9 +70,6 @@
-
-
-
diff --git a/packages/app/src/components/page/about/AboutPortability.vue b/packages/app/src/components/page/about/AboutPortability.vue
index 781c2f8d..ca3f4cfc 100644
--- a/packages/app/src/components/page/about/AboutPortability.vue
+++ b/packages/app/src/components/page/about/AboutPortability.vue
@@ -110,7 +110,9 @@
class="flex flex-col gap-5"
>
.DOCX
+
.TXT
.PDF
.BW
diff --git a/packages/app/src/components/page/editor/header/items/EditorBaseHeaderVault.vue b/packages/app/src/components/page/editor/header/items/EditorBaseHeaderVault.vue
deleted file mode 100644
index 660fef73..00000000
--- a/packages/app/src/components/page/editor/header/items/EditorBaseHeaderVault.vue
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
- {{ t('editor.header.vault') }}
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/app/src/components/page/editor/main/EditorBaseHeader.vue b/packages/app/src/components/page/editor/main/EditorBaseHeader.vue
index fee654bf..81c027ae 100644
--- a/packages/app/src/components/page/editor/main/EditorBaseHeader.vue
+++ b/packages/app/src/components/page/editor/main/EditorBaseHeader.vue
@@ -22,7 +22,6 @@
-
diff --git a/packages/app/tsconfig.json b/packages/app/tsconfig.json
index 2cc22326..337a4f30 100644
--- a/packages/app/tsconfig.json
+++ b/packages/app/tsconfig.json
@@ -1,7 +1,6 @@
{
"compilerOptions": {
"baseUrl": ".",
- "ignoreDeprecations": "6.0",
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
diff --git a/packages/languages/tsconfig.json b/packages/languages/tsconfig.json
index 1cb154a5..1d60880f 100644
--- a/packages/languages/tsconfig.json
+++ b/packages/languages/tsconfig.json
@@ -11,7 +11,6 @@
"exclude": ["dist", "node_modules"],
"compilerOptions": {
"baseUrl": ".",
- "ignoreDeprecations": "6.0",
"rootDir": ".",
"outDir": "dist",
"sourceMap": false,
diff --git a/packages/plugin-dropbox/.gitignore b/packages/plugin-dropbox/.gitignore
deleted file mode 100644
index 25322f72..00000000
--- a/packages/plugin-dropbox/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-.vscode
-.DS_Store
-
-node_modules
-dist
-yarn-error.log
\ No newline at end of file
diff --git a/packages/plugin-dropbox/.prettierrc.json b/packages/plugin-dropbox/.prettierrc.json
deleted file mode 100644
index a9c1b141..00000000
--- a/packages/plugin-dropbox/.prettierrc.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "tabWidth": 2,
- "singleQuote": true,
- "semi": false,
- "vueIndentScriptAndStyle": true
-}
diff --git a/packages/plugin-dropbox/package.json b/packages/plugin-dropbox/package.json
deleted file mode 100644
index 22b9f29d..00000000
--- a/packages/plugin-dropbox/package.json
+++ /dev/null
@@ -1,38 +0,0 @@
-{
- "name": "better-write-plugin-dropbox",
- "version": "1.3.27",
- "author": "Novout",
- "license": "MIT",
- "keywords": [
- "vue",
- "hook",
- "text",
- "editor"
- ],
- "main": "dist/index.js",
- "module": "dist/index.mjs",
- "types": "dist/index.d.ts",
- "scripts": {
- "test": "echo \"Error: no test specified\"",
- "build": "tsup"
- },
- "files": [
- "dist/**/*",
- "package.json",
- "LICENSE",
- "README.md"
- ],
- "dependencies": {
- "better-write-client-storage": "^1.3.27",
- "better-write-extension": "^1.3.27",
- "better-write-plugin-core": "^1.3.27",
- "better-write-types": "^1.3.27",
- "vue-demi": "latest"
- },
- "devDependencies": {
- "prettier": "2.5.1"
- },
- "peerDependencies": {
- "vue": "^2.0.0 || >=3.0.0"
- }
-}
diff --git a/packages/plugin-dropbox/src/index.ts b/packages/plugin-dropbox/src/index.ts
deleted file mode 100644
index 3fd2c30f..00000000
--- a/packages/plugin-dropbox/src/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { PluginTypes } from 'better-write-types'
-import { createPlugin } from 'better-write-plugin-core'
-import { DropboxSet } from './set'
-import { DropboxToken } from './token'
-
-export const DropboxPlugin = (): PluginTypes.Plugin =>
- createPlugin({ name: 'dropbox' }, [DropboxSet, DropboxToken])
diff --git a/packages/plugin-dropbox/src/set.ts b/packages/plugin-dropbox/src/set.ts
deleted file mode 100644
index ae807eca..00000000
--- a/packages/plugin-dropbox/src/set.ts
+++ /dev/null
@@ -1,196 +0,0 @@
-import {
- Dropbox as DBX,
- DropboxAuth,
- DropboxResponse,
- DropboxResponseError,
- files,
-} from 'dropbox'
-import { On } from 'better-write-plugin-core'
-import { ClientStorageOptions, PluginTypes } from 'better-write-types'
-import { readBW, writeBW } from 'better-write-extension'
-import { exclude, set } from 'better-write-client-storage'
-
-export const DropboxSet = (
- emitter: PluginTypes.PluginEmitter,
- stores: PluginTypes.PluginStores,
- hooks: PluginTypes.PluginHooks,
-) => {
- On.externals().PluginDropboxSave(emitter, [
- () => {
- if (!stores.AUTH.account.dropboxAccessToken) {
- return
- }
-
- const dbx = new DBX({
- clientId: hooks.env.dropboxKey(),
- accessToken: stores.AUTH.account.dropboxAccessToken,
- })
-
- const path = `/${hooks.project.utils().exportFullName('bw')}`
-
- hooks.toast.info(hooks.i18n.t('toast.generics.load'))
-
- emitter.emit('plugin-progress-start')
-
- hooks.storage.normalize().then(async () => {
- const payload = hooks.storage.getProjectObject()
- payload.project.externalProvider = 'dropbox'
-
- const obj = JSON.stringify(payload)
- const zip = await writeBW(obj)
-
- dbx
- .filesUpload({
- path,
- contents: zip,
- mode: {
- '.tag': 'overwrite',
- },
- })
- .then(({ result }) => {
- if (!stores.VAULT.dropboxFiles.some(({ id }) => id === result.id))
- stores.VAULT.dropboxFiles.unshift(
- result as files.FileMetadataReference,
- )
-
- hooks.toast.success(hooks.i18n.t('toast.dropbox.save'))
- })
- .catch(() => {
- hooks.toast.error(hooks.i18n.t('toast.project.error'))
- })
- .finally(() => {
- emitter.emit('plugin-progress-end')
- })
- })
- },
- () => {},
- ])
-
- On.externals().PluginDropboxLoad(emitter, [
- async () => {
- if (!stores.AUTH.account.dropboxAccessToken) {
- return
- }
-
- const dbx = new DBX({
- accessToken: stores.AUTH.account.dropboxAccessToken,
- })
-
- hooks.plugin.emit('plugin-progress-start')
-
- const files = await dbx.filesListFolder({ path: '' })
-
- if (!files || files.result.entries.length === 0) {
- hooks.toast.warning(hooks.i18n.t('toast.dropbox.empty'))
-
- hooks.plugin.emit('plugin-progress-end')
-
- return
- }
-
- const targets = (files.result.entries.filter(
- // @ts-expect-error
- (file) => file?.is_downloadable && file?.['.tag'] === 'file',
- ) ?? []) as files.FileMetadataReference[]
-
- stores.VAULT.dropboxFiles.unshift(...targets)
-
- hooks.plugin.emit('plugin-progress-end')
-
- hooks.toast.success(hooks.i18n.t('toast.generics.successChanged'))
- },
- () => {},
- ])
-
- On.externals().PluginDropboxDelete(emitter, [
- (file: files.FileMetadataReference) => {
- if (!stores.AUTH.account.dropboxAccessToken) return
-
- if (!confirm(hooks.i18n.t('toast.generics.fileDelete'))) return
-
- const dbx = new DBX({
- accessToken: stores.AUTH.account.dropboxAccessToken,
- })
-
- hooks.plugin.emit('plugin-progress-start')
-
- dbx
- .filesDeleteV2({ path: file.id })
- .then(() => {
- stores.VAULT.dropboxFiles = stores.VAULT.dropboxFiles.filter(
- ({ id }) => id !== file.id,
- )
- })
- .catch((err: DropboxResponseError) => {
- hooks.toast.error(hooks.i18n.t('toast.project.error'))
- })
- .finally(() => {
- hooks.plugin.emit('plugin-progress-end')
- })
- },
- () => {},
- ])
-
- On.externals().PluginDropboxSet(emitter, [
- (file: files.FileMetadataReference) => {
- if (!stores.AUTH.account.dropboxAccessToken) return
-
- const dbx = new DBX({
- accessToken: stores.AUTH.account.dropboxAccessToken,
- })
-
- hooks.plugin.emit('plugin-progress-start')
-
- dbx
- .filesDownload({ path: file.id })
- .then(async (data: DropboxResponse) => {
- const bw = await readBW(data.result.fileBlob as Blob)
- const content = hooks.storage.support(bw)
-
- hooks.project.onLoadProject(content)
- })
- .catch(() => {
- hooks.toast.error(hooks.i18n.t('toast.project.error'))
- })
- .finally(() => {
- hooks.plugin.emit('plugin-progress-end')
- })
- },
- () => {},
- ])
-
- On.externals().PluginDropboxConnect(emitter, [
- async () => {
- const dbxAuth = new DropboxAuth({
- clientId: hooks.env.dropboxKey(),
- })
-
- const url = await dbxAuth.getAuthenticationUrl(
- hooks.env.getCorrectLocalUrl(),
- 'dropboxbw',
- 'code',
- 'online',
- [
- 'account_info.read',
- 'files.metadata.write',
- 'files.metadata.read',
- 'files.content.write',
- 'files.content.read',
- 'file_requests.read',
- ],
- undefined,
- true,
- )
-
- exclude('code_verifier')
- await set(
- 'code_verifier',
- dbxAuth.getCodeVerifier(),
- stores.EDITOR.configuration.clientStorage as ClientStorageOptions,
- )
-
- window.open(url as string, '_self')
- },
- () => {},
- ])
-}
diff --git a/packages/plugin-dropbox/src/token.ts b/packages/plugin-dropbox/src/token.ts
deleted file mode 100644
index 096e4383..00000000
--- a/packages/plugin-dropbox/src/token.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { exclude, get } from 'better-write-client-storage'
-import { ClientStorageOptions, PluginTypes } from 'better-write-types'
-import { DropboxAuth } from 'dropbox'
-
-export const DropboxToken = (
- emitter: PluginTypes.PluginEmitter,
- stores: PluginTypes.PluginStores,
- hooks: PluginTypes.PluginHooks,
-) => {
- emitter.on('call-editor-mounted', async () => {
- const params = hooks.vueuse.core.useUrlSearchParams()
- const code = params?.code
- const state = params?.state
- const code_verifier = await get(
- 'code_verifier',
- stores.EDITOR.configuration.clientStorage as ClientStorageOptions,
- )
-
- if (code && code_verifier && state === 'dropboxbw') {
- const dbxAuth = new DropboxAuth({
- clientId: hooks.env.dropboxKey(),
- })
- dbxAuth.setCodeVerifier(code_verifier)
-
- const token = await dbxAuth.getAccessTokenFromCode(
- hooks.env.getCorrectLocalUrl(),
- code,
- )
-
- stores.AUTH.account.dropboxAccessToken = token?.result // @ts-expect-error
- ?.access_token as string
-
- await exclude('code_verifier')
-
- hooks.toast.success(hooks.i18n.t('toast.dropbox.load'))
- }
- })
-}
diff --git a/packages/plugin-dropbox/tsconfig.json b/packages/plugin-dropbox/tsconfig.json
deleted file mode 100644
index bdc00469..00000000
--- a/packages/plugin-dropbox/tsconfig.json
+++ /dev/null
@@ -1,36 +0,0 @@
-{
- "include": ["src/**/*.ts"],
- "exclude": ["dist", "node_modules"],
- "compilerOptions": {
- "baseUrl": ".",
- "rootDir": ".",
- "outDir": "dist",
- "sourceMap": false,
- "noEmit": true,
- "declaration": true,
- "target": "esnext",
- "module": "esnext",
- "moduleResolution": "node",
- "skipLibCheck": true,
- "noUnusedLocals": true,
- "strictNullChecks": true,
- "noImplicitAny": true,
- "noImplicitThis": true,
- "noImplicitReturns": true,
- "strict": true,
- "isolatedModules": false,
- "experimentalDecorators": true,
- "resolveJsonModule": true,
- "esModuleInterop": true,
- "removeComments": false,
- "strictPropertyInitialization": false,
- "jsx": "preserve",
- "lib": ["esnext", "dom"],
- "types": ["node"],
- "plugins": [
- {
- "name": "@vuedx/typescript-plugin-vue"
- }
- ]
- }
-}
diff --git a/packages/plugin-dropbox/tsup.config.ts b/packages/plugin-dropbox/tsup.config.ts
deleted file mode 100644
index b0833c89..00000000
--- a/packages/plugin-dropbox/tsup.config.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { defineConfig } from 'tsup'
-
-export default defineConfig({
- entry: ['src/index.ts'],
- format: ['esm', 'cjs'],
- clean: true,
- dts: true,
- external: ['dropbox'],
-})
diff --git a/packages/plugin-editor-window/tsconfig.json b/packages/plugin-editor-window/tsconfig.json
index b016643a..bdc00469 100644
--- a/packages/plugin-editor-window/tsconfig.json
+++ b/packages/plugin-editor-window/tsconfig.json
@@ -3,7 +3,6 @@
"exclude": ["dist", "node_modules"],
"compilerOptions": {
"baseUrl": ".",
- "ignoreDeprecations": "6.0",
"rootDir": ".",
"outDir": "dist",
"sourceMap": false,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index db954d17..7c99dcfe 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -133,9 +133,6 @@ importers:
better-write-plugin-core:
specifier: ^1.3.27
version: link:../plugin-core
- better-write-plugin-dropbox:
- specifier: ^1.3.27
- version: link:../plugin-dropbox
better-write-plugin-editor-window:
specifier: ^1.3.27
version: link:../plugin-editor-window
@@ -271,7 +268,7 @@ importers:
version: 8.46.3(eslint@9.39.1)(typescript@5.9.3)
'@vitejs/plugin-vue':
specifier: 6.0.1
- version: 6.0.1(vite@8.0.16)(vue@3.5.14)
+ version: 6.0.1(vite@7.1.12)(vue@3.5.14)
'@vue/eslint-config-typescript':
specifier: 14.6.0
version: 14.6.0(eslint-plugin-vue@10.5.1)(eslint@9.39.1)(typescript@5.9.3)
@@ -315,29 +312,29 @@ importers:
specifier: 30.0.0
version: 30.0.0(vue@3.5.14)
vite:
- specifier: 8.0.16
- version: 8.0.16(esbuild@0.25.11)
+ specifier: 7.1.12
+ version: 7.1.12
vite-plugin-checker:
specifier: 0.11.0
- version: 0.11.0(eslint@9.39.1)(typescript@5.9.3)(vite@8.0.16)(vue-tsc@1.8.8)
+ version: 0.11.0(eslint@9.39.1)(typescript@5.9.3)(vite@7.1.12)(vue-tsc@1.8.8)
vite-plugin-optimize-persist:
specifier: 0.1.2
- version: 0.1.2(vite-plugin-package-config@0.1.1)(vite@8.0.16)
+ version: 0.1.2(vite-plugin-package-config@0.1.1)(vite@7.1.12)
vite-plugin-package-config:
specifier: 0.1.1
- version: 0.1.1(vite@8.0.16)
+ version: 0.1.1(vite@7.1.12)
vite-plugin-package-version:
specifier: 1.1.0
- version: 1.1.0(vite@8.0.16)
+ version: 1.1.0(vite@7.1.12)
vite-plugin-pwa:
specifier: 1.1.0
- version: 1.1.0(vite@8.0.16)(workbox-build@7.3.0)(workbox-window@7.3.0)
+ version: 1.1.0(vite@7.1.12)(workbox-build@7.3.0)(workbox-window@7.3.0)
vite-plugin-sitemap:
specifier: 0.4.2
version: 0.4.2
vite-plugin-windicss:
specifier: 1.9.4
- version: 1.9.4(vite@8.0.16)
+ version: 1.9.4(vite@7.1.12)
vue-tsc:
specifier: 1.8.8
version: 1.8.8(typescript@5.9.3)
@@ -433,31 +430,6 @@ importers:
specifier: latest
version: 0.14.10(@vue/composition-api@1.0.0-rc.1)(vue@3.5.14)
- packages/plugin-dropbox:
- dependencies:
- better-write-client-storage:
- specifier: ^1.3.27
- version: link:../client-storage
- better-write-extension:
- specifier: ^1.3.27
- version: link:../extension
- better-write-plugin-core:
- specifier: ^1.3.27
- version: link:../plugin-core
- better-write-types:
- specifier: ^1.3.27
- version: link:../types
- vue:
- specifier: ^2.0.0 || >=3.0.0
- version: 3.5.14(typescript@5.9.3)
- vue-demi:
- specifier: latest
- version: 0.14.10(@vue/composition-api@1.0.0-rc.1)(vue@3.5.14)
- devDependencies:
- prettier:
- specifier: 2.5.1
- version: 2.5.1
-
packages/plugin-editor-window:
dependencies:
'@vue/composition-api':
@@ -1869,31 +1841,6 @@ packages:
resolution: {integrity: sha512-MmFKN0DEIS+78wtfag7DiQDuE7eSpHRt4tYh0m8bEUnxbH1v2pieQ6Ir+1WZ3Xxkkf5L5tmDfeYQtCSwUz1Hyg==}
dev: false
- /@emnapi/core@1.10.0:
- resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==}
- requiresBuild: true
- dependencies:
- '@emnapi/wasi-threads': 1.2.1
- tslib: 2.8.1
- dev: true
- optional: true
-
- /@emnapi/runtime@1.10.0:
- resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==}
- requiresBuild: true
- dependencies:
- tslib: 2.8.1
- dev: true
- optional: true
-
- /@emnapi/wasi-threads@1.2.1:
- resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==}
- requiresBuild: true
- dependencies:
- tslib: 2.8.1
- dev: true
- optional: true
-
/@esbuild/aix-ppc64@0.25.11:
resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==}
engines: {node: '>=18'}
@@ -2758,19 +2705,6 @@ packages:
tslib: 2.8.1
dev: false
- /@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0):
- resolution: {integrity: sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==}
- requiresBuild: true
- peerDependencies:
- '@emnapi/core': ^1.7.1
- '@emnapi/runtime': ^1.7.1
- dependencies:
- '@emnapi/core': 1.10.0
- '@emnapi/runtime': 1.10.0
- '@tybys/wasm-util': 0.10.2
- dev: true
- optional: true
-
/@nodelib/fs.scandir@2.1.5:
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -3238,10 +3172,6 @@ packages:
'@octokit/openapi-types': 18.1.1
dev: true
- /@oxc-project/types@0.133.0:
- resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==}
- dev: true
-
/@parcel/watcher@2.0.4:
resolution: {integrity: sha512-cTDi+FUDBIUOBKEtj+nhiJ71AZVlkAsQFuGQTun5tV9mwQBQgZvhCzG+URPQc8myeN32yRVZEfVAPCs1RW+Jvg==}
engines: {node: '>= 10.0.0'}
@@ -3262,152 +3192,10 @@ packages:
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
dev: false
- /@rolldown/binding-android-arm64@1.0.3:
- resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==}
- engines: {node: ^20.19.0 || >=22.12.0}
- cpu: [arm64]
- os: [android]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rolldown/binding-darwin-arm64@1.0.3:
- resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==}
- engines: {node: ^20.19.0 || >=22.12.0}
- cpu: [arm64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rolldown/binding-darwin-x64@1.0.3:
- resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==}
- engines: {node: ^20.19.0 || >=22.12.0}
- cpu: [x64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rolldown/binding-freebsd-x64@1.0.3:
- resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==}
- engines: {node: ^20.19.0 || >=22.12.0}
- cpu: [x64]
- os: [freebsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rolldown/binding-linux-arm-gnueabihf@1.0.3:
- resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==}
- engines: {node: ^20.19.0 || >=22.12.0}
- cpu: [arm]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rolldown/binding-linux-arm64-gnu@1.0.3:
- resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==}
- engines: {node: ^20.19.0 || >=22.12.0}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rolldown/binding-linux-arm64-musl@1.0.3:
- resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==}
- engines: {node: ^20.19.0 || >=22.12.0}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rolldown/binding-linux-ppc64-gnu@1.0.3:
- resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==}
- engines: {node: ^20.19.0 || >=22.12.0}
- cpu: [ppc64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rolldown/binding-linux-s390x-gnu@1.0.3:
- resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==}
- engines: {node: ^20.19.0 || >=22.12.0}
- cpu: [s390x]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rolldown/binding-linux-x64-gnu@1.0.3:
- resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==}
- engines: {node: ^20.19.0 || >=22.12.0}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rolldown/binding-linux-x64-musl@1.0.3:
- resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==}
- engines: {node: ^20.19.0 || >=22.12.0}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rolldown/binding-openharmony-arm64@1.0.3:
- resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==}
- engines: {node: ^20.19.0 || >=22.12.0}
- cpu: [arm64]
- os: [openharmony]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rolldown/binding-wasm32-wasi@1.0.3:
- resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==}
- engines: {node: ^20.19.0 || >=22.12.0}
- cpu: [wasm32]
- requiresBuild: true
- dependencies:
- '@emnapi/core': 1.10.0
- '@emnapi/runtime': 1.10.0
- '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
- dev: true
- optional: true
-
- /@rolldown/binding-win32-arm64-msvc@1.0.3:
- resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==}
- engines: {node: ^20.19.0 || >=22.12.0}
- cpu: [arm64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /@rolldown/binding-win32-x64-msvc@1.0.3:
- resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==}
- engines: {node: ^20.19.0 || >=22.12.0}
- cpu: [x64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
/@rolldown/pluginutils@1.0.0-beta.29:
resolution: {integrity: sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==}
dev: true
- /@rolldown/pluginutils@1.0.1:
- resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
- dev: true
-
/@rollup/plugin-babel@5.3.1(@babel/core@7.28.5)(rollup@2.79.2):
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
engines: {node: '>= 10.0.0'}
@@ -3842,14 +3630,6 @@ packages:
minimatch: 9.0.5
dev: true
- /@tybys/wasm-util@0.10.2:
- resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==}
- requiresBuild: true
- dependencies:
- tslib: 2.8.1
- dev: true
- optional: true
-
/@types/chai@5.2.3:
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
dependencies:
@@ -4148,7 +3928,7 @@ packages:
vue: 3.5.14(typescript@5.9.3)
dev: false
- /@vitejs/plugin-vue@6.0.1(vite@8.0.16)(vue@3.5.14):
+ /@vitejs/plugin-vue@6.0.1(vite@7.1.12)(vue@3.5.14):
resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==}
engines: {node: ^20.19.0 || >=22.12.0}
peerDependencies:
@@ -4156,7 +3936,7 @@ packages:
vue: ^3.2.25
dependencies:
'@rolldown/pluginutils': 1.0.0-beta.29
- vite: 8.0.16(esbuild@0.25.11)
+ vite: 7.1.12
vue: 3.5.14(typescript@5.9.3)
dev: true
@@ -5955,11 +5735,6 @@ packages:
engines: {node: '>=4'}
dev: true
- /detect-libc@2.1.2:
- resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
- engines: {node: '>=8'}
- dev: true
-
/dfa@1.2.0:
resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==}
@@ -8303,124 +8078,6 @@ packages:
immediate: 3.0.6
dev: false
- /lightningcss-android-arm64@1.32.0:
- resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
- engines: {node: '>= 12.0.0'}
- cpu: [arm64]
- os: [android]
- requiresBuild: true
- dev: true
- optional: true
-
- /lightningcss-darwin-arm64@1.32.0:
- resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
- engines: {node: '>= 12.0.0'}
- cpu: [arm64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /lightningcss-darwin-x64@1.32.0:
- resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
- engines: {node: '>= 12.0.0'}
- cpu: [x64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /lightningcss-freebsd-x64@1.32.0:
- resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
- engines: {node: '>= 12.0.0'}
- cpu: [x64]
- os: [freebsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /lightningcss-linux-arm-gnueabihf@1.32.0:
- resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
- engines: {node: '>= 12.0.0'}
- cpu: [arm]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /lightningcss-linux-arm64-gnu@1.32.0:
- resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
- engines: {node: '>= 12.0.0'}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /lightningcss-linux-arm64-musl@1.32.0:
- resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
- engines: {node: '>= 12.0.0'}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /lightningcss-linux-x64-gnu@1.32.0:
- resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
- engines: {node: '>= 12.0.0'}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /lightningcss-linux-x64-musl@1.32.0:
- resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
- engines: {node: '>= 12.0.0'}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /lightningcss-win32-arm64-msvc@1.32.0:
- resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
- engines: {node: '>= 12.0.0'}
- cpu: [arm64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /lightningcss-win32-x64-msvc@1.32.0:
- resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
- engines: {node: '>= 12.0.0'}
- cpu: [x64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /lightningcss@1.32.0:
- resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
- engines: {node: '>= 12.0.0'}
- dependencies:
- detect-libc: 2.1.2
- optionalDependencies:
- lightningcss-android-arm64: 1.32.0
- lightningcss-darwin-arm64: 1.32.0
- lightningcss-darwin-x64: 1.32.0
- lightningcss-freebsd-x64: 1.32.0
- lightningcss-linux-arm-gnueabihf: 1.32.0
- lightningcss-linux-arm64-gnu: 1.32.0
- lightningcss-linux-arm64-musl: 1.32.0
- lightningcss-linux-x64-gnu: 1.32.0
- lightningcss-linux-x64-musl: 1.32.0
- lightningcss-win32-arm64-msvc: 1.32.0
- lightningcss-win32-x64-msvc: 1.32.0
- dev: true
-
/lilconfig@3.1.3:
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
engines: {node: '>=14'}
@@ -10838,31 +10495,6 @@ packages:
inherits: 2.0.4
dev: true
- /rolldown@1.0.3:
- resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==}
- engines: {node: ^20.19.0 || >=22.12.0}
- hasBin: true
- dependencies:
- '@oxc-project/types': 0.133.0
- '@rolldown/pluginutils': 1.0.1
- optionalDependencies:
- '@rolldown/binding-android-arm64': 1.0.3
- '@rolldown/binding-darwin-arm64': 1.0.3
- '@rolldown/binding-darwin-x64': 1.0.3
- '@rolldown/binding-freebsd-x64': 1.0.3
- '@rolldown/binding-linux-arm-gnueabihf': 1.0.3
- '@rolldown/binding-linux-arm64-gnu': 1.0.3
- '@rolldown/binding-linux-arm64-musl': 1.0.3
- '@rolldown/binding-linux-ppc64-gnu': 1.0.3
- '@rolldown/binding-linux-s390x-gnu': 1.0.3
- '@rolldown/binding-linux-x64-gnu': 1.0.3
- '@rolldown/binding-linux-x64-musl': 1.0.3
- '@rolldown/binding-openharmony-arm64': 1.0.3
- '@rolldown/binding-wasm32-wasi': 1.0.3
- '@rolldown/binding-win32-arm64-msvc': 1.0.3
- '@rolldown/binding-win32-x64-msvc': 1.0.3
- dev: true
-
/rollup@2.79.2:
resolution: {integrity: sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==}
engines: {node: '>=10.0.0'}
@@ -12335,7 +11967,7 @@ packages:
vfile-message: 3.1.4
dev: false
- /vite-plugin-checker@0.11.0(eslint@9.39.1)(typescript@5.9.3)(vite@8.0.16)(vue-tsc@1.8.8):
+ /vite-plugin-checker@0.11.0(eslint@9.39.1)(typescript@5.9.3)(vite@7.1.12)(vue-tsc@1.8.8):
resolution: {integrity: sha512-iUdO9Pl9UIBRPAragwi3as/BXXTtRu4G12L3CMrjx+WVTd9g/MsqNakreib9M/2YRVkhZYiTEwdH2j4Dm0w7lw==}
engines: {node: '>=16.11'}
peerDependencies:
@@ -12381,12 +12013,12 @@ packages:
tiny-invariant: 1.3.3
tinyglobby: 0.2.15
typescript: 5.9.3
- vite: 8.0.16(esbuild@0.25.11)
+ vite: 7.1.12
vscode-uri: 3.1.0
vue-tsc: 1.8.8(typescript@5.9.3)
dev: true
- /vite-plugin-optimize-persist@0.1.2(vite-plugin-package-config@0.1.1)(vite@8.0.16):
+ /vite-plugin-optimize-persist@0.1.2(vite-plugin-package-config@0.1.1)(vite@7.1.12):
resolution: {integrity: sha512-H/Ebn2kZO8PvwUF08SsT5K5xMJNCWKoGX71+e9/ER3yNj7GHiFjNQlvGg5ih/zEx09MZ9m7WCxOwmEKbeIVzww==}
peerDependencies:
vite: ^2.0.0
@@ -12394,32 +12026,32 @@ packages:
dependencies:
debug: 4.4.3
fs-extra: 10.1.0
- vite: 8.0.16(esbuild@0.25.11)
- vite-plugin-package-config: 0.1.1(vite@8.0.16)
+ vite: 7.1.12
+ vite-plugin-package-config: 0.1.1(vite@7.1.12)
transitivePeerDependencies:
- supports-color
dev: true
- /vite-plugin-package-config@0.1.1(vite@8.0.16):
+ /vite-plugin-package-config@0.1.1(vite@7.1.12):
resolution: {integrity: sha512-w9B3I8ZnqoyhlbzimXjXNk85imrMZgvI9m8f6j3zonK5IVA5KXzpT+PZOHlDz8lqh1vqvoEI1uhy+ZDoLAiA/w==}
peerDependencies:
vite: ^2.0.0
dependencies:
debug: 4.4.3
- vite: 8.0.16(esbuild@0.25.11)
+ vite: 7.1.12
transitivePeerDependencies:
- supports-color
dev: true
- /vite-plugin-package-version@1.1.0(vite@8.0.16):
+ /vite-plugin-package-version@1.1.0(vite@7.1.12):
resolution: {integrity: sha512-TPoFZXNanzcaKCIrC3e2L/TVRkkRLB6l4RPN/S7KbG7rWfyLcCEGsnXvxn6qR7fyZwXalnnSN/I9d6pSFjHpEA==}
peerDependencies:
vite: '>=2.0.0-beta.69'
dependencies:
- vite: 8.0.16(esbuild@0.25.11)
+ vite: 7.1.12
dev: true
- /vite-plugin-pwa@1.1.0(vite@8.0.16)(workbox-build@7.3.0)(workbox-window@7.3.0):
+ /vite-plugin-pwa@1.1.0(vite@7.1.12)(workbox-build@7.3.0)(workbox-window@7.3.0):
resolution: {integrity: sha512-VsSpdubPzXhHWVINcSx6uHRMpOHVHQcHsef1QgkOlEoaIDAlssFEW88LBq1a59BuokAhsh2kUDJbaX1bZv4Bjw==}
engines: {node: '>=16.0.0'}
peerDependencies:
@@ -12434,7 +12066,7 @@ packages:
debug: 4.4.3
pretty-bytes: 6.1.1
tinyglobby: 0.2.15
- vite: 8.0.16(esbuild@0.25.11)
+ vite: 7.1.12
workbox-build: 7.3.0
workbox-window: 7.3.0
transitivePeerDependencies:
@@ -12445,7 +12077,7 @@ packages:
resolution: {integrity: sha512-xNTYPcNW+XthOJqI459DYyCZ6nPDKpnFFnvMHVlshtDn+UUp1y37HgmvmWEsCdLp0LZILnzHfrjVrH44V2krAg==}
dev: true
- /vite-plugin-windicss@1.9.4(vite@8.0.16):
+ /vite-plugin-windicss@1.9.4(vite@7.1.12):
resolution: {integrity: sha512-3t1AUVrs2XBXGc2BefRPRvy1CLy8qA/5A1J1Z73Ej1DIx+puXn39MQSWluxZ2FHEz8z9OEIvsoIIPc/s/P3OmQ==}
peerDependencies:
vite: '*'
@@ -12453,7 +12085,7 @@ packages:
'@windicss/plugin-utils': 1.9.4
debug: 4.4.3
kolorist: 1.8.0
- vite: 8.0.16(esbuild@0.25.11)
+ vite: 7.1.12
windicss: 3.5.6
transitivePeerDependencies:
- supports-color
@@ -12509,59 +12141,6 @@ packages:
fsevents: 2.3.3
dev: true
- /vite@8.0.16(esbuild@0.25.11):
- resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==}
- engines: {node: ^20.19.0 || >=22.12.0}
- hasBin: true
- peerDependencies:
- '@types/node': ^20.19.0 || >=22.12.0
- '@vitejs/devtools': ^0.1.18
- esbuild: ^0.27.0 || ^0.28.0
- jiti: '>=1.21.0'
- less: ^4.0.0
- sass: ^1.70.0
- sass-embedded: ^1.70.0
- stylus: '>=0.54.8'
- sugarss: ^5.0.0
- terser: ^5.16.0
- tsx: ^4.8.1
- yaml: ^2.4.2
- peerDependenciesMeta:
- '@types/node':
- optional: true
- '@vitejs/devtools':
- optional: true
- esbuild:
- optional: true
- jiti:
- optional: true
- less:
- optional: true
- sass:
- optional: true
- sass-embedded:
- optional: true
- stylus:
- optional: true
- sugarss:
- optional: true
- terser:
- optional: true
- tsx:
- optional: true
- yaml:
- optional: true
- dependencies:
- esbuild: 0.25.11
- lightningcss: 1.32.0
- picomatch: 4.0.4
- postcss: 8.5.15
- rolldown: 1.0.3
- tinyglobby: 0.2.17
- optionalDependencies:
- fsevents: 2.3.3
- dev: true
-
/vitest@4.0.6(happy-dom@20.0.10):
resolution: {integrity: sha512-gR7INfiVRwnEOkCk47faros/9McCZMp5LM+OMNWGLaDBSvJxIzwjgNFufkuePBNaesGRnLmNfW+ddbUJRZn0nQ==}
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
diff --git a/tsconfig.json b/tsconfig.json
index d3afb00d..25e020f1 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,3 @@
{
- "extends": "./packages/app/tsconfig.json",
- "compilerOptions": {
- "ignoreDeprecations": "6.0"
- }
+ "extends": "./packages/app/tsconfig.json"
}
\ No newline at end of file
From 3d3ef9b2a86f880e9fed6a29b757b1402a88e57c Mon Sep 17 00:00:00 2001
From: Novout
Date: Wed, 10 Jun 2026 19:38:08 -0300
Subject: [PATCH 06/30] refactor: remove head dynamic set
---
packages/plugin-editor-window/src/seo.ts | 13 +------------
1 file changed, 1 insertion(+), 12 deletions(-)
diff --git a/packages/plugin-editor-window/src/seo.ts b/packages/plugin-editor-window/src/seo.ts
index 35ce7399..8737b081 100644
--- a/packages/plugin-editor-window/src/seo.ts
+++ b/packages/plugin-editor-window/src/seo.ts
@@ -7,22 +7,11 @@ export const PluginSeoSet = (
hooks: PluginTypes.PluginHooks,
) => {
emitter.on('call-editor-created', () => {
- // dynamic head title
const title = computed(() => hooks.i18n.t('seo.editor.title'))
const description = computed(() => hooks.i18n.t('seo.editor.description'))
- const _title = computed(() =>
- stores.PROJECT.nameRaw === hooks.env.projectEmpty() ||
- !stores.CONTEXT.entities[0] ||
- stores.CONTEXT.entities[0].raw === hooks.env.emptyLine() ||
- hooks.entity.utils().isFixed(0)
- ? title.value
- : stores.PROJECT.nameRaw +
- (stores.CONTEXT.entities[0]?.raw ? ' - ' : '') +
- stores.CONTEXT.entities[0]?.raw,
- )
hooks.vueuse.head({
- title: _title,
+ title,
meta: [
{
name: `description`,
From 6be2b614dd71eba82e4783d129183a1a6c2689d6 Mon Sep 17 00:00:00 2001
From: Novout
Date: Wed, 10 Jun 2026 19:46:43 -0300
Subject: [PATCH 07/30] fix!: clean save in before set entity raw
---
packages/app/src/use/block/text.ts | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/packages/app/src/use/block/text.ts b/packages/app/src/use/block/text.ts
index bd5aa907..2d75c2da 100644
--- a/packages/app/src/use/block/text.ts
+++ b/packages/app/src/use/block/text.ts
@@ -51,7 +51,11 @@ export const useBlockText = ({
const save = (target: number, raw: string) => {
if (raw === null) return
- CONTEXT.entities[target].raw = raw
+ const clean = raw
+ .replaceAll(/ /gi, '')
+ .replaceAll('&', '&')
+
+ CONTEXT.entities[target].raw = clean
if (EDITOR.configuration.trackEntities) {
CONTEXT.entities[target].updatedAt = format.actually('iso')
From fb0b5db8ddb827fb2c8c874ee951003bee64b1f1 Mon Sep 17 00:00:00 2001
From: Novout
Date: Fri, 12 Jun 2026 18:33:22 -0300
Subject: [PATCH 08/30] feat: initial .epub implementation
---
package.json | 7 +-
packages/app/package.json | 1 +
packages/app/src/App.vue | 4 +-
.../page/about/AboutPortability.vue | 2 -
.../header/items/EditorBaseHeaderCreate.vue | 2 -
packages/app/vite.config.ts | 1 +
packages/plugin-exporter-epub/package.json | 2 +-
packages/plugin-exporter-epub/src/set.ts | 177 +++---------------
pnpm-lock.yaml | 158 +++-------------
9 files changed, 66 insertions(+), 288 deletions(-)
diff --git a/package.json b/package.json
index 763e966d..beebd6f5 100644
--- a/package.json
+++ b/package.json
@@ -15,9 +15,12 @@
"devDependencies": {
"generi": "2.0.4",
"happy-dom": "20.0.10",
- "prettier": "3.6.2",
"lerna": "6.6.2",
+ "prettier": "3.6.2",
"vitest": "4.0.6"
},
- "packageManager": "pnpm@8.5.1"
+ "packageManager": "pnpm@8.5.1",
+ "dependencies": {
+ "epub-gen3": "^0.2.0"
+ }
}
diff --git a/packages/app/package.json b/packages/app/package.json
index 9e3093c3..8dffb43b 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -72,6 +72,7 @@
"docx": "7.5.0",
"drauu": "0.4.3",
"dropbox": "10.34.0",
+ "epub-gen3": "0.2.2",
"file-saver": "2.0.5",
"floating-vue": "2.0.0-beta.20",
"hast": "1.0.0",
diff --git a/packages/app/src/App.vue b/packages/app/src/App.vue
index 1a15465c..880429a2 100644
--- a/packages/app/src/App.vue
+++ b/packages/app/src/App.vue
@@ -9,7 +9,7 @@
import { ImporterPlugin } from 'better-write-plugin-importer'
import { PDFPlugin } from 'better-write-plugin-exporter-pdf'
import { DocxPlugin } from 'better-write-plugin-exporter-docx'
- // import { EpubPlugin } from 'better-write-plugin-exporter-epub'
+ import { EpubPlugin } from 'better-write-plugin-exporter-epub'
import { TxtPlugin } from 'better-write-plugin-exporter-txt'
import { HtmlPlugin } from 'better-write-plugin-exporter-html'
import { SchemasPlugin } from 'better-write-plugin-schemas'
@@ -25,7 +25,7 @@
ImporterPlugin(),
PDFPlugin(),
DocxPlugin(),
- // EpubPlugin(),
+ EpubPlugin(),
TxtPlugin(),
HtmlPlugin(),
SchemasPlugin(),
diff --git a/packages/app/src/components/page/about/AboutPortability.vue b/packages/app/src/components/page/about/AboutPortability.vue
index ca3f4cfc..781c2f8d 100644
--- a/packages/app/src/components/page/about/AboutPortability.vue
+++ b/packages/app/src/components/page/about/AboutPortability.vue
@@ -110,9 +110,7 @@
class="flex flex-col gap-5"
>
.DOCX
-
.TXT
.PDF
.BW
diff --git a/packages/app/src/components/page/editor/header/items/EditorBaseHeaderCreate.vue b/packages/app/src/components/page/editor/header/items/EditorBaseHeaderCreate.vue
index d881d870..b06a494c 100644
--- a/packages/app/src/components/page/editor/header/items/EditorBaseHeaderCreate.vue
+++ b/packages/app/src/components/page/editor/header/items/EditorBaseHeaderCreate.vue
@@ -120,8 +120,6 @@
const onEPUBGenerate = async () => {
await storage.normalize()
- toast.warning(t('toast.epub.disabled'))
-
plugin.emit('plugin-epub-generate')
}
diff --git a/packages/app/vite.config.ts b/packages/app/vite.config.ts
index db0f4db0..b59e536f 100644
--- a/packages/app/vite.config.ts
+++ b/packages/app/vite.config.ts
@@ -31,6 +31,7 @@ export default ({ mode }: any) => {
},
optimizeDeps: {
include: ['buffer', 'process'],
+ exclude: ['epub-gen3']
},
define: {
__VUE_I18N_FULL_INSTALL__: true,
diff --git a/packages/plugin-exporter-epub/package.json b/packages/plugin-exporter-epub/package.json
index 041788e6..6191cc0f 100644
--- a/packages/plugin-exporter-epub/package.json
+++ b/packages/plugin-exporter-epub/package.json
@@ -20,6 +20,6 @@
"better-write-contenteditable-ast": "^1.3.27",
"better-write-plugin-core": "^1.3.27",
"better-write-types": "^1.3.27",
- "epub-gen-memory": "1.0.10"
+ "epub-gen3": "0.2.2"
}
}
diff --git a/packages/plugin-exporter-epub/src/set.ts b/packages/plugin-exporter-epub/src/set.ts
index 548e93f0..6651c0ec 100644
--- a/packages/plugin-exporter-epub/src/set.ts
+++ b/packages/plugin-exporter-epub/src/set.ts
@@ -1,179 +1,58 @@
-import { saveAs } from 'file-saver'
-import { ContextState, Entity, PluginTypes } from 'better-write-types'
+import { ContextState, PluginTypes } from 'better-write-types'
import { On } from 'better-write-plugin-core'
-import { getRows, parse } from 'better-write-contenteditable-ast'
-import EPUB, { Chapter } from 'epub-gen-memory/bundle'
-import { getStyles } from './styles'
+import { Epub, ready } from 'epub-gen3/browser'
export const PluginEpubSet = (
emitter: PluginTypes.PluginEmitter,
stores: PluginTypes.PluginStores,
hooks: PluginTypes.PluginHooks,
) => {
- const entities = () => {
- const headingOne = (entity: Entity) => {
- return hooks.substitution.purge(entity.raw)
- }
-
- const headingTwo = (entity: Entity) => {
- return `${hooks.substitution.purge(entity.raw)} `
- }
-
- const headingThree = (entity: Entity) => {
- return `${hooks.substitution.purge(entity.raw)} `
- }
-
- const image = (entity: Entity) => {
- return ``
- }
-
- const svg = (entity: Entity) => {
- return `${entity.raw}
`
- }
-
- const checkbox = (entity: Entity) => {
- const id = String(hooks.utils.id().uuidv4())
-
- return `
-${hooks.substitution.purge(entity.raw)}
`
- }
-
- const list = (entity: Entity) => {
- return `${hooks.substitution.purge(
- entity.raw,
- )} `
- }
-
- const paragraph = (entity: Entity): string[] => {
- if (
- hooks.env.emptyLine() === entity.raw ||
- entity.raw === '' ||
- entity.raw === ' '
- )
- return [lineBreak()]
-
- return getRows(entity.raw).map((row) => {
- const target = parse(hooks.substitution.purge(row))
-
- return target.reduce((acc, item) => {
- // boldItalics
- if (item.italic && item.bold)
- return (acc += item.text.trim() ? `${item.text} ` : '')
-
- // italics
- if (item.italic)
- return (acc += item.text.trim() ? `${item.text} ` : '')
-
- // bold
- if (item.bold)
- return (acc += item.text.trim() ? `${item.text} ` : '')
-
- // common case
- return (acc += item.text.trim() ? `${item.text} ` : '')
- }, '')
- })
- }
-
- const pageBreak = () => {
- return `
`
- }
-
- const lineBreak = () => {
- return '
'
- }
-
- return {
- paragraph,
- headingOne,
- headingTwo,
- headingThree,
- image,
- svg,
- checkbox,
- list,
- pageBreak,
- lineBreak,
- }
- }
-
- const contents = (): Chapter[] => {
- const chapters: Chapter[] = []
+ const contents = (): string[][] => {
+ const raw: string[][] = []
stores.PROJECT.chapters.forEach(({ entities: list }: ContextState) => {
- const chapter = {
- title: '',
- content: '',
- }
+ let data = []
for (const entity of list) {
- switch (entity.type) {
- case 'checkbox':
- chapter.content += entities().checkbox(entity)
- break
- case 'list':
- chapter.content += entities().list(entity)
- break
- case 'paragraph':
- entities()
- .paragraph(entity)
- ?.forEach(
- (paragraph) => (chapter.content += `${paragraph}
`),
- )
- break
- case 'heading-one':
- chapter.title = entities().headingOne(entity)
- break
- case 'heading-two':
- chapter.content += entities().headingTwo(entity)
- break
- case 'heading-three':
- chapter.content += entities().headingThree(entity)
- break
- case 'page-break':
- chapter.content += entities().pageBreak()
- break
- case 'line-break':
- chapter.content += entities().lineBreak()
- break
- case 'image':
- chapter.content += entities().image(entity)
- break
- case 'drau':
- chapter.content += entities().svg(entity)
- break
- }
+ data.push(hooks.substitution.purge(entity.raw))
}
- chapters.push(chapter)
+ raw.push(data)
})
- return chapters
+ return raw
}
- const generate = () => {
- EPUB(
+ const generate = async () => {
+ await ready
+
+ const epub = new Epub(
{
title: stores.PROJECT.nameRaw,
author: stores.PROJECT.creator,
description: stores.PROJECT.subject,
publisher: stores.PROJECT.producer,
tocTitle: hooks.i18n.t('editor.bar.epub.table') ?? undefined,
- tocInTOC: stores.PROJECT.type === 'creative',
- date: new Date().toString(),
- css: getStyles(stores, hooks),
- cover: undefined,
+ lang: 'en',
+ fonts: [],
+ version: 3,
+ // TODO: support CSS styles
+ // css: getStyles(stores, hooks),
},
- contents(),
- ).then(
- (content) => download(content),
- (_) => hooks.toast.error(hooks.i18n.t('toast.generics.error')),
+ [
+ contents(),
+ ]
)
+
+ download(epub, stores.PROJECT.nameRaw)
}
- const download = (blob: Blob) => {
- saveAs(blob, hooks.project.utils().exportFullName('epub'))
+ const download = (epub: Epub, title: string) => {
+ const bytes = epub.archive()
+ const blob = new Blob([bytes as any], { type: 'application/epub+zip' })
+ const url = URL.createObjectURL(blob)
+ const a = Object.assign(document.createElement('a'), { href: url, download: `${title}.epub` })
+ a.click()
hooks.toast.success(hooks.i18n.t('toast.project.epub.generate'))
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7c99dcfe..57107c01 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3,6 +3,10 @@ lockfileVersion: '6.0'
importers:
.:
+ dependencies:
+ epub-gen3:
+ specifier: ^0.2.0
+ version: 0.2.0
devDependencies:
generi:
specifier: 2.0.4
@@ -190,6 +194,9 @@ importers:
dropbox:
specifier: 10.34.0
version: 10.34.0(@types/node-fetch@2.6.13)
+ epub-gen3:
+ specifier: 0.2.2
+ version: 0.2.2
file-saver:
specifier: 2.0.5
version: 2.0.5
@@ -514,9 +521,9 @@ importers:
better-write-types:
specifier: ^1.3.27
version: link:../types
- epub-gen-memory:
- specifier: 1.0.10
- version: 1.0.10
+ epub-gen3:
+ specifier: 0.2.2
+ version: 0.2.2
packages/plugin-exporter-html:
dependencies:
@@ -4443,13 +4450,6 @@ packages:
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
dev: true
- /abort-controller@3.0.0:
- resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
- engines: {node: '>=6.5'}
- dependencies:
- event-target-shim: 5.0.1
- dev: false
-
/acorn-jsx@5.3.2(acorn@8.15.0):
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@@ -4666,6 +4666,7 @@ packages:
/async@3.2.6:
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
+ dev: true
/asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
@@ -4734,6 +4735,7 @@ packages:
/balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+ dev: true
/base64-js@0.0.8:
resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==}
@@ -4792,6 +4794,7 @@ packages:
/boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
+ dev: true
/brace-expansion@1.1.12:
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
@@ -4804,6 +4807,7 @@ packages:
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
dependencies:
balanced-match: 1.0.2
+ dev: true
/braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
@@ -5060,6 +5064,7 @@ packages:
/callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
+ dev: true
/camelcase-keys@6.2.2:
resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==}
@@ -5541,16 +5546,6 @@ packages:
engines: {node: '>=8'}
dev: true
- /css-select@4.3.0:
- resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
- dependencies:
- boolbase: 1.0.0
- css-what: 6.2.2
- domhandler: 4.3.1
- domutils: 2.8.0
- nth-check: 2.1.1
- dev: false
-
/css-tree@3.1.0:
resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
@@ -5559,11 +5554,6 @@ packages:
source-map-js: 1.2.1
dev: true
- /css-what@6.2.2:
- resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
- engines: {node: '>= 6'}
- dev: false
-
/cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
@@ -5738,10 +5728,6 @@ packages:
/dfa@1.2.0:
resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==}
- /diacritics@1.3.0:
- resolution: {integrity: sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA==}
- dev: false
-
/diff@5.2.0:
resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
engines: {node: '>=0.3.1'}
@@ -5773,38 +5759,11 @@ packages:
xml-js: 1.6.11
dev: false
- /dom-serializer@1.4.1:
- resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
- dependencies:
- domelementtype: 2.3.0
- domhandler: 4.3.1
- entities: 2.2.0
- dev: false
-
/domain-browser@4.22.0:
resolution: {integrity: sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw==}
engines: {node: '>=10'}
dev: true
- /domelementtype@2.3.0:
- resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
- dev: false
-
- /domhandler@4.3.1:
- resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
- engines: {node: '>= 4'}
- dependencies:
- domelementtype: 2.3.0
- dev: false
-
- /domutils@2.8.0:
- resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
- dependencies:
- dom-serializer: 1.4.1
- domelementtype: 2.3.0
- domhandler: 4.3.1
- dev: false
-
/dot-prop@5.3.0:
resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
engines: {node: '>=8'}
@@ -5817,6 +5776,7 @@ packages:
engines: {node: '>=10'}
dependencies:
is-obj: 2.0.0
+ dev: true
/dotenv@10.0.0:
resolution: {integrity: sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==}
@@ -5868,6 +5828,7 @@ packages:
hasBin: true
dependencies:
jake: 10.9.4
+ dev: true
/electron-to-chromium@1.5.244:
resolution: {integrity: sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==}
@@ -5926,15 +5887,6 @@ packages:
ansi-colors: 4.1.3
dev: true
- /entities@2.2.0:
- resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
- dev: false
-
- /entities@3.0.1:
- resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==}
- engines: {node: '>=0.12'}
- dev: false
-
/entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
@@ -5954,25 +5906,12 @@ packages:
hasBin: true
dev: true
- /epub-gen-memory@1.0.10:
- resolution: {integrity: sha512-QS4rEGCO/AUaaofhvAzXAxz0bJ/u23ZGdsdfjVBsWE6LHqM1Mi4R0rs+f6BbGDrHWBvASZkadvrfMc73TFpC6Q==}
- engines: {node: '>=10.0.0'}
- dependencies:
- abort-controller: 3.0.0
- css-select: 4.3.0
- diacritics: 1.3.0
- dom-serializer: 1.4.1
- domhandler: 4.3.1
- domutils: 2.8.0
- ejs: 3.1.10
- htmlparser2: 7.2.0
- jszip: 3.10.1
- mime: 2.6.0
- node-fetch: 2.7.0
- ow: 0.28.2
- slugify: 1.6.6
- transitivePeerDependencies:
- - encoding
+ /epub-gen3@0.2.0:
+ resolution: {integrity: sha512-l1LWgYNGWYf5O/xLSEpHY9y96jlkMQugiF4kmf6q9WsmHHLdAkhs1xfeTCTmSlHRK0v9+q73eILSj0KvvsyibQ==}
+ dev: false
+
+ /epub-gen3@0.2.2:
+ resolution: {integrity: sha512-lLgYwbEJWDfTlVIIrsO6lU97sIDEO4EcZNAHT54qkwayZNXjGbahY026Yg1Tin0vJrnKMArwWJ7MALyFyPoykg==}
dev: false
/err-code@2.0.3:
@@ -6286,11 +6225,6 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
- /event-target-shim@5.0.1:
- resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
- engines: {node: '>=6'}
- dev: false
-
/eventemitter3@4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
dev: true
@@ -6445,6 +6379,7 @@ packages:
resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
dependencies:
minimatch: 5.1.6
+ dev: true
/fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
@@ -7166,15 +7101,6 @@ packages:
resolution: {integrity: sha512-iARIBPgcQrwtEr+tALF+rapJ8qSc+Set2GJQl7xT1MQzWaVkFebdJhR3alVlSiUf5U7nAANKuj3aWpwerocD5w==}
dev: false
- /htmlparser2@7.2.0:
- resolution: {integrity: sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==}
- dependencies:
- domelementtype: 2.3.0
- domhandler: 4.3.1
- domutils: 2.8.0
- entities: 3.0.1
- dev: false
-
/http-cache-semantics@4.2.0:
resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
dev: true
@@ -7578,6 +7504,7 @@ packages:
/is-obj@2.0.0:
resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
engines: {node: '>=8'}
+ dev: true
/is-path-cwd@2.2.0:
resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==}
@@ -7759,6 +7686,7 @@ packages:
async: 3.2.6
filelist: 1.0.4
picocolors: 1.1.1
+ dev: true
/jiti@1.21.7:
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
@@ -8158,11 +8086,6 @@ packages:
/lodash.debounce@4.0.8:
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
- /lodash.isequal@4.5.0:
- resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
- deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
- dev: false
-
/lodash.ismatch@4.4.0:
resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==}
dev: true
@@ -8638,12 +8561,6 @@ packages:
dependencies:
mime-db: 1.52.0
- /mime@2.6.0:
- resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==}
- engines: {node: '>=4.0.0'}
- hasBin: true
- dev: false
-
/mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
@@ -8679,6 +8596,7 @@ packages:
engines: {node: '>=10'}
dependencies:
brace-expansion: 2.0.2
+ dev: true
/minimatch@6.2.0:
resolution: {integrity: sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==}
@@ -9246,6 +9164,7 @@ packages:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
dependencies:
boolbase: 1.0.0
+ dev: true
/nx@15.9.7:
resolution: {integrity: sha512-1qlEeDjX9OKZEryC8i4bA+twNg+lB5RKrozlNwWx/lLJHqWPUfvUTvxh+uxlPYL9KzVReQjUuxMLFMsHNqWUrA==}
@@ -9422,17 +9341,6 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
- /ow@0.28.2:
- resolution: {integrity: sha512-dD4UpyBh/9m4X2NVjA+73/ZPBRF+uF4zIMFvvQsabMiEK8x41L3rQ8EENOi35kyyoaJwNxEeJcP6Fj1H4U409Q==}
- engines: {node: '>=12'}
- dependencies:
- '@sindresorhus/is': 4.6.0
- callsites: 3.1.0
- dot-prop: 6.0.1
- lodash.isequal: 4.5.0
- vali-date: 1.0.0
- dev: false
-
/own-keys@1.0.1:
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
engines: {node: '>= 0.4'}
@@ -10793,11 +10701,6 @@ packages:
engines: {node: '>=8'}
dev: true
- /slugify@1.6.6:
- resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==}
- engines: {node: '>=8.0.0'}
- dev: false
-
/smart-buffer@4.2.0:
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
@@ -11914,11 +11817,6 @@ packages:
resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}
dev: true
- /vali-date@1.0.0:
- resolution: {integrity: sha512-sgECfZthyaCKW10N0fm27cg8HYTFK5qMWgypqkXMQ4Wbl/zZKx7xZICgcoxIIE+WFAP/MBL2EFwC/YvLxw3Zeg==}
- engines: {node: '>=0.10.0'}
- dev: false
-
/validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
dependencies:
From bb3f4aa88910998a21b2420540de5370391f2aac Mon Sep 17 00:00:00 2001
From: Novout
Date: Fri, 12 Jun 2026 18:46:59 -0300
Subject: [PATCH 09/30] chore(epub): toc title
---
packages/plugin-exporter-epub/src/set.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/plugin-exporter-epub/src/set.ts b/packages/plugin-exporter-epub/src/set.ts
index 6651c0ec..35b34309 100644
--- a/packages/plugin-exporter-epub/src/set.ts
+++ b/packages/plugin-exporter-epub/src/set.ts
@@ -32,7 +32,7 @@ export const PluginEpubSet = (
author: stores.PROJECT.creator,
description: stores.PROJECT.subject,
publisher: stores.PROJECT.producer,
- tocTitle: hooks.i18n.t('editor.bar.epub.table') ?? undefined,
+ tocTitle: stores.PROJECT.nameRaw ?? undefined,
lang: 'en',
fonts: [],
version: 3,
From 21f27acd0d2ce005ae9bf4bab2edf59df57f8838 Mon Sep 17 00:00:00 2001
From: Novout
Date: Fri, 12 Jun 2026 20:00:22 -0300
Subject: [PATCH 10/30] chore: update epub-gen-rs
---
packages/app/package.json | 2 +-
packages/plugin-exporter-epub/package.json | 2 +-
packages/plugin-exporter-epub/src/set.ts | 6 +-----
pnpm-lock.yaml | 12 ++++++------
4 files changed, 9 insertions(+), 13 deletions(-)
diff --git a/packages/app/package.json b/packages/app/package.json
index 8dffb43b..d852fd73 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -72,7 +72,7 @@
"docx": "7.5.0",
"drauu": "0.4.3",
"dropbox": "10.34.0",
- "epub-gen3": "0.2.2",
+ "epub-gen3": "0.3.0",
"file-saver": "2.0.5",
"floating-vue": "2.0.0-beta.20",
"hast": "1.0.0",
diff --git a/packages/plugin-exporter-epub/package.json b/packages/plugin-exporter-epub/package.json
index 6191cc0f..27cd98d9 100644
--- a/packages/plugin-exporter-epub/package.json
+++ b/packages/plugin-exporter-epub/package.json
@@ -20,6 +20,6 @@
"better-write-contenteditable-ast": "^1.3.27",
"better-write-plugin-core": "^1.3.27",
"better-write-types": "^1.3.27",
- "epub-gen3": "0.2.2"
+ "epub-gen3": "0.3.0"
}
}
diff --git a/packages/plugin-exporter-epub/src/set.ts b/packages/plugin-exporter-epub/src/set.ts
index 35b34309..39d6d108 100644
--- a/packages/plugin-exporter-epub/src/set.ts
+++ b/packages/plugin-exporter-epub/src/set.ts
@@ -36,12 +36,8 @@ export const PluginEpubSet = (
lang: 'en',
fonts: [],
version: 3,
- // TODO: support CSS styles
- // css: getStyles(stores, hooks),
},
- [
- contents(),
- ]
+ contents()
)
download(epub, stores.PROJECT.nameRaw)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 57107c01..d5ee648d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -195,8 +195,8 @@ importers:
specifier: 10.34.0
version: 10.34.0(@types/node-fetch@2.6.13)
epub-gen3:
- specifier: 0.2.2
- version: 0.2.2
+ specifier: 0.3.0
+ version: 0.3.0
file-saver:
specifier: 2.0.5
version: 2.0.5
@@ -522,8 +522,8 @@ importers:
specifier: ^1.3.27
version: link:../types
epub-gen3:
- specifier: 0.2.2
- version: 0.2.2
+ specifier: 0.3.0
+ version: 0.3.0
packages/plugin-exporter-html:
dependencies:
@@ -5910,8 +5910,8 @@ packages:
resolution: {integrity: sha512-l1LWgYNGWYf5O/xLSEpHY9y96jlkMQugiF4kmf6q9WsmHHLdAkhs1xfeTCTmSlHRK0v9+q73eILSj0KvvsyibQ==}
dev: false
- /epub-gen3@0.2.2:
- resolution: {integrity: sha512-lLgYwbEJWDfTlVIIrsO6lU97sIDEO4EcZNAHT54qkwayZNXjGbahY026Yg1Tin0vJrnKMArwWJ7MALyFyPoykg==}
+ /epub-gen3@0.3.0:
+ resolution: {integrity: sha512-Vi14IzFEUF2OdwPybbJdiOXUzXJPv76aADvOrQ4FqfmSYanpqw08bw3WM0o4q55SPagGszo2y9GwOsRG+8xVQg==}
dev: false
/err-code@2.0.3:
From a06d38b224df1e6d702cad4ba3414282eac13b74 Mon Sep 17 00:00:00 2001
From: Novout
Date: Sat, 13 Jun 2026 18:14:51 -0300
Subject: [PATCH 11/30] feat(epub): ignore fixed and image case
---
packages/plugin-exporter-epub/src/set.ts | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/packages/plugin-exporter-epub/src/set.ts b/packages/plugin-exporter-epub/src/set.ts
index 39d6d108..a490ee00 100644
--- a/packages/plugin-exporter-epub/src/set.ts
+++ b/packages/plugin-exporter-epub/src/set.ts
@@ -1,5 +1,6 @@
-import { ContextState, PluginTypes } from 'better-write-types'
+import { ContextState, Entity, PluginTypes } from 'better-write-types'
import { On } from 'better-write-plugin-core'
+// @ts-ignore
import { Epub, ready } from 'epub-gen3/browser'
export const PluginEpubSet = (
@@ -7,6 +8,14 @@ export const PluginEpubSet = (
stores: PluginTypes.PluginStores,
hooks: PluginTypes.PluginHooks,
) => {
+ const isValidType = (val: Entity) => {
+ return (
+ !hooks.entity.utils().isFixedRaw(val.raw) &&
+ val.type !== 'image' &&
+ val.type !== 'drau'
+ )
+ }
+
const contents = (): string[][] => {
const raw: string[][] = []
@@ -14,7 +23,9 @@ export const PluginEpubSet = (
let data = []
for (const entity of list) {
- data.push(hooks.substitution.purge(entity.raw))
+ if(isValidType(entity)) {
+ data.push(hooks.substitution.purge(entity.raw))
+ }
}
raw.push(data)
From 3c5b472e9bafc5bf2f374c95eaa77c2e92dcc73f Mon Sep 17 00:00:00 2001
From: Novout
Date: Sat, 13 Jun 2026 19:07:49 -0300
Subject: [PATCH 12/30] feat: bionic reading docx and pdf
---
.../generator/pdf/generate/PDFGenerate.vue | 1 +
.../header/items/EditorBaseHeaderCreate.vue | 31 +++++++++-
packages/app/src/store/absolute.ts | 5 ++
packages/languages/src/en-US.ts | 3 +
packages/languages/src/pt-BR.ts | 3 +
packages/plugin-core/src/on.ts | 10 +--
packages/plugin-exporter-docx/src/set.ts | 62 +++++++++++++------
packages/plugin-exporter-epub/src/set.ts | 30 ++++++---
packages/plugin-exporter-pdf/src/generate.ts | 19 ++++--
packages/types/src/absolute.ts | 7 +++
packages/types/src/docx.ts | 5 ++
packages/types/src/epub.ts | 3 +
packages/types/src/index.ts | 1 +
packages/types/src/pdf.ts | 1 +
14 files changed, 142 insertions(+), 39 deletions(-)
create mode 100644 packages/types/src/epub.ts
diff --git a/packages/app/src/components/page/editor/generator/pdf/generate/PDFGenerate.vue b/packages/app/src/components/page/editor/generator/pdf/generate/PDFGenerate.vue
index 5df2fe29..6a1db818 100644
--- a/packages/app/src/components/page/editor/generator/pdf/generate/PDFGenerate.vue
+++ b/packages/app/src/components/page/editor/generator/pdf/generate/PDFGenerate.vue
@@ -106,6 +106,7 @@
plugin.emit('plugin-pdf-generate', {
chapters: chapters.value,
color: color.value,
+ bionicReading: ABSOLUTE.pdf.bionicReading,
})
}, 100)
}
diff --git a/packages/app/src/components/page/editor/header/items/EditorBaseHeaderCreate.vue b/packages/app/src/components/page/editor/header/items/EditorBaseHeaderCreate.vue
index b06a494c..7ee79c65 100644
--- a/packages/app/src/components/page/editor/header/items/EditorBaseHeaderCreate.vue
+++ b/packages/app/src/components/page/editor/header/items/EditorBaseHeaderCreate.vue
@@ -20,6 +20,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
await storage.normalize()
- plugin.emit('plugin-epub-generate')
+ plugin.emit('plugin-epub-generate', { bionicReading: ABSOLUTE.epub.bionicReading })
}
const onPDFGenerate = async () => {
@@ -134,6 +159,8 @@
plugin.emit('plugin-pdf-generate', {
chapters: project.utils().getChaptersSelection(),
+ color: 'RGB',
+ bionicReading: ABSOLUTE.pdf.bionicReading,
})
}
diff --git a/packages/app/src/store/absolute.ts b/packages/app/src/store/absolute.ts
index 1603cfd9..336a68d3 100644
--- a/packages/app/src/store/absolute.ts
+++ b/packages/app/src/store/absolute.ts
@@ -30,9 +30,14 @@ export const useAbsoluteStore = defineStore('absolute', {
pdf: {
configuration: false,
generate: false,
+ bionicReading: false,
+ },
+ epub: {
+ bionicReading: false,
},
docx: {
configuration: false,
+ bionicReading: false,
},
// deprecated
auth: {
diff --git a/packages/languages/src/en-US.ts b/packages/languages/src/en-US.ts
index d16898f9..205d135d 100644
--- a/packages/languages/src/en-US.ts
+++ b/packages/languages/src/en-US.ts
@@ -239,10 +239,12 @@ export default {
preview: 'Preview (.PDF)',
generate: 'Generate (.PDF)',
configuration: 'Configure (.PDF)',
+ bionicReading: 'Enable Bionic Reading',
},
epub: {
generate: 'Generate (.EPUB)',
table: 'Table of Content',
+ bionicReading: 'Enable Bionic Reading',
},
txt: {
generate: 'Generate (.TXT)',
@@ -253,6 +255,7 @@ export default {
docx: {
configuration: 'Configure (.DOCX)',
generate: 'Generate (.DOCX)',
+ bionicReading: 'Enable Bionic Reading',
},
project: {
new: 'New Project',
diff --git a/packages/languages/src/pt-BR.ts b/packages/languages/src/pt-BR.ts
index 26200ce6..9545de5d 100644
--- a/packages/languages/src/pt-BR.ts
+++ b/packages/languages/src/pt-BR.ts
@@ -238,10 +238,12 @@ export default {
preview: 'Simular (.PDF)',
generate: 'Gerar (.PDF)',
configuration: 'Configurar (.PDF)',
+ bionicReading: 'Bionic Reading',
},
epub: {
generate: 'Gerar (.EPUB)',
table: 'Tabela de Conteúdos',
+ bionicReading: 'Bionic Reading',
},
txt: {
generate: 'Gerar (.TXT)',
@@ -252,6 +254,7 @@ export default {
docx: {
configuration: 'Configurar (.DOCX)',
generate: 'Gerar (.DOCX)',
+ bionicReading: 'Bionic Reading',
},
project: {
new: 'Novo Projeto',
diff --git a/packages/plugin-core/src/on.ts b/packages/plugin-core/src/on.ts
index f6eb11f3..2c8536bb 100644
--- a/packages/plugin-core/src/on.ts
+++ b/packages/plugin-core/src/on.ts
@@ -1,5 +1,7 @@
import type {
PDFDocOptions,
+ EPUBDocOptions,
+ DOCXDocOptions,
PluginTypes,
ImporterParams,
ProjectStateSchemaCreate,
@@ -315,10 +317,10 @@ export const externals = () => {
emitter: PluginTypes.PluginEmitter,
content: PluginTypes.PluginContentOn,
) => {
- emitter.on('plugin-docx-generate', () => {
+ emitter.on('plugin-docx-generate', (options: DOCXDocOptions) => {
const created = content[0]
- created && created()
+ created && created(options)
})
}
@@ -337,10 +339,10 @@ export const externals = () => {
emitter: PluginTypes.PluginEmitter,
content: PluginTypes.PluginContentOn,
) => {
- emitter.on('plugin-epub-generate', () => {
+ emitter.on('plugin-epub-generate', (options: EPUBDocOptions) => {
const created = content[0]
- created && created()
+ created && created(options)
})
}
diff --git a/packages/plugin-exporter-docx/src/set.ts b/packages/plugin-exporter-docx/src/set.ts
index 0c729318..9c1b2d90 100644
--- a/packages/plugin-exporter-docx/src/set.ts
+++ b/packages/plugin-exporter-docx/src/set.ts
@@ -4,6 +4,7 @@ import {
Entity,
PluginTypes,
ProjectStateTemplatesGenerator,
+ DOCXDocOptions,
} from 'better-write-types'
import { On } from 'better-write-plugin-core'
import { getRows, parse } from 'better-write-contenteditable-ast'
@@ -11,6 +12,17 @@ import { ContextState } from 'better-write-types'
type DocxPurge = Array
+const bionicRuns = (
+ word: string,
+ base: Record,
+): docx.TextRun[] => {
+ const len = Math.ceil(word.length * 0.45)
+ return [
+ new docx.TextRun({ ...base, text: word.slice(0, len), bold: true }),
+ new docx.TextRun({ ...base, text: word.slice(len) }),
+ ]
+}
+
export const PluginDocxSet = (
emitter: PluginTypes.PluginEmitter,
stores: PluginTypes.PluginStores,
@@ -18,21 +30,31 @@ export const PluginDocxSet = (
) => {
const { isLoading } = hooks.vueuse.integration.progress
- const purge = (raw: string, custom: Record): DocxPurge => {
+ const purge = (raw: string, custom: Record, bionicReading = false): DocxPurge => {
const arr: DocxPurge = []
const ast = parse(hooks.substitution.purge(raw))
ast.forEach((node) => {
- arr.push(
- new docx.TextRun({
- text: node.text,
- italics: node.italic,
- bold: node.bold,
- underline: custom.isUnderline(node.underline),
- ...custom.textRun,
- }),
- )
+ const base = {
+ italics: node.italic,
+ bold: node.bold,
+ underline: custom.isUnderline(node.underline),
+ ...custom.textRun,
+ }
+
+ if (bionicReading) {
+ const words = node.text.split(/(\s+)/)
+ words.forEach((chunk) => {
+ if (/^\s+$/.test(chunk) || chunk === '') {
+ arr.push(new docx.TextRun({ ...base, text: chunk }))
+ } else {
+ bionicRuns(chunk, base).forEach((r) => arr.push(r))
+ }
+ })
+ } else {
+ arr.push(new docx.TextRun({ ...base, text: node.text }))
+ }
})
return arr
@@ -67,7 +89,7 @@ export const PluginDocxSet = (
return { bw }
}
- const create = () => {
+ const create = (bionicReading = false) => {
const properties = (): docx.ISectionPropertiesOptions => {
return {
page: {
@@ -252,7 +274,7 @@ export const PluginDocxSet = (
return getRows(entity.raw).map((row) => {
return new docx.Paragraph({
- children: purge(row, custom),
+ children: purge(row, custom, bionicReading),
...custom.paragraph,
...custom.isList,
})
@@ -342,19 +364,21 @@ export const PluginDocxSet = (
return { properties, footer, styles, entities, content, flow }
}
- const doc = (): docx.File => {
+ const doc = (options: DOCXDocOptions): docx.File => {
+ const c = create(options.bionicReading)
+
return new docx.Document({
creator: stores.PROJECT.creator,
title: stores.PROJECT.nameRaw,
description: stores.PROJECT.subject,
subject: stores.PROJECT.subject,
keywords: stores.PROJECT.keywords,
- styles: create().styles(),
+ styles: c.styles(),
sections: [
{
- properties: create().properties(),
- children: create().flow(),
- footers: create().footer(),
+ properties: c.properties(),
+ children: c.flow(),
+ footers: c.footer(),
},
],
})
@@ -382,8 +406,8 @@ export const PluginDocxSet = (
}
On.externals().PluginDocxGenerate(emitter, [
- () => {
- download(doc())
+ (options: DOCXDocOptions) => {
+ download(doc(options ?? { bionicReading: false }))
},
() => {},
])
diff --git a/packages/plugin-exporter-epub/src/set.ts b/packages/plugin-exporter-epub/src/set.ts
index a490ee00..f9b204fc 100644
--- a/packages/plugin-exporter-epub/src/set.ts
+++ b/packages/plugin-exporter-epub/src/set.ts
@@ -1,8 +1,21 @@
-import { ContextState, Entity, PluginTypes } from 'better-write-types'
+import { ContextState, Entity, EPUBDocOptions, PluginTypes } from 'better-write-types'
import { On } from 'better-write-plugin-core'
// @ts-ignore
import { Epub, ready } from 'epub-gen3/browser'
+const bionicWord = (word: string): string => {
+ const len = Math.ceil(word.length * 0.45)
+ return `${word.slice(0, len)} ${word.slice(len)}`
+}
+
+const bionicHtml = (text: string): string => {
+ return text.replace(/(\S+)/g, (word) => {
+ // skip html tags
+ if (word.startsWith('<')) return word
+ return bionicWord(word)
+ })
+}
+
export const PluginEpubSet = (
emitter: PluginTypes.PluginEmitter,
stores: PluginTypes.PluginStores,
@@ -16,15 +29,16 @@ export const PluginEpubSet = (
)
}
- const contents = (): string[][] => {
+ const contents = (options: EPUBDocOptions): string[][] => {
const raw: string[][] = []
stores.PROJECT.chapters.forEach(({ entities: list }: ContextState) => {
let data = []
for (const entity of list) {
- if(isValidType(entity)) {
- data.push(hooks.substitution.purge(entity.raw))
+ if (isValidType(entity)) {
+ const purged = hooks.substitution.purge(entity.raw)
+ data.push(options.bionicReading ? bionicHtml(purged) : purged)
}
}
@@ -34,7 +48,7 @@ export const PluginEpubSet = (
return raw
}
- const generate = async () => {
+ const generate = async (options: EPUBDocOptions) => {
await ready
const epub = new Epub(
@@ -48,7 +62,7 @@ export const PluginEpubSet = (
fonts: [],
version: 3,
},
- contents()
+ contents(options)
)
download(epub, stores.PROJECT.nameRaw)
@@ -65,8 +79,8 @@ export const PluginEpubSet = (
}
On.externals().PluginEpubGenerate(emitter, [
- () => {
- generate()
+ (options: EPUBDocOptions) => {
+ generate(options ?? { bionicReading: false })
},
() => {},
])
diff --git a/packages/plugin-exporter-pdf/src/generate.ts b/packages/plugin-exporter-pdf/src/generate.ts
index bc0f30d1..9618a3d8 100644
--- a/packages/plugin-exporter-pdf/src/generate.ts
+++ b/packages/plugin-exporter-pdf/src/generate.ts
@@ -358,12 +358,19 @@ export const PluginPDFSet = (
ast.forEach(({ text, italic, bold, underline }) => {
const und = underline ? { decoration: 'underline' } : {}
- arr.push({
- text,
- italics: italic,
- bold,
- ...und,
- })
+ if (options.bionicReading) {
+ text.split(/(\s+)/).forEach((chunk) => {
+ if (/^\s+$/.test(chunk) || chunk === '') {
+ arr.push({ text: chunk, italics: italic, bold, ...und })
+ } else {
+ const len = Math.ceil(chunk.length * 0.45)
+ arr.push({ text: chunk.slice(0, len), italics: italic, bold: true, ...und })
+ arr.push({ text: chunk.slice(len), italics: italic, bold, ...und })
+ }
+ })
+ } else {
+ arr.push({ text, italics: italic, bold, ...und })
+ }
})
return arr
diff --git a/packages/types/src/absolute.ts b/packages/types/src/absolute.ts
index 0c388188..a9c9414e 100644
--- a/packages/types/src/absolute.ts
+++ b/packages/types/src/absolute.ts
@@ -25,10 +25,16 @@ export interface AbsoluteStateShortcuts {
export interface AbsoluteStatePDF {
configuration: boolean
generate: boolean
+ bionicReading: boolean
}
export interface AbsoluteStateDOCX {
configuration: boolean
+ bionicReading: boolean
+}
+
+export interface AbsoluteStateEPUB {
+ bionicReading: boolean
}
export interface AbsoluteStateAuth {
@@ -67,6 +73,7 @@ export interface AbsoluteState {
modal: AbsoluteStateModal
shortcuts: AbsoluteStateShortcuts
pdf: AbsoluteStatePDF
+ epub: AbsoluteStateEPUB
docx: AbsoluteStateDOCX
auth: AbsoluteStateAuth
entity: AbsoluteStateEntity
diff --git a/packages/types/src/docx.ts b/packages/types/src/docx.ts
index 10612905..a98146b9 100644
--- a/packages/types/src/docx.ts
+++ b/packages/types/src/docx.ts
@@ -59,3 +59,8 @@ export interface DOCXState {
flow: DOCXStateFlow
styles: DOCXStateStyles
}
+
+
+export interface DOCXDocOptions {
+ bionicReading: boolean
+}
diff --git a/packages/types/src/epub.ts b/packages/types/src/epub.ts
new file mode 100644
index 00000000..3c79a0e2
--- /dev/null
+++ b/packages/types/src/epub.ts
@@ -0,0 +1,3 @@
+export interface EPUBDocOptions {
+ bionicReading: boolean
+}
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 94f2f864..d8fb85d1 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -13,6 +13,7 @@ export * from './global'
export * from './google'
export * from './image'
export * from './pdf'
+export * from './epub'
export * from './docx'
export * from './project'
export * from './raw'
diff --git a/packages/types/src/pdf.ts b/packages/types/src/pdf.ts
index e8cb4833..ea43246d 100644
--- a/packages/types/src/pdf.ts
+++ b/packages/types/src/pdf.ts
@@ -131,6 +131,7 @@ export interface PDFDocOptions {
select: boolean
}[]
color: ColorSchema
+ bionicReading: boolean
}
export interface PDFState {
From d4a88e04a39652039ed6000c03879c7e3b0e2c7e Mon Sep 17 00:00:00 2001
From: Novout
Date: Sat, 13 Jun 2026 19:27:42 -0300
Subject: [PATCH 13/30] feat!: encrypt option for .bw
---
.../header/items/EditorBaseHeaderProject.vue | 11 ++
packages/app/src/store/absolute.ts | 3 +
packages/app/src/use/project.ts | 23 +++-
packages/extension/src/index.ts | 116 +++++++++++++++++-
packages/languages/src/en-US.ts | 8 ++
packages/languages/src/pt-BR.ts | 8 ++
packages/plugin-editor-window/src/drop.ts | 15 ++-
packages/plugin-importer/src/bw.ts | 15 ++-
packages/types/src/absolute.ts | 5 +
9 files changed, 192 insertions(+), 12 deletions(-)
diff --git a/packages/app/src/components/page/editor/header/items/EditorBaseHeaderProject.vue b/packages/app/src/components/page/editor/header/items/EditorBaseHeaderProject.vue
index 96559990..7d5c9aaf 100644
--- a/packages/app/src/components/page/editor/header/items/EditorBaseHeaderProject.vue
+++ b/packages/app/src/components/page/editor/header/items/EditorBaseHeaderProject.vue
@@ -56,6 +56,17 @@
@action="ABSOLUTE.project.preferences = true"
/>
+
+
+
+
+
+
+
{
ABSOLUTE.project.tutorial = !localStorage.getItem('tutorial')
}
+ const getBWBlob = async (target: string): Promise => {
+ if (!ABSOLUTE.bw.encryptOnExport) return writeBW(target)
+
+ const password = window.prompt(t('toast.project.bw.passwordSet'))
+
+ if (!password) throw new Error('cancelled')
+
+ const confirm = window.prompt(t('toast.project.bw.passwordConfirm'))
+
+ if (confirm !== password) {
+ toast.error(t('toast.project.bw.passwordMismatch'))
+ throw new Error('mismatch')
+ }
+
+ return encryptBW(target, password)
+ }
+
const onExportProject = () => {
storage
.normalize()
.then(async () => {
const target = JSON.stringify(storage.getProjectObject())
- const zip = await writeBW(target)
+ const zip = await getBWBlob(target)
await saveAs(zip, utils().exportName('bw'))
@@ -233,7 +250,7 @@ export const useProject = () => {
if (!res.isSupported.value) return
const target = JSON.stringify(storage.getProjectObject())
- const zip = await writeBW(target)
+ const zip = await getBWBlob(target)
res.data.value = zip
res.fileName.value = utils().exportName('bw')
diff --git a/packages/extension/src/index.ts b/packages/extension/src/index.ts
index c1f690ea..90d60177 100644
--- a/packages/extension/src/index.ts
+++ b/packages/extension/src/index.ts
@@ -1,6 +1,8 @@
import {
BlobReader,
BlobWriter,
+ Entry,
+ FileEntry,
TextReader,
TextWriter,
ZipReader,
@@ -32,13 +34,117 @@ export const writeBW = async (data: string) => {
return target
}
-export const readBW = async (blob: Blob): Promise => {
- const zipFileReader = new BlobReader(blob)
+// --- WebCrypto helpers ---
+
+const PBKDF2_ITERATIONS = 200_000
+const SALT_LEN = 16
+const IV_LEN = 12
+
+const deriveKey = async (password: string, salt: Uint8Array): Promise => {
+ const keyMaterial = await crypto.subtle.importKey(
+ 'raw',
+ new TextEncoder().encode(password),
+ 'PBKDF2',
+ false,
+ ['deriveKey'],
+ )
+
+ return crypto.subtle.deriveKey(
+ { name: 'PBKDF2', salt: salt.buffer as ArrayBuffer, iterations: PBKDF2_ITERATIONS, hash: 'SHA-256' },
+ keyMaterial,
+ { name: 'AES-GCM', length: 256 },
+ false,
+ ['encrypt', 'decrypt'],
+ )
+}
+
+export const encryptBW = async (data: string, password: string): Promise => {
+ const salt = crypto.getRandomValues(new Uint8Array(SALT_LEN))
+ const iv = crypto.getRandomValues(new Uint8Array(IV_LEN))
+ const key = await deriveKey(password, salt)
+
+ const ciphertext = new Uint8Array(
+ await crypto.subtle.encrypt(
+ { name: 'AES-GCM', iv },
+ key,
+ new TextEncoder().encode(data),
+ ),
+ )
+
+ // layout: [16 salt][12 iv][...ciphertext]
+ const payload = new Uint8Array(SALT_LEN + IV_LEN + ciphertext.byteLength)
+ payload.set(salt, 0)
+ payload.set(iv, SALT_LEN)
+ payload.set(ciphertext, SALT_LEN + IV_LEN)
+
+ const blobWriter = new BlobWriter()
+ const zipWriter = new ZipWriter(blobWriter)
+
+ await zipWriter.add('meta.json', new TextReader(JSON.stringify({ encrypted: true, version: 1 })))
+ await zipWriter.add('encrypted.bin', new BlobReader(new Blob([payload])))
+ await zipWriter.add('mimetype', writeMimetype())
+ await zipWriter.close()
+
+ return blobWriter.getData()
+}
+
+const isEncryptedZip = async (zipReader: ZipReader): Promise => {
+ const entries = await zipReader.getEntries()
+
+ return entries.some((e: Entry) => e.filename === 'meta.json')
+}
+
+export const decryptBW = async (blob: Blob, password: string): Promise => {
+ const zipReader = new ZipReader(new BlobReader(blob))
+ const entries = await zipReader.getEntries()
- const writer = new TextWriter()
+ const encEntry = entries.find((e) => e.filename === 'encrypted.bin')
- const zipReader = new ZipReader(zipFileReader)
- const firstEntry = (await zipReader.getEntries()).shift()
+ if (!encEntry) throw new Error('encrypted.bin not found')
+
+ const payloadBlob = await (encEntry as FileEntry).getData(new BlobWriter())
+ const payload = new Uint8Array(await payloadBlob.arrayBuffer())
+
+ const salt = payload.slice(0, SALT_LEN)
+ const iv = payload.slice(SALT_LEN, SALT_LEN + IV_LEN)
+ const ciphertext = payload.slice(SALT_LEN + IV_LEN)
+
+ const key = await deriveKey(password, salt)
+
+ let plaintext: ArrayBuffer
+
+ try {
+ plaintext = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext)
+ } catch {
+ throw new Error('wrong-password')
+ }
+
+ await zipReader.close()
+
+ return destr(new TextDecoder().decode(plaintext))
+}
+
+export const readBW = async (blob: Blob, getPassword?: () => Promise): Promise => {
+ // peek at entries to decide the path
+ const peekReader = new ZipReader(new BlobReader(blob))
+ const encrypted = await isEncryptedZip(peekReader)
+ await peekReader.close()
+
+ if (encrypted) {
+ if (!getPassword) throw new Error('File is encrypted but no password provider was given')
+
+ const password = await getPassword()
+
+ if (password === null) throw new Error('cancelled')
+
+ return decryptBW(blob, password)
+ }
+
+ // original unencrypted path
+ const zipFileReader = new BlobReader(blob)
+ const writer = new TextWriter()
+ const zipReader = new ZipReader(zipFileReader)
+ const firstEntry = (await zipReader.getEntries()).shift()
// @ts-expect-error
const data = await firstEntry.getData(writer)
diff --git a/packages/languages/src/en-US.ts b/packages/languages/src/en-US.ts
index 205d135d..4bc61829 100644
--- a/packages/languages/src/en-US.ts
+++ b/packages/languages/src/en-US.ts
@@ -273,6 +273,7 @@ export default {
import: 'Import',
export: 'Export',
exportAs: 'Export as...',
+ encryptBW: 'Encrypt .bw',
},
chapter: {
drafts: 'Drafts',
@@ -1079,6 +1080,13 @@ export default {
export: 'Project exported to extension (.bw) successfully!',
delete: 'Project successfully deleted!',
unsupportedExtension: 'This extension is not supported by Better Write!',
+ bw: {
+ passwordSet: 'Set a password to encrypt the file:',
+ passwordConfirm: 'Confirm the password:',
+ passwordMismatch: 'Passwords do not match. Export cancelled.',
+ passwordGet: 'This file is encrypted. Enter the password:',
+ passwordWrong: 'Wrong password. Could not open the file.',
+ },
docx: {
generate: 'Successfully Downloaded DOCX!',
},
diff --git a/packages/languages/src/pt-BR.ts b/packages/languages/src/pt-BR.ts
index 9545de5d..bf6ffc97 100644
--- a/packages/languages/src/pt-BR.ts
+++ b/packages/languages/src/pt-BR.ts
@@ -270,6 +270,7 @@ export default {
import: 'Importar',
export: 'Exportar',
exportAs: 'Exportar como...',
+ encryptBW: 'Criptografar .bw',
},
chapter: {
drafts: 'Rascunhos',
@@ -1087,6 +1088,13 @@ export default {
export: 'Projeto exportado para a extensão (.bw) com sucesso!',
unsupportedExtension: 'Esta extensão não é suportada por Better Write!',
delete: 'Projeto deletado com sucesso!',
+ bw: {
+ passwordSet: 'Defina uma senha para criptografar o arquivo:',
+ passwordConfirm: 'Confirme a senha:',
+ passwordMismatch: 'As senhas não coincidem. Exportação cancelada.',
+ passwordGet: 'Este arquivo está criptografado. Digite a senha:',
+ passwordWrong: 'Senha incorreta. Não foi possível abrir o arquivo.',
+ },
docx: {
generate: 'DOCX Baixado com Sucesso!',
},
diff --git a/packages/plugin-editor-window/src/drop.ts b/packages/plugin-editor-window/src/drop.ts
index f9dd4c16..c05bbd07 100644
--- a/packages/plugin-editor-window/src/drop.ts
+++ b/packages/plugin-editor-window/src/drop.ts
@@ -22,9 +22,20 @@ export const PluginDropSet = (
if (
confirm(hooks.i18n.t('toast.project.import', { name: file.name }))
) {
- const data = await readBW(file)
+ const getPassword = async () =>
+ window.prompt(hooks.i18n.t('toast.project.bw.passwordGet'))
- hooks.project.onLoadProject(data, false)
+ try {
+ const data = await readBW(file, getPassword)
+
+ hooks.project.onLoadProject(data, false)
+ } catch (e: any) {
+ if (e?.message === 'wrong-password') {
+ hooks.toast.error(hooks.i18n.t('toast.project.bw.passwordWrong'))
+ } else if (e?.message !== 'cancelled') {
+ hooks.toast.error(hooks.i18n.t('toast.generics.error'))
+ }
+ }
}
return
diff --git a/packages/plugin-importer/src/bw.ts b/packages/plugin-importer/src/bw.ts
index 870cfb6c..a26f9472 100644
--- a/packages/plugin-importer/src/bw.ts
+++ b/packages/plugin-importer/src/bw.ts
@@ -9,9 +9,20 @@ export const BWSet = (
) => {
On.externals().PluginImporterBW(emitter, [
async ({ data }: ImporterParams) => {
- const content = await readBW(data as any)
+ const getPassword = async () =>
+ window.prompt(hooks.i18n.t('toast.project.bw.passwordGet'))
- hooks.project.onLoadProject(content)
+ try {
+ const content = await readBW(data as any, getPassword)
+
+ hooks.project.onLoadProject(content)
+ } catch (e: any) {
+ if (e?.message === 'wrong-password') {
+ hooks.toast.error(hooks.i18n.t('toast.project.bw.passwordWrong'))
+ } else if (e?.message !== 'cancelled') {
+ hooks.toast.error(hooks.i18n.t('toast.generics.error'))
+ }
+ }
},
() => {},
])
diff --git a/packages/types/src/absolute.ts b/packages/types/src/absolute.ts
index a9c9414e..097a78ae 100644
--- a/packages/types/src/absolute.ts
+++ b/packages/types/src/absolute.ts
@@ -64,6 +64,10 @@ export interface AbsoluteStateSchemas {
template: boolean
}
+export interface AbsoluteStateBW {
+ encryptOnExport: boolean
+}
+
export interface AbsoluteState {
cmd: boolean
commands: boolean
@@ -82,4 +86,5 @@ export interface AbsoluteState {
generator: AbsoluteStateGenerator
spinner: boolean
schemas: AbsoluteStateSchemas
+ bw: AbsoluteStateBW
}
From d117e8b122540993a027d7a6279583a61ea67b32 Mon Sep 17 00:00:00 2001
From: Novout
Date: Sat, 13 Jun 2026 19:45:30 -0300
Subject: [PATCH 14/30] refactor: alert erros in def components
---
.../page/editor/header/EditorHeaderButton.vue | 8 ++++----
.../main/render/history/EditorBaseRenderHistory.vue | 10 +++++-----
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/packages/app/src/components/page/editor/header/EditorHeaderButton.vue b/packages/app/src/components/page/editor/header/EditorHeaderButton.vue
index 69c7a102..70c775fa 100644
--- a/packages/app/src/components/page/editor/header/EditorHeaderButton.vue
+++ b/packages/app/src/components/page/editor/header/EditorHeaderButton.vue
@@ -1,6 +1,6 @@
-
+
diff --git a/packages/app/src/components/page/editor/main/render/history/EditorBaseRenderHistory.vue b/packages/app/src/components/page/editor/main/render/history/EditorBaseRenderHistory.vue
index fbcfcdd4..15c24c85 100644
--- a/packages/app/src/components/page/editor/main/render/history/EditorBaseRenderHistory.vue
+++ b/packages/app/src/components/page/editor/main/render/history/EditorBaseRenderHistory.vue
@@ -14,13 +14,13 @@
class="flex-1 font-bold flex justify-around items-center font-raleway min-w-12 h-10 md:(h-9)"
@click.prevent.stop="history.onLoad(item)"
>
-
+
- {{ item.name || item.id }}
-
-
+ {{ item.name || item.id }}
+
+
-
+
From 43d382694909a7bac7daae6de8998ca119c672e7 Mon Sep 17 00:00:00 2001
From: Novout
Date: Sat, 13 Jun 2026 19:52:54 -0300
Subject: [PATCH 15/30] chore: lint
---
packages/app/src/App.vue | 2 +-
.../page/editor/header/EditorHeaderButton.vue | 78 ++++++++++---------
.../header/items/EditorBaseHeaderCreate.vue | 37 +++++++--
.../header/items/EditorBaseHeaderProject.vue | 16 +++-
packages/app/src/use/block/text.ts | 4 +-
packages/extension/src/index.ts | 67 +++++++++++-----
packages/plugin-editor-window/src/drop.ts | 4 +-
packages/plugin-exporter-docx/src/set.ts | 6 +-
packages/plugin-exporter-epub/src/set.ts | 18 +++--
packages/plugin-exporter-pdf/src/generate.ts | 14 +++-
packages/types/src/docx.ts | 1 -
11 files changed, 167 insertions(+), 80 deletions(-)
diff --git a/packages/app/src/App.vue b/packages/app/src/App.vue
index 880429a2..630bcbf3 100644
--- a/packages/app/src/App.vue
+++ b/packages/app/src/App.vue
@@ -34,6 +34,6 @@
EntityHistoryPlugin(),
CharactersPlugin(),
ProgressBarPlugin(),
- EditorWindowPlugin()
+ EditorWindowPlugin(),
]).init()
diff --git a/packages/app/src/components/page/editor/header/EditorHeaderButton.vue b/packages/app/src/components/page/editor/header/EditorHeaderButton.vue
index 70c775fa..90fe4a53 100644
--- a/packages/app/src/components/page/editor/header/EditorHeaderButton.vue
+++ b/packages/app/src/components/page/editor/header/EditorHeaderButton.vue
@@ -1,9 +1,6 @@
-