@rg-dev/tinymce-helpers 1.0.10 → 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 +68 -4
  2. package/index.mjs +157 -8
  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,16 +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];
22
+ type RawEditorOptionsExtended = RawEditorOptions & {
23
+ quickbars_insert_toolbar?: string;
24
+ quickbars_selection_toolbar?: string;
25
+ image_advtab: boolean;
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;
15
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);
16
63
  type MyTinymceOpts = {
17
64
  skin?: keyof typeof skinsTable;
18
65
  direction?: 'rtl' | 'ltr';
19
66
  customSetup?: (editor: Editor) => void;
20
- 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;
21
86
  };
22
- type EditorOpts = Omit<RemoveIndexSignature<RawEditorOptions>, 'setup' | 'target' | 'selector'> & Record<string, unknown>;
23
87
  declare class tinymceHelpers {
24
88
  private _editor;
25
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
  }
@@ -338,6 +471,7 @@ var tinymceHelpers = class _tinymceHelpers {
338
471
  extended_valid_elements: "img[drawio-diagram|contenteditable|style|class|src]",
339
472
  font_family_formats: "Rubik=Rubik, Helvetica, Arial, sans-serif; Andale Mono=andale mono,times; Arial=arial,helvetica,sans-serif; Arial Black=arial black,avant garde; Book Antiqua=book antiqua,palatino; Comic Sans MS=comic sans ms,sans-serif; Courier New=courier new,courier; Georgia=georgia,palatino; Helvetica=helvetica; Impact=impact,chicago; Symbol=symbol; Tahoma=tahoma,arial,helvetica,sans-serif; Terminal=terminal,monaco; Times New Roman=times new roman,times; Trebuchet MS=trebuchet ms,geneva; Verdana=verdana,geneva; Webdings=webdings; Wingdings=wingdings,zapf dingbats; Afacad=afacad,sans-serif; Marcellus=marcellus,serif; Poppins=poppins,sans-serif; Raleway=raleway,sans-serif;",
340
473
  "statusbar": false,
474
+ quickbars_insert_toolbar: "",
341
475
  skin: theSkin.skin,
342
476
  highlight_on_focus: false,
343
477
  content_style: theSkin.css,
@@ -377,7 +511,22 @@ var tinymceHelpers = class _tinymceHelpers {
377
511
  // contextmenu: false,
378
512
  contextmenu: "insert-image link image table create-block-above create-block-below reset-style-button-img clearTextStyle"
379
513
  };
380
- 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
+ }
381
530
  if (!opts.skin) {
382
531
  throw new Error("no skin");
383
532
  }
@@ -416,7 +565,7 @@ var tinymceHelpers = class _tinymceHelpers {
416
565
  }
417
566
  if (opts.menu) {
418
567
  for (const [key, value] of Object.entries(opts.menu)) {
419
- if (!isNonEmptyString((_b = value.items) == null ? void 0 : _b.trim())) {
568
+ if (!isNonEmptyString((_a = value.items) == null ? void 0 : _a.trim())) {
420
569
  console.error(`Menu "${key}" is missing items`);
421
570
  continue;
422
571
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rg-dev/tinymce-helpers",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {