@licium/editor-plugin-details 1.0.2 → 1.0.13

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.
@@ -0,0 +1,7 @@
1
+ /*!
2
+ * TOAST UI Editor : Text Align Plugin
3
+ * @version 1.0.12 | Mon Jan 05 2026
4
+ * @author NHN Cloud FE Development Lab <dl_javascript@nhn.com>
5
+ * @license MIT
6
+ */
7
+ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.toastui=t():(e.toastui=e.toastui||{},e.toastui.Editor=e.toastui.Editor||{},e.toastui.Editor.plugin=e.toastui.Editor.plugin||{},e.toastui.Editor.plugin.details=t())}(self,(function(){return function(){"use strict";var e={d:function(t,n){for(var r in n)e.o(n,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:n[r]})},o:function(e,t){return Object.prototype.hasOwnProperty.call(e,t)}},t={};e.d(t,{default:function(){return r}});var n=function(){return n=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var o in t=arguments[n])Object.prototype.hasOwnProperty.call(t,o)&&(e[o]=t[o]);return e},n.apply(this,arguments)};Object.create;Object.create;"function"==typeof SuppressedError&&SuppressedError;function r(e,t){void 0===t&&(t={});var r=e.i18n,o=e.eventEmitter;!function(e){e.setLanguage(["en","en-US"],{details:"Collapsible Block",summary:"Summary header",content:"Hidden content"}),e.setLanguage(["de","de-DE"],{details:"Aufklappbarer Block",summary:"Klick mich zum Aufklappen",content:"Hier steht der versteckte Inhalt"})}(r);var a=function(e){return{name:"details",tooltip:e.get("details"),className:"toastui-editor-toolbar-icons details",command:"details"}}(r);return o.listen("keydown",(function(t,n){console.log("[Details Plugin] keydown via eventEmitter!",{editorType:t,key:n.key}),"wysiwyg"===t&&("Enter"!==n.key&&"ArrowDown"!==n.key||function(e,t){var n=e.instance;if(n){var r=n.getCurrentModeEditor();if(r&&r.view){for(var o=r.view,a=o.state,i=a.selection.$from,s=-1,l=i.depth;l>0;l-=1)if("details"===i.node(l).type.name){s=l;break}if(!(s<0)){var c=i.node(s),d=i.before(s)+c.nodeSize,u=i.after(),p=u>=d-1;if(console.log("[Details Plugin] Inside details:",{key:t.key,isAtEnd:p,afterPara:u,nodeEnd:d,parentType:i.parent.type.name,parentEmpty:0===i.parent.content.size}),p){var f=a.tr,m=a.schema,y=a.selection.constructor;if("Enter"===t.key){if("paragraph"!==i.parent.type.name||0!==i.parent.content.size)return;var g=i.index(s),v=c.child(g-1);if(!v||"paragraph"!==v.type.name||0!==v.content.size)return void console.log("[Details Plugin] Allowing one empty line. Not exiting yet.");console.log("[Details Plugin] EXITING via Double Enter (Two empty paras)!"),t.preventDefault();var h=i.before();f.delete(h,u);var w=m.nodes.paragraph.createAndFill(),k=d-(u-h);f.insert(k,w),y.near&&f.setSelection(y.near(f.doc.resolve(k+1))),o.dispatch(f)}if("ArrowDown"===t.key){if(i.parentOffset!==i.parent.content.size)return;console.log("[Details Plugin] EXITING via ArrowDown!"),t.preventDefault(),d<a.doc.content.size?y.near&&(f.setSelection(y.near(f.doc.resolve(d))),o.dispatch(f)):(w=m.nodes.paragraph.createAndFill(),f.insert(d,w),y.near&&f.setSelection(y.near(f.doc.resolve(d+1))),o.dispatch(f))}}}}else console.log("[Details Plugin] No editor view found")}else console.log("[Details Plugin] No instance found")}(e,n))})),{toHTMLRenderers:{htmlBlock:{renderer:function(e){return/<(\/)?(details|summary)/i.test(e.literal||"")?[{type:"html",content:e.literal||""}]:[{type:"openTag",tagName:"div",outerNewLine:!0},{type:"html",content:e.literal||""},{type:"closeTag",tagName:"div",outerNewLine:!0}]}}},markdownCommands:{details:function(e,t,n){var o=t.tr,a=t.selection,i=t.schema,s=a.content(),l=s.content.textBetween(0,s.content.size,"\n")||r.get("content"),c=""+("<details>\n<summary>"+r.get("summary")+"</summary>\n")+l+"\n</details>";return o.replaceSelectionWith(i.text(c)),n(o),!0}},wysiwygNodeViews:{details:function(e,t,r){var o=document.createElement("details"),a=e.attrs.htmlAttrs||{};return null!==a.open&&void 0!==a.open&&(o.open=!0),o.addEventListener("click",(function(o){var i=o.target;if("SUMMARY"===i.tagName||i.closest("summary")){o.preventDefault();var s=r();if("number"==typeof s){var l=null!==a.open&&void 0!==a.open,c=n({},a);l?delete c.open:c.open="";var d=n(n({},e.attrs),{htmlAttrs:c});t.dispatch(t.state.tr.setNodeMarkup(s,null,d))}}})),{dom:o,contentDOM:o}}},wysiwygCommands:{details:function(e,t,n){var o=t.tr,a=t.schema,i=r.get("summary"),s=r.get("content"),l=a.nodes,c=l.details,d=l.summary,u=l.paragraph;if(c&&d&&u){var p=d.create({},a.text(i)),f=u.create({},a.text(s)),m=c.create({},[p,f]);return o.replaceSelectionWith(m),n(o),!0}return!1}},toolbarItems:[{groupIndex:4,itemIndex:3,item:a}]}}return t=t.default}()}));
@@ -0,0 +1,42 @@
1
+ /*!
2
+ * TOAST UI Editor : Text Align Plugin
3
+ * @version 1.0.12 | Tue Jan 06 2026
4
+ * @author NHN Cloud FE Development Lab <dl_javascript@nhn.com>
5
+ * @license MIT
6
+ */
7
+ /* Light Mode */
8
+ .toastui-editor-toolbar-icons.details {
9
+ background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 24 24%27 fill=%27none%27 stroke=%27%23333%27 stroke-width=%272%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27%3E%3Cpath d=%27M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z%27/%3E%3Cpath d=%27M9 10L12 13 15 10%27 stroke=%27%23333%27/%3E%3C/svg%3E") !important;
10
+ background-size: 24px 24px !important;
11
+ background-repeat: no-repeat !important;
12
+ background-position: center center !important;
13
+ text-indent: -9999px !important;
14
+ }
15
+
16
+ details {
17
+ display: block;
18
+ background-color: #f7f7f7;
19
+ border: 1px solid #e1e1e1;
20
+ border-radius: 4px;
21
+ padding: 10px;
22
+ margin: 10px 0;
23
+ }
24
+
25
+ summary {
26
+ cursor: pointer;
27
+ font-weight: bold;
28
+ margin-bottom: 5px;
29
+ outline: none;
30
+ }
31
+
32
+ /* Dark Mode */
33
+ .toastui-editor-dark .toastui-editor-toolbar-icons.details {
34
+ background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 24 24%27 fill=%27none%27 stroke=%27%23eee%27 stroke-width=%272%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27%3E%3Cpath d=%27M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z%27/%3E%3Cpath d=%27M9 10L12 13 15 10%27 stroke=%27%23eee%27/%3E%3C/svg%3E") !important;
35
+ }
36
+
37
+ .toastui-editor-dark details {
38
+ background-color: #282a36;
39
+ /* Darker background for contrast against editor bg */
40
+ border-color: #44475a;
41
+ color: #f8f8f2;
42
+ }
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * TOAST UI Editor : Text Align Plugin
3
- * @version 1.0.1 | Sat Jan 03 2026
3
+ * @version 1.0.12 | Tue Jan 06 2026
4
4
  * @author NHN Cloud FE Development Lab <dl_javascript@nhn.com>
