autumnnote 1.3.0 → 1.4.0

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.
@@ -803,6 +803,34 @@
803
803
  isDisabled
804
804
  };
805
805
  }
806
+ /**
807
+ * Global registry for custom buttons registered via AutumnNote.registerButton()
808
+ * or via a plugin's `buttons` array. Toolbar resolves string names from here.
809
+ * @type {Map<string, object>}
810
+ */
811
+ var _buttonRegistry = /* @__PURE__ */ new Map();
812
+ /**
813
+ * Registers a button definition in the global registry so it can be referenced
814
+ * by string name in toolbar configuration: `toolbar: [['myBtn', boldBtn]]`.
815
+ * @param {object} btnDef - Any ToolbarItemDef-compatible object with a `name` string.
816
+ */
817
+ function registerButton(btnDef) {
818
+ if (!btnDef || typeof btnDef.name !== "string") {
819
+ console.warn("[AutumnNote] registerButton: btnDef must have a string `name` property.");
820
+ return;
821
+ }
822
+ if (_buttonRegistry.has(btnDef.name)) console.warn(`[AutumnNote] registerButton: overwriting existing button "${btnDef.name}".`);
823
+ _buttonRegistry.set(btnDef.name, btnDef);
824
+ }
825
+ /**
826
+ * Looks up a button definition by name from the global registry.
827
+ * Returns undefined when not found.
828
+ * @param {string} name
829
+ * @returns {object|undefined}
830
+ */
831
+ function getButton(name) {
832
+ return _buttonRegistry.get(name);
833
+ }
806
834
  var boldBtn = btn("bold", "bold", "Bold (Ctrl+B)", () => bold(), () => document.queryCommandState("bold"));
807
835
  var italicBtn = btn("italic", "italic", "Italic (Ctrl+I)", () => italic(), () => document.queryCommandState("italic"));
