@rg-dev/tinymce-helpers 1.0.11 → 1.0.12

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.
Files changed (3) hide show
  1. package/index.d.mts +65 -5
  2. package/index.mjs +156 -9
  3. package/package.json +1 -1
package/index.d.mts CHANGED
@@ -1,5 +1,14 @@
1
1
  import { Editor, RawEditorOptions } from 'tinymce';
2
2
 
3
+ type RemoveIndexSignature<T> = {
4
+ [K in keyof T as
5
+ string extends K ? never :
6
+ number extends K ? never :
7
+ symbol extends K ? never :
8
+ K
9
+ ]: T[K]
10
+ };
11
+
3
12
  declare const skinsTable: {
4
13
  dark: () => Promise<{
5
14
  css: string;
@@ -10,20 +19,71 @@ declare const skinsTable: {
10
19
  skin: string;
11
20
  }>;
12
21
  };
13
- type RemoveIndexSignature<T> = {
14
- [K in keyof T as string extends K ? never : number extends K ? never : symbol extends K ? never : K]: T[K];
15
- };
16
22
  type RawEditorOptionsExtended = RawEditorOptions & {
17
23
  quickbars_insert_toolbar?: string;
18
24
  quickbars_selection_toolbar?: string;
25
+ image_advtab: boolean;
19
26
  };
27
+ type EditorOpts = Omit<RemoveIndexSignature<RawEditorOptionsExtended>, 'setup' | 'target' | 'selector'> & Record<string, unknown>;
28
+ /** A partial patch object, or a function that derives one from the defaults. */
29
+ type TinyMCEPatch = Partial<EditorOpts> | ((defaults: EditorOpts) => Partial<EditorOpts>);
30
+ declare class ListBuilder {
31
+ private items;
32
+ constructor(existing: string);
33
+ /** add('btn') appends at the end. add(3, 'btn') inserts at index 3. */
34
+ add(indexOrItem: number | string, item?: string): this;
35
+ start(item: string): this;
36
+ after(target: string, item: string): this;
37
+ before(target: string, item: string): this;
38
+ remove(...names: string[]): this;
39
+ /** Insert a '|' separator at the end, or at a given index. */
40
+ separator(index?: number): this;
41
+ toString(): string;
42
+ }
43
+ type ListPatch = string | ((t: ListBuilder) => ListBuilder | void);
44
+ type MenuEntry = {
45
+ title: string;
46
+ items: string;
47
+ };
48
+ type MenuConfig = Record<string, MenuEntry>;
49
+ declare class MenuBuilder {
50
+ private menus;
51
+ constructor(existing: MenuConfig);
52
+ /** Add or replace an entire menu, e.g. m.addMenu('others_menu', { title: 'Others', items: 'foo bar' }) */
53
+ addMenu(key: string, config: MenuEntry): this;
54
+ removeMenu(key: string): this;
55
+ /** Mutate one menu's items string via the same ListBuilder used for toolbar/contextmenu. */
56
+ items(key: string, patch: ListPatch): this;
57
+ /** Shorthand: m.addItem('file', 'newitem') / m.addItem('file', 2, 'newitem') */
58
+ addItem(key: string, indexOrItem: number | string, item?: string): this;
59
+ removeItem(key: string, ...names: string[]): this;
60
+ toObject(): MenuConfig;
61
+ }
62
+ type MenuPatch = MenuConfig | ((m: MenuBuilder) => MenuBuilder | void);
20
63
  type MyTinymceOpts = {
21
64
  skin?: keyof typeof skinsTable;
22
65
  direction?: 'rtl' | 'ltr';
23
66
  customSetup?: (editor: Editor) => void;
24
- overrides?: (defaultOpts: EditorOpts) => EditorOpts;
67
+ /** Free-form deep-merge patch applied over the default options.
68
+ * Accepts a plain partial object, or a function of the defaults
69
+ * that returns a partial object. */
70
+ overrides?: TinyMCEPatch;
71
+ /** Toolbar string, or a builder function: (t) => t.add('btn'), t.after('x','y'), etc. */
72
+ toolbar?: ListPatch;
73
+ /** Context menu string, or a builder function, same API as toolbar. */
74
+ contextmenu?: ListPatch;
75
+ /** Plugin list string, or a builder function, same API as toolbar.
76
+ * Note: this only toggles the string passed to tinymce.init - the
77
+ * underlying plugin JS still needs to be imported at the top of this
78
+ * file regardless of what's listed here. */
79
+ plugins?: ListPatch;
80
+ /** Menubar string (top-level menu keys, e.g. 'file edit view'), or a
81
+ * builder function, same API as toolbar. */
82
+ menubar?: ListPatch;
83
+ /** Menu config object ({ key: { title, items } }), or a builder
84
+ * function: (m) => m.addItem('file','newitem').removeItem('file','print') */
85
+ menu?: MenuPatch;
25
86
  };
26
- type EditorOpts = Omit<RemoveIndexSignature<RawEditorOptionsExtended>, 'setup' | 'target' | 'selector'> & Record<string, unknown>;
27
87
  declare class tinymceHelpers {
28
88
  private _editor;
29
89
  private _lockButton?;
package/index.mjs CHANGED
@@ -14,6 +14,18 @@ var __spreadValues = (a, b) => {
14
14
  }
15
15
  return a;
16
16
  };
17
+ var __objRest = (source, exclude) => {
18
+ var target = {};
19
+ for (var prop in source)
20
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
21
+ target[prop] = source[prop];
22
+ if (source != null && __getOwnPropSymbols)
23
+ for (var prop of __getOwnPropSymbols(source)) {
24
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
25
+ target[prop] = source[prop];
26
+ }
27
+ return target;
28
+ };
17
29
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
18
30
 
19
31
  // src/client/custom-components/tinymce-helpers.ts
@@ -65,9 +77,9 @@ async function defaultSkin(document2 = false) {
65
77
  ]);