5
5
  * @license MIT
6
6
  */
@@ -465,6 +465,7 @@ function addLangs(i18n) {
465
465
  ;// CONCATENATED MODULE: ./src/index.ts
466
466
 
467
467
 
468
+
468
469
  var PREFIX = 'toastui-editor-';
469
470
  function createToolbarItemOption(i18n) {
470
471
  return {
@@ -474,16 +475,140 @@ function createToolbarItemOption(i18n) {
474
475
  command: 'details',
475
476
  };
476
477
  }
478
+ function handleDetailsExit(context, event) {
479
+ var instance = context.instance;
480
+ if (!instance) {
481
+ console.log('[Details Plugin] No instance found');
482
+ return;
483
+ }
484
+ var editor = instance.getCurrentModeEditor();
485
+ if (!editor || !editor.view) {
486
+ console.log('[Details Plugin] No editor view found');
487
+ return;
488
+ }
489
+ var view = editor.view;
490
+ var state = view.state;
491
+ var selection = state.selection;
492
+ var $from = selection.$from;
493
+ // Find details node in ancestry
494
+ var detailsDepth = -1;
495
+ for (var d = $from.depth; d > 0; d -= 1) {
496
+ if ($from.node(d).type.name === 'details') {
497
+ detailsDepth = d;
498
+ break;
499
+ }
500
+ }
501
+ if (detailsDepth < 0) {
502
+ return;
503
+ }
504
+ var detailsNode = $from.node(detailsDepth);
505
+ var nodeStart = $from.before(detailsDepth);
506
+ var nodeEnd = nodeStart + detailsNode.nodeSize;
507
+ var afterPara = $from.after();
508
+ var isAtEnd = afterPara >= nodeEnd - 1;
509
+ console.log('[Details Plugin] Inside details:', {
510
+ key: event.key,
511
+ isAtEnd: isAtEnd,
512
+ afterPara: afterPara,
513
+ nodeEnd: nodeEnd,
514
+ parentType: $from.parent.type.name,
515
+ parentEmpty: $from.parent.content.size === 0,
516
+ });
517
+ if (!isAtEnd) {
518
+ return;
519
+ }
520
+ var tr = state.tr, schema = state.schema;
521
+ var SelectionClass = state.selection.constructor;
522
+ // Enter on empty paragraph
523
+ if (event.key === 'Enter') {
524
+ var isEmptyPara = $from.parent.type.name === 'paragraph' && $from.parent.content.size === 0;
525
+ if (!isEmptyPara) {
526
+ return;
527
+ }
528
+ // Check previous node to see if it's also an empty paragraph
529
+ // $from.index(0) gives the index in the parent details node
530
+ var index = $from.index(detailsDepth);
531
+ var prevNode = detailsNode.child(index - 1);
532
+ var isPrevEmptyPara = prevNode && prevNode.type.name === 'paragraph' && prevNode.content.size === 0;
533
+ if (!isPrevEmptyPara) {
534
+ console.log('[Details Plugin] Allowing one empty line. Not exiting yet.');
535
+ return;
536
+ }
537
+ console.log('[Details Plugin] EXITING via Double Enter (Two empty paras)!');
538
+ event.preventDefault();
539
+ var beforePara = $from.before();
540
+ // delete the current empty para and the previous one?
541
+ // Usually "Exit" means: Lift the current empty para out.
542
+ // But if we have TWO empty paras, maybe we want to keep one inside?
543
+ // Let's stick to: If two empty paras, we treat it as an exit signal.
544
+ // We should probably remove the *current* empty para and move the cursor out.
545
+ tr.delete(beforePara, afterPara);
546
+ var p = schema.nodes.paragraph.createAndFill();
547
+ var insertPos = nodeEnd - (afterPara - beforePara);
548
+ tr.insert(insertPos, p);
549
+ if (SelectionClass.near) {
550
+ tr.setSelection(SelectionClass.near(tr.doc.resolve(insertPos + 1)));
551
+ }
552
+ view.dispatch(tr);
553
+ }
554
+ // ArrowDown at end of content
555
+ if (event.key === 'ArrowDown') {
556
+ var atEndOfParent = $from.parentOffset === $from.parent.content.size;
557
+ if (!atEndOfParent) {
558
+ return;
559
+ }
560
+ console.log('[Details Plugin] EXITING via ArrowDown!');
561
+ event.preventDefault();
562
+ if (nodeEnd < state.doc.content.size) {
563
+ if (SelectionClass.near) {
564
+ tr.setSelection(SelectionClass.near(tr.doc.resolve(nodeEnd)));
565
+ view.dispatch(tr);
566
+ }
567
+ }
568
+ else {
569
+ var p = schema.nodes.paragraph.createAndFill();
570
+ tr.insert(nodeEnd, p);
571
+ if (SelectionClass.near) {
572
+ tr.setSelection(SelectionClass.near(tr.doc.resolve(nodeEnd + 1)));
573
+ }
574
+ view.dispatch(tr);
575
+ }
576
+ }
577
+ }
477
578
  function detailsPlugin(context, options) {
478
579
  if (options === void 0) { options = {}; }
479
580
  var i18n = context.i18n, eventEmitter = context.eventEmitter;
480
581
  addLangs(i18n);
481
582
  var toolbarItem = createToolbarItemOption(i18n);
583
+ // Hook into the global keydown event for exit behavior
584
+ eventEmitter.listen('keydown', function (editorType, event) {
585
+ console.log('[Details Plugin] keydown via eventEmitter!', { editorType: editorType, key: event.key });
586
+ if (editorType !== 'wysiwyg') {
587
+ return;
588
+ }
589
+ if (event.key !== 'Enter' && event.key !== 'ArrowDown') {
590
+ return;
591
+ }
592
+ handleDetailsExit(context, event);
593
+ });
482
594
  return {
595
+ toHTMLRenderers: {
596
+ htmlBlock: {
597
+ renderer: function (node) {
598
+ var isDetails = /<(\/)?(details|summary)/i.test(node.literal || '');
599
+ return isDetails
600
+ ? [{ type: 'html', content: node.literal || '' }]
601
+ : [
602
+ { type: 'openTag', tagName: 'div', outerNewLine: true },
603
+ { type: 'html', content: node.literal || '' },
604
+ { type: 'closeTag', tagName: 'div', outerNewLine: true },
605
+ ];
606
+ },
607
+ },
608
+ },
483
609
  markdownCommands: {
484
610
  details: function (payload, _a, dispatch) {
485
611
  var tr = _a.tr, selection = _a.selection, schema = _a.schema;
486
- var from = selection.from, to = selection.to;
487
612
  var slice = selection.content();
488
613
  var textContent = slice.content.textBetween(0, slice.content.size, '\n') || i18n.get('content');
489
614
  var summaryText = i18n.get('summary');
@@ -495,25 +620,14 @@ function detailsPlugin(context, options) {
495
620
  return true;
496
621
  },
497
622
  },
498
- toHTMLRenderers: {
499
- htmlBlock: {
500
- details: function (node) { return [
501
- { type: 'openTag', tagName: 'details', outerNewLine: true },
502
- { type: 'html', content: node.childrenHTML || '' },
503
- { type: 'closeTag', tagName: 'details', outerNewLine: true },
504
- ]; },
505
- summary: function (node) { return [
506
- { type: 'openTag', tagName: 'summary', outerNewLine: true },
507
- { type: 'html', content: node.childrenHTML || '' },
508
- { type: 'closeTag', tagName: 'summary', outerNewLine: true },
509
- ]; },
510
- },
511
- },
512
623
  wysiwygNodeViews: {
513
624
  details: function (node, view, getPos) {
514
625
  var dom = document.createElement('details');
515
- var htmlAttrs = node.attrs.htmlAttrs;
516
- if (htmlAttrs.open) {
626
+ var htmlAttrs = (node.attrs && node.attrs.htmlAttrs) || {};
627
+ var isOpenAttr = node.attrs.open; // Core definition uses direct attribute
628
+ // Check both direct attribute (priority) and htmlAttrs (legacy/plugin)
629
+ var isOpen = isOpenAttr === true || (htmlAttrs && htmlAttrs.open !== null && typeof htmlAttrs.open !== 'undefined');
630
+ if (isOpen) {
517
631
  dom.open = true;
518
632
  }
519
633
  dom.addEventListener('click', function (e) {
@@ -522,87 +636,16 @@ function detailsPlugin(context, options) {
522
636
  e.preventDefault();
523
637
  var pos = getPos();
524
638
  if (typeof pos === 'number') {
525
- var newAttrs = __assign(__assign({}, node.attrs), { htmlAttrs: __assign(__assign({}, htmlAttrs), { open: !htmlAttrs.open }) });
526
- view.dispatch(view.state.tr.setNodeMarkup(pos, null, newAttrs));
527
- }
528
- }
529
- });
530
- // Simplified keydown handler with better logic
531
- dom.addEventListener('keydown', function (e) {
532
- var state = view.state;
533
- var selection = state.selection;
534
- var pos = getPos();
535
- if (typeof pos !== 'number') {
536
- return;
537
- }
538
- // Get fresh node
539
- var currentNode = state.doc.nodeAt(pos);
540
- if (!currentNode || currentNode.type.name !== 'details') {
541
- return;
542
- }
543
- // Calculate end position
544
- var endPos = pos + currentNode.nodeSize;
545
- // Get TextSelection constructor
546
- var SelectionCtor = state.selection.constructor;
547
- // Handle Enter key
548
- if (e.key === 'Enter') {
549
- var $from = selection.$from;
550
- // Check if we're in an empty paragraph
551
- if ($from.parent.type.name === 'paragraph' && $from.parent.content.size === 0) {
552
- // Get position after this paragraph
553
- var afterPara = $from.after();
554
- // Check if this empty paragraph is the last thing in the details block
555
- // endPos - 1 is the position just before the closing tag
556
- if (afterPara >= endPos - 1) {
557
- e.preventDefault();
558
- e.stopPropagation();
559
- var tr = state.tr, schema = state.schema;
560
- // Delete the empty paragraph
561
- var beforePara = $from.before();
562
- tr.delete(beforePara, afterPara);
563
- // Insert new paragraph after the details block
564
- var p = schema.nodes.paragraph.createAndFill();
565
- var insertPos = endPos - (afterPara - beforePara);
566
- tr.insert(insertPos, p);
567
- // Move cursor into new paragraph
568
- if (SelectionCtor && SelectionCtor.near) {
569
- tr.setSelection(SelectionCtor.near(tr.doc.resolve(insertPos + 1)));
570
- }
571
- view.dispatch(tr);
572
- }
573
- }
574
- }
575
- // Handle ArrowDown key
576
- if (e.key === 'ArrowDown') {
577
- var $from = selection.$from;
578
- // Check if cursor is at the end of its parent node
579
- var atEndOfParent = $from.parentOffset === $from.parent.content.size;
580
- if (!atEndOfParent) {
581
- return;
582
- }
583
- // Check if parent is the last child in details
584
- var afterPara = $from.after();
585
- if (afterPara >= endPos - 1) {
586
- e.preventDefault();
587
- e.stopPropagation();
588
- var tr = state.tr, schema = state.schema;
589
- // Try to move to position after details block
590
- if (endPos < state.doc.content.size) {
591
- // There's content after, just move there
592
- if (SelectionCtor && SelectionCtor.near) {
593
- tr.setSelection(SelectionCtor.near(tr.doc.resolve(endPos)));
594
- view.dispatch(tr);
595
- }
639
+ var isOpen_1 = htmlAttrs.open !== null && typeof htmlAttrs.open !== 'undefined';
640
+ var newHtmlAttrs = __assign({}, htmlAttrs);
641
+ if (isOpen_1) {
642
+ delete newHtmlAttrs.open;
596
643
  }
597
644
  else {
598
- // No content after, create a new paragraph
599
- var p = schema.nodes.paragraph.createAndFill();
600
- tr.insert(endPos, p);
601
- if (SelectionCtor && SelectionCtor.near) {
602
- tr.setSelection(SelectionCtor.near(tr.doc.resolve(endPos + 1)));
603
- }
604
- view.dispatch(tr);
645
+ newHtmlAttrs.open = '';
605
646
  }
647
+ var newAttrs = __assign(__assign({}, node.attrs), { htmlAttrs: newHtmlAttrs });
648
+ view.dispatch(view.state.tr.setNodeMarkup(pos, null, newAttrs));
606
649
  }
607
650
  }
608
651
  });
@@ -611,15 +654,15 @@ function detailsPlugin(context, options) {
611
654
  },
612
655
  wysiwygCommands: {
613
656
  details: function (payload, _a, dispatch) {
614
- var tr = _a.tr, selection = _a.selection, schema = _a.schema;
657
+ var tr = _a.tr, schema = _a.schema;
615
658
  var summaryText = i18n.get('summary');
616
659
  var contentText = i18n.get('content');
617
660
  var _b = schema.nodes, details = _b.details, summary = _b.summary, paragraph = _b.paragraph;
618
661
  if (details && summary && paragraph) {
619
662
  var summaryNode = summary.create({}, schema.text(summaryText));
620
663
  var contentNode = paragraph.create({}, schema.text(contentText));
621
- var node = details.create({}, [summaryNode, contentNode]);
622
- tr.replaceSelectionWith(node);
664
+ var detailsNode = details.create({}, [summaryNode, contentNode]);
665
+ tr.replaceSelectionWith(detailsNode);
623
666
  dispatch(tr);
624
667
  return true;
625
668
  }
@@ -628,8 +671,8 @@ function detailsPlugin(context, options) {
628
671
  },
629
672
  toolbarItems: [
630
673
  {
631
- groupIndex: 0,
632
- itemIndex: 4,
674
+ groupIndex: 4,
675
+ itemIndex: 3,
633
676
  item: toolbarItem,
634
677
  },
635
678
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@licium/editor-plugin-details",
3
- "version": "1.0.2",
3
+ "version": "1.0.13",
4
4
  "description": "Details/Summary plugin for Toast UI Editor",
5
5
  "keywords": [
6
6
  "nhn",
@@ -7,7 +7,30 @@
7
7
  text-indent: -9999px !important;
8
8
  }
9
9
 
10
+ details {
11
+ display: block;
12
+ background-color: #f7f7f7;
13
+ border: 1px solid #e1e1e1;
14
+ border-radius: 4px;
15
+ padding: 10px;
16
+ margin: 10px 0;
17
+ }
18
+
19
+ summary {
20
+ cursor: pointer;
21
+ font-weight: bold;
22
+ margin-bottom: 5px;
23
+ outline: none;
24
+ }
25
+
10
26
  /* Dark Mode */
11
27
  .toastui-editor-dark .toastui-editor-toolbar-icons.details {
12
28
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23eee' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z'/%3E%3Cpath d='M9 10L12 13 15 10' stroke='%23eee'/%3E%3C/svg%3E") !important;
29
+ }
30
+
31
+ .toastui-editor-dark details {
32
+ background-color: #282a36;
33
+ /* Darker background for contrast against editor bg */
34
+ border-color: #44475a;
35
+ color: #f8f8f2;
13
36
  }