@mdaemon/html-editor 1.0.12 → 1.1.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.
package/dist/index.d.ts CHANGED
@@ -131,6 +131,8 @@ export declare interface EditorConfig {
131
131
  toolbar?: string;
132
132
  toolbar_mode?: 'sliding' | 'floating' | 'wrap';
133
133
  toolbar_sticky?: boolean;
134
+ toolbar_narrow_breakpoint?: number;
135
+ toolbar_priority?: Record<string, number>;
134
136
  menubar?: boolean;
135
137
  contextmenu?: boolean | string;
136
138
  quickbars_selection_toolbar?: string;
@@ -369,6 +371,8 @@ export declare interface MDHTMLEditor {
369
371
  getTipTap(): Editor | null;
370
372
  }
371
373
 
374
+ export declare const Mention: Node_2<any, any>;
375
+
372
376
  /**
373
377
  * Reset the global translation function to the default identity function.
374
378
  * This also clears the customized flag so that the next editor created
@@ -490,6 +494,11 @@ export declare class Toolbar {
490
494
  private icon;
491
495
  private render;
492
496
  private observeOverflow;
497
+ private scrollHandler;
498
+ private bindScrollIndicators;
499
+ private unbindScrollIndicators;
500
+ private updateScrollIndicators;
501
+ private getButtonPriority;
493
502
  private renderGroups;
494
503
  private createToggleButton;
495
504
  private toggleOverflow;
@@ -552,6 +561,8 @@ declare interface ToolbarOptions {
552
561
  customButtons: Map<string, ToolbarButtonSpec>;
553
562
  config: EditorConfig;
554
563
  iconSet: IconSet;
564
+ narrowBreakpoint: number;
565
+ priorityOverrides: Record<string, number>;
555
566
  }
556
567
 
557
568
  /**
package/dist/index.js CHANGED
@@ -14316,9 +14316,9 @@ function createInnerSelectionForWholeDocList(tr2) {
14316
14316
  if (!list) {
14317
14317
  return null;
14318
14318
  }
14319
- const from2 = 1;
14320
- const to = list.nodeSize - 1;
14321
- return TextSelection.create(doc2, from2, to);
14319
+ const $start = doc2.resolve(1);
14320
+ const $end = doc2.resolve(list.nodeSize - 1);
14321
+ return TextSelection.between($start, $end);
14322
14322
  }
14323
14323
  var toggleList = (listTypeOrName, itemTypeOrName, keepMarks, attributes = {}) => ({ editor, tr: tr2, state, dispatch, chain, commands, can }) => {
14324
14324
  const { extensions, splittableMarks } = editor.extensionManager;
@@ -14898,6 +14898,7 @@ var Extendable = class {
14898
14898
  });
14899
14899
  extension.name = this.name;
14900
14900
  extension.parent = this.parent;
14901
+ this.child = null;
14901
14902
  return extension;
14902
14903
  }
14903
14904
  extend(extendedConfig = {}) {
@@ -15425,6 +15426,36 @@ var ExtensionManager = class {
15425
15426
  })
15426
15427
  );
15427
15428
  }
15429
+ /**
15430
+ * Destroy the extension manager and clean up all extension references
15431
+ * to prevent memory leaks through parent/child extension chains.
15432
+ *
15433
+ * Walks each extension's full parent chain and nulls every forward
15434
+ * `parent.child → current` link where the parent still points to the
15435
+ * current node. This breaks the retention path from module-scope
15436
+ * singleton roots through deep extend() chains.
15437
+ *
15438
+ * Only ancestor `.child` links matching the current chain are cleared.
15439
+ * The `.parent` pointer on ancestors is never touched — extensions
15440
+ * may be shared across live editors, so their own backward references
15441
+ * and non-matching forward links must remain intact.
15442
+ */
15443
+ destroy() {
15444
+ this.extensions.forEach((extension) => {
15445
+ let current = extension;
15446
+ while (current.parent) {
15447
+ const parent = current.parent;
15448
+ if (parent.child === current) {
15449
+ parent.child = null;
15450
+ }
15451
+ current = parent;
15452
+ }
15453
+ });
15454
+ this.extensions = [];
15455
+ this.baseExtensions = [];
15456
+ this.schema = null;
15457
+ this.editor = null;
15458
+ }
15428
15459
  /**
15429
15460
  * Go through all extensions, create extension storages & setup marks
15430
15461
  * & bind editor event listener.
@@ -15830,12 +15861,23 @@ var Paste = Extension.create({
15830
15861
  });
15831
15862
  var Tabindex = Extension.create({
15832
15863
  name: "tabindex",
15864
+ addOptions() {
15865
+ return {
15866
+ value: void 0
15867
+ };
15868
+ },
15833
15869
  addProseMirrorPlugins() {
15834
15870
  return [
15835
15871
  new Plugin({
15836
15872
  key: new PluginKey("tabindex"),
15837
15873
  props: {
15838
- attributes: () => this.editor.isEditable ? { tabindex: "0" } : {}
15874
+ attributes: () => {
15875
+ var _a;
15876
+ if (!this.editor.isEditable && this.options.value === void 0) {
15877
+ return {};
15878
+ }
15879
+ return { tabindex: (_a = this.options.value) != null ? _a : "0" };
15880
+ }
15839
15881
  }
15840
15882
  })
15841
15883
  ];
@@ -16166,6 +16208,7 @@ var Editor = class extends EventEmitter {
16166
16208
  this.className = "tiptap";
16167
16209
  this.editorView = null;
16168
16210
  this.isFocused = false;
16211
+ this.destroyed = false;
16169
16212
  this.isInitialized = false;
16170
16213
  this.extensionStorage = {};
16171
16214
  this.instanceId = Math.random().toString(36).slice(2, 9);
@@ -16448,7 +16491,7 @@ var Editor = class extends EventEmitter {
16448
16491
  * Creates an extension manager.
16449
16492
  */
16450
16493
  createExtensionManager() {
16451
- var _a, _b;
16494
+ var _a, _b, _c, _d;
16452
16495
  const coreExtensions = this.options.enableCoreExtensions ? [
16453
16496
  Editable,
16454
16497
  ClipboardTextSerializer.configure({
@@ -16457,7 +16500,9 @@ var Editor = class extends EventEmitter {
16457
16500
  Commands,
16458
16501
  FocusEvents,
16459
16502
  Keymap,
16460
- Tabindex,
16503
+ Tabindex.configure({
16504
+ value: (_d = (_c = this.options.coreExtensionOptions) == null ? void 0 : _c.tabindex) == null ? void 0 : _d.value
16505
+ }),
16461
16506
  Drop,
16462
16507
  Paste,
16463
16508
  Delete,
@@ -16694,9 +16739,18 @@ var Editor = class extends EventEmitter {
16694
16739
  * Destroy the editor.
16695
16740
  */
16696
16741
  destroy() {
16742
+ if (this.destroyed) {
16743
+ return;
16744
+ }
16745
+ this.destroyed = true;
16697
16746
  this.emit("destroy");
16698
16747
  this.unmount();
16699
16748
  this.removeAllListeners();
16749
+ this.extensionManager.destroy();
16750
+ this.extensionManager = null;
16751
+ this.schema = null;
16752
+ this.commandManager = null;
16753
+ this.extensionStorage = {};
16700
16754
  }
16701
16755
  /**
16702
16756
  * Check if the editor is already destroyed.
@@ -20461,6 +20515,25 @@ var BulletList = Node3.create({
20461
20515
  return [inputRule];
20462
20516
  }
20463
20517
  });
20518
+ function isSameLineOrderedListToken(token) {
20519
+ var _a, _b;
20520
+ const nestedToken = (_a = token.tokens) == null ? void 0 : _a[0];
20521
+ return Boolean(
20522
+ token.text && ((_b = token.tokens) == null ? void 0 : _b.length) === 1 && (nestedToken == null ? void 0 : nestedToken.type) === "list" && nestedToken.ordered && nestedToken.raw === token.text
20523
+ );
20524
+ }
20525
+ function parseSameLineOrderedListText(text, helpers) {
20526
+ if (helpers.tokenizeInline) {
20527
+ return helpers.parseInline(helpers.tokenizeInline(text));
20528
+ }
20529
+ return helpers.parseInline([
20530
+ {
20531
+ type: "text",
20532
+ raw: text,
20533
+ text
20534
+ }
20535
+ ]);
20536
+ }
20464
20537
  var ListItem = Node3.create({
20465
20538
  name: "listItem",
20466
20539
  addOptions() {
@@ -20491,6 +20564,17 @@ var ListItem = Node3.create({
20491
20564
  const parseBlockChildren = (_a = helpers.parseBlockChildren) != null ? _a : helpers.parseChildren;
20492
20565
  let content = [];
20493
20566
  if (token.tokens && token.tokens.length > 0) {
20567
+ if (isSameLineOrderedListToken(token)) {
20568
+ return {
20569
+ type: "listItem",
20570
+ content: [
20571
+ {
20572
+ type: "paragraph",
20573
+ content: parseSameLineOrderedListText(token.text || "", helpers)
20574
+ }
20575
+ ]
20576
+ };
20577
+ }
20494
20578
  const hasParagraphTokens = token.tokens.some((t) => t.type === "paragraph");
20495
20579
  if (hasParagraphTokens) {
20496
20580
  content = parseBlockChildren(token.tokens);
@@ -20795,6 +20879,36 @@ var ListKeymap = Extension.create({
20795
20879
  });
20796
20880
  var ORDERED_LIST_ITEM_REGEX = /^(\s*)(\d+)\.\s+(.*)$/;
20797
20881
  var INDENTED_LINE_REGEX = /^\s/;
20882
+ function isBlockContentLine(line) {
20883
+ const trimmedLine = line.trimStart();
20884
+ return /^[-+*]\s+/.test(trimmedLine) || /^\d+\.\s+/.test(trimmedLine) || /^>\s?/.test(trimmedLine) || /^```/.test(trimmedLine) || /^~~~/.test(trimmedLine);
20885
+ }
20886
+ function splitItemContent(contentLines) {
20887
+ const paragraphLines = [];
20888
+ const blockLines = [];
20889
+ let reachedBlockBoundary = false;
20890
+ contentLines.forEach((line) => {
20891
+ if (reachedBlockBoundary) {
20892
+ blockLines.push(line);
20893
+ return;
20894
+ }
20895
+ if (line.trim() === "") {
20896
+ reachedBlockBoundary = true;
20897
+ blockLines.push(line);
20898
+ return;
20899
+ }
20900
+ if (paragraphLines.length > 0 && isBlockContentLine(line)) {
20901
+ reachedBlockBoundary = true;
20902
+ blockLines.push(line);
20903
+ return;
20904
+ }
20905
+ paragraphLines.push(line);
20906
+ });
20907
+ return {
20908
+ paragraphLines,
20909
+ blockLines
20910
+ };
20911
+ }
20798
20912
  function collectOrderedListItems(lines) {
20799
20913
  const listItems = [];
20800
20914
  let currentLineIndex = 0;
@@ -20807,9 +20921,10 @@ function collectOrderedListItems(lines) {
20807
20921
  }
20808
20922
  const [, indent, number, content] = match;
20809
20923
  const indentLevel = indent.length;
20810
- let itemContent = content;
20924
+ const itemContentLines = [content];
20811
20925
  let nextLineIndex = currentLineIndex + 1;
20812
20926
  const itemLines = [line];
20927
+ let sawBlankLine = false;
20813
20928
  while (nextLineIndex < lines.length) {
20814
20929
  const nextLine = lines[nextLineIndex];
20815
20930
  const nextMatch = nextLine.match(ORDERED_LIST_ITEM_REGEX);
@@ -20818,21 +20933,27 @@ function collectOrderedListItems(lines) {
20818
20933
  }
20819
20934
  if (nextLine.trim() === "") {
20820
20935
  itemLines.push(nextLine);
20821
- itemContent += "\n";
20936
+ itemContentLines.push("");
20937
+ sawBlankLine = true;
20822
20938
  nextLineIndex += 1;
20823
20939
  } else if (nextLine.match(INDENTED_LINE_REGEX)) {
20824
20940
  itemLines.push(nextLine);
20825
- itemContent += `
20826
- ${nextLine.slice(indentLevel + 2)}`;
20941
+ itemContentLines.push(nextLine.slice(indentLevel + 2));
20827
20942
  nextLineIndex += 1;
20828
20943
  } else {
20829
- break;
20944
+ if (sawBlankLine) {
20945
+ break;
20946
+ }
20947
+ itemLines.push(nextLine);
20948
+ itemContentLines.push(nextLine);
20949
+ nextLineIndex += 1;
20830
20950
  }
20831
20951
  }
20832
20952
  listItems.push({
20833
20953
  indent: indentLevel,
20834
20954
  number: parseInt(number, 10),
20835
- content: itemContent.trim(),
20955
+ content: itemContentLines.join("\n").trim(),
20956
+ contentLines: itemContentLines,
20836
20957
  raw: itemLines.join("\n")
20837
20958
  });
20838
20959
  consumed = nextLineIndex;
@@ -20841,14 +20962,13 @@ ${nextLine.slice(indentLevel + 2)}`;
20841
20962
  return [listItems, consumed];
20842
20963
  }
20843
20964
  function buildNestedStructure(items, baseIndent, lexer) {
20844
- var _a;
20845
20965
  const result = [];
20846
20966
  let currentIndex = 0;
20847
20967
  while (currentIndex < items.length) {
20848
20968
  const item = items[currentIndex];
20849
20969
  if (item.indent === baseIndent) {
20850
- const contentLines = item.content.split("\n");
20851
- const mainText = ((_a = contentLines[0]) == null ? void 0 : _a.trim()) || "";
20970
+ const { paragraphLines, blockLines } = splitItemContent(item.contentLines);
20971
+ const mainText = paragraphLines.join("\n").trim();
20852
20972
  const tokens = [];
20853
20973
  if (mainText) {
20854
20974
  tokens.push({
@@ -20857,7 +20977,7 @@ function buildNestedStructure(items, baseIndent, lexer) {
20857
20977
  tokens: lexer.inlineTokens(mainText)
20858
20978
  });
20859
20979
  }
20860
- const additionalContent = contentLines.slice(1).join("\n").trim();
20980
+ const additionalContent = blockLines.join("\n").trim();
20861
20981
  if (additionalContent) {
20862
20982
  const blockTokens = lexer.blockTokens(additionalContent);
20863
20983
  tokens.push(...blockTokens);
@@ -25857,6 +25977,14 @@ var Table = Node3.create({
25857
25977
  })
25858
25978
  ];
25859
25979
  },
25980
+ addNodeView() {
25981
+ const isResizable = this.options.resizable && this.editor.isEditable;
25982
+ const View = this.options.View;
25983
+ if (isResizable || !View) {
25984
+ return null;
25985
+ }
25986
+ return ({ node, view }) => new View(node, this.options.cellMinWidth, view);
25987
+ },
25860
25988
  extendNodeSchema(extension) {
25861
25989
  const context = {
25862
25990
  name: extension.name,
@@ -42742,6 +42870,15 @@ class LinkEditor {
42742
42870
  }
42743
42871
  }
42744
42872
  }
42873
+ const DEFAULT_BUTTON_PRIORITY = {
42874
+ bold: 1,
42875
+ italic: 1,
42876
+ underline: 1,
42877
+ undo: 1,
42878
+ redo: 1,
42879
+ link: 1,
42880
+ forecolor: 1
42881
+ };
42745
42882
  const DEFAULT_COLORS = [
42746
42883
  { value: "#000000", label: "Black" },
42747
42884
  { value: "#434343", label: "Dark Gray 4" },
@@ -42806,7 +42943,8 @@ class Toolbar {
42806
42943
  this.options = options;
42807
42944
  this.state = {
42808
42945
  isFullscreen: false,
42809
- showMoreButtons: false
42946
+ showMoreButtons: false,
42947
+ isNarrow: false
42810
42948
  };
42811
42949
  this.render();
42812
42950
  this.bindEvents();
@@ -42841,12 +42979,79 @@ class Toolbar {
42841
42979
  if (this.resizeObserver) {
42842
42980
  this.resizeObserver.disconnect();
42843
42981
  }
42982
+ const editorRoot = this.container.closest(".md-editor");
42844
42983
  this.resizeObserver = new ResizeObserver(() => {
42845
42984
  if (!this.buttonsEl || !this.toggleBtn) return;
42846
- const hasOverflow = this.buttonsEl.scrollHeight > this.buttonsEl.clientHeight;
42847
- this.toggleBtn.style.display = hasOverflow || this.state.showMoreButtons ? "" : "none";
42985
+ if (editorRoot) {
42986
+ const width = editorRoot.offsetWidth;
42987
+ const breakpoint = this.options.narrowBreakpoint;
42988
+ const wasNarrow = this.state.isNarrow;
42989
+ this.state.isNarrow = width <= breakpoint;
42990
+ editorRoot.classList.toggle("md-editor-narrow", this.state.isNarrow);
42991
+ if (this.state.isNarrow) {
42992
+ this.toggleBtn.style.display = "none";
42993
+ if (this.state.showMoreButtons) {
42994
+ this.state.showMoreButtons = false;
42995
+ this.buttonsEl.classList.remove("md-toolbar-expanded");
42996
+ this.toggleBtn.classList.remove("md-toolbar-btn-active");
42997
+ }
42998
+ this.updateScrollIndicators();
42999
+ if (!wasNarrow) {
43000
+ this.bindScrollIndicators();
43001
+ }
43002
+ } else {
43003
+ if (wasNarrow) {
43004
+ this.unbindScrollIndicators();
43005
+ }
43006
+ const hasOverflow = this.buttonsEl.scrollHeight > this.buttonsEl.clientHeight;
43007
+ this.toggleBtn.style.display = hasOverflow || this.state.showMoreButtons ? "" : "none";
43008
+ }
43009
+ } else {
43010
+ const hasOverflow = this.buttonsEl.scrollHeight > this.buttonsEl.clientHeight;
43011
+ this.toggleBtn.style.display = hasOverflow || this.state.showMoreButtons ? "" : "none";
43012
+ }
42848
43013
  });
42849
43014
  this.resizeObserver.observe(this.buttonsEl);
43015
+ if (editorRoot) {
43016
+ this.resizeObserver.observe(editorRoot);
43017
+ }
43018
+ }
43019
+ scrollHandler = null;
43020
+ bindScrollIndicators() {
43021
+ if (!this.buttonsEl) return;
43022
+ this.scrollHandler = () => this.updateScrollIndicators();
43023
+ this.buttonsEl.addEventListener("scroll", this.scrollHandler, { passive: true });
43024
+ }
43025
+ unbindScrollIndicators() {
43026
+ if (this.buttonsEl && this.scrollHandler) {
43027
+ this.buttonsEl.removeEventListener("scroll", this.scrollHandler);
43028
+ this.scrollHandler = null;
43029
+ }
43030
+ this.container.classList.remove("md-toolbar-scroll-start", "md-toolbar-scroll-middle", "md-toolbar-scroll-end");
43031
+ }
43032
+ updateScrollIndicators() {
43033
+ if (!this.buttonsEl) return;
43034
+ const { scrollLeft, scrollWidth, clientWidth } = this.buttonsEl;
43035
+ const maxScroll = scrollWidth - clientWidth;
43036
+ if (maxScroll <= 1) {
43037
+ this.container.classList.remove("md-toolbar-scroll-start", "md-toolbar-scroll-middle", "md-toolbar-scroll-end");
43038
+ return;
43039
+ }
43040
+ const atStart = scrollLeft <= 1;
43041
+ const atEnd = scrollLeft >= maxScroll - 1;
43042
+ this.container.classList.remove("md-toolbar-scroll-start", "md-toolbar-scroll-middle", "md-toolbar-scroll-end");
43043
+ if (atStart) {
43044
+ this.container.classList.add("md-toolbar-scroll-start");
43045
+ } else if (atEnd) {
43046
+ this.container.classList.add("md-toolbar-scroll-end");
43047
+ } else {
43048
+ this.container.classList.add("md-toolbar-scroll-middle");
43049
+ }
43050
+ }
43051
+ getButtonPriority(name) {
43052
+ const overrides = this.options.priorityOverrides;
43053
+ if (overrides[name] !== void 0) return overrides[name];
43054
+ return DEFAULT_BUTTON_PRIORITY[name] ?? 2;
42850
43055
  }
42851
43056
  renderGroups(buttonsStr, parent) {
42852
43057
  const groups = buttonsStr.split("|").map((g) => g.trim()).filter(Boolean);
@@ -42854,13 +43059,18 @@ class Toolbar {
42854
43059
  const groupEl = document.createElement("div");
42855
43060
  groupEl.className = "md-toolbar-group";
42856
43061
  const buttons = group.split(" ").filter(Boolean);
43062
+ let groupMinPriority = 2;
42857
43063
  buttons.forEach((buttonName) => {
42858
43064
  const buttonEl = this.createButton(buttonName);
42859
43065
  if (buttonEl) {
43066
+ const priority = this.getButtonPriority(buttonName);
43067
+ buttonEl.setAttribute("data-priority", String(priority));
43068
+ if (priority < groupMinPriority) groupMinPriority = priority;
42860
43069
  groupEl.appendChild(buttonEl);
42861
43070
  this.buttonElements.set(buttonName, buttonEl);
42862
43071
  }
42863
43072
  });
43073
+ groupEl.setAttribute("data-priority", String(groupMinPriority));
42864
43074
  parent.appendChild(groupEl);
42865
43075
  if (index < groups.length - 1) {
42866
43076
  const separator = document.createElement("div");
@@ -43534,6 +43744,7 @@ class Toolbar {
43534
43744
  this.resizeObserver.disconnect();
43535
43745
  this.resizeObserver = null;
43536
43746
  }
43747
+ this.unbindScrollIndicators();
43537
43748
  this.unbindEvents();
43538
43749
  this.charMap?.destroy();
43539
43750
  this.emojiPicker?.destroy();
@@ -43774,6 +43985,54 @@ const SignatureBlock = Node3.create({
43774
43985
  return ["div", mergeAttributes(HTMLAttributes), 0];
43775
43986
  }
43776
43987
  });
43988
+ const Mention = Node3.create({
43989
+ name: "mention",
43990
+ group: "inline",
43991
+ inline: true,
43992
+ atom: true,
43993
+ selectable: true,
43994
+ draggable: false,
43995
+ addAttributes() {
43996
+ return {
43997
+ jid: {
43998
+ default: null,
43999
+ parseHTML: (element) => element.getAttribute("data-jid"),
44000
+ renderHTML: (attributes) => {
44001
+ if (!attributes.jid) return {};
44002
+ return { "data-jid": attributes.jid };
44003
+ }
44004
+ },
44005
+ display: {
44006
+ default: null,
44007
+ parseHTML: (element) => element.getAttribute("data-display"),
44008
+ renderHTML: (attributes) => {
44009
+ if (!attributes.display) return {};
44010
+ return { "data-display": attributes.display };
44011
+ }
44012
+ }
44013
+ };
44014
+ },
44015
+ parseHTML() {
44016
+ return [
44017
+ {
44018
+ tag: "span.composer-mention"
44019
+ }
44020
+ ];
44021
+ },
44022
+ renderHTML({ node, HTMLAttributes }) {
44023
+ return [
44024
+ "span",
44025
+ mergeAttributes(
44026
+ {
44027
+ class: "composer-mention",
44028
+ contenteditable: "false"
44029
+ },
44030
+ HTMLAttributes
44031
+ ),
44032
+ `@${node.attrs.display}`
44033
+ ];
44034
+ }
44035
+ });
43777
44036
  const en = {
43778
44037
  "Bold": "Bold",
43779
44038
  "Italic": "Italic",
@@ -46142,7 +46401,9 @@ class HTMLEditor {
46142
46401
  entity_encoding: config.entity_encoding ?? "raw",
46143
46402
  valid_children: config.valid_children,
46144
46403
  convert_unsafe_embeds: config.convert_unsafe_embeds ?? true,
46145
- format_empty_lines: config.format_empty_lines ?? true
46404
+ format_empty_lines: config.format_empty_lines ?? true,
46405
+ toolbar_narrow_breakpoint: config.toolbar_narrow_breakpoint,
46406
+ toolbar_priority: config.toolbar_priority
46146
46407
  };
46147
46408
  }
46148
46409
  createEditor() {
@@ -46220,7 +46481,9 @@ class HTMLEditor {
46220
46481
  sticky: this.config.toolbar_sticky ?? true,
46221
46482
  customButtons: this.customButtons,
46222
46483
  config: this.config,
46223
- iconSet
46484
+ iconSet,
46485
+ narrowBreakpoint: this.config.toolbar_narrow_breakpoint ?? 768,
46486
+ priorityOverrides: this.config.toolbar_priority ?? {}
46224
46487
  });
46225
46488
  if (this.config.auto_focus) {
46226
46489
  setTimeout(() => this.focus(), 10);
@@ -46304,6 +46567,7 @@ class HTMLEditor {
46304
46567
  // We use CodeBlockLowlight instead
46305
46568
  }),
46306
46569
  SignatureBlock,
46570
+ Mention,
46307
46571
  Underline,
46308
46572
  TextStyle,
46309
46573
  FontFamily,
@@ -46565,6 +46829,7 @@ exports.FontSize = FontSize;
46565
46829
  exports.HTMLEditor = HTMLEditor;
46566
46830
  exports.LineHeight = LineHeight;
46567
46831
  exports.LinkEditor = LinkEditor;
46832
+ exports.Mention = Mention;
46568
46833
  exports.SearchReplace = SearchReplace;
46569
46834
  exports.SignatureBlock = SignatureBlock;
46570
46835
  exports.SourceEditor = SourceEditor;
package/dist/index.mjs CHANGED
@@ -14314,9 +14314,9 @@ function createInnerSelectionForWholeDocList(tr2) {
14314
14314
  if (!list) {
14315
14315
  return null;
14316
14316
  }
14317
- const from2 = 1;
14318
- const to = list.nodeSize - 1;
14319
- return TextSelection.create(doc2, from2, to);
14317
+ const $start = doc2.resolve(1);
14318
+ const $end = doc2.resolve(list.nodeSize - 1);
14319
+ return TextSelection.between($start, $end);
14320
14320
  }
14321
14321
  var toggleList = (listTypeOrName, itemTypeOrName, keepMarks, attributes = {}) => ({ editor, tr: tr2, state, dispatch, chain, commands, can }) => {
14322
14322
  const { extensions, splittableMarks } = editor.extensionManager;
@@ -14896,6 +14896,7 @@ var Extendable = class {
14896
14896
  });
14897
14897
  extension.name = this.name;
14898
14898
  extension.parent = this.parent;
14899
+ this.child = null;
14899
14900
  return extension;
14900
14901
  }
14901
14902
  extend(extendedConfig = {}) {
@@ -15423,6 +15424,36 @@ var ExtensionManager = class {
15423
15424
  })
15424
15425
  );
15425
15426
  }
15427
+ /**
15428
+ * Destroy the extension manager and clean up all extension references
15429
+ * to prevent memory leaks through parent/child extension chains.
15430
+ *
15431
+ * Walks each extension's full parent chain and nulls every forward
15432
+ * `parent.child → current` link where the parent still points to the
15433
+ * current node. This breaks the retention path from module-scope
15434
+ * singleton roots through deep extend() chains.
15435
+ *
15436
+ * Only ancestor `.child` links matching the current chain are cleared.
15437
+ * The `.parent` pointer on ancestors is never touched — extensions
15438
+ * may be shared across live editors, so their own backward references
15439
+ * and non-matching forward links must remain intact.
15440
+ */
15441
+ destroy() {
15442
+ this.extensions.forEach((extension) => {
15443
+ let current = extension;
15444
+ while (current.parent) {
15445
+ const parent = current.parent;
15446
+ if (parent.child === current) {
15447
+ parent.child = null;
15448
+ }
15449
+ current = parent;
15450
+ }
15451
+ });
15452
+ this.extensions = [];
15453
+ this.baseExtensions = [];
15454
+ this.schema = null;
15455
+ this.editor = null;
15456
+ }
15426
15457
  /**
15427
15458
  * Go through all extensions, create extension storages & setup marks
15428
15459
  * & bind editor event listener.
@@ -15828,12 +15859,23 @@ var Paste = Extension.create({
15828
15859
  });
15829
15860
  var Tabindex = Extension.create({
15830
15861
  name: "tabindex",
15862
+ addOptions() {
15863
+ return {
15864
+ value: void 0
15865
+ };
15866
+ },
15831
15867
  addProseMirrorPlugins() {
15832
15868
  return [
15833
15869
  new Plugin({
15834
15870
  key: new PluginKey("tabindex"),
15835
15871
  props: {
15836
- attributes: () => this.editor.isEditable ? { tabindex: "0" } : {}
15872
+ attributes: () => {
15873
+ var _a;
15874
+ if (!this.editor.isEditable && this.options.value === void 0) {
15875
+ return {};
15876
+ }
15877
+ return { tabindex: (_a = this.options.value) != null ? _a : "0" };
15878
+ }
15837
15879
  }
15838
15880
  })
15839
15881
  ];
@@ -16164,6 +16206,7 @@ var Editor = class extends EventEmitter {
16164
16206
  this.className = "tiptap";
16165
16207
  this.editorView = null;
16166
16208
  this.isFocused = false;
16209
+ this.destroyed = false;
16167
16210
  this.isInitialized = false;
16168
16211
  this.extensionStorage = {};
16169
16212
  this.instanceId = Math.random().toString(36).slice(2, 9);
@@ -16446,7 +16489,7 @@ var Editor = class extends EventEmitter {
16446
16489
  * Creates an extension manager.
16447
16490
  */
16448
16491
  createExtensionManager() {
16449
- var _a, _b;
16492
+ var _a, _b, _c, _d;
16450
16493
  const coreExtensions = this.options.enableCoreExtensions ? [
16451
16494
  Editable,
16452
16495
  ClipboardTextSerializer.configure({
@@ -16455,7 +16498,9 @@ var Editor = class extends EventEmitter {
16455
16498
  Commands,
16456
16499
  FocusEvents,
16457
16500
  Keymap,
16458
- Tabindex,
16501
+ Tabindex.configure({
16502
+ value: (_d = (_c = this.options.coreExtensionOptions) == null ? void 0 : _c.tabindex) == null ? void 0 : _d.value
16503
+ }),
16459
16504
  Drop,
16460
16505
  Paste,
16461
16506
  Delete,
@@ -16692,9 +16737,18 @@ var Editor = class extends EventEmitter {
16692
16737
  * Destroy the editor.
16693
16738
  */
16694
16739
  destroy() {
16740
+ if (this.destroyed) {
16741
+ return;
16742
+ }
16743
+ this.destroyed = true;
16695
16744
  this.emit("destroy");
16696
16745
  this.unmount();
16697
16746
  this.removeAllListeners();
16747
+ this.extensionManager.destroy();
16748
+ this.extensionManager = null;
16749
+ this.schema = null;
16750
+ this.commandManager = null;
16751
+ this.extensionStorage = {};
16698
16752
  }
16699
16753
  /**
16700
16754
  * Check if the editor is already destroyed.
@@ -20459,6 +20513,25 @@ var BulletList = Node3.create({
20459
20513
  return [inputRule];
20460
20514
  }
20461
20515
  });
20516
+ function isSameLineOrderedListToken(token) {
20517
+ var _a, _b;
20518
+ const nestedToken = (_a = token.tokens) == null ? void 0 : _a[0];
20519
+ return Boolean(
20520
+ token.text && ((_b = token.tokens) == null ? void 0 : _b.length) === 1 && (nestedToken == null ? void 0 : nestedToken.type) === "list" && nestedToken.ordered && nestedToken.raw === token.text
20521
+ );
20522
+ }
20523
+ function parseSameLineOrderedListText(text, helpers) {
20524
+ if (helpers.tokenizeInline) {
20525
+ return helpers.parseInline(helpers.tokenizeInline(text));
20526
+ }
20527
+ return helpers.parseInline([
20528
+ {
20529
+ type: "text",
20530
+ raw: text,
20531
+ text
20532
+ }
20533
+ ]);
20534
+ }
20462
20535
  var ListItem = Node3.create({
20463
20536
  name: "listItem",
20464
20537
  addOptions() {
@@ -20489,6 +20562,17 @@ var ListItem = Node3.create({
20489
20562
  const parseBlockChildren = (_a = helpers.parseBlockChildren) != null ? _a : helpers.parseChildren;
20490
20563
  let content = [];
20491
20564
  if (token.tokens && token.tokens.length > 0) {
20565
+ if (isSameLineOrderedListToken(token)) {
20566
+ return {
20567
+ type: "listItem",
20568
+ content: [
20569
+ {
20570
+ type: "paragraph",
20571
+ content: parseSameLineOrderedListText(token.text || "", helpers)
20572
+ }
20573
+ ]
20574
+ };
20575
+ }
20492
20576
  const hasParagraphTokens = token.tokens.some((t) => t.type === "paragraph");
20493
20577
  if (hasParagraphTokens) {
20494
20578
  content = parseBlockChildren(token.tokens);
@@ -20793,6 +20877,36 @@ var ListKeymap = Extension.create({
20793
20877
  });
20794
20878
  var ORDERED_LIST_ITEM_REGEX = /^(\s*)(\d+)\.\s+(.*)$/;
20795
20879
  var INDENTED_LINE_REGEX = /^\s/;
20880
+ function isBlockContentLine(line) {
20881
+ const trimmedLine = line.trimStart();
20882
+ return /^[-+*]\s+/.test(trimmedLine) || /^\d+\.\s+/.test(trimmedLine) || /^>\s?/.test(trimmedLine) || /^```/.test(trimmedLine) || /^~~~/.test(trimmedLine);
20883
+ }
20884
+ function splitItemContent(contentLines) {
20885
+ const paragraphLines = [];
20886
+ const blockLines = [];
20887
+ let reachedBlockBoundary = false;
20888
+ contentLines.forEach((line) => {
20889
+ if (reachedBlockBoundary) {
20890
+ blockLines.push(line);
20891
+ return;
20892
+ }
20893
+ if (line.trim() === "") {
20894
+ reachedBlockBoundary = true;
20895
+ blockLines.push(line);
20896
+ return;
20897
+ }
20898
+ if (paragraphLines.length > 0 && isBlockContentLine(line)) {
20899
+ reachedBlockBoundary = true;
20900
+ blockLines.push(line);
20901
+ return;
20902
+ }
20903
+ paragraphLines.push(line);
20904
+ });
20905
+ return {
20906
+ paragraphLines,
20907
+ blockLines
20908
+ };
20909
+ }
20796
20910
  function collectOrderedListItems(lines) {
20797
20911
  const listItems = [];
20798
20912
  let currentLineIndex = 0;
@@ -20805,9 +20919,10 @@ function collectOrderedListItems(lines) {
20805
20919
  }
20806
20920
  const [, indent, number, content] = match;
20807
20921
  const indentLevel = indent.length;
20808
- let itemContent = content;
20922
+ const itemContentLines = [content];
20809
20923
  let nextLineIndex = currentLineIndex + 1;
20810
20924
  const itemLines = [line];
20925
+ let sawBlankLine = false;
20811
20926
  while (nextLineIndex < lines.length) {
20812
20927
  const nextLine = lines[nextLineIndex];
20813
20928
  const nextMatch = nextLine.match(ORDERED_LIST_ITEM_REGEX);
@@ -20816,21 +20931,27 @@ function collectOrderedListItems(lines) {
20816
20931
  }
20817
20932
  if (nextLine.trim() === "") {
20818
20933
  itemLines.push(nextLine);
20819
- itemContent += "\n";
20934
+ itemContentLines.push("");
20935
+ sawBlankLine = true;
20820
20936
  nextLineIndex += 1;
20821
20937
  } else if (nextLine.match(INDENTED_LINE_REGEX)) {
20822
20938
  itemLines.push(nextLine);
20823
- itemContent += `
20824
- ${nextLine.slice(indentLevel + 2)}`;
20939
+ itemContentLines.push(nextLine.slice(indentLevel + 2));
20825
20940
  nextLineIndex += 1;
20826
20941
  } else {
20827
- break;
20942
+ if (sawBlankLine) {
20943
+ break;
20944
+ }
20945
+ itemLines.push(nextLine);
20946
+ itemContentLines.push(nextLine);
20947
+ nextLineIndex += 1;
20828
20948
  }
20829
20949
  }
20830
20950
  listItems.push({
20831
20951
  indent: indentLevel,
20832
20952
  number: parseInt(number, 10),
20833
- content: itemContent.trim(),
20953
+ content: itemContentLines.join("\n").trim(),
20954
+ contentLines: itemContentLines,
20834
20955
  raw: itemLines.join("\n")
20835
20956
  });
20836
20957
  consumed = nextLineIndex;
@@ -20839,14 +20960,13 @@ ${nextLine.slice(indentLevel + 2)}`;
20839
20960
  return [listItems, consumed];
20840
20961
  }
20841
20962
  function buildNestedStructure(items, baseIndent, lexer) {
20842
- var _a;
20843
20963
  const result = [];
20844
20964
  let currentIndex = 0;
20845
20965
  while (currentIndex < items.length) {
20846
20966
  const item = items[currentIndex];
20847
20967
  if (item.indent === baseIndent) {
20848
- const contentLines = item.content.split("\n");
20849
- const mainText = ((_a = contentLines[0]) == null ? void 0 : _a.trim()) || "";
20968
+ const { paragraphLines, blockLines } = splitItemContent(item.contentLines);
20969
+ const mainText = paragraphLines.join("\n").trim();
20850
20970
  const tokens = [];
20851
20971
  if (mainText) {
20852
20972
  tokens.push({
@@ -20855,7 +20975,7 @@ function buildNestedStructure(items, baseIndent, lexer) {
20855
20975
  tokens: lexer.inlineTokens(mainText)
20856
20976
  });
20857
20977
  }
20858
- const additionalContent = contentLines.slice(1).join("\n").trim();
20978
+ const additionalContent = blockLines.join("\n").trim();
20859
20979
  if (additionalContent) {
20860
20980
  const blockTokens = lexer.blockTokens(additionalContent);
20861
20981
  tokens.push(...blockTokens);
@@ -25855,6 +25975,14 @@ var Table = Node3.create({
25855
25975
  })
25856
25976
  ];
25857
25977
  },
25978
+ addNodeView() {
25979
+ const isResizable = this.options.resizable && this.editor.isEditable;
25980
+ const View = this.options.View;
25981
+ if (isResizable || !View) {
25982
+ return null;
25983
+ }
25984
+ return ({ node, view }) => new View(node, this.options.cellMinWidth, view);
25985
+ },
25858
25986
  extendNodeSchema(extension) {
25859
25987
  const context = {
25860
25988
  name: extension.name,
@@ -42740,6 +42868,15 @@ class LinkEditor {
42740
42868
  }
42741
42869
  }
42742
42870
  }
42871
+ const DEFAULT_BUTTON_PRIORITY = {
42872
+ bold: 1,
42873
+ italic: 1,
42874
+ underline: 1,
42875
+ undo: 1,
42876
+ redo: 1,
42877
+ link: 1,
42878
+ forecolor: 1
42879
+ };
42743
42880
  const DEFAULT_COLORS = [
42744
42881
  { value: "#000000", label: "Black" },
42745
42882
  { value: "#434343", label: "Dark Gray 4" },
@@ -42804,7 +42941,8 @@ class Toolbar {
42804
42941
  this.options = options;
42805
42942
  this.state = {
42806
42943
  isFullscreen: false,
42807
- showMoreButtons: false
42944
+ showMoreButtons: false,
42945
+ isNarrow: false
42808
42946
  };
42809
42947
  this.render();
42810
42948
  this.bindEvents();
@@ -42839,12 +42977,79 @@ class Toolbar {
42839
42977
  if (this.resizeObserver) {
42840
42978
  this.resizeObserver.disconnect();
42841
42979
  }
42980
+ const editorRoot = this.container.closest(".md-editor");
42842
42981
  this.resizeObserver = new ResizeObserver(() => {
42843
42982
  if (!this.buttonsEl || !this.toggleBtn) return;
42844
- const hasOverflow = this.buttonsEl.scrollHeight > this.buttonsEl.clientHeight;
42845
- this.toggleBtn.style.display = hasOverflow || this.state.showMoreButtons ? "" : "none";
42983
+ if (editorRoot) {
42984
+ const width = editorRoot.offsetWidth;
42985
+ const breakpoint = this.options.narrowBreakpoint;
42986
+ const wasNarrow = this.state.isNarrow;
42987
+ this.state.isNarrow = width <= breakpoint;
42988
+ editorRoot.classList.toggle("md-editor-narrow", this.state.isNarrow);
42989
+ if (this.state.isNarrow) {
42990
+ this.toggleBtn.style.display = "none";
42991
+ if (this.state.showMoreButtons) {
42992
+ this.state.showMoreButtons = false;
42993
+ this.buttonsEl.classList.remove("md-toolbar-expanded");
42994
+ this.toggleBtn.classList.remove("md-toolbar-btn-active");
42995
+ }
42996
+ this.updateScrollIndicators();
42997
+ if (!wasNarrow) {
42998
+ this.bindScrollIndicators();
42999
+ }
43000
+ } else {
43001
+ if (wasNarrow) {
43002
+ this.unbindScrollIndicators();
43003
+ }
43004
+ const hasOverflow = this.buttonsEl.scrollHeight > this.buttonsEl.clientHeight;
43005
+ this.toggleBtn.style.display = hasOverflow || this.state.showMoreButtons ? "" : "none";
43006
+ }
43007
+ } else {
43008
+ const hasOverflow = this.buttonsEl.scrollHeight > this.buttonsEl.clientHeight;
43009
+ this.toggleBtn.style.display = hasOverflow || this.state.showMoreButtons ? "" : "none";
43010
+ }
42846
43011
  });
42847
43012
  this.resizeObserver.observe(this.buttonsEl);
43013
+ if (editorRoot) {
43014
+ this.resizeObserver.observe(editorRoot);
43015
+ }
43016
+ }
43017
+ scrollHandler = null;
43018
+ bindScrollIndicators() {
43019
+ if (!this.buttonsEl) return;
43020
+ this.scrollHandler = () => this.updateScrollIndicators();
43021
+ this.buttonsEl.addEventListener("scroll", this.scrollHandler, { passive: true });
43022
+ }
43023
+ unbindScrollIndicators() {
43024
+ if (this.buttonsEl && this.scrollHandler) {
43025
+ this.buttonsEl.removeEventListener("scroll", this.scrollHandler);
43026
+ this.scrollHandler = null;
43027
+ }
43028
+ this.container.classList.remove("md-toolbar-scroll-start", "md-toolbar-scroll-middle", "md-toolbar-scroll-end");
43029
+ }
43030
+ updateScrollIndicators() {
43031
+ if (!this.buttonsEl) return;
43032
+ const { scrollLeft, scrollWidth, clientWidth } = this.buttonsEl;
43033
+ const maxScroll = scrollWidth - clientWidth;
43034
+ if (maxScroll <= 1) {
43035
+ this.container.classList.remove("md-toolbar-scroll-start", "md-toolbar-scroll-middle", "md-toolbar-scroll-end");
43036
+ return;
43037
+ }
43038
+ const atStart = scrollLeft <= 1;
43039
+ const atEnd = scrollLeft >= maxScroll - 1;
43040
+ this.container.classList.remove("md-toolbar-scroll-start", "md-toolbar-scroll-middle", "md-toolbar-scroll-end");
43041
+ if (atStart) {
43042
+ this.container.classList.add("md-toolbar-scroll-start");
43043
+ } else if (atEnd) {
43044
+ this.container.classList.add("md-toolbar-scroll-end");
43045
+ } else {
43046
+ this.container.classList.add("md-toolbar-scroll-middle");
43047
+ }
43048
+ }
43049
+ getButtonPriority(name) {
43050
+ const overrides = this.options.priorityOverrides;
43051
+ if (overrides[name] !== void 0) return overrides[name];
43052
+ return DEFAULT_BUTTON_PRIORITY[name] ?? 2;
42848
43053
  }
42849
43054
  renderGroups(buttonsStr, parent) {
42850
43055
  const groups = buttonsStr.split("|").map((g) => g.trim()).filter(Boolean);
@@ -42852,13 +43057,18 @@ class Toolbar {
42852
43057
  const groupEl = document.createElement("div");
42853
43058
  groupEl.className = "md-toolbar-group";
42854
43059
  const buttons = group.split(" ").filter(Boolean);
43060
+ let groupMinPriority = 2;
42855
43061
  buttons.forEach((buttonName) => {
42856
43062
  const buttonEl = this.createButton(buttonName);
42857
43063
  if (buttonEl) {
43064
+ const priority = this.getButtonPriority(buttonName);
43065
+ buttonEl.setAttribute("data-priority", String(priority));
43066
+ if (priority < groupMinPriority) groupMinPriority = priority;
42858
43067
  groupEl.appendChild(buttonEl);
42859
43068
  this.buttonElements.set(buttonName, buttonEl);
42860
43069
  }
42861
43070
  });
43071
+ groupEl.setAttribute("data-priority", String(groupMinPriority));
42862
43072
  parent.appendChild(groupEl);
42863
43073
  if (index < groups.length - 1) {
42864
43074
  const separator = document.createElement("div");
@@ -43532,6 +43742,7 @@ class Toolbar {
43532
43742
  this.resizeObserver.disconnect();
43533
43743
  this.resizeObserver = null;
43534
43744
  }
43745
+ this.unbindScrollIndicators();
43535
43746
  this.unbindEvents();
43536
43747
  this.charMap?.destroy();
43537
43748
  this.emojiPicker?.destroy();
@@ -43772,6 +43983,54 @@ const SignatureBlock = Node3.create({
43772
43983
  return ["div", mergeAttributes(HTMLAttributes), 0];
43773
43984
  }
43774
43985
  });
43986
+ const Mention = Node3.create({
43987
+ name: "mention",
43988
+ group: "inline",
43989
+ inline: true,
43990
+ atom: true,
43991
+ selectable: true,
43992
+ draggable: false,
43993
+ addAttributes() {
43994
+ return {
43995
+ jid: {
43996
+ default: null,
43997
+ parseHTML: (element) => element.getAttribute("data-jid"),
43998
+ renderHTML: (attributes) => {
43999
+ if (!attributes.jid) return {};
44000
+ return { "data-jid": attributes.jid };
44001
+ }
44002
+ },
44003
+ display: {
44004
+ default: null,
44005
+ parseHTML: (element) => element.getAttribute("data-display"),
44006
+ renderHTML: (attributes) => {
44007
+ if (!attributes.display) return {};
44008
+ return { "data-display": attributes.display };
44009
+ }
44010
+ }
44011
+ };
44012
+ },
44013
+ parseHTML() {
44014
+ return [
44015
+ {
44016
+ tag: "span.composer-mention"
44017
+ }
44018
+ ];
44019
+ },
44020
+ renderHTML({ node, HTMLAttributes }) {
44021
+ return [
44022
+ "span",
44023
+ mergeAttributes(
44024
+ {
44025
+ class: "composer-mention",
44026
+ contenteditable: "false"
44027
+ },
44028
+ HTMLAttributes
44029
+ ),
44030
+ `@${node.attrs.display}`
44031
+ ];
44032
+ }
44033
+ });
43775
44034
  const en = {
43776
44035
  "Bold": "Bold",
43777
44036
  "Italic": "Italic",
@@ -46140,7 +46399,9 @@ class HTMLEditor {
46140
46399
  entity_encoding: config.entity_encoding ?? "raw",
46141
46400
  valid_children: config.valid_children,
46142
46401
  convert_unsafe_embeds: config.convert_unsafe_embeds ?? true,
46143
- format_empty_lines: config.format_empty_lines ?? true
46402
+ format_empty_lines: config.format_empty_lines ?? true,
46403
+ toolbar_narrow_breakpoint: config.toolbar_narrow_breakpoint,
46404
+ toolbar_priority: config.toolbar_priority
46144
46405
  };
46145
46406
  }
46146
46407
  createEditor() {
@@ -46218,7 +46479,9 @@ class HTMLEditor {
46218
46479
  sticky: this.config.toolbar_sticky ?? true,
46219
46480
  customButtons: this.customButtons,
46220
46481
  config: this.config,
46221
- iconSet
46482
+ iconSet,
46483
+ narrowBreakpoint: this.config.toolbar_narrow_breakpoint ?? 768,
46484
+ priorityOverrides: this.config.toolbar_priority ?? {}
46222
46485
  });
46223
46486
  if (this.config.auto_focus) {
46224
46487
  setTimeout(() => this.focus(), 10);
@@ -46302,6 +46565,7 @@ class HTMLEditor {
46302
46565
  // We use CodeBlockLowlight instead
46303
46566
  }),
46304
46567
  SignatureBlock,
46568
+ Mention,
46305
46569
  Underline,
46306
46570
  TextStyle,
46307
46571
  FontFamily,
@@ -46564,6 +46828,7 @@ export {
46564
46828
  HTMLEditor,
46565
46829
  LineHeight,
46566
46830
  LinkEditor,
46831
+ Mention,
46567
46832
  SearchReplace,
46568
46833
  SignatureBlock,
46569
46834
  SourceEditor,
package/dist/styles.css CHANGED
@@ -48,6 +48,7 @@
48
48
  align-items: flex-start;
49
49
  padding: 4px;
50
50
  background: #f0f0f0;
51
+ --md-toolbar-bg: #f0f0f0;
51
52
  border-bottom: 1px solid #ccc;
52
53
  gap: 2px;
53
54
  min-height: 39px;
@@ -340,9 +341,12 @@
340
341
  min-height: 0;
341
342
  overflow: auto;
342
343
  background: #fff;
344
+ display: flex;
345
+ flex-direction: column;
343
346
  }
344
347
 
345
348
  .md-editor-body {
349
+ flex: 1;
346
350
  padding: 16px;
347
351
  min-height: 200px;
348
352
  outline: none;
@@ -982,6 +986,7 @@
982
986
  }
983
987
  .md-editor-oxide-dark .md-toolbar {
984
988
  background: #1a252f;
989
+ --md-toolbar-bg: #1a252f;
985
990
  border-color: #364049;
986
991
  }
987
992
  .md-editor-oxide-dark .md-toolbar-btn {
@@ -1127,6 +1132,7 @@
1127
1132
  }
1128
1133
  .md-editor-confab .md-toolbar {
1129
1134
  background: var(--color-confab-gray-50, #f9fafb);
1135
+ --md-toolbar-bg: var(--color-confab-gray-50, #f9fafb);
1130
1136
  border-color: var(--color-confab-gray-200, #e5e7eb);
1131
1137
  }
1132
1138
  .md-editor-confab .md-toolbar-btn {
@@ -1290,6 +1296,7 @@
1290
1296
  }
1291
1297
  .md-editor-confab-dark .md-toolbar {
1292
1298
  background: var(--color-dark-bg-secondary, #1e1e1e);
1299
+ --md-toolbar-bg: var(--color-dark-bg-secondary, #1e1e1e);
1293
1300
  border-color: var(--color-dark-border, #333);
1294
1301
  }
1295
1302
  .md-editor-confab-dark .md-toolbar-btn {
@@ -1486,4 +1493,108 @@
1486
1493
  }
1487
1494
  .md-editor-confab-dark .md-link-editor-row label {
1488
1495
  color: var(--color-dark-text-muted, #888);
1496
+ }
1497
+
1498
+ .md-editor-narrow .md-toolbar-buttons {
1499
+ flex-wrap: nowrap;
1500
+ overflow-x: auto;
1501
+ overflow-y: hidden;
1502
+ max-height: none;
1503
+ -webkit-overflow-scrolling: touch;
1504
+ scrollbar-width: thin;
1505
+ scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
1506
+ }
1507
+ .md-editor-narrow .md-toolbar-buttons::-webkit-scrollbar {
1508
+ height: 4px;
1509
+ }
1510
+ .md-editor-narrow .md-toolbar-buttons::-webkit-scrollbar-track {
1511
+ background: transparent;
1512
+ }
1513
+ .md-editor-narrow .md-toolbar-buttons::-webkit-scrollbar-thumb {
1514
+ background: rgba(0, 0, 0, 0.2);
1515
+ border-radius: 2px;
1516
+ }
1517
+ .md-editor-narrow .md-toolbar-toggle-btn {
1518
+ display: none !important;
1519
+ }
1520
+ .md-editor-narrow .md-toolbar-group {
1521
+ flex-shrink: 0;
1522
+ }
1523
+ .md-editor-narrow .md-toolbar-separator {
1524
+ flex-shrink: 0;
1525
+ }
1526
+
1527
+ @media (pointer: coarse) {
1528
+ .md-editor-narrow .md-toolbar-buttons {
1529
+ scrollbar-width: none;
1530
+ }
1531
+ .md-editor-narrow .md-toolbar-buttons::-webkit-scrollbar {
1532
+ display: none;
1533
+ }
1534
+ }
1535
+ .md-editor-oxide-dark.md-editor-narrow .md-toolbar-buttons,
1536
+ .md-editor-confab-dark.md-editor-narrow .md-toolbar-buttons {
1537
+ scrollbar-color: rgba(255, 255, 255, 0.25) transparent;
1538
+ }
1539
+ .md-editor-oxide-dark.md-editor-narrow .md-toolbar-buttons::-webkit-scrollbar-thumb,
1540
+ .md-editor-confab-dark.md-editor-narrow .md-toolbar-buttons::-webkit-scrollbar-thumb {
1541
+ background: rgba(255, 255, 255, 0.25);
1542
+ }
1543
+
1544
+ .md-editor-narrow .md-toolbar {
1545
+ position: relative;
1546
+ }
1547
+ .md-editor-narrow .md-toolbar::before, .md-editor-narrow .md-toolbar::after {
1548
+ content: "";
1549
+ position: absolute;
1550
+ top: 0;
1551
+ bottom: 0;
1552
+ width: 20px;
1553
+ pointer-events: none;
1554
+ z-index: 2;
1555
+ opacity: 0;
1556
+ transition: opacity 0.2s ease;
1557
+ }
1558
+ .md-editor-narrow .md-toolbar::before {
1559
+ left: 0;
1560
+ background: linear-gradient(to right, var(--md-toolbar-bg, #f0f0f0), transparent);
1561
+ }
1562
+ .md-editor-narrow .md-toolbar::after {
1563
+ right: 0;
1564
+ background: linear-gradient(to left, var(--md-toolbar-bg, #f0f0f0), transparent);
1565
+ }
1566
+ .md-editor-narrow .md-toolbar.md-toolbar-scroll-start::after {
1567
+ opacity: 1;
1568
+ }
1569
+ .md-editor-narrow .md-toolbar.md-toolbar-scroll-middle::before, .md-editor-narrow .md-toolbar.md-toolbar-scroll-middle::after {
1570
+ opacity: 1;
1571
+ }
1572
+ .md-editor-narrow .md-toolbar.md-toolbar-scroll-end::before {
1573
+ opacity: 1;
1574
+ }
1575
+
1576
+ @media (pointer: coarse) {
1577
+ .md-editor .md-toolbar {
1578
+ min-height: 52px;
1579
+ }
1580
+ .md-editor:not(.md-editor-narrow) .md-editor .md-toolbar-buttons {
1581
+ max-height: 44px;
1582
+ }
1583
+ .md-editor:not(.md-editor-narrow) .md-editor .md-toolbar-buttons.md-toolbar-expanded {
1584
+ max-height: none;
1585
+ }
1586
+ .md-editor .md-toolbar-btn {
1587
+ min-width: 44px;
1588
+ min-height: 44px;
1589
+ }
1590
+ .md-editor .md-toolbar-dropdown-btn {
1591
+ min-height: 44px;
1592
+ }
1593
+ .md-editor .md-toolbar-colorpicker-btn {
1594
+ min-height: 44px;
1595
+ min-width: 44px;
1596
+ }
1597
+ .md-editor .md-toolbar-separator {
1598
+ height: 32px;
1599
+ }
1489
1600
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mdaemon/html-editor",
3
- "version": "1.0.12",
3
+ "version": "1.1.0",
4
4
  "description": "A TinyMCE-compatible HTML editor built on TipTap",
5
5
  "homepage": "https://github.com/mdaemon-technologies/MDHTMLEditor",
6
6
  "repository": {
@@ -60,24 +60,24 @@
60
60
  },
61
61
  "license": "LGPL-3.0-or-later",
62
62
  "dependencies": {
63
- "@tiptap/core": "^3.22.3",
64
- "@tiptap/extension-character-count": "^3.22.3",
65
- "@tiptap/extension-code-block-lowlight": "^3.22.3",
66
- "@tiptap/extension-color": "^3.22.3",
67
- "@tiptap/extension-font-family": "^3.22.3",
68
- "@tiptap/extension-highlight": "^3.22.3",
69
- "@tiptap/extension-image": "^3.22.3",
70
- "@tiptap/extension-link": "^3.22.3",
71
- "@tiptap/extension-placeholder": "^3.22.3",
72
- "@tiptap/extension-table": "^3.22.3",
73
- "@tiptap/extension-table-cell": "^3.22.3",
74
- "@tiptap/extension-table-header": "^3.22.3",
75
- "@tiptap/extension-table-row": "^3.22.3",
76
- "@tiptap/extension-text-align": "^3.22.3",
77
- "@tiptap/extension-text-style": "^3.22.3",
78
- "@tiptap/extension-underline": "^3.22.3",
79
- "@tiptap/pm": "^3.22.3",
80
- "@tiptap/starter-kit": "^3.22.3",
63
+ "@tiptap/core": "^3.23.1",
64
+ "@tiptap/extension-character-count": "^3.23.1",
65
+ "@tiptap/extension-code-block-lowlight": "^3.23.1",
66
+ "@tiptap/extension-color": "^3.23.1",
67
+ "@tiptap/extension-font-family": "^3.23.1",
68
+ "@tiptap/extension-highlight": "^3.23.1",
69
+ "@tiptap/extension-image": "^3.23.1",
70
+ "@tiptap/extension-link": "^3.23.1",
71
+ "@tiptap/extension-placeholder": "^3.23.1",
72
+ "@tiptap/extension-table": "^3.23.1",
73
+ "@tiptap/extension-table-cell": "^3.23.1",
74
+ "@tiptap/extension-table-header": "^3.23.1",
75
+ "@tiptap/extension-table-row": "^3.23.1",
76
+ "@tiptap/extension-text-align": "^3.23.1",
77
+ "@tiptap/extension-text-style": "^3.23.1",
78
+ "@tiptap/extension-underline": "^3.23.1",
79
+ "@tiptap/pm": "^3.23.1",
80
+ "@tiptap/starter-kit": "^3.23.1",
81
81
  "lowlight": "^3.3.0"
82
82
  },
83
83
  "devDependencies": {