66
78
  return {
67
79
  css: tinymce.Resource.get(`content/${contentSkin}/content.css`) + tinymce.Resource.get(`ui/${shellSkin}/content.css`) + `
68
-
69
-
70
-
80
+
81
+
82
+
71
83
  `,
72
84
  skin: shellSkin
73
85
  };
@@ -84,7 +96,6 @@ async function darkSkin() {
84
96
  );
85
97
  return {
86
98
  css: tinymce.Resource.get(`content/${contentSkin}/content.css`) + tinymce.Resource.get(`ui/${shellSkin}/content.css`) + `
87
-
88
99
 
89
100
 
90
101
  .mce-content-body {
@@ -105,6 +116,126 @@ var skinsTable = {
105
116
  dark: () => darkSkin(),
106
117
  default: () => defaultSkin()
107
118
  };
119
+ function resolvePatch(defaults, patch) {
120
+ if (!patch) return {};
121
+ const resolved = typeof patch === "function" ? patch(defaults) : patch;
122
+ const _a = resolved, { target, setup, selector } = _a, safe = __objRest(_a, ["target", "setup", "selector"]);
123
+ return safe;
124
+ }
125
+ function deepMerge(base, patch) {
126
+ const out = __spreadValues({}, base);
127
+ for (const key in patch) {
128
+ const value = patch[key];
129
+ const baseValue = base[key];
130
+ const isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
131
+ if (isPlainObject(value) && isPlainObject(baseValue)) {
132
+ out[key] = deepMerge(baseValue, value);
133
+ } else {
134
+ out[key] = value;
135
+ }
136
+ }
137
+ return out;
138
+ }
139
+ var ListBuilder = class {
140
+ constructor(existing) {
141
+ __publicField(this, "items");
142
+ this.items = existing.trim().length ? existing.trim().split(/\s+/) : [];
143
+ }
144
+ /** add('btn') appends at the end. add(3, 'btn') inserts at index 3. */
145
+ add(indexOrItem, item) {
146
+ if (typeof indexOrItem === "number") {
147
+ this.items.splice(indexOrItem, 0, item);
148
+ } else {
149
+ this.items.push(indexOrItem);
150
+ }
151
+ return this;
152
+ }
153
+ start(item) {
154
+ this.items.unshift(item);
155
+ return this;
156
+ }
157
+ after(target, item) {
158
+ const i = this.items.indexOf(target);
159
+ this.items.splice(i === -1 ? this.items.length : i + 1, 0, item);
160
+ return this;
161
+ }
162
+ before(target, item) {
163
+ const i = this.items.indexOf(target);
164
+ this.items.splice(i === -1 ? this.items.length : i, 0, item);
165
+ return this;
166
+ }
167
+ remove(...names) {
168
+ const removeSet = new Set(names.map((n) => n.toLowerCase()));
169
+ this.items = this.items.filter((i) => i === "|" || !removeSet.has(i.toLowerCase()));
170
+ return this;
171
+ }
172
+ /** Insert a '|' separator at the end, or at a given index. */
173
+ separator(index) {
174
+ if (index === void 0) {
175
+ this.items.push("|");
176
+ } else {
177
+ this.items.splice(index, 0, "|");
178
+ }
179
+ return this;
180
+ }
181
+ toString() {
182
+ return this.items.join(" ");
183
+ }
184
+ };
185
+ function applyListPatch(existing, patch) {
186
+ if (!patch) return existing;
187
+ if (typeof patch === "string") return patch;
188
+ const builder = new ListBuilder(existing);
189
+ const result = patch(builder);
190
+ return (result != null ? result : builder).toString();
191
+ }
192
+ var MenuBuilder = class {
193
+ constructor(existing) {
194
+ __publicField(this, "menus");
195
+ this.menus = Object.fromEntries(
196
+ Object.entries(existing).map(([k, v]) => [k, __spreadValues({}, v)])
197
+ );
198
+ }
199
+ /** Add or replace an entire menu, e.g. m.addMenu('others_menu', { title: 'Others', items: 'foo bar' }) */
200
+ addMenu(key, config) {
201
+ this.menus[key] = __spreadValues({}, config);
202
+ return this;
203
+ }
204
+ removeMenu(key) {
205
+ delete this.menus[key];
206
+ return this;
207
+ }
208
+ /** Mutate one menu's items string via the same ListBuilder used for toolbar/contextmenu. */
209
+ items(key, patch) {
210
+ const existing = this.menus[key];
211
+ if (!existing) {
212
+ console.error(`MenuBuilder.items: menu "${key}" does not exist`);
213
+ return this;
214
+ }
215
+ existing.items = applyListPatch(existing.items, patch);
216
+ return this;
217
+ }
218
+ /** Shorthand: m.addItem('file', 'newitem') / m.addItem('file', 2, 'newitem') */
219
+ addItem(key, indexOrItem, item) {
220
+ return this.items(
221
+ key,
222
+ (t) => typeof indexOrItem === "number" ? t.add(indexOrItem, item) : t.add(indexOrItem)
223
+ );
224
+ }
225
+ removeItem(key, ...names) {
226
+ return this.items(key, (t) => t.remove(...names));
227
+ }
228
+ toObject() {
229
+ return this.menus;
230
+ }
231
+ };
232
+ function applyMenuPatch(existing, patch) {
233
+ if (!patch) return existing;
234
+ if (typeof patch !== "function") return patch;
235
+ const builder = new MenuBuilder(existing);
236
+ const result = patch(builder);
237
+ return (result != null ? result : builder).toObject();
238
+ }
108
239
  var tinymceHelpers = class _tinymceHelpers {
109
240
  constructor() {
110
241
  __publicField(this, "_editor");
@@ -118,7 +249,7 @@ var tinymceHelpers = class _tinymceHelpers {
118
249
  this.editor.insertContent(`<img src="${src}" alt="">`);
119
250
  }
120
251
  async _initMce(el, customOpts = Object()) {
121
- var _a, _b;
252
+ var _a;
122
253
  if (!(el instanceof HTMLElement)) {
123
254
  throw new Error("invalid element");
124
255
  }
@@ -293,7 +424,9 @@ var tinymceHelpers = class _tinymceHelpers {
293
424
  () => {
294
425
  if (blockquote) {
295
426
  const parent = blockquote.parentNode;
296
- if (!parent) return;
427
+ if (!parent) {
428
+ return;
429
+ }
297
430
  while (blockquote.firstChild) {
298
431
  parent.insertBefore(blockquote.firstChild, blockquote);
299
432
  }
@@ -378,14 +511,28 @@ var tinymceHelpers = class _tinymceHelpers {
378
511
  // contextmenu: false,
379
512
  contextmenu: "insert-image link image table create-block-above create-block-below reset-style-button-img clearTextStyle"
380
513
  };
381
- opts = __spreadValues(__spreadValues({}, opts), ((_a = customOpts.overrides) == null ? void 0 : _a.call(customOpts, opts)) || {});
514
+ opts = deepMerge(opts, resolvePatch(opts, customOpts.overrides));
515
+ if (typeof opts.toolbar === "string") {
516
+ opts.toolbar = applyListPatch(opts.toolbar, customOpts.toolbar);
517
+ }
518
+ if (typeof opts.contextmenu === "string") {
519
+ opts.contextmenu = applyListPatch(opts.contextmenu, customOpts.contextmenu);
520
+ }
521
+ if (typeof opts.plugins === "string") {
522
+ opts.plugins = applyListPatch(opts.plugins, customOpts.plugins);
523
+ }
524
+ if (typeof opts.menubar === "string") {
525
+ opts.menubar = applyListPatch(opts.menubar, customOpts.menubar);
526
+ }
527
+ if (opts.menu) {
528
+ opts.menu = applyMenuPatch(opts.menu, customOpts.menu);
529
+ }
382
530
  if (!opts.skin) {
383
531
  throw new Error("no skin");
384
532
  }
385
533
  const mce = this._editor = await tinymce.init(opts).then((x) => x[0]);
386
534
  this.originalContextMenu = mce.options.get("contextmenu");
387
535
  const all = mce.ui.registry.getAll();
388
- console.log(all);
389
536
  const availableMenuItems = all.menuItems;
390
537
  const availableButtons = all.buttons;
391
538
  if (typeof opts.toolbar == "string") {
@@ -418,7 +565,7 @@ var tinymceHelpers = class _tinymceHelpers {
418
565
  }
419
566
  if (opts.menu) {
420
567
  for (const [key, value] of Object.entries(opts.menu)) {
421
- if (!isNonEmptyString((_b = value.items) == null ? void 0 : _b.trim())) {
568
+ if (!isNonEmptyString((_a = value.items) == null ? void 0 : _a.trim())) {
422
569
  console.error(`Menu "${key}" is missing items`);
423
570
  continue;
424
571
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rg-dev/tinymce-helpers",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {