xiaodao-editor 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +306 -0
- package/dist/block-editor.js +23254 -0
- package/dist/block-editor.umd.cjs +26 -0
- package/dist/core/Editor.d.ts +83 -0
- package/dist/core/command/Command.d.ts +32 -0
- package/dist/core/command/InputRule.d.ts +21 -0
- package/dist/core/command/Keymap.d.ts +42 -0
- package/dist/core/command/SlashCommand.d.ts +24 -0
- package/dist/core/command/primitiveCommands.d.ts +97 -0
- package/dist/core/extension/Extension.d.ts +52 -0
- package/dist/core/extension/Registry.d.ts +46 -0
- package/dist/core/history/HistoryManager.d.ts +37 -0
- package/dist/core/ids.d.ts +5 -0
- package/dist/core/index.d.ts +35 -0
- package/dist/core/plugin/Plugin.d.ts +26 -0
- package/dist/core/schema/BlockSchema.d.ts +70 -0
- package/dist/core/schema/SchemaRegistry.d.ts +22 -0
- package/dist/core/selection/Selection.d.ts +25 -0
- package/dist/core/serialize/Serializer.d.ts +31 -0
- package/dist/core/state/EditorState.d.ts +26 -0
- package/dist/core/state/Step.d.ts +39 -0
- package/dist/core/state/Transaction.d.ts +52 -0
- package/dist/core/state/invert.d.ts +19 -0
- package/dist/core/state/store.d.ts +78 -0
- package/dist/core/types.d.ts +105 -0
- package/dist/extensions/BulletList.d.ts +2 -0
- package/dist/extensions/CodeBlock.d.ts +2 -0
- package/dist/extensions/Divider.d.ts +2 -0
- package/dist/extensions/Heading.d.ts +2 -0
- package/dist/extensions/History.d.ts +2 -0
- package/dist/extensions/Image.d.ts +34 -0
- package/dist/extensions/Keymap.d.ts +2 -0
- package/dist/extensions/OrderedList.d.ts +18 -0
- package/dist/extensions/Paragraph.d.ts +5 -0
- package/dist/extensions/Quote.d.ts +2 -0
- package/dist/extensions/Table.d.ts +74 -0
- package/dist/extensions/TableOfContents.d.ts +18 -0
- package/dist/extensions/TodoList.d.ts +2 -0
- package/dist/extensions/_commonAttrs.d.ts +84 -0
- package/dist/extensions/builtin.d.ts +15 -0
- package/dist/extensions/tableModel.d.ts +199 -0
- package/dist/i18n.d.ts +31 -0
- package/dist/index.d.ts +30 -0
- package/dist/style.css +1 -0
- package/dist/test-img.png +0 -0
- package/dist/view/BlockContent.vue.d.ts +21 -0
- package/dist/view/BlockEditor.vue.d.ts +67 -0
- package/dist/view/BlockHost.vue.d.ts +51 -0
- package/dist/view/BlockList.vue.d.ts +64 -0
- package/dist/view/clipboard.d.ts +11 -0
- package/dist/view/context.d.ts +116 -0
- package/dist/view/domSelection.d.ts +65 -0
- package/dist/view/imageUpload.d.ts +130 -0
- package/dist/view/inlineDom.d.ts +23 -0
- package/dist/view/keymapHandler.d.ts +7 -0
- package/dist/view/ui/BlockHandle.vue.d.ts +33 -0
- package/dist/view/ui/BlockSettingsMenu.vue.d.ts +32 -0
- package/dist/view/ui/CodeLangPicker.vue.d.ts +20 -0
- package/dist/view/ui/HoverToolbar.vue.d.ts +81 -0
- package/dist/view/ui/LinkPopover.vue.d.ts +62 -0
- package/dist/view/ui/MobileToolbar.vue.d.ts +29 -0
- package/dist/view/ui/NumberPicker.vue.d.ts +20 -0
- package/dist/view/ui/OrderedListMenu.vue.d.ts +37 -0
- package/dist/view/ui/PlusMenu.vue.d.ts +29 -0
- package/dist/view/ui/SafeHtml.vue.d.ts +8 -0
- package/dist/view/ui/icons.d.ts +51 -0
- package/dist/view/ui/inputRulesEngine.d.ts +33 -0
- package/dist/view/ui/popup.d.ts +75 -0
- package/dist/view/ui/useMenuDismiss.d.ts +9 -0
- package/dist/view/ui/useMenuScroll.d.ts +2 -0
- package/dist/view/urlUtils.d.ts +39 -0
- package/package.json +73 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { InlineSeq, JSONValue } from '../core/types';
|
|
2
|
+
export interface TableCellData {
|
|
3
|
+
/** Cell content — reuses the editor's InlineSeq so rich inline marks
|
|
4
|
+
* (bold/italic/link/code) are first-class inside cells. */
|
|
5
|
+
readonly content: InlineSeq;
|
|
6
|
+
/** How many rows this cell spans (≥ 1). 1 = no row span. */
|
|
7
|
+
readonly rowspan: number;
|
|
8
|
+
/** How many columns this cell spans (≥ 1). 1 = no column span. */
|
|
9
|
+
readonly colspan: number;
|
|
10
|
+
/**
|
|
11
|
+
* True when this logical (row,col) position is *covered* by a merged
|
|
12
|
+
* cell anchored at an earlier position. Covered cells carry no content
|
|
13
|
+
* and renderers must emit no DOM for them.
|
|
14
|
+
*/
|
|
15
|
+
readonly covered: boolean;
|
|
16
|
+
/** Cell text type: 'paragraph' (default), 'heading'. Controls how cell text is styled. */
|
|
17
|
+
readonly cellType?: string;
|
|
18
|
+
/** Cell text alignment: 'left' (default), 'center', 'right'. */
|
|
19
|
+
readonly align?: string;
|
|
20
|
+
/** Cell vertical alignment: 'top', 'middle' (default), 'bottom'. */
|
|
21
|
+
readonly verticalAlign?: string;
|
|
22
|
+
/** Cell background color key (e.g. 'red', 'blue', or undefined for none). */
|
|
23
|
+
readonly bgColor?: string;
|
|
24
|
+
/** Todo-list cell checkbox state. Only meaningful when cellType === 'todoList'. */
|
|
25
|
+
readonly checked?: boolean;
|
|
26
|
+
/** Ordered-list cell explicit start number (≥ 1). Only meaningful when
|
|
27
|
+
* cellType === 'orderedList'. When set, overrides auto-continuation. */
|
|
28
|
+
readonly startNumber?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Expand a selection rectangle so that it fully covers every merged cell
|
|
32
|
+
* whose anchor or covered portion intersects the initial rectangle.
|
|
33
|
+
*
|
|
34
|
+
* The expansion iterates: on each pass, it walks the currently-included
|
|
35
|
+
* logical cells, and for every (covered or anchor) cell that belongs to
|
|
36
|
+
* a merged block whose anchor rectangle pokes outside the current bounds,
|
|
37
|
+
* the bounds are widened to include that merged block in full. The loop
|
|
38
|
+
* repeats until a pass produces no change, guaranteeing a closed rectangle.
|
|
39
|
+
*
|
|
40
|
+
* This implements the invariant: "the user can never select only half of a
|
|
41
|
+
* merged cell". The returned rectangle is guaranteed to contain the full
|
|
42
|
+
* footprint of every merged cell that originally touched the selection.
|
|
43
|
+
*/
|
|
44
|
+
export declare function expandSelectionToFullRect(attrs: TableAttrs, r1: number, c1: number, r2: number, c2: number): {
|
|
45
|
+
r1: number;
|
|
46
|
+
c1: number;
|
|
47
|
+
r2: number;
|
|
48
|
+
c2: number;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Return true if the non-covered cells inside a rectangle include at least
|
|
52
|
+
* one merged cell (rowspan > 1 or colspan > 1). Used by the floating
|
|
53
|
+
* toolbar to decide whether to show the "Split cells" button.
|
|
54
|
+
*/
|
|
55
|
+
export declare function rectContainsMergedCells(attrs: TableAttrs, r1: number, c1: number, r2: number, c2: number): boolean;
|
|
56
|
+
/** Count non-covered cells inside a rectangle. Used by the toolbar to
|
|
57
|
+
* distinguish single-cell vs multi-cell selection. */
|
|
58
|
+
export declare function countNonCoveredInRect(attrs: TableAttrs, r1: number, c1: number, r2: number, c2: number): number;
|
|
59
|
+
export interface TableAttrs {
|
|
60
|
+
readonly rows: number;
|
|
61
|
+
readonly cols: number;
|
|
62
|
+
/** cells[r][c] for all 0 ≤ r < rows, 0 ≤ c < cols. */
|
|
63
|
+
readonly cells: readonly (readonly TableCellData[])[];
|
|
64
|
+
readonly colWidths: readonly number[];
|
|
65
|
+
readonly headerRow: boolean;
|
|
66
|
+
}
|
|
67
|
+
export declare const TABLE_DEFAULTS: TableAttrs;
|
|
68
|
+
/** Build a fresh N×M grid of empty cells with no merges. */
|
|
69
|
+
export declare function buildEmptyCells(rows: number, cols: number): TableCellData[][];
|
|
70
|
+
/** Validate + coerce arbitrary raw attrs into TableAttrs. Used by schema
|
|
71
|
+
* validate and by every command that reads attrs (never trust runtime
|
|
72
|
+
* user-supplied JSON without a pass through here). */
|
|
73
|
+
export declare function coerceTableAttrs(raw: Readonly<Record<string, unknown>>): TableAttrs;
|
|
74
|
+
/**
|
|
75
|
+
* Walk every cell top→bottom, left→right. When we hit an anchor cell with
|
|
76
|
+
* rowspan/colspan > 1 we mark the covered cells accordingly (setting
|
|
77
|
+
* content=[], rowspan=colspan=1, covered=true so the renderer skips).
|
|
78
|
+
*
|
|
79
|
+
* Clamps out-of-bounds spans to the actual grid — this prevents malformed
|
|
80
|
+
* input from ever producing a cell with a span larger than the grid.
|
|
81
|
+
*/
|
|
82
|
+
export declare function recomputeCovered(cells: readonly (readonly TableCellData[])[], rows: number, cols: number): TableCellData[][];
|
|
83
|
+
/** Insert a new row before `beforeRow` (0 ≤ beforeRow ≤ rows). When
|
|
84
|
+
* beforeRow === rows appends at the bottom. New row cells are empty
|
|
85
|
+
* clones of the row above (same column spans — but only if they start
|
|
86
|
+
* on the row being cloned). Otherwise plain empty cells. */
|
|
87
|
+
export declare function insertRow(attrs: TableAttrs, beforeRow: number): TableAttrs;
|
|
88
|
+
/** Remove row `rowIndex`. When the last row is removed we still keep 1 row
|
|
89
|
+
* (empty tables with zero rows are not allowed). */
|
|
90
|
+
export declare function removeRow(attrs: TableAttrs, rowIndex: number): TableAttrs;
|
|
91
|
+
/** Insert a new column before `beforeCol` (0 ≤ beforeCol ≤ cols). */
|
|
92
|
+
export declare function insertCol(attrs: TableAttrs, beforeCol: number): TableAttrs;
|
|
93
|
+
/** Remove column `colIndex`. Last column cannot be removed. */
|
|
94
|
+
export declare function removeCol(attrs: TableAttrs, colIndex: number): TableAttrs;
|
|
95
|
+
/** Replace the InlineSeq of a single non-covered cell. No-ops if the cell
|
|
96
|
+
* is covered or indices out of range. Content is sanitized through
|
|
97
|
+
* sanitizeCellContent to enforce mark validation rules. */
|
|
98
|
+
export declare function setCellContent(attrs: TableAttrs, row: number, col: number, content: InlineSeq): TableAttrs;
|
|
99
|
+
/** Update a single cell's attrs (rowspan / colspan). Used by merge/split. */
|
|
100
|
+
export declare function setCellSpans(attrs: TableAttrs, row: number, col: number, rowspan: number, colspan: number): TableAttrs;
|
|
101
|
+
/** Set align / verticalAlign / bgColor / cellType / checked / startNumber on a single non-covered cell.
|
|
102
|
+
* Values are validated: cellType must be in VALID_CELL_TYPES, align in
|
|
103
|
+
* VALID_CELL_ALIGN, verticalAlign in VALID_CELL_VERTICAL_ALIGN, bgColor in
|
|
104
|
+
* VALID_CELL_BG_COLORS. Invalid values are silently dropped (reverted to default). */
|
|
105
|
+
export declare function setCellAttrs(attrs: TableAttrs, row: number, col: number, changes: {
|
|
106
|
+
cellType?: string;
|
|
107
|
+
align?: string;
|
|
108
|
+
verticalAlign?: string;
|
|
109
|
+
bgColor?: string;
|
|
110
|
+
checked?: boolean;
|
|
111
|
+
startNumber?: number | null;
|
|
112
|
+
}): TableAttrs;
|
|
113
|
+
/** Apply align / verticalAlign / bgColor / cellType / checked / startNumber changes to multiple cells at once.
|
|
114
|
+
* Same validation as setCellAttrs. */
|
|
115
|
+
export declare function setCellsAttrs(attrs: TableAttrs, positions: readonly {
|
|
116
|
+
row: number;
|
|
117
|
+
col: number;
|
|
118
|
+
}[], changes: {
|
|
119
|
+
cellType?: string;
|
|
120
|
+
align?: string;
|
|
121
|
+
verticalAlign?: string;
|
|
122
|
+
bgColor?: string;
|
|
123
|
+
checked?: boolean;
|
|
124
|
+
startNumber?: number | null;
|
|
125
|
+
}): TableAttrs;
|
|
126
|
+
/** Apply a mark (bold/italic/etc.) to ALL text runs in the given cells.
|
|
127
|
+
* Passing `attrs === null` removes the mark instead.
|
|
128
|
+
* Enforces code incompatibility: adding `code` strips incompatible marks,
|
|
129
|
+
* and adding an incompatible mark to a code segment is a no-op. */
|
|
130
|
+
export declare function setCellsMark(attrs: TableAttrs, positions: readonly {
|
|
131
|
+
row: number;
|
|
132
|
+
col: number;
|
|
133
|
+
}[], markType: string, markAttrs?: Record<string, JSONValue> | null): TableAttrs;
|
|
134
|
+
/** Toggle a mark on all text runs in the given cells. Removes the mark if
|
|
135
|
+
* every selected cell already has it on all its text runs, otherwise adds. */
|
|
136
|
+
export declare function toggleCellsMark(attrs: TableAttrs, positions: readonly {
|
|
137
|
+
row: number;
|
|
138
|
+
col: number;
|
|
139
|
+
}[], markType: string): TableAttrs;
|
|
140
|
+
/**
|
|
141
|
+
* Merge the rectangle of cells from (r1,c1) → (r2,c2) inclusive. Anchor is
|
|
142
|
+
* always the top-left. Existing merges inside the rect are flattened.
|
|
143
|
+
*
|
|
144
|
+
* Content policy: ONLY the cell that originally occupied the top-left
|
|
145
|
+
* anchor position (rs, cs) — or the anchor of the merged block that covers
|
|
146
|
+
* it — contributes its InlineSeq to the merged cell. Every other cell's
|
|
147
|
+
* content is discarded (matches Notion's "first cell wins" semantics).
|
|
148
|
+
* The anchor's cell-level attrs (cellType / align / bgColor) are also
|
|
149
|
+
* preserved unchanged.
|
|
150
|
+
*/
|
|
151
|
+
export declare function mergeRect(attrs: TableAttrs, r1: number, c1: number, r2: number, c2: number): TableAttrs;
|
|
152
|
+
/** Split a previously-merged cell back to rowspan=colspan=1. Data stays on
|
|
153
|
+
* the top-left (anchor) cell; every other previously-covered cell becomes
|
|
154
|
+
* an empty plain 1x1 cell. */
|
|
155
|
+
export declare function splitCell(attrs: TableAttrs, row: number, col: number): TableAttrs;
|
|
156
|
+
/** Split ALL merged cells whose anchors lie within the given rectangle.
|
|
157
|
+
* This is the operation triggered by the "Split cells" toolbar button
|
|
158
|
+
* when the user has a multi-cell selection. Each merged cell inside the
|
|
159
|
+
* (expanded) rect is split independently using the same rule as splitCell
|
|
160
|
+
* above — its data stays on its own top-left position. */
|
|
161
|
+
export declare function splitCellsInRect(attrs: TableAttrs, r1: number, c1: number, r2: number, c2: number): TableAttrs;
|
|
162
|
+
/** Toggle the header-row flag on/off. */
|
|
163
|
+
export declare function toggleHeaderRow(attrs: TableAttrs): TableAttrs;
|
|
164
|
+
/** Set a single column width (0 = auto). */
|
|
165
|
+
export declare function setColWidth(attrs: TableAttrs, col: number, width: number): TableAttrs;
|
|
166
|
+
/** Plain-text dump of a single cell. Used by Markdown and for copying a
|
|
167
|
+
* single cell to the clipboard. */
|
|
168
|
+
export declare function cellPlainText(cell: TableCellData): string;
|
|
169
|
+
/** Escape a Markdown table cell body so pipes/newlines don't break the grid. */
|
|
170
|
+
export declare function escapeMdCell(s: string): string;
|
|
171
|
+
/** Build a Markdown pipe-delimited table (compatible with GitHub). */
|
|
172
|
+
export declare function tableToMarkdown(attrs: TableAttrs): string;
|
|
173
|
+
/** Build an HTML <table> representation. */
|
|
174
|
+
export declare function tableToHtml(attrs: TableAttrs): string;
|
|
175
|
+
/** Parse a DOM <table> element into TableAttrs. Used by clipboard HTML
|
|
176
|
+
* parsing (Word/Google Docs). */
|
|
177
|
+
export declare function parseHtmlTable(tableEl: HTMLTableElement): TableAttrs | null;
|
|
178
|
+
export declare function validateTableAttrs(v: unknown): boolean;
|
|
179
|
+
/** Shallow-cast Attrs → TableAttrs via JSONValue round trip. Used in
|
|
180
|
+
* commands after the schema has already validated the raw attrs shape. */
|
|
181
|
+
export declare function attrsToTable(a: Readonly<Record<string, JSONValue>>): TableAttrs;
|
|
182
|
+
/**
|
|
183
|
+
* Compute the displayed 1-based ordinal for an ordered-list cell at (row, col).
|
|
184
|
+
*
|
|
185
|
+
* Mirrors the text-block ordered-list numbering rules:
|
|
186
|
+
* 1. If the cell has `startNumber = N` (explicit override), return N.
|
|
187
|
+
* 2. Otherwise, walk backwards in the SAME COLUMN (skipping covered cells):
|
|
188
|
+
* - If the previous non-covered cell in the same column is also an
|
|
189
|
+
* orderedList, this cell's ordinal is previousOrdinal + 1.
|
|
190
|
+
* - If it is NOT an orderedList (or there is no previous cell), the
|
|
191
|
+
* ordinal is 1 (implicit start of a list).
|
|
192
|
+
*
|
|
193
|
+
* Numbering is per-column because table cells are laid out in a grid, and a
|
|
194
|
+
* vertical sequence of orderedList cells in one column should form a numbered
|
|
195
|
+
* list independent of other columns.
|
|
196
|
+
*
|
|
197
|
+
* Returns 1 for non-orderedList cells (should not be called for them).
|
|
198
|
+
*/
|
|
199
|
+
export declare function orderedListCellNumber(attrs: TableAttrs, row: number, col: number): number;
|
package/dist/i18n.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { InjectionKey, Ref } from 'vue';
|
|
2
|
+
export type Theme = 'light' | 'dark';
|
|
3
|
+
export type Locale = 'zh-CN' | 'en-US';
|
|
4
|
+
/** Normalise user-supplied locale values to the canonical two-member set. */
|
|
5
|
+
export declare function normalizeLocale(raw: string | undefined | null): Locale;
|
|
6
|
+
export declare function normalizeTheme(raw: string | undefined | null): Theme;
|
|
7
|
+
export interface I18nBundle {
|
|
8
|
+
readonly locale: Locale;
|
|
9
|
+
readonly theme: Theme;
|
|
10
|
+
/** Translate a key. Falls back to the raw key if missing. */
|
|
11
|
+
t(key: string): string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Reactive refs for locale and theme. These are provided directly (not
|
|
15
|
+
* wrapped in an object) so that each consumer's `t()` function accesses
|
|
16
|
+
* `localeRef.value` — a plain ref read — which Vue's reactivity system
|
|
17
|
+
* tracks reliably across <Teleport> boundaries.
|
|
18
|
+
*/
|
|
19
|
+
export declare const localeKey: InjectionKey<Ref<Locale>>;
|
|
20
|
+
export declare const themeKey: InjectionKey<Ref<Theme>>;
|
|
21
|
+
export declare function provideI18n(locale: Ref<Locale>, theme: Ref<Theme>): void;
|
|
22
|
+
/**
|
|
23
|
+
* Access the i18n/theme bundle within a child component. Each call
|
|
24
|
+
* injects the raw locale/theme refs and builds a fresh `t()` that reads
|
|
25
|
+
* `localeRef.value` directly — no computed, no closure-over-computed.
|
|
26
|
+
*
|
|
27
|
+
* When the parent updates `localeRef.value`, every template or computed
|
|
28
|
+
* that called `t('key')` re-renders because the ref read was tracked
|
|
29
|
+
* at call time.
|
|
30
|
+
*/
|
|
31
|
+
export declare function useI18n(): I18nBundle;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entry point for the xiaodao-editor package.
|
|
3
|
+
*
|
|
4
|
+
* Exports:
|
|
5
|
+
* - Core types and engine (framework-agnostic)
|
|
6
|
+
* - Vue components (BlockEditor, BlockList, BlockHost, BlockContent)
|
|
7
|
+
* - Built-in extensions (Paragraph, Heading, Keymap, History)
|
|
8
|
+
* - Extension/Schema/Command building blocks for custom block types
|
|
9
|
+
*/
|
|
10
|
+
export * from './core/index';
|
|
11
|
+
export { default as BlockEditor } from './view/BlockEditor.vue';
|
|
12
|
+
export { default as BlockList } from './view/BlockList.vue';
|
|
13
|
+
export { default as BlockHost } from './view/BlockHost.vue';
|
|
14
|
+
export { default as BlockContent } from './view/BlockContent.vue';
|
|
15
|
+
export { editorKey, useEditor } from './view/context';
|
|
16
|
+
export type { BlockRenderItem } from './view/context';
|
|
17
|
+
export { BuiltinExtensions } from './extensions/builtin';
|
|
18
|
+
export { ParagraphExtension } from './extensions/Paragraph';
|
|
19
|
+
export { HeadingExtension } from './extensions/Heading';
|
|
20
|
+
export { KeymapExtension } from './extensions/Keymap';
|
|
21
|
+
export { HistoryExtension } from './extensions/History';
|
|
22
|
+
export { ImageExtension } from './extensions/Image';
|
|
23
|
+
export type { ImageAttrs } from './extensions/Image';
|
|
24
|
+
export { TableExtension } from './extensions/Table';
|
|
25
|
+
export type { TableAttrs, TableCellData } from './extensions/tableModel';
|
|
26
|
+
export { DividerExtension } from './extensions/Divider';
|
|
27
|
+
export { TableOfContentsExtension } from './extensions/TableOfContents';
|
|
28
|
+
export type { ImageUploadResult, UploadImageHandler } from './view/imageUpload';
|
|
29
|
+
export type { Theme, Locale, I18nBundle } from './i18n';
|
|
30
|
+
export { useI18n, normalizeLocale, normalizeTheme } from './i18n';
|
package/dist/style.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--be-bg: #ffffff;--be-border: #ebebea;--be-text: #37352f;--be-muted: #9b9a97;--be-accent: #2383e2;--be-hover: #f7f6f3;--be-active: #eef1ff;--be-danger: #e03e3e;--be-shadow: 0 0 0 1px rgb(15 15 15 / .05), 0 3px 6px rgb(15 15 15 / .1), 0 9px 24px rgb(15 15 15 / .2);--be-radius: 6px;--be-fg: #1f2328;--be-color-gray: #6e7781;--be-color-brown: #9a6744;--be-color-orange: #d97706;--be-color-yellow: #ca8a04;--be-color-green: #2f9e44;--be-color-blue: #1c7ed6;--be-color-purple: #845ef7;--be-color-pink: #d63384;--be-color-red: #e03131;--be-swatch-bg-gray: rgba(110, 120, 130, .14);--be-swatch-bg-brown: rgba(140, 100, 65, .16);--be-swatch-bg-orange: rgba(220, 130, 30, .16);--be-swatch-bg-yellow: rgba(200, 160, 20, .16);--be-swatch-bg-green: rgba(50, 150, 70, .16);--be-swatch-bg-blue: rgba(30, 120, 200, .16);--be-swatch-bg-purple: rgba(130, 90, 220, .16);--be-swatch-bg-pink: rgba(210, 60, 130, .16);--be-swatch-bg-red: rgba(220, 50, 50, .13)}.block-editor.theme-dark,body.theme-dark{--be-bg: #1e1f22;--be-border: #2d2f33;--be-text: #e6e5e1;--be-muted: #8b8a87;--be-accent: #57a6ff;--be-hover: #2b2d31;--be-active: #3a4663;--be-danger: #ff6b6b;--be-shadow: 0 0 0 1px rgb(0 0 0 / .4), 0 3px 6px rgb(0 0 0 / .5), 0 9px 24px rgb(0 0 0 / .7);--be-fg: #e6e5e1;color-scheme:dark;--be-color-gray: #98a1b3;--be-color-brown: #d8b4a0;--be-color-orange: #ffa94d;--be-color-yellow: #ffe066;--be-color-green: #69db7c;--be-color-blue: #74c0fc;--be-color-purple: #b197fc;--be-color-pink: #faa2c1;--be-color-red: #ff8787;--be-swatch-bg-gray: rgba(150, 160, 170, .22);--be-swatch-bg-brown: rgba(200, 150, 110, .24);--be-swatch-bg-orange: rgba(240, 160, 60, .22);--be-swatch-bg-yellow: rgba(240, 200, 50, .2);--be-swatch-bg-green: rgba(90, 190, 110, .22);--be-swatch-bg-blue: rgba(70, 150, 230, .22);--be-swatch-bg-purple: rgba(170, 120, 240, .22);--be-swatch-bg-pink: rgba(230, 90, 160, .22);--be-swatch-bg-red: rgba(240, 80, 80, .2)}body.theme-light{--be-bg: #ffffff;--be-border: #ebebea;--be-text: #37352f;--be-muted: #9b9a97;--be-accent: #2383e2;--be-hover: #f7f6f3;--be-active: #eef1ff;--be-danger: #e03e3e;--be-fg: #1f2328;color-scheme:light;--be-color-gray: #6e7781;--be-color-brown: #9a6744;--be-color-orange: #d97706;--be-color-yellow: #ca8a04;--be-color-green: #2f9e44;--be-color-blue: #1c7ed6;--be-color-purple: #845ef7;--be-color-pink: #d63384;--be-color-red: #e03131;--be-swatch-bg-gray: rgba(110, 120, 130, .14);--be-swatch-bg-brown: rgba(140, 100, 65, .16);--be-swatch-bg-orange: rgba(220, 130, 30, .16);--be-swatch-bg-yellow: rgba(200, 160, 20, .16);--be-swatch-bg-green: rgba(50, 150, 70, .16);--be-swatch-bg-blue: rgba(30, 120, 200, .16);--be-swatch-bg-purple: rgba(130, 90, 220, .16);--be-swatch-bg-pink: rgba(210, 60, 130, .16);--be-swatch-bg-red: rgba(220, 50, 50, .13)}.block-editor{outline:none;position:relative;color:var(--be-text)}.block-editor-dragging,.block-editor-dragging .block-content,.block-editor-dragging [contenteditable=true]{user-select:none!important;-webkit-user-select:none!important}.block-host.block-focused[data-block-type=paragraph]>.block-host-content,.block-host.block-focused[data-block-type=heading]>.block-host-content,.block-host.block-focused[data-block-type=bulletList]>.block-host-content,.block-host.block-focused[data-block-type=orderedList]>.block-host-content,.block-host.block-focused[data-block-type=todoList]>.block-host-content,.block-host.block-focused[data-block-type=quote]>.block-host-content,.block-host.block-focused[data-block-type=codeBlock]>.block-host-content{background:none;box-shadow:none}.block-host.block-focused[data-block-type=image] .block-image-wrapper{box-shadow:inset 0 0 0 2px #3370ff33;border-radius:4px}.block-host.block-focused[data-block-type=divider]>.block-host-content:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:#3370ff12;border:2px solid rgba(51,112,255,.35);border-radius:4px;pointer-events:none;box-sizing:border-box}.block-host.block-focused[data-block-type=divider] .block-divider{background-color:var(--be-focus, #3370ff)}.block-host.block-focused[data-block-type=tableOfContents]>.block-host-content,.block-host.block-focused[data-block-type=table]>.block-host-content{box-shadow:none}.block-host.block-focused[data-block-type=tableOfContents]>.block-host-content:after,.block-host.block-focused[data-block-type=table]>.block-host-content:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;border:2px solid rgba(51,112,255,.35);border-radius:4px;pointer-events:none;box-sizing:border-box}.block-list{display:flex;flex-direction:column;gap:2px}.block-list.block-children{padding-left:0;margin-top:0}.block-host{position:relative;display:flex;align-items:flex-start;padding-left:48px;min-height:1.7em}.block-host.block-host-readonly{padding-left:0}.block-host-content{position:relative;flex:1;min-width:0}.block-handle{position:absolute;left:0;top:50%;transform:translateY(-50%);display:flex;align-items:center;gap:2px;opacity:0;pointer-events:none;transition:opacity .12s ease;width:44px;height:24px}.block-handle.visible{opacity:1;pointer-events:auto}.block-handle.dragging{pointer-events:auto}.handle-btn{background:transparent;border:0;color:var(--be-muted);width:20px;height:24px;border-radius:2px;font-size:13px;line-height:1;cursor:pointer;padding:0;display:inline-flex;align-items:center;justify-content:center;-webkit-user-select:none;user-select:none}.handle-btn:hover{background:var(--be-hover);color:var(--be-text)}.handle-btn:active{background:var(--be-active)}.handle-grip{cursor:grab}.block-handle.dragging .handle-grip{cursor:grabbing}.block-content{outline:none;min-height:1.5em;font-size:16px;line-height:1.6;padding:3px 2px;word-break:break-word;white-space:pre-wrap}.block-content b,.block-content strong{font-weight:700}.block-content i,.block-content em{font-style:italic}.block-content u{text-decoration:underline}.block-content s,.block-content strike,.block-content del{text-decoration:line-through}.block-content code{font-family:ui-monospace,SF Mono,Cascadia Code,Roboto Mono,Consolas,Liberation Mono,Menlo,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Microsoft YaHei,monospace,sans-serif;background:var(--be-hover);color:inherit;padding:.1em .3em;border:1px solid var(--be-border);border-radius:4px;font-size:.88em}.block-content code:hover{background:var(--be-active)}.block-content a{color:var(--be-accent);text-decoration:underline;text-decoration-color:var(--be-accent);text-underline-offset:2px;cursor:pointer}.block-content a:hover{text-decoration-thickness:1px}.link-popover{position:fixed;z-index:65;background:var(--be-bg);border:1px solid var(--be-border);border-radius:4px;box-shadow:var(--be-shadow);padding:7px;display:flex;align-items:center;gap:6px;font-size:13px;max-width:360px;opacity:0;transform:translateY(-3px);animation:olm-fade-in .12s ease}.link-popover.visible{opacity:1;transform:translateY(0)}.link-popover-view{display:flex;align-items:center;gap:4px}.link-popover-url{color:var(--be-accent);text-decoration:none;padding:2px 6px;border-radius:4px;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.link-popover-url:hover{background:var(--be-hover)}.link-popover-actions{display:flex;align-items:center;gap:2px}.link-popover-btn{display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;color:var(--be-fg);border-radius:3px;padding:5px 8px;cursor:pointer;font-size:13px;white-space:nowrap;min-width:28px;min-height:28px}.link-popover-btn:hover{background:var(--be-hover)}.link-popover-btn:active{background:var(--be-active)}.link-popover-danger{color:var(--be-danger)}.link-popover-danger:hover{background:#e03e3e1a}.link-popover-edit{display:flex;flex-direction:column;gap:6px;min-width:240px}.link-popover-input{border:1px solid var(--be-border);border-radius:3px;padding:5px 8px;font-size:13px;outline:none;background:var(--be-bg);color:var(--be-text);width:100%;box-sizing:border-box;transition:border-color .1s ease,box-shadow .1s ease}.link-popover-input:focus{border-color:var(--be-accent);box-shadow:0 0 0 3px #1c7ed624}.link-popover-input-error,.link-popover-input-error:focus{border-color:#e74c3c}.link-popover-error{color:#e74c3c;font-size:12px;margin-top:4px;line-height:1.3}.link-popover-edit-actions{display:flex;justify-content:flex-end;gap:4px}.link-popover-save{background:var(--be-accent);color:#fff;padding:5px 14px;border-radius:3px;font-weight:500;font-size:13.5px}.link-popover-save:hover{opacity:.9;background:var(--be-accent)}.link-popover-cancel{padding:5px 14px;border-radius:3px;font-size:13.5px}.block-content[data-empty=true]:after{content:attr(data-placeholder);color:var(--be-muted);pointer-events:none;-webkit-user-select:none;user-select:none}.block-heading{font-weight:600;line-height:1.3}.block-heading-h1{font-size:2rem;margin-top:1.4rem;margin-bottom:.4rem}.block-heading-h2{font-size:1.5rem;margin-top:1.2rem;margin-bottom:.3rem}.block-heading-h3{font-size:1.25rem;margin-top:1rem;margin-bottom:.25rem}.block-heading-h4{font-size:1.1rem;margin-top:.8rem;margin-bottom:.2rem}.block-heading-h5{font-size:1rem;margin-top:.6rem;margin-bottom:.15rem}.block-heading-h6{font-size:.9rem;color:var(--be-muted);margin-top:.5rem;margin-bottom:.1rem}.block-bullet-list{position:relative;padding-left:calc(1.9em + 2px)}.block-bullet-list:before{content:"•";position:absolute;left:2px;top:3px;width:1.9em;height:1.6em;display:flex;align-items:flex-start;justify-content:left;font-weight:700;font-size:.95em;color:inherit;opacity:.78;pointer-events:none}.block-ordered-list-wrapper{position:relative;display:flex;align-items:flex-start;font-size:16px}.ol-marker{flex:0 0 auto;width:calc(1.9em + 2px);height:1.6em;line-height:1.6;margin-top:3px;padding-left:2px;display:flex;align-items:flex-start;justify-content:left;font-weight:500;color:inherit;font-size:inherit;font-variant-numeric:tabular-nums;cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:2px;transition:background-color .12s ease,color .12s ease}.ol-marker:hover{background:#1c7ed61f;color:#1c7ed6}.ol-marker:active{background:#1c7ed633}.block-ordered-list-content{flex:1 1 auto;min-width:0}.block-todo-wrapper{display:flex;align-items:flex-start;gap:8px;--todo-font-size: 16px;--todo-line-height: 1.6;--todo-padding-top: 3px;--todo-checkbox-size: calc(var(--todo-font-size) * 1);font-size:var(--todo-font-size)}.block-todo-checkbox{margin-top:calc(var(--todo-padding-top) + (var(--todo-line-height) * var(--todo-font-size) - var(--todo-checkbox-size)) / 2 + 2px);width:var(--todo-checkbox-size);height:var(--todo-checkbox-size);accent-color:var(--be-accent);cursor:pointer;flex-shrink:0}.block-todo-list.todo-checked{color:var(--be-muted);text-decoration:line-through;text-decoration-thickness:from-font}.block-quote{border-left:3px solid var(--be-border);padding-left:14px;color:var(--be-muted);font-style:italic}.block-code-wrapper{position:relative;background:var(--be-hover);border:1px solid var(--be-border);border-radius:4px;padding:8px 12px}.block-code-lang,.block-code-title{position:absolute;top:4px;font-size:.68rem;font-weight:600;letter-spacing:.05em;color:var(--be-muted);text-transform:uppercase;-webkit-user-select:none;user-select:none;padding:2px 6px;border-radius:4px}.block-code-lang{font-size:.68rem;right:6px;cursor:pointer;transition:background-color .1s ease,color .1s ease}.block-code-title{font-size:.8rem;left:6px;pointer-events:none}.block-code-lang:hover{background:var(--be-hover);color:var(--be-accent)}.block-code{position:relative;z-index:1;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Microsoft YaHei,monospace,sans-serif;font-size:.92em;line-height:1.55;white-space:pre-wrap;word-break:break-word;padding:18px 2px 2px;color:transparent;caret-color:var(--be-text);transition:color .12s ease}.block-code::selection{background:var(--be-accent);color:#fff}.block-code:focus{color:var(--be-text)}.block-code-wrapper:focus-within .block-code-highlight{opacity:0}.block-code-highlight{position:absolute;top:0;right:0;bottom:0;left:0;z-index:0;margin:0;padding:26px 14px 10px;overflow:hidden;pointer-events:none;-webkit-user-select:none;user-select:none;white-space:pre-wrap;word-break:break-word;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Microsoft YaHei,monospace,sans-serif;font-size:.92em;line-height:1.55;color:var(--be-text);transition:opacity .12s ease}.block-code-highlight .hljs{font:inherit;white-space:inherit;word-break:inherit}.block-code-lang,.block-code-title{z-index:2}.block-code-highlight .hljs-comment,.block-code-highlight .hljs-quote,.block-code-highlight .hljs-meta{color:var(--be-color-gray);font-style:italic}.block-code-highlight .hljs-keyword,.block-code-highlight .hljs-selector-tag,.block-code-highlight .hljs-literal,.block-code-highlight .hljs-doctag,.block-code-highlight .hljs-section,.block-code-highlight .hljs-meta .hljs-keyword{color:var(--be-color-purple)}.block-code-highlight .hljs-string,.block-code-highlight .hljs-regexp,.block-code-highlight .hljs-addition,.block-code-highlight .hljs-attribute,.block-code-highlight .hljs-symbol,.block-code-highlight .hljs-bullet{color:var(--be-color-green)}.block-code-highlight .hljs-number,.block-code-highlight .hljs-title,.block-code-highlight .hljs-title.function_,.block-code-highlight .hljs-function .hljs-title{color:var(--be-color-blue)}.block-code-highlight .hljs-built_in,.block-code-highlight .hljs-builtin-name,.block-code-highlight .hljs-type,.block-code-highlight .hljs-class .hljs-title,.block-code-highlight .hljs-selector-class,.block-code-highlight .hljs-selector-id,.block-code-highlight .hljs-template-variable,.block-code-highlight .hljs-params{color:var(--be-color-orange)}.block-code-highlight .hljs-attr,.block-code-highlight .hljs-variable,.block-code-highlight .hljs-property{color:var(--be-color-brown)}.block-code-highlight .hljs-tag,.block-code-highlight .hljs-name,.block-code-highlight .hljs-selector-pseudo,.block-code-highlight .hljs-deletion,.block-code-highlight .hljs-diff .hljs-meta{color:var(--be-color-red)}.block-code-highlight .hljs-emphasis{font-style:italic}.block-code-highlight .hljs-strong{font-weight:600}.block-divider{width:100%;height:1px;background:var(--be-border);background-clip:content-box;margin:14px 0;display:block;-webkit-user-select:none;user-select:none;position:relative;padding:7px 0;box-sizing:content-box}.block-host-item:hover>.block-divider,.block-host-item[data-block-selected=true]>.block-divider{background:var(--be-accent-soft);height:2px;margin:13px 0;transition:background-color .15s ease,height .15s ease}.block-table-of-contents{display:flex;flex-direction:column;gap:2px;padding:4px;-webkit-user-select:none;user-select:none;cursor:default;box-sizing:border-box}.toc-title{font-size:.8rem;font-weight:600;color:var(--be-muted);margin-bottom:4px}.toc-empty{font-size:.875rem;color:var(--be-muted)}.toc-item{display:block;width:100%;text-align:left;font-size:.9375rem;line-height:1.5;color:var(--be-text);background:transparent;border:none;border-radius:3px;padding:1px 8px;cursor:pointer;text-decoration:none}.toc-item:hover{color:var(--be-accent);background:var(--be-hover)}.plus-menu-header{padding:4px 10px 6px;border-bottom:1px solid var(--be-border);margin-bottom:4px}.plus-menu-title{font-size:.78rem;font-weight:600;color:var(--be-muted);text-transform:uppercase;letter-spacing:.04em}.slash-menu{position:fixed;z-index:50;background:var(--be-bg);border:1px solid var(--be-border);border-radius:4px;box-shadow:var(--be-shadow);padding:6px;overflow:hidden;display:flex;flex-direction:column;min-width:260px;animation:slash-fade-in .14s ease;transition:opacity .14s ease,transform .14s ease}.slash-menu.menu-closing{opacity:0;transform:translateY(4px) scale(.96);pointer-events:none;visibility:hidden;transition:opacity .18s ease .12s,transform .18s ease .12s,visibility 0s linear .3s}@keyframes slash-fade-in{0%{opacity:0;transform:translateY(-4px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}.slash-menu-scroll{overflow:hidden;max-height:var(--scroll-max-height, 360px);flex:1;min-height:0}.slash-menu-item{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:2px;cursor:pointer;-webkit-user-select:none;user-select:none}.slash-menu-item:hover,.slash-menu-item.active{background:var(--be-hover)}.slash-menu-item.active{background:var(--be-active)}.slash-menu-item:hover .slash-menu-icon,.slash-menu-item.active .slash-menu-icon{border-color:color-mix(in srgb,var(--be-accent) 25%,var(--be-border))}.slash-menu-icon{width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;font-size:14px;font-weight:600;border:1px solid var(--be-border);border-radius:2px;background:var(--be-bg);color:var(--be-text);flex-shrink:0}.slash-menu-icon svg{width:16px;height:16px}.slash-menu-text{display:flex;flex-direction:column;min-width:0}.slash-menu-title{font-size:.92rem;font-weight:500;color:var(--be-text)}.slash-menu-desc{font-size:.78rem;color:var(--be-muted);line-height:1.2}.slash-menu-empty{padding:16px 12px;color:var(--be-muted);font-size:.85rem;text-align:center}.slash-menu-group{padding:2px 0}.slash-menu-group-label{padding:6px 12px 4px;font-size:.72rem;font-weight:600;color:var(--be-muted);text-transform:uppercase;letter-spacing:.04em}.slash-menu-group+.slash-menu-group{border-top:1px solid var(--be-border);margin-top:2px}.menu-scroll-btn{display:flex;align-items:center;justify-content:center;height:24px;flex-shrink:0;background:transparent;border:0;color:var(--be-muted);cursor:pointer;padding:0;border-radius:2px;margin:2px 0;transition:background .1s ease,color .1s ease}.menu-scroll-btn:hover{background:var(--be-hover);color:var(--be-text)}.menu-scroll-up{border-bottom:1px solid var(--be-border);margin-bottom:2px}.menu-scroll-down{border-top:1px solid var(--be-border);margin-top:2px}.block-settings-menu{position:fixed;z-index:60;background:var(--be-bg);border:1px solid var(--be-border);border-radius:4px;box-shadow:var(--be-shadow);padding:6px;overflow:hidden;display:flex;flex-direction:column;font-size:.88rem;animation:bsm-fade-in .14s ease;transition:opacity .14s ease,transform .14s ease}.block-settings-menu.menu-closing{opacity:0;transform:translateY(4px) scale(.96);pointer-events:none;visibility:hidden;transition:opacity .18s ease .12s,transform .18s ease .12s,visibility 0s linear .3s}@keyframes bsm-fade-in{0%{opacity:0;transform:translateY(-4px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}.bsm-scroll{overflow:hidden;max-height:var(--scroll-max-height, 520px);flex:1;min-height:0}.bsm-group{display:flex;flex-direction:column;padding:4px 0}.bsm-label{padding:4px 10px;color:var(--be-muted);font-size:.72rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em}.bsm-item{display:flex;align-items:center;gap:10px;padding:6px 10px;border:0;background:transparent;border-radius:2px;cursor:pointer;color:var(--be-text);font:inherit;text-align:left}.bsm-item:hover,.bsm-item.active{background:var(--be-hover)}.bsm-item.active{background:var(--be-active)}.bsm-item.danger{color:var(--be-danger)}.bsm-item.danger:hover,.bsm-item.danger.active{background:var(--be-hover)}.bsm-icon{width:18px;text-align:center;font-size:.92rem;font-weight:600;color:inherit;flex-shrink:0}.bsm-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.4}.bsm-shortcut{color:var(--be-muted);font-size:.72rem;margin-left:12px;font-variant-numeric:tabular-nums;display:inline-flex;align-items:center;gap:2px;line-height:1;flex-shrink:0}.bsm-action-icon>svg,.bsm-action-icon svg,.bsm-shortcut svg{display:block}.bsm-action{width:100%}.bsm-action-icon{width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;color:var(--be-muted)}.bsm-action:hover .bsm-action-icon,.bsm-action.active .bsm-action-icon{color:var(--be-text)}.bsm-action.danger .bsm-action-icon{color:var(--be-danger)}.bsm-sep{height:1px;background:var(--be-border);margin:4px 6px}.bsm-collapse-header{display:flex;align-items:center;justify-content:space-between;width:100%;padding:6px 10px;border:0;background:transparent;border-radius:2px;cursor:pointer;color:var(--be-text);font:inherit;text-align:left;font-size:.8rem;font-weight:500}.bsm-collapse-header:hover{background:var(--be-hover)}.bsm-collapse-header.bsm-disabled{opacity:.35;cursor:not-allowed;pointer-events:none}.bsm-collapse-title{flex:1;min-width:0}.bsm-collapse-chevron{flex-shrink:0;transition:transform .15s ease}.bsm-collapse-header.expanded .bsm-collapse-chevron{transform:rotate(180deg)}.bsm-collapse-body{padding-top:6px}.bsm-icon-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:2px;padding:0 6px}.bsm-icon-cell{display:inline-flex;align-items:center;justify-content:center;width:100%;height:34px;border:1px solid transparent;border-radius:2px;background:transparent;color:var(--be-text);cursor:pointer;padding:0;transition:background .1s,border-color .1s}.bsm-icon-cell:hover{background:var(--be-hover)}.bsm-icon-cell.active{background:var(--be-accent);border-color:var(--be-accent);color:#fff}.bsm-icon-svg{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;flex-shrink:0}.bsm-icon-svg svg{width:16px;height:16px}.bsm-row{display:flex;gap:4px;padding:2px 8px}.bsm-icon-btn{background:transparent;border:1px solid transparent;border-radius:2px;color:var(--be-text);width:32px;height:28px;font-size:14px;line-height:1;cursor:pointer;padding:0;display:inline-flex;align-items:center;justify-content:center}.bsm-icon-btn:hover{background:var(--be-hover)}.bsm-icon-btn.active{background:var(--be-accent);border-color:var(--be-accent);color:#fff}.bsm-icon-btn.bsm-disabled,.bsm-icon-btn:disabled{opacity:.35;cursor:not-allowed;pointer-events:none}.bsm-colors{padding-bottom:6px}.bsm-swatches{display:flex;flex-wrap:wrap;gap:6px;padding:2px 8px 4px}.bsm-swatch{width:20px;height:20px;border-radius:50%;border:1px solid var(--be-border);cursor:pointer;padding:0;box-sizing:border-box}.bsm-swatch:hover{box-shadow:0 0 0 2px color-mix(in srgb,var(--be-accent) 30%,transparent)}.bsm-swatch.active{box-shadow:0 0 0 2px var(--be-accent);border-color:var(--be-accent)}.be-align-left{text-align:left}.be-align-center{text-align:center}.be-align-right{text-align:right}.be-align-justify{text-align:justify}.be-color-gray{color:var(--be-color-gray)}.be-color-brown{color:var(--be-color-brown)}.be-color-orange{color:var(--be-color-orange)}.be-color-yellow{color:var(--be-color-yellow)}.be-color-green{color:var(--be-color-green)}.be-color-blue{color:var(--be-color-blue)}.be-color-purple{color:var(--be-color-purple)}.be-color-pink{color:var(--be-color-pink)}.be-color-red{color:var(--be-color-red)}.be-bg-gray{background-color:var(--be-swatch-bg-gray);border-radius:2px}.be-bg-brown{background-color:var(--be-swatch-bg-brown);border-radius:2px}.be-bg-orange{background-color:var(--be-swatch-bg-orange);border-radius:2px}.be-bg-yellow{background-color:var(--be-swatch-bg-yellow);border-radius:2px}.be-bg-green{background-color:var(--be-swatch-bg-green);border-radius:2px}.be-bg-blue{background-color:var(--be-swatch-bg-blue);border-radius:2px}.be-bg-purple{background-color:var(--be-swatch-bg-purple);border-radius:2px}.be-bg-pink{background-color:var(--be-swatch-bg-pink);border-radius:2px}.be-bg-red{background-color:var(--be-swatch-bg-red);border-radius:2px}.be-indent-1{margin-left:calc(1.9em + 2px)}.be-indent-2{margin-left:calc(2*(1.9em + 2px))}.be-indent-3{margin-left:calc(3*(1.9em + 2px))}.be-indent-4{margin-left:calc(4*(1.9em + 2px))}.be-indent-5{margin-left:calc(5*(1.9em + 2px))}.be-indent-6{margin-left:calc(6*(1.9em + 2px))}.be-indent-7{margin-left:calc(7*(1.9em + 2px))}.be-indent-8{margin-left:calc(8*(1.9em + 2px))}.be-indent-9{margin-left:calc(9*(1.9em + 2px))}.be-indent-10{margin-left:calc(10*(1.9em + 2px))}.hover-toolbar-shell{position:fixed;z-index:55;pointer-events:none;overflow:visible}.hover-toolbar-shell.visible{pointer-events:none}.hover-toolbar{pointer-events:auto;display:flex;align-items:center;gap:0;background:#1f1f1f;color:#fff;border-radius:4px;padding:4px;box-shadow:0 0 0 1px #ffffff0f,0 1px 3px #0000002e,0 6px 16px #00000047;position:absolute;opacity:0;transform:translateY(4px) scale(.96);pointer-events:none;transition:opacity .14s ease,transform .14s ease;transition-delay:0s}.ht-content{display:flex;align-items:center;gap:2px;overflow:hidden;overflow-x:scroll;scrollbar-width:none;-ms-overflow-style:none}.ht-content::-webkit-scrollbar{display:none}.ht-content>*{flex-shrink:0}.ht-content.dropdown-open{overflow:visible}.ht-nav-btn{pointer-events:auto;flex-shrink:0;background:transparent;border:0;color:inherit;width:22px;height:30px;border-radius:3px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background .1s ease}.ht-nav-btn:hover{background:#ffffff1f}.ht-nav-btn.disabled{opacity:.3;cursor:default;pointer-events:none}.ht-nav-left{margin-right:2px}.ht-nav-right{margin-left:2px}.hover-toolbar-shell.visible .hover-toolbar{opacity:1;transform:translateY(0) scale(1);pointer-events:auto;visibility:visible;transition:opacity .14s ease 0s,transform .14s ease 0s,visibility 0s linear 0s}.hover-toolbar-shell:not(.visible) .hover-toolbar,.hover-toolbar-shell:not(.visible) .hover-toolbar *{pointer-events:none!important;cursor:default!important;visibility:hidden;opacity:0;transform:translateY(4px) scale(.96);transition:opacity .18s ease .12s,transform .18s ease .12s,visibility 0s linear .3s}@keyframes ht-fade-in{0%{opacity:0;transform:translateY(4px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.hover-toolbar.above:after{content:"";position:absolute;left:var(--ht-arrow-x, 50%);bottom:-5px;width:0;height:0;border:5px solid transparent;border-top-color:#1f1f1f;transform:translate(-50%)}.hover-toolbar.below:after{content:"";position:absolute;left:var(--ht-arrow-x, 50%);top:-5px;width:0;height:0;border:5px solid transparent;border-bottom-color:#1f1f1f;transform:translate(-50%)}.ht-btn{pointer-events:auto;background:transparent;border:0;color:inherit;height:30px;min-width:30px;padding:0 8px;border-radius:3px;cursor:pointer;font:inherit;display:inline-flex;align-items:center;justify-content:center;gap:4px;transition:background .1s ease}.ht-btn:hover{background:#ffffff1f}.ht-btn.disabled,.ht-btn:disabled{opacity:.35;cursor:not-allowed;pointer-events:none}.ht-btn.active{background:var(--be-accent);color:#fff}.ht-icon-only{padding:0;width:30px}.ht-type-btn{font-size:.82rem;font-weight:500;padding:0 6px 0 10px}.ht-type-btn.open{background:#ffffff26}.ht-type-label{white-space:nowrap}.ht-caret{opacity:.7;flex-shrink:0}.ht-sep{width:1px;height:18px;background:#ffffff26;margin:0 3px}.ht-content>.ht-sep:first-child,.ht-content>.ht-sep:last-child,.ht-content>.ht-sep+.ht-sep{display:none}.ht-danger:hover{background:#e03e3ed9!important;color:#fff}.ht-dropdown-wrap{position:relative}.ht-dropdown{position:absolute;top:calc(100% + 6px);left:0;min-width:180px;background:var(--be-bg);color:var(--be-text);border:1px solid var(--be-border);border-radius:4px;box-shadow:var(--be-shadow);padding:4px;z-index:10;display:flex;flex-direction:column;animation:ht-dropdown-in .12s ease}.ht-dropdown-scroll{overflow:hidden;flex:1;min-height:0}.ht-dropdown.above{top:auto;bottom:calc(100% + 6px)}@keyframes ht-dropdown-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.ht-dropdown-item{display:flex;align-items:center;gap:8px;padding:7px 10px;border:0;background:transparent;border-radius:3px;cursor:pointer;color:var(--be-text);font:inherit;font-size:.85rem;text-align:left;width:100%;transition:background .08s ease}.ht-dropdown-item:hover{background:var(--be-hover)}.ht-dropdown-item.active{color:var(--be-accent);font-weight:500}.ht-dropdown-icon{width:20px;height:20px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.ht-dropdown-icon svg{width:16px;height:16px}.ht-check{margin-left:auto;color:var(--be-accent);flex-shrink:0}.ht-align-item{gap:10px}.ht-color-dropdown{min-width:200px;padding:8px 10px;left:auto;right:0}.ht-color-section+.ht-color-section{margin-top:10px;padding-top:10px;border-top:1px solid var(--be-border)}.ht-color-label{font-size:.72rem;font-weight:600;color:var(--be-muted);text-transform:uppercase;letter-spacing:.04em;margin-bottom:6px}.ht-swatches{display:grid;grid-template-columns:repeat(10,1fr);gap:6px}.ht-swatch{width:20px;height:20px;border-radius:50%;border:1.5px solid var(--be-border);cursor:pointer;padding:0;transition:transform .1s ease,border-color .1s ease}.ht-swatch:hover{transform:scale(1.15);border-color:var(--be-muted)}.ht-swatch.active{border-color:var(--be-accent);box-shadow:0 0 0 2px var(--be-accent)}.ordered-list-menu{position:fixed;z-index:65;min-width:230px;background:var(--be-bg);color:var(--be-text);border:1px solid var(--be-border);border-radius:4px;padding:4px;box-shadow:var(--be-shadow);display:flex;flex-direction:column;gap:1px;animation:olm-fade-in .12s ease}@keyframes olm-fade-in{0%{opacity:0;transform:translateY(-3px)}to{opacity:1;transform:translateY(0)}}.olm-item{display:flex;align-items:center;gap:10px;background:transparent;border:0;color:inherit;padding:7px 9px;border-radius:3px;cursor:pointer;text-align:left;font-size:13.5px;line-height:1.4;font-family:inherit;transition:background-color .1s ease}.olm-item:hover{background:var(--be-hover)}.olm-item.disabled{cursor:not-allowed;opacity:.45}.olm-item.disabled:hover{background:transparent}.olm-label{flex:1 1 auto;min-width:0}.number-picker{position:fixed;z-index:70;background:var(--be-bg);color:var(--be-text);border:1px solid var(--be-border);border-radius:4px;padding:7px;box-shadow:var(--be-shadow);animation:olm-fade-in .12s ease}.np-form{display:flex;align-items:center;gap:8px}.np-input{width:90px;padding:5px 8px;border:1px solid var(--be-border);border-radius:3px;font-size:14px;font-family:ui-monospace,SF Mono,Cascadia Code,Consolas,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Microsoft YaHei,monospace,sans-serif;font-variant-numeric:tabular-nums;outline:none;color:var(--be-text);background:var(--be-bg);transition:border-color .1s ease,box-shadow .1s ease}.np-input:focus{border-color:var(--be-accent);box-shadow:0 0 0 3px #1c7ed624}.np-ok{background:var(--be-accent);color:#fff;border:0;padding:5px 14px;border-radius:3px;font-size:13.5px;font-weight:500;cursor:pointer;font-family:inherit;transition:background-color .1s ease}.np-ok:hover{background:var(--be-accent);filter:brightness(1.1)}.code-lang-picker{position:fixed;z-index:70;min-width:210px;background:var(--be-bg);color:var(--be-text);border:1px solid var(--be-border);border-radius:4px;padding:4px;box-shadow:var(--be-shadow);display:flex;flex-direction:column;overflow:hidden;animation:olm-fade-in .12s ease}.clp-list{display:flex;flex-direction:column;gap:1px;overflow:hidden;flex:1;min-height:0}.clp-item{display:flex;align-items:center;justify-content:space-between;gap:10px;background:transparent;border:0;color:inherit;padding:7px 9px;border-radius:3px;cursor:pointer;text-align:left;font-size:13.5px;line-height:1.4;font-family:inherit;flex-shrink:0;transition:background-color .1s ease}.clp-item:hover{background:var(--be-hover)}.clp-item.active{background:var(--be-active);color:var(--be-accent)}.clp-item-check{font-size:12px;color:var(--be-accent);flex-shrink:0}.clp-item-custom{border-top:1px solid var(--be-border);border-radius:0 0 3px 3px;margin-top:2px;padding-top:8px}.clp-custom{display:flex;flex-direction:column;gap:6px;padding:3px}.clp-form{display:flex;flex-direction:column;gap:6px}.clp-actions{display:flex;justify-content:flex-end;gap:6px}.clp-input{width:100%;box-sizing:border-box;padding:5px 8px;border:1px solid var(--be-border);border-radius:3px;font-size:14px;font-family:ui-monospace,SF Mono,Cascadia Code,Consolas,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Microsoft YaHei,monospace,sans-serif;text-transform:lowercase;outline:none;color:var(--be-text);background:var(--be-bg);transition:border-color .1s ease,box-shadow .1s ease}.clp-input:focus{border-color:var(--be-accent);box-shadow:0 0 0 3px #1c7ed624}.clp-ok{background:var(--be-accent);color:#fff;border:0;padding:5px 14px;border-radius:3px;font-size:13.5px;font-weight:500;cursor:pointer;font-family:inherit;white-space:nowrap;flex-shrink:0;transition:background-color .1s ease}.clp-ok:hover{background:var(--be-accent);filter:brightness(1.1)}.clp-cancel{background:transparent;border:1px solid var(--be-border);color:inherit;padding:5px 14px;border-radius:3px;font-size:13.5px;cursor:pointer;font-family:inherit;white-space:nowrap;transition:background-color .1s ease,border-color .1s ease}.clp-cancel:hover{background:var(--be-hover)}.block-host.block-dragging>.block-host-content>:first-child,.block-host.block-menu-active>.block-host-content>:first-child{background:var(--be-active)!important;border-radius:4px;transition:background-color .12s ease}.block-host.block-drop-target>.block-host-content>:first-child{background:var(--be-hover)!important;border-radius:4px}.block-host.block-drop-before:before{content:"";position:absolute;left:48px;right:0;top:-2px;height:3px;background:var(--be-accent);border-radius:2px;pointer-events:none;z-index:10}.block-host.block-drop-after:after{content:"";position:absolute;left:48px;right:0;bottom:-2px;height:3px;background:var(--be-accent);border-radius:2px;pointer-events:none;z-index:10}.block-host.block-drop-into{background:color-mix(in srgb,var(--be-accent) 18%,transparent);border-radius:6px;transition:background-color .12s ease}.block-host.block-drop-into:before,.block-host.block-drop-into:after{display:none!important}.block-host.block-drop-into>.block-host-content>:first-child{background:transparent!important}.block-host.block-being-dragged>.block-host-content>:first-child{opacity:0;visibility:hidden}.drag-ghost{position:fixed;z-index:9999;pointer-events:none;opacity:.85;transform:rotate(-.6deg);background:var(--be-bg);border:1px solid var(--be-border);border-radius:4px;padding:2px 8px;margin:-2px -8px;box-shadow:var(--be-shadow);overflow:hidden}.drag-ghost>*{margin:0!important}.block-list.drop-indicator-first:before,.block-list.drop-indicator-last:after{content:"";display:block;height:3px;margin:4px 0 4px 48px;background:#1c7ed6;border-radius:2px}.cross-block-selection-overlay{position:fixed;top:0;right:0;bottom:0;left:0;pointer-events:none;z-index:5}.cross-block-selection-rect{position:absolute;background:#235fd72e;border-radius:1px}@keyframes be-spinner-rotate{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.be-spinner-arc{transform-origin:12px 12px;animation:be-spinner-rotate .85s linear infinite;color:var(--be-accent)}.image-block-img{max-width:100%;max-height:600px;width:auto;height:auto;object-fit:contain;display:block;box-sizing:border-box}.block-image-wrapper{position:relative;display:inline-block;max-width:100%;margin:6px 0;padding:4px;border-radius:4px;-webkit-user-select:none;user-select:none;box-sizing:border-box}.image-block-wrapper-error,.image-block-wrapper-loading{display:block;width:100%!important;max-width:420px;min-width:200px;min-height:140px;height:auto!important}.image-block-wrapper-error .image-block-error-overlay,.image-block-wrapper-loading .image-block-upload-overlay{position:static;inset:auto;height:140px;min-height:140px;padding:16px 20px}.block-image-wrapper:hover{background:var(--be-hover)}.image-block-toolbar{position:absolute;top:6px;right:6px;display:flex;gap:4px;padding:3px;background:#ffffffeb;border:1px solid var(--be-border);border-radius:4px;box-shadow:0 1px 3px #00000014,0 4px 12px #0000001a;opacity:0;transform:translateY(-2px);transition:opacity .12s ease,transform .12s ease;z-index:5}.block-image-wrapper:hover .image-block-toolbar,.image-block-toolbar-visible{opacity:1!important;transform:translateY(0)!important}.theme-dark .image-block-toolbar,body.theme-dark .image-block-toolbar{background:#222428f0}.image-block-btn{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:0;background:transparent;color:var(--be-text);border-radius:2px;cursor:pointer;line-height:1}.image-block-btn:hover{background:var(--be-hover);color:var(--be-accent)}.image-block-upload-overlay{position:absolute;top:4px;right:4px;bottom:4px;left:4px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:16px 20px;background:#ffffffeb;border:1px solid var(--be-border);border-radius:4px;z-index:4;text-align:center}.theme-dark .image-block-upload-overlay,body.theme-dark .image-block-upload-overlay{background:#222428f0}.image-block-spinner{color:var(--be-accent);display:flex}.image-block-upload-label{font-size:13px;color:var(--be-text);font-weight:500}.image-block-progress-track{width:100%;max-width:260px;height:6px;background:var(--be-hover);border-radius:2px;overflow:hidden}.image-block-progress-bar{height:100%;background:var(--be-accent);border-radius:2px;transition:width .15s ease}.image-block-progress-pct{font-size:12px;color:var(--be-muted);font-variant-numeric:tabular-nums}.image-block-error-overlay{position:absolute;top:4px;right:4px;bottom:4px;left:4px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:16px 20px;background:#fff0f0f5;border:1px solid rgba(224,62,62,.28);border-radius:4px;z-index:4;text-align:center}.theme-dark .image-block-error-overlay,body.theme-dark .image-block-error-overlay{background:#4a1e1ee0;border-color:#ff6b6b59}.image-block-error-icon{width:28px;height:28px;border-radius:50%;background:var(--be-danger);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:16px;line-height:1}.image-block-error-title{font-size:13px;font-weight:600;color:var(--be-danger)}.image-block-error-msg{font-size:12px;color:var(--be-muted);max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.image-block-retry-btn{display:inline-flex;align-items:center;gap:4px;padding:5px 10px;margin-top:4px;background:var(--be-accent);color:#fff;border:0;border-radius:2px;font-size:12px;font-weight:500;cursor:pointer;transition:filter .12s ease}.image-block-retry-btn:hover{filter:brightness(1.08)}.image-block-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;min-height:140px;padding:24px 20px;border:2px dashed var(--be-border);border-radius:4px;cursor:pointer;color:var(--be-muted);background:var(--be-hover);transition:background .12s ease,border-color .12s ease,color .12s ease}.image-block-empty:hover{background:var(--be-active);border-color:var(--be-accent);color:var(--be-accent)}.image-block-empty-icon{color:inherit;display:flex;margin-bottom:4px}.image-block-empty-title{font-size:14px;font-weight:600;color:var(--be-text)}.image-block-empty:hover .image-block-empty-title{color:inherit}.image-block-empty-sub{font-size:12px;color:inherit;opacity:.85}.image-block-img{display:block;max-width:100%;height:auto;border-radius:2px;box-shadow:0 1px 2px #0000000f;-webkit-user-select:none;user-select:none;-webkit-user-drag:none;box-sizing:border-box}.image-block-main{position:relative;display:inline-block;line-height:0;max-width:100%;box-sizing:border-box}.image-block-resize-handle{position:absolute;right:0;bottom:0;width:14px;height:14px;cursor:nwse-resize;background:linear-gradient(135deg,transparent 50%,var(--be-accent) 50%);border-radius:2px;opacity:0;transition:opacity .12s ease;z-index:3}.block-image-wrapper:hover .image-block-resize-handle,.block-host.block-menu-active .image-block-resize-handle{opacity:1}.image-block-caption{width:100%;margin-top:8px;padding:2px 4px;font-size:13px;line-height:1.55;color:var(--be-muted);text-align:center;outline:none;min-height:1.2em;border-radius:2px}.image-block-caption:focus{color:var(--be-text);background:var(--be-hover)}.image-block-caption:empty:before{content:attr(data-placeholder);color:var(--be-muted);opacity:.6;pointer-events:none}.block-table-container{position:relative;padding:0;overflow-x:clip;overflow-y:visible;max-width:100%;box-sizing:border-box;outline:none}.table-toolbar{display:flex;align-items:center;gap:8px;-webkit-user-select:none;user-select:none;position:absolute;top:0;left:0;right:0;height:20px;padding-left:24px;pointer-events:none;z-index:3}.table-toolbar-title{display:inline-flex;align-items:center;gap:6px;height:14px;font-size:12px;line-height:14px;color:var(--be-muted);font-weight:500}.table-toolbar-icon{display:inline-flex;align-items:center;justify-content:center;height:14px;width:14px}.table-toolbar-icon svg{display:block}.table-wrapper{position:relative;margin-left:20px;padding:20px 0 0;width:calc(100% - 20px);overflow-x:auto;overflow-y:hidden}.table-col-handles{position:absolute;top:0;left:0;height:20px;z-index:5;pointer-events:none}.table-row-handles{position:absolute;top:0;left:0;width:20px;z-index:5;pointer-events:none}.table-corner-handle{position:absolute;top:0;left:0;width:20px;height:20px;cursor:pointer;z-index:6;pointer-events:auto;display:flex;align-items:center;justify-content:center;color:var(--be-muted);opacity:.6;transition:opacity .12s ease,color .12s ease,background .12s ease}.table-corner-handle:hover{opacity:1;color:var(--be-accent)}.table-corner-handle:before{content:"";width:10px;height:10px;border:1.5px solid currentColor;border-radius:2px}.table-row-strip{position:absolute;left:0;width:20px;cursor:pointer;pointer-events:auto;transition:background .12s ease}.table-row-strip:before{content:"";position:absolute;left:4px;top:50%;transform:translateY(-50%);width:12px;height:3px;border-radius:1.5px;background:var(--be-muted);opacity:0;transition:opacity .12s ease,background .12s ease}.table-row-strip:hover:before{opacity:.4}.table-row-strip.is-selected{background:var(--be-accent)}.table-row-strip.is-selected:before{opacity:1;background:#fff}.table-col-strip{position:absolute;top:0;height:20px;cursor:pointer;pointer-events:auto;transition:background .12s ease}.table-col-strip:before{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:3px;height:12px;border-radius:1.5px;background:var(--be-muted);opacity:0;transition:opacity .12s ease,background .12s ease}.table-col-strip:hover:before{opacity:.4}.table-col-strip.is-selected{background:var(--be-accent)}.table-col-strip.is-selected:before{opacity:1;background:#fff}.table-insert-dot{position:absolute;pointer-events:auto;cursor:pointer;z-index:7;opacity:.35;transition:opacity .12s ease,transform .12s ease}.table-insert-dot-row{left:5px;width:10px;height:10px;transform:translateY(-50%)}.table-insert-dot-col{top:5px;width:10px;height:10px;transform:translate(-50%)}.table-insert-dot:before{content:"";display:block;width:6px;height:6px;border-radius:50%;background:var(--be-muted);transition:background .12s ease,transform .12s ease}.table-insert-dot.is-hovered,.table-insert-dot:hover{opacity:1}.table-insert-dot.is-hovered:before,.table-insert-dot:hover:before{background:var(--be-accent);transform:scale(1.4)}.table-insert-dot:hover{transform:translateY(-50%) scale(1.3)}.table-insert-dot-col:hover{transform:translate(-50%) scale(1.3)}.table-col-insert-dots{position:absolute;top:0;left:20px;right:0;height:20px;pointer-events:none;z-index:7;transition:opacity .15s ease}.table-col-insert-dots.is-scrolling{opacity:0}.table-col-insert-dots.is-scrolling .table-insert-dot{pointer-events:none}.table-col-resizers{position:absolute;top:20px;left:0;width:100%;height:calc(100% - 20px);pointer-events:none;z-index:4}.table-col-resize-item{position:absolute;top:0;height:100%;pointer-events:none}.table-col-resizer{position:absolute;top:0;right:-3px;width:6px;height:100%;cursor:col-resize;background:transparent;pointer-events:auto;transition:background .1s ease;z-index:2}.table-col-resizer:hover,.table-col-resizer:active{background:var(--be-accent);opacity:.4}.block-table-grid,.table-block{width:max-content;border-collapse:collapse;table-layout:fixed;font-size:14px;line-height:1.5;color:var(--be-text);background:var(--be-bg);border:1px solid var(--be-border)}.block-table-grid .table-row,.table-block .table-row{border-bottom:1px solid var(--be-border)}.block-table-grid .table-row:last-child,.table-block .table-row:last-child{border-bottom:none}.block-table-grid .table-row-head,.table-block .table-row-head{background:var(--be-hover)}.table-cell-header{font-weight:600;text-align:left}.table-cell.be-align-left{text-align:left}.table-cell.be-align-center{text-align:center}.table-cell.be-align-right{text-align:right}.table-cell{vertical-align:top;padding:0;border-left:1px solid var(--be-border);border-right:1px solid var(--be-border);word-break:break-word;overflow-wrap:break-word}.table-cell:last-child{border-right:none}.table-cell-selected{background:var(--be-active)}.table-cell-focused{outline:2px solid var(--be-accent);outline-offset:-2px}.table-cell-inner{min-height:2.4em;padding:6px 8px;outline:none;white-space:pre-wrap;word-break:break-word;border-radius:2px}.block-ordered-list-wrapper .table-cell-inner,.table-cell-todo-wrapper .table-cell-inner{padding-left:0}.table-cell-inner.table-cell-heading{font-weight:600;font-size:1.05em}.table-cell-inner.table-cell-h1{font-size:2rem;font-weight:600;line-height:1.3}.table-cell-inner.table-cell-h2{font-size:1.5rem;font-weight:600;line-height:1.3}.table-cell-inner.table-cell-h3{font-size:1.25rem;font-weight:600;line-height:1.3}.table-cell-inner.table-cell-h4{font-size:1.1rem;font-weight:600;line-height:1.3}.table-cell-inner.table-cell-h5{font-size:1rem;font-weight:600;line-height:1.3}.table-cell-inner.table-cell-h6{font-size:.9rem;font-weight:600;color:var(--be-muted);line-height:1.3}.table-cell-inner.table-cell-bullet{position:relative;padding-left:calc(1.9em + 2px)}.table-cell-inner.table-cell-bullet:before{content:"•";position:absolute;left:4px;top:6px;width:1.9em;height:1.6em;display:flex;align-items:flex-start;justify-content:left;font-weight:700;font-size:.95em;color:inherit;opacity:.78;pointer-events:none}.block-ordered-list-wrapper .table-cell-ol-marker{margin-top:6px;margin-left:2px;cursor:pointer;width:calc(1.9em + 1px)}.table-cell-todo-wrapper{display:flex;align-items:flex-start;gap:8px;width:100%}.table-cell-todo-checkbox{margin-top:calc(.45em + 2px);margin-left:2px;width:16px;height:16px;accent-color:var(--be-accent);cursor:pointer;flex-shrink:0;pointer-events:auto}.table-cell-inner.table-cell-todo{flex:1 1 auto;min-width:0}.table-cell-inner.table-cell-todo.todo-checked{color:var(--be-muted);text-decoration:line-through;text-decoration-thickness:from-font}.table-cell-inner.table-cell-quote{border-left:3px solid var(--be-border);padding-left:14px;color:var(--be-muted);font-style:italic}.table-cell-inner.table-cell-code{font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Microsoft YaHei,monospace,sans-serif;font-size:.92em;line-height:1.55;white-space:pre-wrap;word-break:break-word;background:var(--be-hover);border-radius:4px;margin:3px}.table-cell.be-bg-gray,.table-cell.be-bg-brown,.table-cell.be-bg-orange,.table-cell.be-bg-yellow,.table-cell.be-bg-green,.table-cell.be-bg-blue,.table-cell.be-bg-purple,.table-cell.be-bg-pink,.table-cell.be-bg-red{border-radius:0}.table-cell-inner code{font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Microsoft YaHei,monospace,sans-serif;background:var(--be-hover);color:inherit;padding:.1em .3em;border:1px solid var(--be-border);border-radius:4px;font-size:.88em}.table-cell-inner code:hover{background:var(--be-active)}.table-cell-inner a{color:var(--be-accent);text-decoration:underline;text-decoration-color:var(--be-accent);text-underline-offset:2px;cursor:pointer}.table-cell-inner[contenteditable=false]{pointer-events:none;-webkit-user-select:none;user-select:none}.table-cell-inner[contenteditable=false] a{pointer-events:auto}.table-floating-toolbar{position:fixed;display:inline-flex;align-items:center;gap:2px;background:var(--be-bg);border:1px solid var(--be-border);border-radius:4px;padding:3px 4px;box-shadow:0 2px 8px #0000001f;z-index:1000;transform:translate(-50%,-100%);margin-top:-8px;-webkit-user-select:none;user-select:none}.table-floating-toolbar .ht-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:26px;padding:0;border:none;border-radius:2px;background:transparent;color:var(--be-text);cursor:pointer;transition:background .1s ease,color .1s ease}.table-floating-toolbar .ht-btn:hover{background:var(--be-hover)}.table-floating-toolbar .ht-btn.active{background:var(--be-accent);color:#fff}.table-floating-toolbar .ht-btn.ht-danger{color:var(--be-danger)}.table-floating-toolbar .ht-btn.ht-danger:hover{background:#e03e3e24}.table-floating-toolbar .ht-sep{width:1px;height:18px;background:var(--be-border);margin:0 2px}.theme-dark .block-table-grid,.theme-dark .table-block,body.theme-dark .block-table-grid,body.theme-dark .table-block{background:var(--be-bg)}.theme-dark .table-cell-inner,body.theme-dark .table-cell-inner{color:var(--be-text)}.block-editor.is-mobile .block-host{padding-left:0}.block-editor.is-mobile .block-handle{display:none!important}.mobile-toolbar{position:fixed;left:0;right:0;bottom:0;z-index:60;display:flex;align-items:stretch;gap:0;background:#1f1f1f;color:#fff;padding-bottom:env(safe-area-inset-bottom,0px);box-shadow:0 -1px #ffffff0f,0 -4px 12px #00000038;overflow:hidden;max-width:100vw}.mobile-toolbar .mt-left{display:flex;align-items:center;gap:0;flex-shrink:0;padding:0 2px}.mobile-toolbar .mt-btn{background:transparent;border:0;color:inherit;width:40px;height:40px;border-radius:4px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background .1s ease;-webkit-tap-highlight-color:transparent}.mobile-toolbar .mt-btn:active{background:#ffffff29}.mobile-toolbar .mt-btn.disabled,.mobile-toolbar .mt-btn:disabled{opacity:.3;cursor:default;pointer-events:none}.mobile-toolbar .mt-sep{flex-shrink:0;width:1px;align-self:center;height:24px;background:#ffffff26;margin:0 4px}.hover-toolbar-shell.mobile-shell{position:static;z-index:auto;pointer-events:auto;flex:1;min-width:0;display:flex;align-items:stretch;overflow:hidden}.hover-toolbar.mobile-mode{position:static;flex:1;min-width:0;display:flex;align-items:center;gap:0;background:transparent;color:inherit;box-shadow:none;border-radius:0;padding:0;height:40px;opacity:1;transform:none;visibility:visible;transition:none;pointer-events:auto}.hover-toolbar.mobile-mode.above:after,.hover-toolbar.mobile-mode.below:after{display:none}.hover-toolbar-shell.mobile-shell:not(.mobile-visible){display:none}.hover-toolbar.mobile-mode .ht-btn{height:40px;min-width:40px;padding:0 8px;-webkit-tap-highlight-color:transparent}.hover-toolbar.mobile-mode .ht-btn:active{background:#ffffff29}.hover-toolbar.mobile-mode .ht-nav-btn{width:32px;height:40px;border-radius:4px}.hover-toolbar.mobile-mode .ht-content{touch-action:pan-x}.ht-dropdown-mobile{position:fixed;top:auto;left:auto;z-index:5000}.hover-toolbar:not(.mobile-mode) .ht-dropdown.above{top:auto;bottom:calc(100% + 6px)}.block-editor.is-mobile{padding-bottom:calc(40px + env(safe-area-inset-bottom,0px))}
|
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Block, BlockId } from '../core/types';
|
|
2
|
+
declare const _default: import('vue').DefineComponent<{
|
|
3
|
+
block: Block;
|
|
4
|
+
/** Placeholder text shown when the block is empty. */
|
|
5
|
+
placeholder?: string;
|
|
6
|
+
}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
7
|
+
slashTrigger: (el: HTMLElement, blockId: BlockId, query: string) => any;
|
|
8
|
+
inputChanged: (blockId: BlockId, text: string) => any;
|
|
9
|
+
linkClick: (blockId: BlockId, offset: number) => any;
|
|
10
|
+
focusIn: (blockId: BlockId) => any;
|
|
11
|
+
}, string, import('vue').PublicProps, Readonly<{
|
|
12
|
+
block: Block;
|
|
13
|
+
/** Placeholder text shown when the block is empty. */
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
}> & Readonly<{
|
|
16
|
+
onSlashTrigger?: ((el: HTMLElement, blockId: BlockId, query: string) => any) | undefined;
|
|
17
|
+
onInputChanged?: ((blockId: BlockId, text: string) => any) | undefined;
|
|
18
|
+
onLinkClick?: ((blockId: BlockId, offset: number) => any) | undefined;
|
|
19
|
+
onFocusIn?: ((blockId: BlockId) => any) | undefined;
|
|
20
|
+
}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
21
|
+
export default _default;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Extension } from '../core/extension/Extension';
|
|
2
|
+
import { DocumentData } from '../core/types';
|
|
3
|
+
import { Editor } from '../core/Editor';
|
|
4
|
+
import { Theme, Locale } from '../i18n';
|
|
5
|
+
import { UploadImageHandler } from './imageUpload';
|
|
6
|
+
declare const _default: import('vue').DefineComponent<{
|
|
7
|
+
extensions?: readonly Extension[];
|
|
8
|
+
modelValue?: DocumentData;
|
|
9
|
+
editable?: boolean;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
/** 'light' (default) or 'dark'. */
|
|
12
|
+
theme?: Theme | string;
|
|
13
|
+
/** 'zh-CN' (default) or 'en-US'. Any non-empty non-'zh-CN' value ⇒ en-US. */
|
|
14
|
+
locale?: Locale | string;
|
|
15
|
+
/**
|
|
16
|
+
* Optional: image upload delegation function. When provided, the editor
|
|
17
|
+
* delegates image uploads to this function instead of using the built-in
|
|
18
|
+
* mock upload. The function receives the file name, File, an
|
|
19
|
+
* AbortController (aborted if the block is removed or the editor unmounts),
|
|
20
|
+
* and an onProgress callback (0–100). It must return a Promise that
|
|
21
|
+
* resolves with the final uploaded URL or rejects with an error.
|
|
22
|
+
*
|
|
23
|
+
* If omitted, the editor falls back to an internal mock upload that stores
|
|
24
|
+
* the file in memory as an object URL — fine for demos but NOT for
|
|
25
|
+
* persisted documents.
|
|
26
|
+
*/
|
|
27
|
+
uploadImage?: UploadImageHandler;
|
|
28
|
+
}, {
|
|
29
|
+
editor: Editor;
|
|
30
|
+
}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
31
|
+
"update:modelValue": (args_0: DocumentData) => any;
|
|
32
|
+
"cleanup:image-file": (fileId: number) => any;
|
|
33
|
+
}, string, import('vue').PublicProps, Readonly<{
|
|
34
|
+
extensions?: readonly Extension[];
|
|
35
|
+
modelValue?: DocumentData;
|
|
36
|
+
editable?: boolean;
|
|
37
|
+
placeholder?: string;
|
|
38
|
+
/** 'light' (default) or 'dark'. */
|
|
39
|
+
theme?: Theme | string;
|
|
40
|
+
/** 'zh-CN' (default) or 'en-US'. Any non-empty non-'zh-CN' value ⇒ en-US. */
|
|
41
|
+
locale?: Locale | string;
|
|
42
|
+
/**
|
|
43
|
+
* Optional: image upload delegation function. When provided, the editor
|
|
44
|
+
* delegates image uploads to this function instead of using the built-in
|
|
45
|
+
* mock upload. The function receives the file name, File, an
|
|
46
|
+
* AbortController (aborted if the block is removed or the editor unmounts),
|
|
47
|
+
* and an onProgress callback (0–100). It must return a Promise that
|
|
48
|
+
* resolves with the final uploaded URL or rejects with an error.
|
|
49
|
+
*
|
|
50
|
+
* If omitted, the editor falls back to an internal mock upload that stores
|
|
51
|
+
* the file in memory as an object URL — fine for demos but NOT for
|
|
52
|
+
* persisted documents.
|
|
53
|
+
*/
|
|
54
|
+
uploadImage?: UploadImageHandler;
|
|
55
|
+
}> & Readonly<{
|
|
56
|
+
"onUpdate:modelValue"?: ((args_0: DocumentData) => any) | undefined;
|
|
57
|
+
"onCleanup:image-file"?: ((fileId: number) => any) | undefined;
|
|
58
|
+
}>, {
|
|
59
|
+
placeholder: string;
|
|
60
|
+
editable: boolean;
|
|
61
|
+
extensions: readonly Extension[];
|
|
62
|
+
modelValue: DocumentData;
|
|
63
|
+
theme: Theme | string;
|
|
64
|
+
locale: Locale | string;
|
|
65
|
+
uploadImage: UploadImageHandler;
|
|
66
|
+
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
67
|
+
export default _default;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Block, BlockId } from '../core/types';
|
|
2
|
+
type DropPosition = 'before' | 'after' | 'first' | 'last' | 'into';
|
|
3
|
+
declare function setHandleDragging(active: boolean): void;
|
|
4
|
+
declare const _default: import('vue').DefineComponent<{
|
|
5
|
+
block: Block;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
hoveredBlockId: BlockId | null;
|
|
8
|
+
focusedBlockId: BlockId | null;
|
|
9
|
+
hasTextSelection?: boolean;
|
|
10
|
+
draggingBlockId?: BlockId | null;
|
|
11
|
+
dropTargetBlockId?: BlockId | null;
|
|
12
|
+
dropPosition?: DropPosition;
|
|
13
|
+
menuOpenBlockId?: BlockId | null;
|
|
14
|
+
}, {
|
|
15
|
+
setHandleDragging: typeof setHandleDragging;
|
|
16
|
+
}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
17
|
+
slashTrigger: (el: HTMLElement, blockId: BlockId, query: string) => any;
|
|
18
|
+
inputChanged: (blockId: BlockId, text: string) => any;
|
|
19
|
+
linkClick: (blockId: BlockId, offset: number) => any;
|
|
20
|
+
focusIn: (blockId: BlockId) => any;
|
|
21
|
+
openPlusMenu: (blockId: BlockId, anchor: HTMLElement) => any;
|
|
22
|
+
openSettingsMenu: (blockId: BlockId, anchor: HTMLElement) => any;
|
|
23
|
+
gripPointerDown: (blockId: BlockId, startX: number, startY: number, options: {
|
|
24
|
+
thresholdPx: number;
|
|
25
|
+
}) => any;
|
|
26
|
+
gripPointerUp: (blockId: BlockId) => any;
|
|
27
|
+
hoverChange: (blockId: BlockId | null) => any;
|
|
28
|
+
}, string, import('vue').PublicProps, Readonly<{
|
|
29
|
+
block: Block;
|
|
30
|
+
placeholder?: string;
|
|
31
|
+
hoveredBlockId: BlockId | null;
|
|
32
|
+
focusedBlockId: BlockId | null;
|
|
33
|
+
hasTextSelection?: boolean;
|
|
34
|
+
draggingBlockId?: BlockId | null;
|
|
35
|
+
dropTargetBlockId?: BlockId | null;
|
|
36
|
+
dropPosition?: DropPosition;
|
|
37
|
+
menuOpenBlockId?: BlockId | null;
|
|
38
|
+
}> & Readonly<{
|
|
39
|
+
onSlashTrigger?: ((el: HTMLElement, blockId: BlockId, query: string) => any) | undefined;
|
|
40
|
+
onInputChanged?: ((blockId: BlockId, text: string) => any) | undefined;
|
|
41
|
+
onLinkClick?: ((blockId: BlockId, offset: number) => any) | undefined;
|
|
42
|
+
onFocusIn?: ((blockId: BlockId) => any) | undefined;
|
|
43
|
+
onOpenPlusMenu?: ((blockId: BlockId, anchor: HTMLElement) => any) | undefined;
|
|
44
|
+
onOpenSettingsMenu?: ((blockId: BlockId, anchor: HTMLElement) => any) | undefined;
|
|
45
|
+
onGripPointerDown?: ((blockId: BlockId, startX: number, startY: number, options: {
|
|
46
|
+
thresholdPx: number;
|
|
47
|
+
}) => any) | undefined;
|
|
48
|
+
onGripPointerUp?: ((blockId: BlockId) => any) | undefined;
|
|
49
|
+
onHoverChange?: ((blockId: BlockId | null) => any) | undefined;
|
|
50
|
+
}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
51
|
+
export default _default;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Block, BlockId } from '../core/types';
|
|
2
|
+
import { BlockRenderItem } from './context';
|
|
3
|
+
type DropPosition = 'before' | 'after' | 'first' | 'last' | 'into';
|
|
4
|
+
declare function setBlockHandleDragging(blockId: BlockId, active: boolean): void;
|
|
5
|
+
declare const _default: import('vue').DefineComponent<{
|
|
6
|
+
items: readonly BlockRenderItem[];
|
|
7
|
+
/** Readonly Map<BlockId, Block> view of DocState.blocks — used by nested
|
|
8
|
+
* lists to resolve children block snapshots for the recursive items. */
|
|
9
|
+
blocksMap: ReadonlyMap<BlockId, Block>;
|
|
10
|
+
firstBlockPlaceholder?: string;
|
|
11
|
+
/** `true` for the recursively-rendered nested lists (disables the root-only
|
|
12
|
+
* drop indicators and the first-block placeholder). */
|
|
13
|
+
isNested?: boolean;
|
|
14
|
+
hoveredBlockId: BlockId | null;
|
|
15
|
+
focusedBlockId: BlockId | null;
|
|
16
|
+
hasTextSelection?: boolean;
|
|
17
|
+
draggingBlockId?: BlockId | null;
|
|
18
|
+
dropTargetBlockId?: BlockId | null;
|
|
19
|
+
dropPosition?: DropPosition;
|
|
20
|
+
menuOpenBlockId?: BlockId | null;
|
|
21
|
+
}, {
|
|
22
|
+
setBlockHandleDragging: typeof setBlockHandleDragging;
|
|
23
|
+
}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
24
|
+
slashTrigger: (el: HTMLElement, blockId: BlockId, query: string) => any;
|
|
25
|
+
inputChanged: (blockId: BlockId, text: string) => any;
|
|
26
|
+
linkClick: (blockId: BlockId, offset: number) => any;
|
|
27
|
+
focusIn: (blockId: BlockId) => any;
|
|
28
|
+
openPlusMenu: (blockId: BlockId, anchor: HTMLElement) => any;
|
|
29
|
+
openSettingsMenu: (blockId: BlockId, anchor: HTMLElement) => any;
|
|
30
|
+
gripPointerDown: (blockId: BlockId, startX: number, startY: number, options: {
|
|
31
|
+
thresholdPx: number;
|
|
32
|
+
}) => any;
|
|
33
|
+
gripPointerUp: (blockId: BlockId) => any;
|
|
34
|
+
hoverChange: (blockId: BlockId | null) => any;
|
|
35
|
+
}, string, import('vue').PublicProps, Readonly<{
|
|
36
|
+
items: readonly BlockRenderItem[];
|
|
37
|
+
/** Readonly Map<BlockId, Block> view of DocState.blocks — used by nested
|
|
38
|
+
* lists to resolve children block snapshots for the recursive items. */
|
|
39
|
+
blocksMap: ReadonlyMap<BlockId, Block>;
|
|
40
|
+
firstBlockPlaceholder?: string;
|
|
41
|
+
/** `true` for the recursively-rendered nested lists (disables the root-only
|
|
42
|
+
* drop indicators and the first-block placeholder). */
|
|
43
|
+
isNested?: boolean;
|
|
44
|
+
hoveredBlockId: BlockId | null;
|
|
45
|
+
focusedBlockId: BlockId | null;
|
|
46
|
+
hasTextSelection?: boolean;
|
|
47
|
+
draggingBlockId?: BlockId | null;
|
|
48
|
+
dropTargetBlockId?: BlockId | null;
|
|
49
|
+
dropPosition?: DropPosition;
|
|
50
|
+
menuOpenBlockId?: BlockId | null;
|
|
51
|
+
}> & Readonly<{
|
|
52
|
+
onSlashTrigger?: ((el: HTMLElement, blockId: BlockId, query: string) => any) | undefined;
|
|
53
|
+
onInputChanged?: ((blockId: BlockId, text: string) => any) | undefined;
|
|
54
|
+
onLinkClick?: ((blockId: BlockId, offset: number) => any) | undefined;
|
|
55
|
+
onFocusIn?: ((blockId: BlockId) => any) | undefined;
|
|
56
|
+
onOpenPlusMenu?: ((blockId: BlockId, anchor: HTMLElement) => any) | undefined;
|
|
57
|
+
onOpenSettingsMenu?: ((blockId: BlockId, anchor: HTMLElement) => any) | undefined;
|
|
58
|
+
onGripPointerDown?: ((blockId: BlockId, startX: number, startY: number, options: {
|
|
59
|
+
thresholdPx: number;
|
|
60
|
+
}) => any) | undefined;
|
|
61
|
+
onGripPointerUp?: ((blockId: BlockId) => any) | undefined;
|
|
62
|
+
onHoverChange?: ((blockId: BlockId | null) => any) | undefined;
|
|
63
|
+
}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
64
|
+
export default _default;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Attrs, BlockType, InlineSeq } from '../core/types';
|
|
2
|
+
export interface ParsedBlock {
|
|
3
|
+
readonly type: BlockType;
|
|
4
|
+
readonly attrs: Attrs;
|
|
5
|
+
readonly content: InlineSeq;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Parse clipboard content into an array of block specs.
|
|
9
|
+
* Prefers HTML; falls back to plain text.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseClipboard(html: string | null, plainText: string | null): ParsedBlock[];
|