@jackuait/blok 0.23.1 → 0.23.3
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 +3 -1
- package/dist/blok.cjs +1 -1
- package/dist/blok.iife.js +73 -73
- package/dist/blok.mjs +3 -3
- package/dist/blok.umd.js +53 -53
- package/dist/chunks/{blok-BPeM6wKv.mjs → blok-1H9roBGf.mjs} +2271 -2249
- package/dist/chunks/{blok-Ci1zCOUg.cjs → blok-J3Lfa_bK.cjs} +13 -13
- package/dist/chunks/{constants-DR3sya6U.mjs → constants-BJ79cqMa.mjs} +288 -284
- package/dist/chunks/constants-BZUc1kAY.cjs +606 -0
- package/dist/chunks/{tools-B72rczVw.mjs → tools-ByOjWADT.mjs} +420 -418
- package/dist/chunks/{tools-DlOZpcVU.cjs → tools-OHDv2C7w.cjs} +2 -2
- package/dist/full.cjs +1 -1
- package/dist/full.mjs +3 -3
- package/dist/react.cjs +1 -1
- package/dist/react.mjs +54 -33
- package/dist/tools.cjs +1 -1
- package/dist/tools.mjs +2 -2
- package/package.json +1 -1
- package/src/blok.ts +12 -0
- package/src/components/block/tool-renderer.ts +2 -2
- package/src/components/blocks.ts +32 -10
- package/src/components/constants/test-ids.ts +17 -0
- package/src/components/constants.ts +5 -0
- package/src/components/events/BlockRendered.ts +9 -0
- package/src/components/events/BlocksRendered.ts +13 -0
- package/src/components/events/index.ts +8 -0
- package/src/components/inline-tools/inline-tool-link.ts +23 -9
- package/src/components/modules/api/events.ts +10 -10
- package/src/components/modules/api/index.ts +3 -0
- package/src/components/modules/api/tools.ts +3 -0
- package/src/components/modules/blockManager/blockManager.ts +1 -1
- package/src/components/modules/paste/index.ts +10 -1
- package/src/components/modules/renderer.ts +146 -131
- package/src/components/modules/toolbar/plus-button.ts +2 -1
- package/src/components/modules/toolbar/settings-toggler.ts +2 -2
- package/src/components/modules/tools.ts +18 -0
- package/src/components/tools/factory.ts +28 -1
- package/src/react/BlokEditor.tsx +17 -5
- package/src/react/config-keys.ts +2 -0
- package/src/react/deep-equal.ts +48 -0
- package/src/react/types.ts +2 -0
- package/src/react/useBlok.ts +37 -0
- package/types/api/events.d.ts +35 -5
- package/types/api/tools.d.ts +16 -1
- package/types/configs/blok-config.d.ts +37 -0
- package/types/events/editor-events.ts +41 -0
- package/types/index.d.ts +5 -1
- package/types/react.d.ts +13 -2
- package/dist/chunks/constants-Bkncy0y5.cjs +0 -606
package/dist/full.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./chunks/blok-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./chunks/blok-J3Lfa_bK.cjs`),t=require(`./chunks/constants-BZUc1kAY.cjs`),n=require(`./chunks/objectSpread2-iNkE6aJh.cjs`),r=require(`./chunks/tools-OHDv2C7w.cjs`);var i={paragraph:{class:r.k,inlineToolbar:!0,config:{preserveBlank:!0}},header:{class:r.O,inlineToolbar:!0},list:{class:r.D,inlineToolbar:!0}},a=n.t(n.t({},i),{},{bold:{class:r.l},italic:{class:r.c},marker:{class:r.o},underline:{class:r.a},strikethrough:{class:r.i},link:{class:r.s}});exports.Blok=e.t,exports.Bold=r.l,exports.DATA_ATTR=t.Ma,exports.Header=r.O,exports.Italic=r.c,exports.Link=r.s,exports.List=r.D,exports.Marker=r.o,exports.Paragraph=r.k,exports.Strikethrough=r.i,exports.Toggle=r.T,exports.Underline=r.a,exports.allTools=a,exports.defaultBlockTools=r.t,exports.defaultInlineTools=r.n,exports.defaultTools=i,exports.version=e.n;
|
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-1H9roBGf.mjs";
|
|
2
|
+
import { Ma as n } from "./chunks/constants-BJ79cqMa.mjs";
|
|
3
3
|
import { t as r } from "./chunks/objectSpread2-jlBcdYeb.mjs";
|
|
4
|
-
import { D as i, O as a, T as o, a as s, c, i as l, k as u, l as d, n as f, o as p, s as m, t as h } from "./chunks/tools-
|
|
4
|
+
import { D as i, O as a, T as o, a as s, c, i as l, k as u, l as d, n as f, o as p, s as m, t as h } from "./chunks/tools-ByOjWADT.mjs";
|
|
5
5
|
//#region src/full.ts
|
|
6
6
|
var g = {
|
|
7
7
|
paragraph: {
|
package/dist/react.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require(`./chunks/chunk-BL1yB9zm.cjs`);const e=require(`./chunks/blok-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require(`./chunks/chunk-BL1yB9zm.cjs`);const e=require(`./chunks/blok-J3Lfa_bK.cjs`),t=require(`./chunks/objectSpread2-iNkE6aJh.cjs`);let n=require(`react`),r=require(`react/jsx-runtime`);var i=new WeakMap;function a(e,t){i.set(e,t)}function o(e){return i.get(e)}function s(e){i.delete(e)}function c(e,t){if(e===t)return!0;if(typeof e!=`object`||!e||typeof t!=`object`||!t)return!1;let n=Array.isArray(e),r=Array.isArray(t);if(n!==r)return!1;if(n&&r)return e.length===t.length?e.every((e,n)=>c(e,t[n])):!1;let i=e,a=t,o=Object.keys(i),s=Object.keys(a);return o.length===s.length?o.every(e=>Object.prototype.hasOwnProperty.call(a,e)&&c(i[e],a[e])):!1}function l(r,i){let[o,l]=(0,n.useState)(null),d=(0,n.useRef)(r);d.current=r;let f=(0,n.useRef)({editor:null,holder:null,destroyTimeout:null,isDestroyed:!1,depsToken:null}),p=(0,n.useMemo)(()=>({}),i==null?[]:i);(0,n.useEffect)(()=>{if(typeof window>`u`)return;let n=f.current;if(n.destroyTimeout!==null&&(clearTimeout(n.destroyTimeout),n.destroyTimeout=null),n.editor!==null&&!n.isDestroyed&&n.depsToken===p)return l(n.editor),()=>{u(n,l)};if(n.editor!==null&&!n.isDestroyed){s(n.editor);try{n.editor.destroy()}catch(e){}n.editor=null,n.holder=null,n.isDestroyed=!0,l(null)}let r=document.createElement(`div`);n.holder=r,n.isDestroyed=!1,n.depsToken=p;let i=d.current,o=new e.t(t.t(t.t({},i),{},{holder:r,onReady:()=>{var e,t;(e=(t=d.current).onReady)==null||e.call(t)},onChange:(...e)=>{var t,n;(t=(n=d.current).onChange)==null||t.call(n,...e)}}));return n.editor=o,a(o,r),o.isReady.then(()=>{n.editor===o&&!n.isDestroyed&&l(o)}).catch(()=>{if(n.editor===o&&!n.isDestroyed){s(o);try{o.destroy()}catch(e){}n.editor=null,n.holder=null,n.isDestroyed=!0,l(null)}}),()=>{u(n,l)}},[p]);let{readOnly:m}=r;(0,n.useEffect)(()=>{o!==null&&o.readOnly.set(m==null?!1:m)},[o,m]);let{autofocus:h}=r;(0,n.useEffect)(()=>{o===null||!h||o.focus()},[o,h]);let{theme:g}=r;(0,n.useEffect)(()=>{o===null||g===void 0||o.theme.set(g)},[o,g]);let{width:_}=r;(0,n.useEffect)(()=>{o===null||_===void 0||o.width.set(_)},[o,_]);let{placeholder:v}=r;(0,n.useEffect)(()=>{o===null||v===void 0||o.placeholder.set(v)},[o,v]);let{data:y}=r,b=(0,n.useRef)(r.data),x=(0,n.useRef)(null),S=(0,n.useRef)(Promise.resolve());return(0,n.useEffect)(()=>{if(!(o===null||y===void 0)){if(x.current!==o){x.current=o,b.current=y;return}c(y,b.current)||(b.current=y,S.current=S.current.catch(()=>void 0).then(()=>o.render(y)))}},[o,y]),o}function u(e,t){e.destroyTimeout=setTimeout(()=>{if(e.editor!==null){s(e.editor);try{e.editor.destroy()}catch(e){}e.editor=null,e.holder=null,e.isDestroyed=!0,e.destroyTimeout=null,t(null)}},0)}var d=[`editor`],f=(0,n.forwardRef)(function(i,a){let{editor:s}=i,c=e.i(i,d),l=(0,n.useRef)(null);return(0,n.useEffect)(()=>{if(s===null||l.current===null)return;let e=o(s);if(e!==void 0)return l.current.appendChild(e),()=>{e.remove()}},[s]),(0,r.jsx)(`div`,t.t({ref:e=>{if(l.current=e,typeof a==`function`)a(e);else if(a!=null){let t=a;t.current=e}}},c))}),p=`autofocus.defaultBlock.dataModel.placeholder.sanitizer.hideToolbar.maxHistoryLength.historyDebounceTime.newGroupDelay.globalUndoRedo.tools.data.minHeight.logLevel.readOnly.i18n.link.linkPaste.onBeforePaste.onReady.onChange.inlineToolbar.tunes.style.theme.onThemeChange.scrollToBlock.user.resolveUser.notifierPosition.notifier.width`.split(`.`),m=[`deps`,`onReady`],h=new Set(p),g=(0,n.forwardRef)(function(i,a){let{deps:o,onReady:s}=i,c=e.i(i,m),u={},d={};for(let e of Object.keys(c)){let t=c[e];h.has(e)?u[e]=t:d[e]=t}let p=l(u,o);(0,n.useImperativeHandle)(a,()=>p,[p]);let g=(0,n.useRef)(s);return g.current=s,(0,n.useEffect)(()=>{if(p!==null){var e;(e=g.current)==null||e.call(g,p)}},[p]),(0,r.jsx)(f,t.t({editor:p},d))});exports.BlokContent=f,exports.BlokEditor=g,exports.useBlok=l;
|
package/dist/react.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as e, t } from "./chunks/blok-
|
|
1
|
+
import { i as e, t } from "./chunks/blok-1H9roBGf.mjs";
|
|
2
2
|
import { t as n } from "./chunks/objectSpread2-jlBcdYeb.mjs";
|
|
3
3
|
import { forwardRef as r, useEffect as i, useImperativeHandle as a, useMemo as o, useRef as s, useState as c } from "react";
|
|
4
4
|
import { jsx as l } from "react/jsx-runtime";
|
|
@@ -14,8 +14,19 @@ function p(e) {
|
|
|
14
14
|
u.delete(e);
|
|
15
15
|
}
|
|
16
16
|
//#endregion
|
|
17
|
+
//#region src/react/deep-equal.ts
|
|
18
|
+
function m(e, t) {
|
|
19
|
+
if (e === t) return !0;
|
|
20
|
+
if (typeof e != "object" || !e || typeof t != "object" || !t) return !1;
|
|
21
|
+
let n = Array.isArray(e), r = Array.isArray(t);
|
|
22
|
+
if (n !== r) return !1;
|
|
23
|
+
if (n && r) return e.length === t.length ? e.every((e, n) => m(e, t[n])) : !1;
|
|
24
|
+
let i = e, a = t, o = Object.keys(i), s = Object.keys(a);
|
|
25
|
+
return o.length === s.length ? o.every((e) => Object.prototype.hasOwnProperty.call(a, e) && m(i[e], a[e])) : !1;
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
17
28
|
//#region src/react/useBlok.ts
|
|
18
|
-
function
|
|
29
|
+
function h(e, r) {
|
|
19
30
|
let [a, l] = c(null), u = s(e);
|
|
20
31
|
u.current = e;
|
|
21
32
|
let f = s({
|
|
@@ -24,12 +35,12 @@ function m(e, r) {
|
|
|
24
35
|
destroyTimeout: null,
|
|
25
36
|
isDestroyed: !1,
|
|
26
37
|
depsToken: null
|
|
27
|
-
}),
|
|
38
|
+
}), h = o(() => ({}), r == null ? [] : r);
|
|
28
39
|
i(() => {
|
|
29
40
|
if (typeof window > "u") return;
|
|
30
41
|
let e = f.current;
|
|
31
|
-
if (e.destroyTimeout !== null && (clearTimeout(e.destroyTimeout), e.destroyTimeout = null), e.editor !== null && !e.isDestroyed && e.depsToken ===
|
|
32
|
-
|
|
42
|
+
if (e.destroyTimeout !== null && (clearTimeout(e.destroyTimeout), e.destroyTimeout = null), e.editor !== null && !e.isDestroyed && e.depsToken === h) return l(e.editor), () => {
|
|
43
|
+
g(e, l);
|
|
33
44
|
};
|
|
34
45
|
if (e.editor !== null && !e.isDestroyed) {
|
|
35
46
|
p(e.editor);
|
|
@@ -39,7 +50,7 @@ function m(e, r) {
|
|
|
39
50
|
e.editor = null, e.holder = null, e.isDestroyed = !0, l(null);
|
|
40
51
|
}
|
|
41
52
|
let r = document.createElement("div");
|
|
42
|
-
e.holder = r, e.isDestroyed = !1, e.depsToken =
|
|
53
|
+
e.holder = r, e.isDestroyed = !1, e.depsToken = h;
|
|
43
54
|
let i = u.current, a = new t(n(n({}, i), {}, {
|
|
44
55
|
holder: r,
|
|
45
56
|
onReady: () => {
|
|
@@ -62,31 +73,41 @@ function m(e, r) {
|
|
|
62
73
|
e.editor = null, e.holder = null, e.isDestroyed = !0, l(null);
|
|
63
74
|
}
|
|
64
75
|
}), () => {
|
|
65
|
-
|
|
76
|
+
g(e, l);
|
|
66
77
|
};
|
|
67
|
-
}, [
|
|
68
|
-
let { readOnly:
|
|
78
|
+
}, [h]);
|
|
79
|
+
let { readOnly: _ } = e;
|
|
69
80
|
i(() => {
|
|
70
|
-
a !== null && a.readOnly.set(
|
|
71
|
-
}, [a, g]);
|
|
72
|
-
let { autofocus: _ } = e;
|
|
73
|
-
i(() => {
|
|
74
|
-
a === null || !_ || a.focus();
|
|
81
|
+
a !== null && a.readOnly.set(_ == null ? !1 : _);
|
|
75
82
|
}, [a, _]);
|
|
76
|
-
let {
|
|
83
|
+
let { autofocus: v } = e;
|
|
77
84
|
i(() => {
|
|
78
|
-
a === null || v
|
|
85
|
+
a === null || !v || a.focus();
|
|
79
86
|
}, [a, v]);
|
|
80
|
-
let {
|
|
87
|
+
let { theme: y } = e;
|
|
81
88
|
i(() => {
|
|
82
|
-
a === null || y === void 0 || a.
|
|
89
|
+
a === null || y === void 0 || a.theme.set(y);
|
|
83
90
|
}, [a, y]);
|
|
84
|
-
let {
|
|
91
|
+
let { width: b } = e;
|
|
92
|
+
i(() => {
|
|
93
|
+
a === null || b === void 0 || a.width.set(b);
|
|
94
|
+
}, [a, b]);
|
|
95
|
+
let { placeholder: x } = e;
|
|
96
|
+
i(() => {
|
|
97
|
+
a === null || x === void 0 || a.placeholder.set(x);
|
|
98
|
+
}, [a, x]);
|
|
99
|
+
let { data: S } = e, C = s(e.data), w = s(null), T = s(Promise.resolve());
|
|
85
100
|
return i(() => {
|
|
86
|
-
a === null ||
|
|
87
|
-
|
|
101
|
+
if (!(a === null || S === void 0)) {
|
|
102
|
+
if (w.current !== a) {
|
|
103
|
+
w.current = a, C.current = S;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
m(S, C.current) || (C.current = S, T.current = T.current.catch(() => void 0).then(() => a.render(S)));
|
|
107
|
+
}
|
|
108
|
+
}, [a, S]), a;
|
|
88
109
|
}
|
|
89
|
-
function
|
|
110
|
+
function g(e, t) {
|
|
90
111
|
e.destroyTimeout = setTimeout(() => {
|
|
91
112
|
if (e.editor !== null) {
|
|
92
113
|
p(e.editor);
|
|
@@ -99,8 +120,8 @@ function h(e, t) {
|
|
|
99
120
|
}
|
|
100
121
|
//#endregion
|
|
101
122
|
//#region src/react/BlokContent.tsx
|
|
102
|
-
var
|
|
103
|
-
let { editor: a } = t, o = e(t,
|
|
123
|
+
var _ = ["editor"], v = r(function(t, r) {
|
|
124
|
+
let { editor: a } = t, o = e(t, _), c = s(null);
|
|
104
125
|
return i(() => {
|
|
105
126
|
if (a === null || c.current === null) return;
|
|
106
127
|
let e = f(a);
|
|
@@ -114,21 +135,21 @@ var g = ["editor"], _ = r(function(t, r) {
|
|
|
114
135
|
t.current = e;
|
|
115
136
|
}
|
|
116
137
|
} }, o));
|
|
117
|
-
}),
|
|
118
|
-
let { deps: o, onReady: c } = t, u = e(t,
|
|
138
|
+
}), y = /* @__PURE__ */ "autofocus.defaultBlock.dataModel.placeholder.sanitizer.hideToolbar.maxHistoryLength.historyDebounceTime.newGroupDelay.globalUndoRedo.tools.data.minHeight.logLevel.readOnly.i18n.link.linkPaste.onBeforePaste.onReady.onChange.inlineToolbar.tunes.style.theme.onThemeChange.scrollToBlock.user.resolveUser.notifierPosition.notifier.width".split("."), b = ["deps", "onReady"], x = new Set(y), S = r(function(t, r) {
|
|
139
|
+
let { deps: o, onReady: c } = t, u = e(t, b), d = {}, f = {};
|
|
119
140
|
for (let e of Object.keys(u)) {
|
|
120
141
|
let t = u[e];
|
|
121
|
-
|
|
142
|
+
x.has(e) ? d[e] = t : f[e] = t;
|
|
122
143
|
}
|
|
123
|
-
let p =
|
|
144
|
+
let p = h(d, o);
|
|
124
145
|
a(r, () => p, [p]);
|
|
125
|
-
let
|
|
126
|
-
return
|
|
146
|
+
let m = s(c);
|
|
147
|
+
return m.current = c, i(() => {
|
|
127
148
|
if (p !== null) {
|
|
128
149
|
var e;
|
|
129
|
-
(e =
|
|
150
|
+
(e = m.current) == null || e.call(m, p);
|
|
130
151
|
}
|
|
131
|
-
}, [p]), /* @__PURE__ */ l(
|
|
152
|
+
}, [p]), /* @__PURE__ */ l(v, n({ editor: p }, f));
|
|
132
153
|
});
|
|
133
154
|
//#endregion
|
|
134
|
-
export {
|
|
155
|
+
export { v as BlokContent, S as BlokEditor, h as useBlok };
|
package/dist/tools.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./chunks/constants-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./chunks/constants-BZUc1kAY.cjs`),t=require(`./chunks/tools-OHDv2C7w.cjs`);exports.Audio=t.g,exports.Bold=t.l,exports.Bookmark=t.u,exports.Callout=t.w,exports.Code=t.y,exports.Column=t.p,exports.ColumnList=t.m,exports.Columns=t.f,exports.Convert=e.m,exports.Database=t.C,exports.DatabaseRow=t.S,exports.Divider=t.x,exports.Embed=t.d,exports.File=t._,exports.Header=t.O,exports.Image=t.v,exports.InlineCode=t.r,exports.Italic=t.c,exports.Link=t.s,exports.List=t.D,exports.Marker=t.o,exports.Paragraph=t.k,exports.Quote=t.b,exports.Strikethrough=t.i,exports.Table=t.E,exports.Toggle=t.T,exports.Underline=t.a,exports.Video=t.h,exports.defaultBlockTools=t.t,exports.defaultInlineTools=t.n;
|
package/dist/tools.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { m as e } from "./chunks/constants-
|
|
2
|
-
import { C as t, D as n, E as r, O as i, S as a, T as o, _ as s, a as c, b as l, c as u, d, f, g as p, h as m, i as h, k as g, l as _, m as v, n as y, o as b, p as x, r as S, s as C, t as w, u as T, v as E, w as D, x as O, y as k } from "./chunks/tools-
|
|
1
|
+
import { m as e } from "./chunks/constants-BJ79cqMa.mjs";
|
|
2
|
+
import { C as t, D as n, E as r, O as i, S as a, T as o, _ as s, a as c, b as l, c as u, d, f, g as p, h as m, i as h, k as g, l as _, m as v, n as y, o as b, p as x, r as S, s as C, t as w, u as T, v as E, w as D, x as O, y as k } from "./chunks/tools-ByOjWADT.mjs";
|
|
3
3
|
export { p as Audio, _ as Bold, T as Bookmark, D as Callout, k as Code, x as Column, v as ColumnList, f as Columns, e as Convert, t as Database, a as DatabaseRow, O as Divider, d as Embed, s as File, i as Header, E as Image, S as InlineCode, u as Italic, C as Link, n as List, b as Marker, g as Paragraph, l as Quote, h as Strikethrough, r as Table, o as Toggle, c as Underline, m as Video, w as defaultBlockTools, y as defaultInlineTools };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jackuait/blok",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.3",
|
|
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
|
"main": "./dist/blok.cjs",
|
package/src/blok.ts
CHANGED
|
@@ -25,6 +25,11 @@ export const version = getBlokVersion();
|
|
|
25
25
|
*/
|
|
26
26
|
export { DATA_ATTR } from './components/constants/data-attributes';
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Re-export the stable test-id hooks for targeting editor chrome in tests
|
|
30
|
+
*/
|
|
31
|
+
export { TEST_ID } from './components/constants/test-ids';
|
|
32
|
+
|
|
28
33
|
/**
|
|
29
34
|
* Blok
|
|
30
35
|
* @license Apache-2.0
|
|
@@ -542,3 +547,10 @@ export default Blok;
|
|
|
542
547
|
* into Blok's `render()→MenuConfig` inline tool contract.
|
|
543
548
|
*/
|
|
544
549
|
export { wrapLegacyInlineTool } from './components/inline-tools/wrap-legacy-inline-tool';
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Typed event-name constants for editor lifecycle events observable via
|
|
553
|
+
* `blok.events.on(...)`. Prefer these over raw strings to avoid typos and
|
|
554
|
+
* get typed payloads (see {@link BlokEditorEventMap}).
|
|
555
|
+
*/
|
|
556
|
+
export { BlockRendered, BlocksRendered } from './components/events';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BlockTool as IBlockTool , ToolConfig } from '../../../types';
|
|
2
|
-
import { DATA_ATTR } from '../constants';
|
|
2
|
+
import { DATA_ATTR, TEST_ID } from '../constants';
|
|
3
3
|
import { Dom as $ } from '../dom';
|
|
4
4
|
import { log } from '../utils';
|
|
5
5
|
|
|
@@ -107,7 +107,7 @@ export class ToolRenderer {
|
|
|
107
107
|
contentNode.setAttribute(DATA_ATTR.elementContent, '');
|
|
108
108
|
contentNode.setAttribute('data-blok-testid', 'block-content');
|
|
109
109
|
|
|
110
|
-
wrapper.setAttribute(
|
|
110
|
+
wrapper.setAttribute(DATA_ATTR.testid, TEST_ID.blockWrapper);
|
|
111
111
|
|
|
112
112
|
if (this.name && !wrapper.hasAttribute('data-blok-component')) {
|
|
113
113
|
wrapper.setAttribute('data-blok-component', this.name);
|
package/src/components/blocks.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { Block } from './block';
|
|
2
2
|
import { BlockToolAPI } from './block';
|
|
3
|
+
import { BlockRendered } from './events';
|
|
4
|
+
import type { BlokEventMap } from './events';
|
|
5
|
+
import type { EventsDispatcher } from './utils/events';
|
|
3
6
|
import { moveElementBefore, moveElementToEnd } from './utils/html';
|
|
4
7
|
|
|
5
8
|
|
|
@@ -20,13 +23,32 @@ export class Blocks {
|
|
|
20
23
|
*/
|
|
21
24
|
public workingArea: HTMLElement;
|
|
22
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Editor-wide event bus used to broadcast block lifecycle events
|
|
28
|
+
* (e.g. `block:rendered`). Optional so the class can be used and
|
|
29
|
+
* unit-tested standalone without an event bus.
|
|
30
|
+
*/
|
|
31
|
+
private readonly eventsDispatcher?: EventsDispatcher<BlokEventMap>;
|
|
32
|
+
|
|
23
33
|
/**
|
|
24
34
|
* @class
|
|
25
35
|
* @param {HTMLElement} workingArea — blok`s working node
|
|
36
|
+
* @param [eventsDispatcher] - editor-wide event bus for lifecycle events
|
|
26
37
|
*/
|
|
27
|
-
constructor(workingArea: HTMLElement) {
|
|
38
|
+
constructor(workingArea: HTMLElement, eventsDispatcher?: EventsDispatcher<BlokEventMap>) {
|
|
28
39
|
this.blocks = [];
|
|
29
40
|
this.workingArea = workingArea;
|
|
41
|
+
this.eventsDispatcher = eventsDispatcher;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Invokes the tool's `rendered()` lifecycle hook and broadcasts the
|
|
46
|
+
* `block:rendered` event for the freshly rendered block.
|
|
47
|
+
* @param block - block that has just been inserted into the DOM
|
|
48
|
+
*/
|
|
49
|
+
private callRenderedHook(block: Block): void {
|
|
50
|
+
block.call(BlockToolAPI.RENDERED);
|
|
51
|
+
this.eventsDispatcher?.emit(BlockRendered, { blockId: block.id });
|
|
30
52
|
}
|
|
31
53
|
|
|
32
54
|
/**
|
|
@@ -226,7 +248,7 @@ export class Blocks {
|
|
|
226
248
|
blockToReplace.holder.replaceWith(block.holder);
|
|
227
249
|
|
|
228
250
|
this.blocks.splice(insertIndex, 1, block);
|
|
229
|
-
|
|
251
|
+
this.callRenderedHook(block);
|
|
230
252
|
|
|
231
253
|
return;
|
|
232
254
|
}
|
|
@@ -358,7 +380,7 @@ export class Blocks {
|
|
|
358
380
|
|
|
359
381
|
this.blocks[index] = block;
|
|
360
382
|
|
|
361
|
-
|
|
383
|
+
this.callRenderedHook(block);
|
|
362
384
|
}
|
|
363
385
|
|
|
364
386
|
/**
|
|
@@ -388,7 +410,7 @@ export class Blocks {
|
|
|
388
410
|
this.blocks.push(...blocks);
|
|
389
411
|
this.workingArea.appendChild(fragment);
|
|
390
412
|
|
|
391
|
-
blocks.forEach((block) =>
|
|
413
|
+
blocks.forEach((block) => this.callRenderedHook(block));
|
|
392
414
|
|
|
393
415
|
return;
|
|
394
416
|
}
|
|
@@ -417,7 +439,7 @@ export class Blocks {
|
|
|
417
439
|
/**
|
|
418
440
|
* Call Rendered event for each block
|
|
419
441
|
*/
|
|
420
|
-
blocks.forEach((block) =>
|
|
442
|
+
blocks.forEach((block) => this.callRenderedHook(block));
|
|
421
443
|
}
|
|
422
444
|
|
|
423
445
|
/**
|
|
@@ -549,7 +571,7 @@ export class Blocks {
|
|
|
549
571
|
*/
|
|
550
572
|
public activateBlock(block: Block): void {
|
|
551
573
|
if (block.holder.parentElement !== null) {
|
|
552
|
-
|
|
574
|
+
this.callRenderedHook(block);
|
|
553
575
|
|
|
554
576
|
return;
|
|
555
577
|
}
|
|
@@ -588,13 +610,13 @@ export class Blocks {
|
|
|
588
610
|
private insertToDOM(block: Block, position?: InsertPosition, target?: Block): void {
|
|
589
611
|
if (!position || target === undefined) {
|
|
590
612
|
this.workingArea.appendChild(block.holder);
|
|
591
|
-
|
|
613
|
+
this.callRenderedHook(block);
|
|
592
614
|
|
|
593
615
|
return;
|
|
594
616
|
}
|
|
595
617
|
|
|
596
618
|
target.holder.insertAdjacentElement(position, block.holder);
|
|
597
|
-
|
|
619
|
+
this.callRenderedHook(block);
|
|
598
620
|
}
|
|
599
621
|
|
|
600
622
|
/**
|
|
@@ -627,7 +649,7 @@ export class Blocks {
|
|
|
627
649
|
);
|
|
628
650
|
}
|
|
629
651
|
|
|
630
|
-
|
|
652
|
+
this.callRenderedHook(block);
|
|
631
653
|
}
|
|
632
654
|
|
|
633
655
|
/**
|
|
@@ -678,7 +700,7 @@ export class Blocks {
|
|
|
678
700
|
moveElementBefore(block.holder, nextBlock.holder);
|
|
679
701
|
}
|
|
680
702
|
|
|
681
|
-
|
|
703
|
+
this.callRenderedHook(block);
|
|
682
704
|
}
|
|
683
705
|
|
|
684
706
|
/**
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable, documented test-id values used with the `data-blok-testid`
|
|
3
|
+
* attribute ({@link DATA_ATTR.testid}).
|
|
4
|
+
*
|
|
5
|
+
* Exported as `Blok.TEST_ID` so consumers (and their test suites) can target
|
|
6
|
+
* editor chrome without reverse-engineering internal selectors. These values
|
|
7
|
+
* are part of the public contract — query them as
|
|
8
|
+
* `[${DATA_ATTR.testid}="${TEST_ID.plusButton}"]`.
|
|
9
|
+
*/
|
|
10
|
+
export const TEST_ID = {
|
|
11
|
+
/** The "+" (add block) toolbar button. */
|
|
12
|
+
plusButton: 'plus-button',
|
|
13
|
+
/** The block settings / drag handle toggler (☰). */
|
|
14
|
+
settingsToggler: 'settings-toggler',
|
|
15
|
+
/** A single block's wrapper element. */
|
|
16
|
+
blockWrapper: 'block-wrapper',
|
|
17
|
+
} as const;
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export { DATA_ATTR, createSelector } from './constants/data-attributes';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Re-export the stable public test-id hooks
|
|
8
|
+
*/
|
|
9
|
+
export { TEST_ID } from './constants/test-ids';
|
|
10
|
+
|
|
6
11
|
/**
|
|
7
12
|
* Debounce timeout for selection change event
|
|
8
13
|
* {@link modules/ui.ts}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type { BlockRenderedPayload } from '../../../types/events/editor-events';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fired when a single block has been rendered into the DOM
|
|
5
|
+
* (its `rendered()` lifecycle hook has been invoked).
|
|
6
|
+
*
|
|
7
|
+
* Public event name: `block:rendered`.
|
|
8
|
+
*/
|
|
9
|
+
export const BlockRendered = 'block:rendered';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type { BlocksRenderedPayload } from '../../../types/events/editor-events';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fired when a batch of blocks has finished rendering into the DOM,
|
|
5
|
+
* e.g. on editor initialization or after `blok.blocks.render()`.
|
|
6
|
+
*
|
|
7
|
+
* Emitted once the browser has painted the inserted blocks, so subscribers
|
|
8
|
+
* can safely read the rendered DOM (build a table of contents, mark blocks,
|
|
9
|
+
* etc.) instead of polling.
|
|
10
|
+
*
|
|
11
|
+
* Public event name: `blocks:rendered`.
|
|
12
|
+
*/
|
|
13
|
+
export const BlocksRendered = 'blocks:rendered';
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { BlockChangedPayload } from './BlockChanged';
|
|
2
2
|
import { BlockChanged } from './BlockChanged';
|
|
3
3
|
import type { BlockHovered, BlockHoveredPayload } from './BlockHovered';
|
|
4
|
+
import type { BlockRenderedPayload } from './BlockRendered';
|
|
5
|
+
import { BlockRendered } from './BlockRendered';
|
|
6
|
+
import type { BlocksRenderedPayload } from './BlocksRendered';
|
|
7
|
+
import { BlocksRendered } from './BlocksRendered';
|
|
4
8
|
import type { BlockSettingsClosedPayload } from './BlockSettingsClosed';
|
|
5
9
|
import { BlockSettingsClosed } from './BlockSettingsClosed';
|
|
6
10
|
import type { BlockSettingsOpenedPayload } from './BlockSettingsOpened';
|
|
@@ -20,6 +24,8 @@ import type { RedactorDomChangedPayload } from './RedactorDomChanged';
|
|
|
20
24
|
export {
|
|
21
25
|
RedactorDomChanged,
|
|
22
26
|
BlockChanged,
|
|
27
|
+
BlockRendered,
|
|
28
|
+
BlocksRendered,
|
|
23
29
|
FakeCursorAboutToBeToggled,
|
|
24
30
|
FakeCursorHaveBeenSet,
|
|
25
31
|
BlokMobileLayoutToggled,
|
|
@@ -34,6 +40,8 @@ export interface BlokEventMap {
|
|
|
34
40
|
[BlockHovered]: BlockHoveredPayload;
|
|
35
41
|
[RedactorDomChanged]: RedactorDomChangedPayload;
|
|
36
42
|
[BlockChanged]: BlockChangedPayload;
|
|
43
|
+
[BlockRendered]: BlockRenderedPayload;
|
|
44
|
+
[BlocksRendered]: BlocksRenderedPayload;
|
|
37
45
|
[FakeCursorAboutToBeToggled]: FakeCursorAboutToBeToggledPayload;
|
|
38
46
|
[FakeCursorHaveBeenSet]: FakeCursorHaveBeenSetPayload;
|
|
39
47
|
[BlokMobileLayoutToggled]: BlokMobileLayoutToggledPayload;
|
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
InlineToolConstructorOptions,
|
|
4
4
|
SanitizerConfig
|
|
5
5
|
} from '../../../types';
|
|
6
|
+
import type { BlokConfig } from '../../../types/configs/blok-config';
|
|
6
7
|
import type { Notifier, Toolbar, I18n, InlineToolbar } from '../../../types/api';
|
|
7
8
|
import type { MenuConfig } from '../../../types/tools';
|
|
8
9
|
import { DATA_ATTR, createSelector, INLINE_TOOLBAR_INTERFACE_VALUE } from '../constants';
|
|
@@ -42,15 +43,18 @@ export class LinkInlineTool implements InlineTool {
|
|
|
42
43
|
|
|
43
44
|
/**
|
|
44
45
|
* Sanitizer Rule
|
|
45
|
-
* Leave <a> tags
|
|
46
|
+
* Leave <a> tags. target/rel pass through (instead of being forced to the
|
|
47
|
+
* defaults) so consumer-configured BlokConfig.link.target / .rel values
|
|
48
|
+
* survive save-time sanitization. Created anchors still default to
|
|
49
|
+
* '_blank'/'nofollow' via insertLink().
|
|
46
50
|
* @returns {object}
|
|
47
51
|
*/
|
|
48
52
|
public static get sanitize(): SanitizerConfig {
|
|
49
53
|
return {
|
|
50
54
|
a: {
|
|
51
55
|
href: true,
|
|
52
|
-
target:
|
|
53
|
-
rel:
|
|
56
|
+
target: true,
|
|
57
|
+
rel: true,
|
|
54
58
|
},
|
|
55
59
|
} as SanitizerConfig;
|
|
56
60
|
}
|
|
@@ -119,6 +123,11 @@ export class LinkInlineTool implements InlineTool {
|
|
|
119
123
|
*/
|
|
120
124
|
private i18n: I18n;
|
|
121
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Global anchor-building config (target / rel / transformHref)
|
|
128
|
+
*/
|
|
129
|
+
private linkConfig: NonNullable<BlokConfig['link']>;
|
|
130
|
+
|
|
122
131
|
/**
|
|
123
132
|
* @param api - Blok API
|
|
124
133
|
*/
|
|
@@ -127,6 +136,7 @@ export class LinkInlineTool implements InlineTool {
|
|
|
127
136
|
this.inlineToolbar = api.inlineToolbar;
|
|
128
137
|
this.notifier = api.notifier;
|
|
129
138
|
this.i18n = api.i18n;
|
|
139
|
+
this.linkConfig = api.config.link ?? {};
|
|
130
140
|
this.selection = new SelectionUtils();
|
|
131
141
|
this.nodes.input = this.createInput();
|
|
132
142
|
this.nodes.suggestion = this.createSuggestion();
|
|
@@ -676,6 +686,10 @@ export class LinkInlineTool implements InlineTool {
|
|
|
676
686
|
* @param {string} link - "href" value
|
|
677
687
|
*/
|
|
678
688
|
private insertLink(link: string): void {
|
|
689
|
+
const href = this.linkConfig.transformHref ? this.linkConfig.transformHref(link) : link;
|
|
690
|
+
const target = this.linkConfig.target ?? '_blank';
|
|
691
|
+
const rel = this.linkConfig.rel ?? 'nofollow';
|
|
692
|
+
|
|
679
693
|
/**
|
|
680
694
|
* Edit all link, not selected part
|
|
681
695
|
*/
|
|
@@ -684,9 +698,9 @@ export class LinkInlineTool implements InlineTool {
|
|
|
684
698
|
if (anchorTag instanceof HTMLAnchorElement) {
|
|
685
699
|
this.selection.expandToTag(anchorTag);
|
|
686
700
|
|
|
687
|
-
anchorTag.href =
|
|
688
|
-
anchorTag.target =
|
|
689
|
-
anchorTag.rel =
|
|
701
|
+
anchorTag.href = href;
|
|
702
|
+
anchorTag.target = target;
|
|
703
|
+
anchorTag.rel = rel;
|
|
690
704
|
|
|
691
705
|
return;
|
|
692
706
|
}
|
|
@@ -699,9 +713,9 @@ export class LinkInlineTool implements InlineTool {
|
|
|
699
713
|
|
|
700
714
|
const anchor = document.createElement('a');
|
|
701
715
|
|
|
702
|
-
anchor.href =
|
|
703
|
-
anchor.target =
|
|
704
|
-
anchor.rel =
|
|
716
|
+
anchor.href = href;
|
|
717
|
+
anchor.target = target;
|
|
718
|
+
anchor.rel = rel;
|
|
705
719
|
|
|
706
720
|
anchor.appendChild(range.extractContents());
|
|
707
721
|
|
|
@@ -13,9 +13,9 @@ export class EventsAPI extends Module {
|
|
|
13
13
|
*/
|
|
14
14
|
public get methods(): Events {
|
|
15
15
|
return {
|
|
16
|
-
emit: (eventName:
|
|
17
|
-
off: (eventName:
|
|
18
|
-
on: (eventName:
|
|
16
|
+
emit: (eventName: string, data?: unknown): void => this.emit(eventName, data),
|
|
17
|
+
off: (eventName: string, callback: (data?: unknown) => void): void => this.off(eventName, callback),
|
|
18
|
+
on: (eventName: string, callback: (data?: unknown) => void): void => this.on(eventName, callback),
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -24,8 +24,8 @@ export class EventsAPI extends Module {
|
|
|
24
24
|
* @param {string} eventName - event name to subscribe
|
|
25
25
|
* @param {Function} callback - event handler
|
|
26
26
|
*/
|
|
27
|
-
public on(eventName:
|
|
28
|
-
this.eventsDispatcher.on(eventName, callback);
|
|
27
|
+
public on(eventName: string, callback: (data?: unknown) => void): void {
|
|
28
|
+
this.eventsDispatcher.on(eventName as keyof BlokEventMap, callback);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
@@ -33,10 +33,10 @@ export class EventsAPI extends Module {
|
|
|
33
33
|
* @param {string} eventName - event to emit
|
|
34
34
|
* @param {object} data - event's data
|
|
35
35
|
*/
|
|
36
|
-
public emit(eventName:
|
|
36
|
+
public emit(eventName: string, data?: unknown): void {
|
|
37
37
|
this.eventsDispatcher.emit(
|
|
38
|
-
eventName,
|
|
39
|
-
data
|
|
38
|
+
eventName as keyof BlokEventMap,
|
|
39
|
+
data as BlokEventMap[keyof BlokEventMap]
|
|
40
40
|
);
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -45,7 +45,7 @@ export class EventsAPI extends Module {
|
|
|
45
45
|
* @param {string} eventName - event to unsubscribe
|
|
46
46
|
* @param {Function} callback - event handler
|
|
47
47
|
*/
|
|
48
|
-
public off(eventName:
|
|
49
|
-
this.eventsDispatcher.off(eventName, callback);
|
|
48
|
+
public off(eventName: string, callback: (data?: unknown) => void): void {
|
|
49
|
+
this.eventsDispatcher.off(eventName as keyof BlokEventMap, callback);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -269,7 +269,7 @@ export class BlockManager extends Module {
|
|
|
269
269
|
* Define this._blocks property
|
|
270
270
|
*/
|
|
271
271
|
public prepare(): void {
|
|
272
|
-
const blocks = new Blocks(this.Blok.UI.nodes.redactor);
|
|
272
|
+
const blocks = new Blocks(this.Blok.UI.nodes.redactor, this.eventsDispatcher);
|
|
273
273
|
|
|
274
274
|
/**
|
|
275
275
|
* We need to use Proxy to overload set/get [] operator.
|
|
@@ -113,7 +113,16 @@ export class Paste extends Module {
|
|
|
113
113
|
*/
|
|
114
114
|
public async processDataTransfer(dataTransfer: DataTransfer, pasteTarget?: Element): Promise<void> {
|
|
115
115
|
const plainData = dataTransfer.getData('text/plain');
|
|
116
|
-
|
|
116
|
+
|
|
117
|
+
// Give consumers a chance to transform or drop the raw clipboard HTML
|
|
118
|
+
// before any Blok preprocessing/sanitization runs. Returning a string
|
|
119
|
+
// replaces the HTML for the rest of the pipeline; returning null aborts the
|
|
120
|
+
// HTML paste path entirely (an empty string makes HtmlHandler bail, so the
|
|
121
|
+
// paste falls through to the plain-text handler).
|
|
122
|
+
const clipboardHtml = dataTransfer.getData('text/html');
|
|
123
|
+
const rawHtmlData = clipboardHtml && this.config.onBeforePaste
|
|
124
|
+
? this.config.onBeforePaste(clipboardHtml) ?? ''
|
|
125
|
+
: clipboardHtml;
|
|
117
126
|
|
|
118
127
|
// Native Blok clipboard data always wins. When absent, fall back to the
|
|
119
128
|
// lossless proprietary flavours of other editors (web→web paste): they
|