808
836
  var underlineBtn = btn("underline", "underline", "Underline (Ctrl+U)", () => underline(), () => {
@@ -5502,6 +5530,8 @@
5502
5530
  * Toolbar.js - Builds and manages the editor toolbar UI
5503
5531
  * Inspired by Summernote's Toolbar module — rewritten without jQuery
5504
5532
  */
5533
+ /** Resolve a toolbar item: string → registry lookup, object → pass-through. */
5534
+ var _resolveBtn = (item) => typeof item === "string" ? getButton(item) : item;
5505
5535
  var _faPageLevelReady = null;
5506
5536
  var _S$1 = "stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"";
5507
5537
  var _svgWrap = (paths) => `<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" ${_S$1} style="display:block">${paths}</svg>`;
@@ -5598,7 +5628,7 @@
5598
5628
  this.el = createElement("div", { class: "an-toolbar" });
5599
5629
  this._faReady = this._detectFontAwesome();
5600
5630
  this._buildButtons();
5601
- this._btnMap = new Map((this.options.toolbar || []).flat().map((b) => [b.name, b]));
5631
+ this._btnMap = new Map((this.options.toolbar || []).flat().map(_resolveBtn).filter(Boolean).map((b) => [b.name, b]));
5602
5632
  return this;
5603
5633
  }
5604
5634
  destroy() {
@@ -5614,7 +5644,12 @@
5614
5644
  const fragment = document.createDocumentFragment();
5615
5645
  toolbar.forEach((group) => {
5616
5646
  const groupEl = createElement("div", { class: "an-btn-group" });
5617
- group.forEach((btnDef) => {
5647
+ group.forEach((item) => {
5648
+ const btnDef = _resolveBtn(item);
5649
+ if (!btnDef) {
5650
+ console.warn(`[AutumnNote] Toolbar: button "${item}" not found in registry. Skipped.`);
5651
+ return;
5652
+ }
5618
5653
  let el;
5619
5654
  if (btnDef.type === "select") el = this._createSelect(btnDef);
5620
5655
  else if (btnDef.type === "grid") el = this._createGridPicker(btnDef);
@@ -6034,6 +6069,24 @@
6034
6069
  hide() {
6035
6070
  if (this.el) this.el.style.display = "none";
6036
6071
  }
6072
+ /**
6073
+ * Tears down and re-renders the toolbar in-place.
6074
+ * Call after registering new buttons post-create via context.use(plugin)
6075
+ * or AutumnNote.registerButton() to make them appear in the toolbar.
6076
+ */
6077
+ rebuild() {
6078
+ if (this._refreshRaf) {
6079
+ cancelAnimationFrame(this._refreshRaf);
6080
+ this._refreshRaf = null;
6081
+ }
6082
+ this._disposers.forEach((d) => d());
6083
+ this._disposers = [];
6084
+ if (this.el) this.el.innerHTML = "";
6085
+ this._faReady = this._detectFontAwesome();
6086
+ this._buildButtons();
6087
+ this._btnMap = new Map((this.options.toolbar || []).flat().map(_resolveBtn).filter(Boolean).map((b) => [b.name, b]));
6088
+ this.refresh();
6089
+ }
6037
6090
  };
6038
6091
  //#endregion
6039
6092
  //#region src/js/module/Statusbar.js
@@ -15481,6 +15534,8 @@
15481
15534
  */
15482
15535
  /** Module registry shared across all Context instances (populated via AutumnNote.registerModule). */
15483
15536
  var _customModules = /* @__PURE__ */ new Map();
15537
+ /** Global plugin registry (populated via AutumnNote.use()). Applied to every new Context. */
15538
+ var _globalPlugins = /* @__PURE__ */ new Map();
15484
15539
  var Context = class {
15485
15540
  /**
15486
15541
  * @param {HTMLElement} targetEl - The element to replace with the editor
@@ -15497,6 +15552,8 @@
15497
15552
  this._listeners = /* @__PURE__ */ new Map();
15498
15553
  /** @type {Map<string, object>} */
15499
15554
  this._modules = /* @__PURE__ */ new Map();
15555
+ /** @type {Map<string, { plugin: object, publicApi: * }>} */
15556
+ this._plugins = /* @__PURE__ */ new Map();
15500
15557
  this._disposers = [];
15501
15558
  this._alive = false;
15502
15559
  }
@@ -15519,6 +15576,7 @@
15519
15576
  if (this.options.focus) editable.focus();
15520
15577
  this._alive = true;
15521
15578
  this.invoke("toolbar.refresh");
15579
+ this._applyGlobalPlugins();
15522
15580
  if (typeof this.options.onInit === "function") this.options.onInit(this);
15523
15581
  return this;
15524
15582
  }
@@ -15570,6 +15628,46 @@
15570
15628
  this._modules.set(name, instance);
15571
15629
  return this;
15572
15630
  }
15631
+ /**
15632
+ * Installs a plugin on this editor instance.
15633
+ * If called after create(), buttons are registered immediately but the toolbar
15634
+ * must be rebuilt via ctx.invoke('toolbar.rebuild') to render new buttons.
15635
+ * @param {object} plugin - { name, version?, buttons?, install?, uninstall? }
15636
+ * @param {object} [options] - Forwarded to plugin.install(context, options)
15637
+ * @returns {this}
15638
+ */
15639
+ use(plugin, options = {}) {
15640
+ if (Array.isArray(plugin.buttons)) plugin.buttons.forEach((b) => registerButton(b));
15641
+ this._installPlugin(plugin, options);
15642
+ return this;
15643
+ }
15644
+ /**
15645
+ * Returns the public API returned by plugin.install(), or null.
15646
+ * @param {string} name
15647
+ * @returns {*}
15648
+ */
15649
+ getPlugin(name) {
15650
+ return this._plugins.get(name)?.publicApi ?? null;
15651
+ }
15652
+ _installPlugin(plugin, pluginOptions = {}) {
15653
+ const { name } = plugin;
15654
+ if (!name || typeof name !== "string") {
15655
+ console.warn("[AutumnNote] Plugin must have a string `name` property.");
15656
+ return;
15657
+ }
15658
+ if (this._plugins.has(name)) {
15659
+ console.warn(`[AutumnNote] Plugin "${name}" already installed on this instance. Skipping.`);
15660
+ return;
15661
+ }
15662
+ const publicApi = typeof plugin.install === "function" ? plugin.install(this, pluginOptions) ?? null : null;
15663
+ this._plugins.set(name, {
15664
+ plugin,
15665
+ publicApi
15666
+ });
15667
+ }
15668
+ _applyGlobalPlugins() {
15669
+ for (const { plugin, options } of _globalPlugins.values()) this._installPlugin(plugin, options);
15670
+ }
15573
15671
  _bindEditorEvents(editable) {
15574
15672
  const d0 = on(editable, "input", () => this._syncToTarget());
15575
15673
  const d1 = on(editable, "focus", () => {
@@ -15820,6 +15918,10 @@
15820
15918
  if (typeof module.destroy === "function") module.destroy();
15821
15919
  });
15822
15920
  this._modules.clear();
15921
+ for (const { plugin } of this._plugins.values()) if (typeof plugin.uninstall === "function") try {
15922
+ plugin.uninstall(this);
15923
+ } catch (_) {}
15924
+ this._plugins.clear();
15823
15925
  this._disposers.forEach((d) => d());
15824
15926
  this._disposers = [];
15825
15927
  const container = this.layoutInfo.container;
@@ -15888,7 +15990,27 @@
15888
15990
  registerModule(name, ModuleClass) {
15889
15991
  _customModules.set(name, ModuleClass);
15890
15992
  },
15891
- version: "1.1.1"
15993
+ use(plugin, options = {}) {
15994
+ if (!plugin || typeof plugin.name !== "string") throw new TypeError("[AutumnNote] AutumnNote.use: plugin must have a string `name` property.");
15995
+ if (_globalPlugins.has(plugin.name)) {
15996
+ console.warn(`[AutumnNote] Plugin "${plugin.name}" already registered globally. Skipping.`);
15997
+ return this;
15998
+ }
15999
+ if (Array.isArray(plugin.buttons)) plugin.buttons.forEach((b) => registerButton(b));
16000
+ _globalPlugins.set(plugin.name, {
16001
+ plugin,
16002
+ options
16003
+ });
16004
+ return this;
16005
+ },
16006
+ hasPlugin(name) {
16007
+ return _globalPlugins.has(name);
16008
+ },
16009
+ registerButton(btnDef) {
16010
+ registerButton(btnDef);
16011
+ return this;
16012
+ },
16013
+ version: "1.4.0"
15892
16014
  };
15893
16015
  /**
15894
16016
  * @param {string|Element|NodeList|Element[]} selector