@jackuait/blok 0.10.0-beta.16 → 0.10.0-beta.18
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/dist/blok.mjs +2 -2
- package/dist/chunks/{blok-CEVrVqlx.mjs → blok-DK7jJnLH.mjs} +1393 -1381
- package/dist/chunks/{constants-BURnHRy_.mjs → constants-B0Vnu9kC.mjs} +48 -48
- package/dist/chunks/{tools-Dt2I14vP.mjs → tools-QLa-0Rbm.mjs} +816 -786
- package/dist/full.mjs +3 -3
- package/dist/react.mjs +2 -2
- package/dist/tools.mjs +2 -2
- package/package.json +1 -1
- package/src/cli/commands/convert-gdocs/index.ts +3 -1
- package/src/cli/commands/convert-html/index.ts +3 -1
- package/src/components/icons/index.ts +16 -0
- package/src/components/modules/blockEvents/composers/keyboardNavigation.ts +18 -0
- package/src/components/modules/rectangleSelection.ts +25 -5
- package/src/components/modules/toolbar/index.ts +48 -7
- package/src/styles/main.css +16 -0
- package/src/tools/callout/constants.ts +2 -1
- package/src/tools/callout/dom-builder.ts +11 -1
- package/src/tools/callout/index.ts +6 -0
- package/src/tools/code/constants.ts +9 -1
- package/src/tools/code/dom-builder.ts +90 -54
- package/src/tools/code/index.ts +33 -19
- package/src/tools/table/table-cell-blocks.ts +3 -1
package/dist/full.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { n as e, t } from "./chunks/blok-
|
|
2
|
-
import {
|
|
1
|
+
import { n as e, t } from "./chunks/blok-DK7jJnLH.mjs";
|
|
2
|
+
import { tr as n } from "./chunks/constants-B0Vnu9kC.mjs";
|
|
3
3
|
import { t as r } from "./chunks/objectSpread2-CWwMYL_U.mjs";
|
|
4
|
-
import { _ as i, a, c as o, g as s, i as c, l, m as u, n as d, o as f, s as p, t as m, v as h } from "./chunks/tools-
|
|
4
|
+
import { _ as i, a, c as o, g as s, i as c, l, m as u, n as d, o as f, s as p, t as m, v as h } from "./chunks/tools-QLa-0Rbm.mjs";
|
|
5
5
|
//#region src/full.ts
|
|
6
6
|
var g = {
|
|
7
7
|
paragraph: {
|
package/dist/react.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as e } from "./chunks/blok-
|
|
2
|
-
import "./chunks/constants-
|
|
1
|
+
import { t as e } from "./chunks/blok-DK7jJnLH.mjs";
|
|
2
|
+
import "./chunks/constants-B0Vnu9kC.mjs";
|
|
3
3
|
import { t } from "./chunks/objectSpread2-CWwMYL_U.mjs";
|
|
4
4
|
import { t as n } from "./chunks/objectWithoutProperties-D0XxKB4n.mjs";
|
|
5
5
|
import { forwardRef as r, useEffect as i, useMemo as a, useRef as o, useState as s } from "react";
|
package/dist/tools.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { m as e } from "./chunks/constants-
|
|
2
|
-
import { _ as t, a as n, c as r, d as i, f as a, g as o, h as s, i as c, l, m as u, n as d, o as f, p, r as m, s as h, t as g, u as _, v } from "./chunks/tools-
|
|
1
|
+
import { m as e } from "./chunks/constants-B0Vnu9kC.mjs";
|
|
2
|
+
import { _ as t, a as n, c as r, d as i, f as a, g as o, h as s, i as c, l, m as u, n as d, o as f, p, r as m, s as h, t as g, u as _, v } from "./chunks/tools-QLa-0Rbm.mjs";
|
|
3
3
|
export { l as Bold, p as Callout, _ as Code, e as Convert, a as Divider, t as Header, m as InlineCode, r as Italic, h as Link, o as List, f as Marker, v as Paragraph, i as Quote, c as Strikethrough, s as Table, u as Toggle, n as Underline, g as defaultBlockTools, d as defaultInlineTools };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jackuait/blok",
|
|
3
|
-
"version": "0.10.0-beta.
|
|
3
|
+
"version": "0.10.0-beta.18",
|
|
4
4
|
"description": "Blok — headless, highly extensible rich text editor built for developers who need to implement a block-based editing experience (similar to Notion) without building it from scratch",
|
|
5
5
|
"module": "dist/blok.mjs",
|
|
6
6
|
"types": "./types/index.d.ts",
|
|
@@ -4,6 +4,8 @@ import { sanitize } from '../convert-html/sanitizer';
|
|
|
4
4
|
import { buildBlocks } from '../convert-html/block-builder';
|
|
5
5
|
import type { OutputData } from '../convert-html/types';
|
|
6
6
|
|
|
7
|
+
declare const __CLI_VERSION__: string;
|
|
8
|
+
|
|
7
9
|
/**
|
|
8
10
|
* Convert Google Docs HTML to Blok JSON.
|
|
9
11
|
* Runs: Google Docs preprocess -> general preprocess -> sanitize -> build blocks -> serialize.
|
|
@@ -18,7 +20,7 @@ export function convertGdocs(html: string): string {
|
|
|
18
20
|
sanitize(wrapper);
|
|
19
21
|
|
|
20
22
|
const blocks = buildBlocks(wrapper);
|
|
21
|
-
const output: OutputData = { version: '
|
|
23
|
+
const output: OutputData = { version: typeof __CLI_VERSION__ !== 'undefined' ? __CLI_VERSION__ : 'dev', blocks };
|
|
22
24
|
|
|
23
25
|
return JSON.stringify(output);
|
|
24
26
|
}
|
|
@@ -3,6 +3,8 @@ import { sanitize } from './sanitizer';
|
|
|
3
3
|
import { buildBlocks } from './block-builder';
|
|
4
4
|
import type { OutputData } from './types';
|
|
5
5
|
|
|
6
|
+
declare const __CLI_VERSION__: string;
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* Convert HTML to Blok JSON.
|
|
8
10
|
* Runs: preprocess → sanitize → build blocks → serialize.
|
|
@@ -15,7 +17,7 @@ export function convertHtml(html: string): string {
|
|
|
15
17
|
sanitize(wrapper);
|
|
16
18
|
|
|
17
19
|
const blocks = buildBlocks(wrapper);
|
|
18
|
-
const output: OutputData = { version: '
|
|
20
|
+
const output: OutputData = { version: typeof __CLI_VERSION__ !== 'undefined' ? __CLI_VERSION__ : 'dev', blocks };
|
|
19
21
|
|
|
20
22
|
return JSON.stringify(output);
|
|
21
23
|
}
|
|
@@ -95,6 +95,22 @@ export const IconPlus = `
|
|
|
95
95
|
</svg>
|
|
96
96
|
`;
|
|
97
97
|
|
|
98
|
+
// Chevron Down icon
|
|
99
|
+
export const IconChevronDown = `
|
|
100
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
101
|
+
<path d="m6 8 4 4 4-4" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
|
102
|
+
</svg>
|
|
103
|
+
`;
|
|
104
|
+
|
|
105
|
+
// Ellipsis (horizontal three dots) icon
|
|
106
|
+
export const IconEllipsis = `
|
|
107
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
108
|
+
<circle cx="5" cy="10" r="1.25" fill="currentColor"/>
|
|
109
|
+
<circle cx="10" cy="10" r="1.25" fill="currentColor"/>
|
|
110
|
+
<circle cx="15" cy="10" r="1.25" fill="currentColor"/>
|
|
111
|
+
</svg>
|
|
112
|
+
`;
|
|
113
|
+
|
|
98
114
|
// Chevron Left icon
|
|
99
115
|
export const IconChevronLeft = `
|
|
100
116
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
@@ -344,6 +344,15 @@ export class KeyboardNavigation extends BlockEventComposer {
|
|
|
344
344
|
// Previous sibling exists in the same parent — fall through to merge/remove logic
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
+
/**
|
|
348
|
+
* Don't merge across table cell boundaries.
|
|
349
|
+
* When the caret is at the start of the first input in a table cell, Backspace should be a no-op
|
|
350
|
+
* rather than merging the previous cell's last block into the current cell.
|
|
351
|
+
*/
|
|
352
|
+
if (this.isCurrentBlockInsideTableCell) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
347
356
|
/**
|
|
348
357
|
* Backspace at the start of the first Block should do nothing
|
|
349
358
|
*/
|
|
@@ -447,6 +456,15 @@ export class KeyboardNavigation extends BlockEventComposer {
|
|
|
447
456
|
return;
|
|
448
457
|
}
|
|
449
458
|
|
|
459
|
+
/**
|
|
460
|
+
* Don't merge across table cell boundaries.
|
|
461
|
+
* When the caret is at the end of the last input in a table cell, Delete should be a no-op
|
|
462
|
+
* rather than merging the next cell's first block into the current cell.
|
|
463
|
+
*/
|
|
464
|
+
if (this.isCurrentBlockInsideTableCell) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
450
468
|
/**
|
|
451
469
|
* If next Block is empty, it should be removed just like a character
|
|
452
470
|
*/
|
|
@@ -72,6 +72,14 @@ export class RectangleSelection extends Module {
|
|
|
72
72
|
*/
|
|
73
73
|
private mouseDownWithinBoundsFromContentEditable = false;
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Set when the user mousedowns in a position that should eventually close the toolbar
|
|
77
|
+
* if a drag begins, but NOT immediately (to avoid interfering with button clicks like
|
|
78
|
+
* the plus button). The toolbar is closed on the first mousemove after mousedown.
|
|
79
|
+
* Cleared on mouseup / endSelection.
|
|
80
|
+
*/
|
|
81
|
+
private pendingToolbarClose = false;
|
|
82
|
+
|
|
75
83
|
/**
|
|
76
84
|
* Is scrolling now
|
|
77
85
|
*/
|
|
@@ -187,7 +195,7 @@ export class RectangleSelection extends Module {
|
|
|
187
195
|
|
|
188
196
|
const selectorsToAvoid = [
|
|
189
197
|
createSelector(DATA_ATTR.elementContent),
|
|
190
|
-
createSelector(DATA_ATTR.
|
|
198
|
+
createSelector(DATA_ATTR.settingsToggler),
|
|
191
199
|
createSelector(DATA_ATTR.popover),
|
|
192
200
|
INLINE_TOOLBAR_INTERFACE_SELECTOR,
|
|
193
201
|
];
|
|
@@ -202,12 +210,13 @@ export class RectangleSelection extends Module {
|
|
|
202
210
|
}
|
|
203
211
|
|
|
204
212
|
/**
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
213
|
+
* Schedule toolbar close for the first mousemove (i.e. when the user actually drags).
|
|
214
|
+
* Deferring prevents a plain click (e.g. on the plus button) from accidentally closing
|
|
215
|
+
* the toolbar before its own click handler fires.
|
|
216
|
+
* Only close if starting within the horizontal bounds of the editor.
|
|
208
217
|
*/
|
|
209
218
|
if (withinEditorHorizontally) {
|
|
210
|
-
this.
|
|
219
|
+
this.pendingToolbarClose = true;
|
|
211
220
|
}
|
|
212
221
|
|
|
213
222
|
this.mousedown = true;
|
|
@@ -221,6 +230,7 @@ export class RectangleSelection extends Module {
|
|
|
221
230
|
public endSelection(): void {
|
|
222
231
|
this.mousedown = false;
|
|
223
232
|
this.mouseDownWithinBoundsFromContentEditable = false;
|
|
233
|
+
this.pendingToolbarClose = false;
|
|
224
234
|
this.startX = 0;
|
|
225
235
|
this.startY = 0;
|
|
226
236
|
this.anchorBlockIndex = null;
|
|
@@ -361,6 +371,16 @@ export class RectangleSelection extends Module {
|
|
|
361
371
|
this.Blok.Toolbar.close();
|
|
362
372
|
}
|
|
363
373
|
|
|
374
|
+
/**
|
|
375
|
+
* Close the toolbar on the first actual drag movement after a mousedown
|
|
376
|
+
* that was scheduled to close it. Using mousemove instead of mousedown ensures
|
|
377
|
+
* plain button clicks (e.g. the plus button) don't close the toolbar prematurely.
|
|
378
|
+
*/
|
|
379
|
+
if (this.pendingToolbarClose) {
|
|
380
|
+
this.pendingToolbarClose = false;
|
|
381
|
+
this.Blok.Toolbar.close();
|
|
382
|
+
}
|
|
383
|
+
|
|
364
384
|
this.changingRectangle(mouseEvent);
|
|
365
385
|
this.scrollByZones(mouseEvent.clientY);
|
|
366
386
|
}
|
|
@@ -427,8 +427,11 @@ export class Toolbar extends Module<ToolbarNodes> {
|
|
|
427
427
|
*/
|
|
428
428
|
const focusIsInsideCell = this.isFocusInsideTableCell();
|
|
429
429
|
const isCalloutFirstChild = this.isFirstChildOfCallout(targetBlock);
|
|
430
|
+
const isCalloutBlock = targetBlock.name === 'callout';
|
|
430
431
|
|
|
431
|
-
|
|
432
|
+
// Hide plus button for callout blocks and their first children to avoid
|
|
433
|
+
// overlap with the callout emoji icon in the left padding area.
|
|
434
|
+
plusButton.style.display = (isCalloutFirstChild || isCalloutBlock) ? 'none' : '';
|
|
432
435
|
|
|
433
436
|
if (settingsToggler) {
|
|
434
437
|
settingsToggler.style.display = (focusIsInsideCell || isCalloutFirstChild) ? 'none' : '';
|
|
@@ -519,6 +522,7 @@ export class Toolbar extends Module<ToolbarNodes> {
|
|
|
519
522
|
|
|
520
523
|
if (hasLeftEdgeInteraction && this.nodes.actions) {
|
|
521
524
|
this.nodes.actions.style.pointerEvents = 'none';
|
|
525
|
+
this.restoreSettingsTogglerForLeftEdgeBlock(targetBlock);
|
|
522
526
|
}
|
|
523
527
|
|
|
524
528
|
/**
|
|
@@ -526,14 +530,15 @@ export class Toolbar extends Module<ToolbarNodes> {
|
|
|
526
530
|
* so toolbar buttons align with the block content edge, even when
|
|
527
531
|
* consumer CSS overrides the block content's margin.
|
|
528
532
|
*
|
|
529
|
-
* Uses
|
|
530
|
-
*
|
|
531
|
-
|
|
533
|
+
* Uses Math.max to guarantee the actions container (positioned via right:100%)
|
|
534
|
+
* never extends beyond the left edge of the viewport, which would make the
|
|
535
|
+
* drag handle unreachable by pointer events.
|
|
532
536
|
*/
|
|
533
537
|
if (blockContentElement && this.nodes.content) {
|
|
534
538
|
const blockMarginLeft = parseFloat(getComputedStyle(blockContentElement).marginLeft) || 0;
|
|
539
|
+
const actionsWidth = this.nodes.actions?.offsetWidth ?? 0;
|
|
535
540
|
|
|
536
|
-
this.nodes.content.style.marginLeft = `${blockMarginLeft}px`;
|
|
541
|
+
this.nodes.content.style.marginLeft = `${Math.max(blockMarginLeft, actionsWidth)}px`;
|
|
537
542
|
}
|
|
538
543
|
}
|
|
539
544
|
|
|
@@ -655,11 +660,13 @@ export class Toolbar extends Module<ToolbarNodes> {
|
|
|
655
660
|
|
|
656
661
|
/**
|
|
657
662
|
* Sync toolbar content wrapper's margin with the block content element.
|
|
663
|
+
* Clamp to actionsWidth so actions never extend beyond the left viewport edge.
|
|
658
664
|
*/
|
|
659
665
|
if (blockContentElement && this.nodes.content) {
|
|
660
666
|
const blockMarginLeft = parseFloat(getComputedStyle(blockContentElement).marginLeft) || 0;
|
|
667
|
+
const actionsWidth = this.nodes.actions?.offsetWidth ?? 0;
|
|
661
668
|
|
|
662
|
-
this.nodes.content.style.marginLeft = `${blockMarginLeft}px`;
|
|
669
|
+
this.nodes.content.style.marginLeft = `${Math.max(blockMarginLeft, actionsWidth)}px`;
|
|
663
670
|
}
|
|
664
671
|
}
|
|
665
672
|
|
|
@@ -851,14 +858,48 @@ export class Toolbar extends Module<ToolbarNodes> {
|
|
|
851
858
|
|
|
852
859
|
const focusIsInsideCell = this.isFocusInsideTableCell();
|
|
853
860
|
const isCalloutFirstChild = this.hoveredBlock !== null && this.isFirstChildOfCallout(this.hoveredBlock);
|
|
861
|
+
const isCalloutBlock = this.hoveredBlock?.name === 'callout';
|
|
854
862
|
|
|
855
|
-
plusButton.style.display = isCalloutFirstChild ? 'none' : '';
|
|
863
|
+
plusButton.style.display = (isCalloutFirstChild || isCalloutBlock) ? 'none' : '';
|
|
856
864
|
|
|
857
865
|
if (settingsToggler) {
|
|
858
866
|
settingsToggler.style.display = (focusIsInsideCell || isCalloutFirstChild) ? 'none' : '';
|
|
859
867
|
}
|
|
860
868
|
}
|
|
861
869
|
|
|
870
|
+
/**
|
|
871
|
+
* Re-enables pointer-events on the settings toggler (and callout drag zone) after
|
|
872
|
+
* the actions container has been set to pointer-events: none for left-edge blocks.
|
|
873
|
+
*
|
|
874
|
+
* For callout blocks: also wires the dedicated drag zone as the drag handle and
|
|
875
|
+
* re-enables the settings toggler so the settings menu remains accessible.
|
|
876
|
+
* The emoji button (at x=32px) no longer overlaps the actions zone (x=[0,29px])
|
|
877
|
+
* because the callout uses pl-8 (32px) left padding.
|
|
878
|
+
*
|
|
879
|
+
* For all other left-edge blocks (toggle, header with arrow): simply re-enables the
|
|
880
|
+
* settings toggler so it continues to function as the drag handle.
|
|
881
|
+
*/
|
|
882
|
+
private restoreSettingsTogglerForLeftEdgeBlock(targetBlock: Block): void {
|
|
883
|
+
if (targetBlock.name === 'callout') {
|
|
884
|
+
if (this.nodes.settingsToggler) {
|
|
885
|
+
this.nodes.settingsToggler.style.pointerEvents = 'auto';
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
const calloutDragZone = targetBlock.holder.querySelector<HTMLElement>('[data-callout-drag-zone]');
|
|
889
|
+
|
|
890
|
+
if (calloutDragZone) {
|
|
891
|
+
calloutDragZone.style.pointerEvents = 'auto';
|
|
892
|
+
targetBlock.setupDraggable(calloutDragZone, this.Blok.DragManager);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
if (this.nodes.settingsToggler) {
|
|
899
|
+
this.nodes.settingsToggler.style.pointerEvents = 'auto';
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
862
903
|
/**
|
|
863
904
|
* If the block is inside a table cell, resolve to the parent table block.
|
|
864
905
|
* This ensures the toolbar shows for the table when clicking/focusing inside cells.
|
package/src/styles/main.css
CHANGED
|
@@ -644,6 +644,10 @@
|
|
|
644
644
|
--color-swatch-ring-hover: var(--blok-swatch-ring-hover);
|
|
645
645
|
--color-swatch-ring-active: var(--blok-swatch-ring-active);
|
|
646
646
|
|
|
647
|
+
/* Surface tokens (code blocks, secondary containers) */
|
|
648
|
+
--color-bg-secondary: var(--blok-bg-secondary);
|
|
649
|
+
--color-border-secondary: var(--blok-border-secondary);
|
|
650
|
+
|
|
647
651
|
}
|
|
648
652
|
|
|
649
653
|
@layer utilities {
|
|
@@ -725,6 +729,10 @@
|
|
|
725
729
|
--blok-swatch-ring-hover: rgba(0, 0, 0, 0.10);
|
|
726
730
|
--blok-swatch-ring-active: rgba(0, 0, 0, 0.30);
|
|
727
731
|
|
|
732
|
+
/* Surface tokens (code blocks, secondary containers) */
|
|
733
|
+
--blok-bg-secondary: #f7f8fa;
|
|
734
|
+
--blok-border-secondary: rgba(55, 53, 47, 0.09);
|
|
735
|
+
|
|
728
736
|
/* Marker colors — light theme */
|
|
729
737
|
--blok-color-gray-text: #787774;
|
|
730
738
|
--blok-color-gray-bg: #f1f1ef;
|
|
@@ -818,6 +826,10 @@
|
|
|
818
826
|
--blok-swatch-ring-hover: rgba(255, 255, 255, 0.15);
|
|
819
827
|
--blok-swatch-ring-active: rgba(255, 255, 255, 0.35);
|
|
820
828
|
|
|
829
|
+
/* Surface tokens (code blocks, secondary containers) */
|
|
830
|
+
--blok-bg-secondary: rgba(255, 255, 255, 0.04);
|
|
831
|
+
--blok-border-secondary: rgba(255, 255, 255, 0.08);
|
|
832
|
+
|
|
821
833
|
/* Marker colors — dark theme */
|
|
822
834
|
--blok-color-gray-text: #9b9b9b;
|
|
823
835
|
--blok-color-gray-bg: #2f2f2f;
|
|
@@ -910,6 +922,10 @@
|
|
|
910
922
|
--blok-swatch-ring-hover: rgba(255, 255, 255, 0.15);
|
|
911
923
|
--blok-swatch-ring-active: rgba(255, 255, 255, 0.35);
|
|
912
924
|
|
|
925
|
+
/* Surface tokens (code blocks, secondary containers) */
|
|
926
|
+
--blok-bg-secondary: rgba(255, 255, 255, 0.04);
|
|
927
|
+
--blok-border-secondary: rgba(255, 255, 255, 0.08);
|
|
928
|
+
|
|
913
929
|
/* Marker colors — dark theme */
|
|
914
930
|
--blok-color-gray-text: #9b9b9b;
|
|
915
931
|
--blok-color-gray-bg: #2f2f2f;
|
|
@@ -25,7 +25,8 @@ export const EMOJI_CATEGORY_FLAGS_KEY = 'tools.callout.emojiCategoryFlags';
|
|
|
25
25
|
export const DEFAULT_EMOJI = '💡';
|
|
26
26
|
|
|
27
27
|
// CSS — Tailwind classes
|
|
28
|
-
export const WRAPPER_STYLES = 'rounded-xl
|
|
28
|
+
export const WRAPPER_STYLES = 'rounded-xl pl-8 pr-4 py-[5px] my-1 flex items-start gap-2 relative';
|
|
29
29
|
// h-[38px] = py-[7px]×2 + 1.5rem×1 = 14+24; explicit height prevents platform-specific emoji font metrics from inflating the button
|
|
30
30
|
export const EMOJI_BUTTON_STYLES = 'text-[1.5rem] leading-[1] cursor-pointer bg-transparent border-0 px-0 py-[7px] h-[38px] flex-shrink-0 select-none';
|
|
31
31
|
export const CHILDREN_STYLES = 'flex-1 min-w-0';
|
|
32
|
+
export const DRAG_ZONE_STYLES = 'absolute left-0 top-0 h-full cursor-grab select-none';
|
|
@@ -6,12 +6,14 @@ import {
|
|
|
6
6
|
WRAPPER_STYLES,
|
|
7
7
|
EMOJI_BUTTON_STYLES,
|
|
8
8
|
CHILDREN_STYLES,
|
|
9
|
+
DRAG_ZONE_STYLES,
|
|
9
10
|
} from './constants';
|
|
10
11
|
|
|
11
12
|
export interface CalloutDOMRefs {
|
|
12
13
|
wrapper: HTMLElement;
|
|
13
14
|
emojiButton: HTMLButtonElement;
|
|
14
15
|
childContainer: HTMLElement;
|
|
16
|
+
dragZone: HTMLElement;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export interface BuildCalloutDOMOptions {
|
|
@@ -51,5 +53,13 @@ export function buildCalloutDOM(options: BuildCalloutDOMOptions): CalloutDOMRefs
|
|
|
51
53
|
wrapper.appendChild(emojiButton);
|
|
52
54
|
wrapper.appendChild(childContainer);
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
// Drag zone — covers left padding area (x=[0,16px]) for drag handle,
|
|
57
|
+
// sits behind emoji button so emoji clicks pass through
|
|
58
|
+
const dragZone = document.createElement('span');
|
|
59
|
+
dragZone.className = DRAG_ZONE_STYLES;
|
|
60
|
+
dragZone.style.width = '32px'; // matches pl-8 left padding
|
|
61
|
+
dragZone.setAttribute('data-callout-drag-zone', '');
|
|
62
|
+
wrapper.prepend(dragZone);
|
|
63
|
+
|
|
64
|
+
return { wrapper, emojiButton, childContainer, dragZone };
|
|
55
65
|
}
|
|
@@ -66,6 +66,7 @@ export class CalloutTool implements BlockTool {
|
|
|
66
66
|
private _dom: CalloutDOMRefs | null = null;
|
|
67
67
|
private _emojiPicker: EmojiPicker | null = null;
|
|
68
68
|
private _colorPicker: ColorPickerHandle | null = null;
|
|
69
|
+
private _dragZone: HTMLElement | null = null;
|
|
69
70
|
private blockId?: string;
|
|
70
71
|
|
|
71
72
|
constructor({ data, api, readOnly, block }: BlockToolConstructorOptions<CalloutData, CalloutConfig>) {
|
|
@@ -120,6 +121,7 @@ export class CalloutTool implements BlockTool {
|
|
|
120
121
|
});
|
|
121
122
|
|
|
122
123
|
this._dom = dom;
|
|
124
|
+
this._dragZone = dom.dragZone;
|
|
123
125
|
this.applyColors();
|
|
124
126
|
|
|
125
127
|
if (!this.readOnly) {
|
|
@@ -260,6 +262,10 @@ export class CalloutTool implements BlockTool {
|
|
|
260
262
|
}
|
|
261
263
|
}
|
|
262
264
|
|
|
265
|
+
public get dragZone(): HTMLElement | null {
|
|
266
|
+
return this._dragZone;
|
|
267
|
+
}
|
|
268
|
+
|
|
263
269
|
private syncPickerActiveColors(): void {
|
|
264
270
|
if (this._colorPicker === null) {
|
|
265
271
|
return;
|
|
@@ -53,7 +53,7 @@ export const LANGUAGES: LanguageEntry[] = [
|
|
|
53
53
|
];
|
|
54
54
|
|
|
55
55
|
// CSS — Tailwind classes
|
|
56
|
-
export const WRAPPER_STYLES = 'flex flex-col rounded-lg bg-bg-secondary overflow-hidden my-1';
|
|
56
|
+
export const WRAPPER_STYLES = 'flex flex-col rounded-lg border border-border-secondary bg-bg-secondary overflow-hidden my-1';
|
|
57
57
|
export const HEADER_STYLES = 'flex items-center gap-1 px-3 py-1.5 border-b border-border-primary text-xs text-gray-text';
|
|
58
58
|
export const LANGUAGE_BUTTON_STYLES = 'px-1.5 py-0.5 rounded cursor-pointer bg-transparent border-0 text-xs text-gray-text font-medium transition-colors can-hover:hover:bg-item-hover-bg select-none';
|
|
59
59
|
export const HEADER_BUTTON_STYLES = 'p-1 rounded cursor-pointer bg-transparent border-0 text-gray-text transition-colors can-hover:hover:bg-item-hover-bg flex items-center justify-center';
|
|
@@ -73,6 +73,14 @@ export const TAB_ACTIVE_STYLES = 'bg-blue-500 text-white';
|
|
|
73
73
|
export const TAB_INACTIVE_STYLES = 'bg-transparent text-gray-text can-hover:hover:bg-item-hover-bg';
|
|
74
74
|
export const PREVIEW_AREA_STYLES = 'px-4 py-3 overflow-x-auto min-h-[1.5em] flex justify-center';
|
|
75
75
|
|
|
76
|
+
// i18n key — preview toggle
|
|
77
|
+
export const PREVIEW_TOGGLE_KEY = 'tools.code.previewToggle';
|
|
78
|
+
|
|
79
|
+
// CSS — more menu dropdown
|
|
80
|
+
export const MORE_MENU_STYLES = 'absolute right-0 top-full mt-1 min-w-[10rem] rounded-lg bg-bg-secondary border border-border-secondary shadow-lg p-1 z-10';
|
|
81
|
+
export const MORE_MENU_ITEM_STYLES = 'flex items-center gap-2 w-full px-2.5 py-1.5 rounded text-xs text-gray-text cursor-pointer bg-transparent border-0 transition-colors can-hover:hover:bg-item-hover-bg select-none';
|
|
82
|
+
export const MORE_MENU_ITEM_ACTIVE_STYLES = 'text-blue-500';
|
|
83
|
+
|
|
76
84
|
// Shiki theme names for syntax highlighting
|
|
77
85
|
export const SHIKI_LIGHT_THEME = 'one-light';
|
|
78
86
|
export const SHIKI_DARK_THEME = 'vitesse-dark';
|