@umbraci/jsmind 0.9.7 → 0.9.9
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/README.md +44 -0
- package/dist/jsmind.draggable-node.js +9 -0
- package/dist/jsmind.draggable-node.js.map +1 -0
- package/{es6 → dist}/jsmind.js +1 -1
- package/dist/jsmind.js.map +1 -0
- package/dist/jsmind.multiline-text.js +9 -0
- package/dist/jsmind.multiline-text.js.map +1 -0
- package/es/jsmind.draggable-node.js +9 -0
- package/es/jsmind.draggable-node.js.map +1 -0
- package/es/jsmind.js +9 -0
- package/es/jsmind.js.map +1 -0
- package/es/jsmind.multiline-text.js +9 -0
- package/es/jsmind.multiline-text.js.map +1 -0
- package/es/jsmind.screenshot.js +9 -0
- package/es/jsmind.screenshot.js.map +1 -0
- package/lib/jsmind.draggable-node.js +9 -0
- package/lib/jsmind.draggable-node.js.map +1 -0
- package/lib/jsmind.js +9 -0
- package/lib/jsmind.js.map +1 -0
- package/lib/jsmind.multiline-text.js +9 -0
- package/lib/jsmind.multiline-text.js.map +1 -0
- package/lib/jsmind.screenshot.js +9 -0
- package/lib/jsmind.screenshot.js.map +1 -0
- package/package.json +16 -11
- package/types/generated/jsmind.common.d.ts +2 -2
- package/types/generated/jsmind.d.ts +66 -4
- package/types/generated/jsmind.dom.d.ts +2 -2
- package/types/generated/jsmind.enhanced-plugin.d.ts +103 -0
- package/types/generated/jsmind.graph.d.ts +0 -2
- package/types/generated/jsmind.option.d.ts +0 -2
- package/types/generated/jsmind.view_provider.d.ts +1 -26
- package/types/generated/plugins/jsmind.multiline-text-v2.d.ts +58 -0
- package/types/generated/plugins/jsmind.multiline-text.d.ts +43 -0
- package/es6/README-en.md +0 -37
- package/es6/README.md +0 -34
- package/es6/jsmind-esm.js +0 -16
- package/es6/jsmind.draggable-node.js +0 -9
- package/es6/jsmind.draggable-node.js.map +0 -1
- package/es6/jsmind.js.map +0 -1
- package/es6/jsmind.multiline-text.js +0 -9
- package/es6/jsmind.multiline-text.js.map +0 -1
- /package/{es6 → dist}/jsmind.screenshot.js +0 -0
- /package/{es6 → dist}/jsmind.screenshot.js.map +0 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license BSD-3-Clause
|
|
3
|
+
* @copyright 2014-2025 hizzgdev@163.com
|
|
4
|
+
*
|
|
5
|
+
* Project Home:
|
|
6
|
+
* https://github.com/hizzgdev/jsmind/
|
|
7
|
+
*/
|
|
8
|
+
"use strict";function e(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}Object.defineProperty(exports,"__esModule",{value:!0});var t=e(require("@umbraci/jsmind"));if(!t.default)throw new Error("jsMind is not defined");const n=t.default.$,i={text_width:200,line_height:"1.5"};function o(e={}){const t=Object.assign({},i,e);return function(e,n,i){return!(!i.topic||!i.topic.includes("\n"))&&(n.style.whiteSpace="pre-wrap",n.style.wordBreak="break-word",n.style.maxWidth=t.text_width+"px",n.textContent=i.topic,!0)}}function d(e,o){console.log("[Multiline Plugin] Initializing...",o);const d=Object.assign({},i,o),l=e.view;let r=null,a=null;function s(){if(!r||!a)return;const e=r._data.view.element;a.parentNode&&a.parentNode.removeChild(a),e.style.zIndex="auto",r=null,l.editing_node=null,a=null}l.opts.custom_node_render&&(l.render_node=l._custom_node_render.bind(l),console.log("[Multiline Plugin] Re-bound view.render_node")),e.mind&&e.mind.root&&(!function(e){const t=e.view,n=e.mind;if(!n||!n.root)return;const i=[];for(const e in n.nodes){const t=n.nodes[e];t._data&&t._data.view&&t._data.view.element&&i.push(t)}for(const e of i){const n=e._data.view.element;t.render_node(n,e)}for(const t of i)if(e.layout.is_visible(t)){const e=t._data.view.element;t._data.view.width=e.clientWidth,t._data.view.height=e.clientHeight}e.layout.layout(),e.view.show(!1)}(e),console.log("[Multiline Plugin] Re-rendered all nodes")),l.edit_node_begin=function(e){if(console.log("[Multiline Plugin] edit_node_begin called",e),!e.topic)return;r&&l.edit_node_end(),r=e,l.editing_node=e;const t=n.c("div");t.contentEditable="plaintext-only",t.className="jsmind-multiline-editor",t.textContent=e.topic,a=t;const i=e._data.view.element;Object.assign(t.style,{width:"auto",minHeight:i.clientHeight+"px",lineHeight:d.line_height,border:"none",outline:"none",whiteSpace:"pre-wrap",wordBreak:"break-word",boxSizing:"border-box",overflow:"hidden"});const o=()=>{t.style.height="auto",t.style.height=t.scrollHeight+"px"};n.on(t,"input",o),setTimeout(o,0),n.on(t,"keydown",e=>{"Enter"!==e.key||e.shiftKey?"Escape"===e.key?(e.preventDefault(),e.stopPropagation(),function(){if(!r||!a)return;const e=r;s(),l.render_node(e._data.view.element,e),l.e_panel.focus()}()):"Tab"===e.key&&(e.preventDefault(),e.stopPropagation(),l.edit_node_end()):(e.preventDefault(),e.stopPropagation(),l.edit_node_end())}),n.on(t,"blur",()=>{setTimeout(()=>{r&&l.edit_node_end()},100)}),i.innerHTML="",i.appendChild(t),i.style.zIndex=5,t.focus();const u=n.d.createRange();u.selectNodeContents(t);const c=n.w.getSelection();c.removeAllRanges(),c.addRange(u)},l.edit_node_end=function(){if(!r||!a)return;const n=r,i=(a.textContent||"").trim().replace(/\r\n/g,"\n").replace(/\r/g,"\n").replace(/\n{3,}/g,"\n\n");s(),t.default.util.text.is_empty(i)||n.topic===i?l.render_node(n._data.view.element,n):e.update_node(n.id,i),l.e_panel.focus()}}t.default.register_plugin(new t.default.plugin("multiline_text",d));var l={name:"multiline_text",init:d,createMultilineRender:o};exports.createMultilineRender=o,exports.default=l;
|
|
9
|
+
//# sourceMappingURL=jsmind.multiline-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsmind.multiline-text.js","sources":["../src/plugins/jsmind.multiline-text.js"],"sourcesContent":["/**\n * @license BSD\n * @copyright 2014-2025 UmbraCi\n *\n * Project Home:\n * https://github.com/hizzgdev/jsmind/\n */\n\nimport jsMind from '@umbraci/jsmind';\n\nif (!jsMind) {\n throw new Error('jsMind is not defined');\n}\n\nconst $ = jsMind.$;\n\n/**\n * Multiline Text Plugin\n * Provides multiline text support for jsMind nodes\n */\n\n/**\n * Default plugin options\n * @typedef {Object} MultilineTextOptions\n * @property {number} text_width - Maximum width for multiline text nodes (default: 200)\n * @property {string} line_height - Line height for text (default: '1.5')\n */\nconst DEFAULT_OPTIONS = {\n text_width: 200,\n line_height: '1.5',\n};\n\n/**\n * Create a custom node render function for multiline text\n * @param {MultilineTextOptions} [options={}] - Plugin options\n * @param {number} [options.text_width=200] - Maximum width for multiline text nodes\n * @param {string} [options.line_height='1.5'] - Line height for text\n * @returns {function(jsMind, HTMLElement, Node): boolean} Custom render function\n * @example\n * const options = {\n * view: {\n * custom_node_render: createMultilineRender({\n * text_width: 250,\n * line_height: '1.6',\n * })\n * }\n * };\n */\nexport function createMultilineRender(options = {}) {\n const opts = Object.assign({}, DEFAULT_OPTIONS, options);\n\n return function (jm, element, node) {\n if (node.topic && node.topic.includes('\\n')) {\n // Multiline text - apply styles BEFORE setting content\n element.style.whiteSpace = 'pre-wrap';\n element.style.wordBreak = 'break-word';\n element.style.maxWidth = opts.text_width + 'px';\n element.textContent = node.topic;\n return true;\n }\n // Single line text - use default render\n return false;\n };\n}\n\n/**\n * Re-render all nodes to apply multiline styles and recalculate sizes\n * @param {import('../jsmind.js').default} jm - jsMind instance\n * @private\n */\nfunction _rerender_all_nodes(jm) {\n const view = jm.view;\n const mind = jm.mind;\n\n if (!mind || !mind.root) {\n return;\n }\n\n // Collect all nodes to update\n const nodesToUpdate = [];\n for (const nodeId in mind.nodes) {\n const node = mind.nodes[nodeId];\n if (node._data && node._data.view && node._data.view.element) {\n nodesToUpdate.push(node);\n }\n }\n\n // Batch render nodes (only update DOM, no layout trigger)\n for (const node of nodesToUpdate) {\n const element = node._data.view.element;\n view.render_node(element, node);\n }\n\n // Batch update node sizes (read all sizes at once to avoid layout thrashing)\n for (const node of nodesToUpdate) {\n if (jm.layout.is_visible(node)) {\n const element = node._data.view.element;\n node._data.view.width = element.clientWidth;\n node._data.view.height = element.clientHeight;\n }\n }\n\n // Finally recalculate layout and show (only trigger reflow/repaint once)\n jm.layout.layout();\n jm.view.show(false);\n}\n\n/**\n * Plugin initialization function\n * @param {import('../jsmind.js').default} jm - jsMind instance\n * @param {MultilineTextOptions} options - Plugin options\n * @private\n */\nfunction init(jm, options) {\n console.log('[Multiline Plugin] Initializing...', options);\n\n const opts = Object.assign({}, DEFAULT_OPTIONS, options);\n const view = jm.view;\n\n // Plugin state\n let editing_node = null;\n let multiline_editor = null;\n\n // IMPORTANT: Re-set view.render_node to use custom render\n // Because ViewProvider constructor already set it based on options.custom_node_render\n // We need to ensure it uses the custom render function\n if (view.opts.custom_node_render) {\n view.render_node = view._custom_node_render.bind(view);\n console.log('[Multiline Plugin] Re-bound view.render_node');\n }\n\n // Re-render all nodes to apply multiline styles\n if (jm.mind && jm.mind.root) {\n _rerender_all_nodes(jm);\n console.log('[Multiline Plugin] Re-rendered all nodes');\n }\n\n /**\n * Begin editing a node with multiline support\n * @param {import('../jsmind.node.js').Node} node\n */\n view.edit_node_begin = function (node) {\n console.log('[Multiline Plugin] edit_node_begin called', node);\n if (!node.topic) {\n return;\n }\n\n // End editing if another node is being edited\n if (editing_node) {\n view.edit_node_end();\n }\n\n editing_node = node;\n view.editing_node = node;\n\n // Create editor (div with contentEditable)\n const editor = $.c('div');\n editor.contentEditable = 'plaintext-only';\n editor.className = 'jsmind-multiline-editor';\n editor.textContent = node.topic;\n multiline_editor = editor;\n\n // Get element and set editor styles to match\n const element = node._data.view.element;\n\n // Set editor styles to match element width, auto-expand height\n Object.assign(editor.style, {\n width: 'auto',\n minHeight: element.clientHeight + 'px',\n lineHeight: opts.line_height,\n border: 'none',\n outline: 'none',\n whiteSpace: 'pre-wrap',\n wordBreak: 'break-word',\n boxSizing: 'border-box',\n overflow: 'hidden',\n });\n\n // Auto-expand height on input\n const autoExpand = () => {\n editor.style.height = 'auto';\n editor.style.height = editor.scrollHeight + 'px';\n };\n $.on(editor, 'input', autoExpand);\n // Initial expand\n setTimeout(autoExpand, 0);\n\n // Keyboard events\n $.on(editor, 'keydown', e => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n e.stopPropagation(); // Prevent jsMind shortcut from triggering\n view.edit_node_end();\n } else if (e.key === 'Escape') {\n e.preventDefault();\n e.stopPropagation();\n cancel_editing();\n } else if (e.key === 'Tab') {\n e.preventDefault();\n e.stopPropagation();\n view.edit_node_end();\n }\n });\n\n // Blur event - save on blur\n $.on(editor, 'blur', () => {\n setTimeout(() => {\n if (editing_node) {\n view.edit_node_end();\n }\n }, 100);\n });\n\n // Replace node content and focus\n element.innerHTML = '';\n element.appendChild(editor);\n element.style.zIndex = 5;\n editor.focus();\n\n // Select all text\n const range = $.d.createRange();\n range.selectNodeContents(editor);\n const selection = $.w.getSelection();\n selection.removeAllRanges();\n selection.addRange(range);\n };\n\n /**\n * End editing and save changes\n */\n view.edit_node_end = function () {\n if (!editing_node || !multiline_editor) {\n return;\n }\n\n const node = editing_node;\n const topic = (multiline_editor.textContent || '')\n .trim()\n .replace(/\\r\\n/g, '\\n')\n .replace(/\\r/g, '\\n')\n .replace(/\\n{3,}/g, '\\n\\n');\n\n // Clean up editor\n cleanup_editor();\n\n // Update node if content changed\n if (!jsMind.util.text.is_empty(topic) && node.topic !== topic) {\n jm.update_node(node.id, topic);\n } else {\n view.render_node(node._data.view.element, node);\n }\n\n // Focus panel\n view.e_panel.focus();\n };\n\n /**\n * Cancel editing without saving changes\n */\n function cancel_editing() {\n if (!editing_node || !multiline_editor) {\n return;\n }\n\n const node = editing_node;\n\n // Clean up editor\n cleanup_editor();\n\n // Re-render node\n view.render_node(node._data.view.element, node);\n\n // Focus panel\n view.e_panel.focus();\n }\n\n /**\n * Clean up editor and reset state\n */\n function cleanup_editor() {\n if (!editing_node || !multiline_editor) {\n return;\n }\n\n const element = editing_node._data.view.element;\n\n // Remove editor\n if (multiline_editor.parentNode) {\n multiline_editor.parentNode.removeChild(multiline_editor);\n }\n\n // Reset styles and state\n element.style.zIndex = 'auto';\n editing_node = null;\n view.editing_node = null;\n multiline_editor = null;\n }\n}\n\n// Register plugin\njsMind.register_plugin(new jsMind.plugin('multiline_text', init));\n\n// Export for ES6 modules\nexport default {\n name: 'multiline_text',\n init,\n createMultilineRender,\n};\n"],"names":["jsMind","Error","$","DEFAULT_OPTIONS","text_width","line_height","createMultilineRender","options","opts","Object","assign","jm","element","node","topic","includes","style","whiteSpace","wordBreak","maxWidth","textContent","init","console","log","view","editing_node","multiline_editor","cleanup_editor","_data","parentNode","removeChild","zIndex","custom_node_render","render_node","_custom_node_render","bind","mind","root","nodesToUpdate","nodeId","nodes","push","layout","is_visible","width","clientWidth","height","clientHeight","show","_rerender_all_nodes","edit_node_begin","edit_node_end","editor","c","contentEditable","className","minHeight","lineHeight","border","outline","boxSizing","overflow","autoExpand","scrollHeight","on","setTimeout","e","key","shiftKey","preventDefault","stopPropagation","e_panel","focus","cancel_editing","innerHTML","appendChild","range","d","createRange","selectNodeContents","selection","w","getSelection","removeAllRanges","addRange","trim","replace","util","text","is_empty","update_node","id","register_plugin","plugin","jsmind_multilineText","name"],"mappings":";;;;;;;gLAUA,IAAKA,UACD,MAAM,IAAIC,MAAM,yBAGpB,MAAMC,EAAIF,EAAM,QAACE,EAaXC,EAAkB,CACpBC,WAAY,IACZC,YAAa,OAmBV,SAASC,EAAsBC,EAAU,IAC5C,MAAMC,EAAOC,OAAOC,OAAO,CAAA,EAAIP,EAAiBI,GAEhD,OAAO,SAAUI,EAAIC,EAASC,GAC1B,SAAIA,EAAKC,QAASD,EAAKC,MAAMC,SAAS,SAElCH,EAAQI,MAAMC,WAAa,WAC3BL,EAAQI,MAAME,UAAY,aAC1BN,EAAQI,MAAMG,SAAWX,EAAKJ,WAAa,KAC3CQ,EAAQQ,YAAcP,EAAKC,OACpB,EAInB,CACA,CAkDA,SAASO,EAAKV,EAAIJ,GACde,QAAQC,IAAI,qCAAsChB,GAElD,MAAMC,EAAOC,OAAOC,OAAO,CAAA,EAAIP,EAAiBI,GAC1CiB,EAAOb,EAAGa,KAGhB,IAAIC,EAAe,KACfC,EAAmB,KA8JvB,SAASC,IACL,IAAKF,IAAiBC,EAClB,OAGJ,MAAMd,EAAUa,EAAaG,MAAMJ,KAAKZ,QAGpCc,EAAiBG,YACjBH,EAAiBG,WAAWC,YAAYJ,GAI5Cd,EAAQI,MAAMe,OAAS,OACvBN,EAAe,KACfD,EAAKC,aAAe,KACpBC,EAAmB,IACtB,CA1KGF,EAAKhB,KAAKwB,qBACVR,EAAKS,YAAcT,EAAKU,oBAAoBC,KAAKX,GACjDF,QAAQC,IAAI,iDAIZZ,EAAGyB,MAAQzB,EAAGyB,KAAKC,QA9D3B,SAA6B1B,GACzB,MAAMa,EAAOb,EAAGa,KACVY,EAAOzB,EAAGyB,KAEhB,IAAKA,IAASA,EAAKC,KACf,OAIJ,MAAMC,EAAgB,GACtB,IAAK,MAAMC,KAAUH,EAAKI,MAAO,CAC7B,MAAM3B,EAAOuB,EAAKI,MAAMD,GACpB1B,EAAKe,OAASf,EAAKe,MAAMJ,MAAQX,EAAKe,MAAMJ,KAAKZ,SACjD0B,EAAcG,KAAK5B,EAE1B,CAGD,IAAK,MAAMA,KAAQyB,EAAe,CAC9B,MAAM1B,EAAUC,EAAKe,MAAMJ,KAAKZ,QAChCY,EAAKS,YAAYrB,EAASC,EAC7B,CAGD,IAAK,MAAMA,KAAQyB,EACf,GAAI3B,EAAG+B,OAAOC,WAAW9B,GAAO,CAC5B,MAAMD,EAAUC,EAAKe,MAAMJ,KAAKZ,QAChCC,EAAKe,MAAMJ,KAAKoB,MAAQhC,EAAQiC,YAChChC,EAAKe,MAAMJ,KAAKsB,OAASlC,EAAQmC,YACpC,CAILpC,EAAG+B,OAAOA,SACV/B,EAAGa,KAAKwB,MAAK,EACjB,CA4BQC,CAAoBtC,GACpBW,QAAQC,IAAI,6CAOhBC,EAAK0B,gBAAkB,SAAUrC,GAE7B,GADAS,QAAQC,IAAI,4CAA6CV,IACpDA,EAAKC,MACN,OAIAW,GACAD,EAAK2B,gBAGT1B,EAAeZ,EACfW,EAAKC,aAAeZ,EAGpB,MAAMuC,EAASlD,EAAEmD,EAAE,OACnBD,EAAOE,gBAAkB,iBACzBF,EAAOG,UAAY,0BACnBH,EAAOhC,YAAcP,EAAKC,MAC1BY,EAAmB0B,EAGnB,MAAMxC,EAAUC,EAAKe,MAAMJ,KAAKZ,QAGhCH,OAAOC,OAAO0C,EAAOpC,MAAO,CACxB4B,MAAO,OACPY,UAAW5C,EAAQmC,aAAe,KAClCU,WAAYjD,EAAKH,YACjBqD,OAAQ,OACRC,QAAS,OACT1C,WAAY,WACZC,UAAW,aACX0C,UAAW,aACXC,SAAU,WAId,MAAMC,EAAa,KACfV,EAAOpC,MAAM8B,OAAS,OACtBM,EAAOpC,MAAM8B,OAASM,EAAOW,aAAe,MAEhD7D,EAAE8D,GAAGZ,EAAQ,QAASU,GAEtBG,WAAWH,EAAY,GAGvB5D,EAAE8D,GAAGZ,EAAQ,UAAWc,IACN,UAAVA,EAAEC,KAAoBD,EAAEE,SAIP,WAAVF,EAAEC,KACTD,EAAEG,iBACFH,EAAEI,kBAgEd,WACI,IAAK7C,IAAiBC,EAClB,OAGJ,MAAMb,EAAOY,EAGbE,IAGAH,EAAKS,YAAYpB,EAAKe,MAAMJ,KAAKZ,QAASC,GAG1CW,EAAK+C,QAAQC,OAChB,CA9EWC,IACiB,QAAVP,EAAEC,MACTD,EAAEG,iBACFH,EAAEI,kBACF9C,EAAK2B,kBAVLe,EAAEG,iBACFH,EAAEI,kBACF9C,EAAK2B,mBAabjD,EAAE8D,GAAGZ,EAAQ,OAAQ,KACjBa,WAAW,KACHxC,GACAD,EAAK2B,iBAEV,OAIPvC,EAAQ8D,UAAY,GACpB9D,EAAQ+D,YAAYvB,GACpBxC,EAAQI,MAAMe,OAAS,EACvBqB,EAAOoB,QAGP,MAAMI,EAAQ1E,EAAE2E,EAAEC,cAClBF,EAAMG,mBAAmB3B,GACzB,MAAM4B,EAAY9E,EAAE+E,EAAEC,eACtBF,EAAUG,kBACVH,EAAUI,SAASR,EAC3B,EAKIpD,EAAK2B,cAAgB,WACjB,IAAK1B,IAAiBC,EAClB,OAGJ,MAAMb,EAAOY,EACPX,GAASY,EAAiBN,aAAe,IAC1CiE,OACAC,QAAQ,QAAS,MACjBA,QAAQ,MAAO,MACfA,QAAQ,UAAW,QAGxB3D,IAGK3B,EAAAA,QAAOuF,KAAKC,KAAKC,SAAS3E,IAAUD,EAAKC,QAAUA,EAGpDU,EAAKS,YAAYpB,EAAKe,MAAMJ,KAAKZ,QAASC,GAF1CF,EAAG+E,YAAY7E,EAAK8E,GAAI7E,GAM5BU,EAAK+C,QAAQC,OACrB,CA2CA,CAGAxE,EAAAA,QAAO4F,gBAAgB,IAAI5F,EAAM,QAAC6F,OAAO,iBAAkBxE,IAG3D,IAAeyE,EAAA,CACXC,KAAM,iBACN1E,OACAf"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license BSD-3-Clause
|
|
3
|
+
* @copyright 2014-2025 hizzgdev@163.com
|
|
4
|
+
*
|
|
5
|
+
* Project Home:
|
|
6
|
+
* https://github.com/hizzgdev/jsmind/
|
|
7
|
+
*/
|
|
8
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("@umbraci/jsmind"),t=require("dom-to-image");function i(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var n=i(e),r=i(t);if(!n.default)throw new Error("jsMind is not defined");if(!r.default)throw new Error("dom-to-image is required");const o=n.default.$,s={filename:null,watermark:{left:o.w.location,right:"https://github.com/UmbraCi/jsmind"},background:"transparent"};class a{constructor(e,t){var i={};n.default.util.json.merge(i,s),n.default.util.json.merge(i,t),this.version="0.2.0",this.jm=e,this.options=i,this.dpr=e.view.device_pixel_ratio}shoot(){let e=this.create_canvas(),t=e.getContext("2d");t.scale(this.dpr,this.dpr),Promise.resolve(t).then(()=>this.draw_background(t)).then(()=>this.draw_lines(t)).then(()=>this.draw_nodes(t)).then(()=>this.draw_watermark(e,t)).then(()=>this.download(e)).then(()=>this.clear(e))}create_canvas(){let e=o.c("canvas");const t=this.jm.view.size.w,i=this.jm.view.size.h;return e.width=t*this.dpr,e.height=i*this.dpr,e.style.width=t+"px",e.style.height=i+"px",e.style.visibility="hidden",this.jm.view.e_panel.appendChild(e),e}clear(e){e.parentNode.removeChild(e)}draw_background(e){return new Promise(function(t,i){const n=this.options.background;n&&"transparent"!==n&&(e.fillStyle=this.options.background,e.fillRect(0,0,this.jm.view.size.w,this.jm.view.size.h)),t(e)}.bind(this))}draw_lines(e){return new Promise(function(t,i){this.jm.view.graph.copy_to(e,function(){t(e)})}.bind(this))}draw_nodes(e){return r.default.toSvg(this.jm.view.e_nodes,{style:{zoom:1}}).then(this.load_image).then(function(t){return e.drawImage(t,0,0),e})}draw_watermark(e,t){return t.textBaseline="bottom",t.fillStyle="#000",t.font="11px Verdana,Arial,Helvetica,sans-serif",this.options.watermark.left&&(t.textAlign="left",t.fillText(this.options.watermark.left,5.5,e.height-2.5)),this.options.watermark.right&&(t.textAlign="right",t.fillText(this.options.watermark.right,e.width-5.5,e.height-2.5)),t}load_image(e){return new Promise(function(t,i){let n=new Image;n.onload=function(){t(n)},n.onerror=i,n.src=e})}download(e){var t=(this.options.filename||this.jm.mind.name)+".png";if(navigator.msSaveBlob&&e.msToBlob){var i=e.msToBlob();navigator.msSaveBlob(i,t)}else{var n=e.toDataURL(),r=o.c("a");if("download"in r){r.style.visibility="hidden",r.href=n,r.download=t,o.d.body.appendChild(r);var s=o.d.createEvent("MouseEvents");s.initEvent("click",!0,!0),r.dispatchEvent(s),o.d.body.removeChild(r)}else location.href=n}}}const l=new n.default.plugin("screenshot",function(e,t){var i=new a(e,t);e.screenshot=i,e.shoot=function(){i.shoot()}});n.default.register_plugin(l),exports.JmScreenshot=a,exports.default=a,exports.screenshot_plugin=l;
|
|
9
|
+
//# sourceMappingURL=jsmind.screenshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsmind.screenshot.js","sources":["../src/plugins/jsmind.screenshot.js"],"sourcesContent":["/**\n * @license BSD\n * @copyright 2014-2025 UmbraCi\n *\n * Project Home:\n * https://github.com/UmbraCi/jsmind/\n */\n\nimport jsMind from '@umbraci/jsmind';\nimport domtoimage from 'dom-to-image';\n\nif (!jsMind) {\n throw new Error('jsMind is not defined');\n}\n\nif (!domtoimage) {\n throw new Error('dom-to-image is required');\n}\n\nconst $ = jsMind.$;\n\n/**\n * Default options for screenshot plugin.\n * @typedef {Object} ScreenshotOptions\n * @property {string|null} [filename]\n * @property {{left?:string|Location,right?:string}} [watermark]\n * @property {string} [background]\n */\nconst DEFAULT_OPTIONS = {\n filename: null,\n watermark: {\n left: $.w.location,\n right: 'https://github.com/UmbraCi/jsmind',\n },\n background: 'transparent',\n};\n\n/**\n * Screenshot plugin for jsMind.\n */\nexport class JmScreenshot {\n /**\n * Create screenshot plugin instance.\n * @param {import('../jsmind.js').default} jm - jsMind instance\n * @param {Partial<ScreenshotOptions>} options - Plugin options\n */\n constructor(jm, options) {\n var opts = {};\n jsMind.util.json.merge(opts, DEFAULT_OPTIONS);\n jsMind.util.json.merge(opts, options);\n\n this.version = '0.2.0';\n /** @type {import('../jsmind.js').default} */\n this.jm = jm;\n /** @type {ScreenshotOptions} */\n this.options = opts;\n /** @type {number} */\n this.dpr = jm.view.device_pixel_ratio;\n }\n\n /** Take a screenshot of the mind map. */\n shoot() {\n let c = this.create_canvas();\n let ctx = c.getContext('2d');\n ctx.scale(this.dpr, this.dpr);\n Promise.resolve(ctx)\n .then(() => this.draw_background(ctx))\n .then(() => this.draw_lines(ctx))\n .then(() => this.draw_nodes(ctx))\n .then(() => this.draw_watermark(c, ctx))\n .then(() => this.download(c))\n .then(() => this.clear(c));\n }\n\n /**\n * Create canvas for screenshot.\n * @returns {HTMLCanvasElement} Canvas element\n */\n create_canvas() {\n let c = $.c('canvas');\n const w = this.jm.view.size.w;\n const h = this.jm.view.size.h;\n c.width = w * this.dpr;\n c.height = h * this.dpr;\n c.style.width = w + 'px';\n c.style.height = h + 'px';\n\n c.style.visibility = 'hidden';\n this.jm.view.e_panel.appendChild(c);\n return c;\n }\n\n /**\n * Clean up canvas element.\n * @param {HTMLCanvasElement} c - Canvas to remove\n */\n clear(c) {\n c.parentNode.removeChild(c);\n }\n\n /**\n * Draw background on canvas.\n * @param {CanvasRenderingContext2D} ctx - Canvas context\n * @returns {Promise<CanvasRenderingContext2D>} Promise resolving to context\n */\n draw_background(ctx) {\n return new Promise(\n function (resolve, _) {\n const bg = this.options.background;\n if (!!bg && bg !== 'transparent') {\n ctx.fillStyle = this.options.background;\n ctx.fillRect(0, 0, this.jm.view.size.w, this.jm.view.size.h);\n }\n resolve(ctx);\n }.bind(this)\n );\n }\n\n /**\n * Draw connection lines on canvas by copying from view graph.\n * @param {CanvasRenderingContext2D} ctx\n * @returns {Promise<CanvasRenderingContext2D>}\n */\n draw_lines(ctx) {\n return new Promise(\n function (resolve, _) {\n this.jm.view.graph.copy_to(ctx, function () {\n resolve(ctx);\n });\n }.bind(this)\n );\n }\n\n /**\n * Draw node DOM into canvas via SVG snapshot.\n * @param {CanvasRenderingContext2D} ctx\n * @returns {Promise<CanvasRenderingContext2D>}\n */\n draw_nodes(ctx) {\n return domtoimage\n .toSvg(this.jm.view.e_nodes, { style: { zoom: 1 } })\n .then(this.load_image)\n .then(function (img) {\n ctx.drawImage(img, 0, 0);\n return ctx;\n });\n }\n\n /**\n * Draw watermark text on canvas.\n * @param {HTMLCanvasElement} c\n * @param {CanvasRenderingContext2D} ctx\n * @returns {CanvasRenderingContext2D}\n */\n draw_watermark(c, ctx) {\n ctx.textBaseline = 'bottom';\n ctx.fillStyle = '#000';\n ctx.font = '11px Verdana,Arial,Helvetica,sans-serif';\n if (!!this.options.watermark.left) {\n ctx.textAlign = 'left';\n ctx.fillText(this.options.watermark.left, 5.5, c.height - 2.5);\n }\n if (!!this.options.watermark.right) {\n ctx.textAlign = 'right';\n ctx.fillText(this.options.watermark.right, c.width - 5.5, c.height - 2.5);\n }\n return ctx;\n }\n\n /**\n * Load image from URL and resolve img element.\n * @param {string} url\n * @returns {Promise<HTMLImageElement>}\n */\n load_image(url) {\n return new Promise(function (resolve, reject) {\n let img = new Image();\n img.onload = function () {\n resolve(img);\n };\n img.onerror = reject;\n img.src = url;\n });\n }\n\n /**\n * Trigger download of canvas content as PNG.\n * @param {HTMLCanvasElement} c\n */\n download(c) {\n var name = (this.options.filename || this.jm.mind.name) + '.png';\n\n if (navigator.msSaveBlob && !!c.msToBlob) {\n var blob = c.msToBlob();\n navigator.msSaveBlob(blob, name);\n } else {\n var blob_url = c.toDataURL();\n var anchor = $.c('a');\n if ('download' in anchor) {\n anchor.style.visibility = 'hidden';\n anchor.href = blob_url;\n anchor.download = name;\n $.d.body.appendChild(anchor);\n var evt = $.d.createEvent('MouseEvents');\n evt.initEvent('click', true, true);\n anchor.dispatchEvent(evt);\n $.d.body.removeChild(anchor);\n } else {\n location.href = blob_url;\n }\n }\n }\n}\n\n/**\n * Screenshot plugin registration.\n * @type {import('../jsmind.plugin.js').Plugin<Partial<ScreenshotOptions>>}\n */\nexport const screenshot_plugin = new jsMind.plugin('screenshot', function (jm, options) {\n var jmss = new JmScreenshot(jm, options);\n jm.screenshot = jmss;\n jm.shoot = function () {\n jmss.shoot();\n };\n});\n\njsMind.register_plugin(screenshot_plugin);\n\nexport default JmScreenshot;\n"],"names":["jsMind","Error","domtoimage","$","DEFAULT_OPTIONS","filename","watermark","left","w","location","right","background","JmScreenshot","constructor","jm","options","opts","util","json","merge","this","version","dpr","view","device_pixel_ratio","shoot","c","create_canvas","ctx","getContext","scale","Promise","resolve","then","draw_background","draw_lines","draw_nodes","draw_watermark","download","clear","size","h","width","height","style","visibility","e_panel","appendChild","parentNode","removeChild","_","bg","fillStyle","fillRect","bind","graph","copy_to","toSvg","e_nodes","zoom","load_image","img","drawImage","textBaseline","font","textAlign","fillText","url","reject","Image","onload","onerror","src","name","mind","navigator","msSaveBlob","msToBlob","blob","blob_url","toDataURL","anchor","href","d","body","evt","createEvent","initEvent","dispatchEvent","screenshot_plugin","plugin","jmss","screenshot","register_plugin"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umbraci/jsmind",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.9",
|
|
4
4
|
"description": "jsMind is a pure javascript library for mindmap, it base on html5 canvas. jsMind was released under BSD license, you can embed it in any project, if only you observe the license.",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "lib/jsmind.js",
|
|
6
|
+
"module": "es/jsmind.js",
|
|
7
|
+
"unpkg": "dist/jsmind.js",
|
|
8
|
+
"jsdelivr": "dist/jsmind.js",
|
|
6
9
|
"types": "types/generated/index.d.ts",
|
|
7
10
|
"exports": {
|
|
8
11
|
".": {
|
|
9
|
-
"import": "./
|
|
10
|
-
"require": "./
|
|
12
|
+
"import": "./es/jsmind.js",
|
|
13
|
+
"require": "./lib/jsmind.js",
|
|
11
14
|
"types": "./types/generated/index.d.ts"
|
|
12
15
|
},
|
|
13
16
|
"./draggable-node": {
|
|
14
|
-
"import": "./
|
|
15
|
-
"require": "./
|
|
17
|
+
"import": "./es/jsmind.draggable-node.js",
|
|
18
|
+
"require": "./lib/jsmind.draggable-node.js",
|
|
16
19
|
"types": "./types/generated/plugins/jsmind.draggable-node.d.ts"
|
|
17
20
|
},
|
|
18
21
|
"./screenshot": {
|
|
19
|
-
"import": "./
|
|
20
|
-
"require": "./
|
|
22
|
+
"import": "./es/jsmind.screenshot.js",
|
|
23
|
+
"require": "./lib/jsmind.screenshot.js",
|
|
21
24
|
"types": "./types/generated/plugins/jsmind.screenshot.d.ts"
|
|
22
25
|
},
|
|
23
26
|
"./multiline-text": {
|
|
24
|
-
"import": "./
|
|
25
|
-
"require": "./
|
|
27
|
+
"import": "./es/jsmind.multiline-text.js",
|
|
28
|
+
"require": "./lib/jsmind.multiline-text.js",
|
|
26
29
|
"types": "./types/generated/plugins/jsmind.multiline-text.d.ts"
|
|
27
30
|
},
|
|
28
31
|
"./style/jsmind.css": "./style/jsmind.css"
|
|
@@ -32,7 +35,9 @@
|
|
|
32
35
|
"example": "example"
|
|
33
36
|
},
|
|
34
37
|
"files": [
|
|
35
|
-
"
|
|
38
|
+
"es",
|
|
39
|
+
"lib",
|
|
40
|
+
"dist",
|
|
36
41
|
"style",
|
|
37
42
|
"types",
|
|
38
43
|
"LICENSE",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* jsMind runtime: orchestrates data/layout/view/shortcut and exposes public API.
|
|
7
7
|
*/
|
|
8
|
-
|
|
8
|
+
declare class jsMind {
|
|
9
9
|
static mind: typeof Mind;
|
|
10
10
|
static node: typeof Node;
|
|
11
11
|
static direction: import("./jsmind.common.js").DirectionType;
|
|
@@ -44,6 +44,22 @@ export default class jsMind {
|
|
|
44
44
|
is_empty: (s?: string) => boolean;
|
|
45
45
|
};
|
|
46
46
|
};
|
|
47
|
+
static enhanced_plugin: typeof EnhancedPlugin;
|
|
48
|
+
/** @type {Array<import('./jsmind.enhanced-plugin.js').PluginDescriptor>} */
|
|
49
|
+
static enhancedPluginList: Array<import("./jsmind.enhanced-plugin.js").PluginDescriptor>;
|
|
50
|
+
/**
|
|
51
|
+
* Register an enhanced plugin
|
|
52
|
+
* @param {typeof EnhancedPlugin} PluginClass - Plugin class
|
|
53
|
+
* @param {object} [options={}] - Plugin options
|
|
54
|
+
* @returns {typeof jsMind}
|
|
55
|
+
*/
|
|
56
|
+
static usePlugin(PluginClass: typeof EnhancedPlugin, options?: object): typeof jsMind;
|
|
57
|
+
/**
|
|
58
|
+
* Check if an enhanced plugin is registered
|
|
59
|
+
* @param {typeof EnhancedPlugin} PluginClass - Plugin class
|
|
60
|
+
* @returns {boolean}
|
|
61
|
+
*/
|
|
62
|
+
static hasEnhancedPlugin(PluginClass: typeof EnhancedPlugin): boolean;
|
|
47
63
|
/**
|
|
48
64
|
* Deprecated: static show constructor helper.
|
|
49
65
|
* @param {import('./jsmind.option.js').JsMindRuntimeOptions} options
|
|
@@ -64,6 +80,7 @@ export default class jsMind {
|
|
|
64
80
|
event_handles: Array<(type: number, data: EventData) => void>;
|
|
65
81
|
/** Initialize sub-systems and plugins. */
|
|
66
82
|
init(): void;
|
|
83
|
+
enhancedPluginManager: EnhancedPluginManager;
|
|
67
84
|
data: DataProvider;
|
|
68
85
|
layout: LayoutProvider;
|
|
69
86
|
view: ViewProvider;
|
|
@@ -202,16 +219,39 @@ export default class jsMind {
|
|
|
202
219
|
add_node(parent_node: string | import("./jsmind.node.js").Node, node_id: string, topic: string, data?: Record<string, any> | undefined, direction?: ("left" | "center" | "right" | "-1" | "0" | "1" | number) | undefined): import("./jsmind.node.js").Node | null;
|
|
203
220
|
/**
|
|
204
221
|
* Add multiple nodes to the mind map with optimized performance.
|
|
222
|
+
* Supports standard jsMind formats: node_tree, node_array, and freemind with nested children structure.
|
|
205
223
|
* @param {string | import('./jsmind.node.js').Node} parent_node - Parent node for all new nodes
|
|
206
|
-
* @param {Array<{
|
|
207
|
-
* @returns {Array<import('./jsmind.node.js').Node|null>} Array of created nodes
|
|
224
|
+
* @param {Array<{id: string, topic: string, data?: Record<string, any>, direction?: ('left'|'center'|'right'|'-1'|'0'|'1'|number), children?: Array}>} nodes_data - Array of node data objects with same format as add_node
|
|
225
|
+
* @returns {Array<import('./jsmind.node.js').Node|null>} Array of created nodes (flattened from all levels)
|
|
208
226
|
*/
|
|
209
227
|
add_nodes(parent_node: string | import("./jsmind.node.js").Node, nodes_data: Array<{
|
|
210
|
-
|
|
228
|
+
id: string;
|
|
211
229
|
topic: string;
|
|
212
230
|
data?: Record<string, any>;
|
|
213
231
|
direction?: ("left" | "center" | "right" | "-1" | "0" | "1" | number);
|
|
232
|
+
children?: any[];
|
|
214
233
|
}>): Array<import("./jsmind.node.js").Node | null>;
|
|
234
|
+
/**
|
|
235
|
+
* Recursively add nodes using existing format processors.
|
|
236
|
+
* @private
|
|
237
|
+
* @param {import('./jsmind.node.js').Node} parent_node
|
|
238
|
+
* @param {object} node_data
|
|
239
|
+
* @returns {Array<import('./jsmind.node.js').Node|null>}
|
|
240
|
+
*/
|
|
241
|
+
private _add_nodes_recursive;
|
|
242
|
+
/**
|
|
243
|
+
* Count expected nodes recursively.
|
|
244
|
+
* @private
|
|
245
|
+
* @param {Array} nodes_data
|
|
246
|
+
* @returns {number}
|
|
247
|
+
*/
|
|
248
|
+
private _count_expected_nodes;
|
|
249
|
+
/**
|
|
250
|
+
* Clean up partially created nodes without triggering UI refresh for each node.
|
|
251
|
+
* @private
|
|
252
|
+
* @param {Array<import('./jsmind.node.js').Node>} created_nodes
|
|
253
|
+
*/
|
|
254
|
+
private _cleanup_partial_nodes;
|
|
215
255
|
/**
|
|
216
256
|
* Insert a node before target node.
|
|
217
257
|
* @param {string | import('./jsmind.node.js').Node} node_before
|
|
@@ -324,7 +364,26 @@ export default class jsMind {
|
|
|
324
364
|
invoke_event_handle(type: number, data: EventData): void;
|
|
325
365
|
/** @param {number} type @param {EventData} data */
|
|
326
366
|
_invoke_event_handle(type: number, data: EventData): void;
|
|
367
|
+
/**
|
|
368
|
+
* Remove an enhanced plugin
|
|
369
|
+
* @param {typeof EnhancedPlugin} PluginClass - Plugin class
|
|
370
|
+
*/
|
|
371
|
+
removePlugin(PluginClass: typeof EnhancedPlugin): void;
|
|
372
|
+
removePlugin(PluginClass: typeof EnhancedPlugin): void;
|
|
373
|
+
/**
|
|
374
|
+
* Get an enhanced plugin instance
|
|
375
|
+
* @param {string} instanceName - Plugin instance name
|
|
376
|
+
* @returns {EnhancedPlugin | undefined}
|
|
377
|
+
*/
|
|
378
|
+
getPlugin(instanceName: string): EnhancedPlugin | undefined;
|
|
379
|
+
getPlugin(instanceName: string): EnhancedPlugin | undefined;
|
|
380
|
+
/**
|
|
381
|
+
* Destroy the jsMind instance and clean up resources
|
|
382
|
+
*/
|
|
383
|
+
destroy(): void;
|
|
384
|
+
destroy(): void;
|
|
327
385
|
}
|
|
386
|
+
export default jsMind;
|
|
328
387
|
/**
|
|
329
388
|
* Event callback payload
|
|
330
389
|
*/
|
|
@@ -333,11 +392,14 @@ export type EventData = {
|
|
|
333
392
|
data?: unknown[];
|
|
334
393
|
node?: string;
|
|
335
394
|
};
|
|
395
|
+
export namespace jsMind { }
|
|
336
396
|
import { Mind } from './jsmind.mind.js';
|
|
397
|
+
import { EnhancedPluginManager } from './jsmind.enhanced-plugin.js';
|
|
337
398
|
import { DataProvider } from './jsmind.data_provider.js';
|
|
338
399
|
import { LayoutProvider } from './jsmind.layout_provider.js';
|
|
339
400
|
import { ViewProvider } from './jsmind.view_provider.js';
|
|
340
401
|
import { ShortcutProvider } from './jsmind.shortcut_provider.js';
|
|
402
|
+
import { EnhancedPlugin } from './jsmind.enhanced-plugin.js';
|
|
341
403
|
import { Node } from './jsmind.node.js';
|
|
342
404
|
import { Plugin } from './jsmind.plugin.js';
|
|
343
405
|
import { register as _register_plugin } from './jsmind.plugin.js';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export const $: Dom;
|
|
2
2
|
/**
|
|
3
3
|
* @license BSD
|
|
4
|
-
* @copyright 2014-2025
|
|
4
|
+
* @copyright 2014-2025 UmbraCi
|
|
5
5
|
*
|
|
6
6
|
* Project Home:
|
|
7
|
-
* https://github.com/
|
|
7
|
+
* https://github.com/UmbraCi/jsmind/
|
|
8
8
|
*/
|
|
9
9
|
/**
|
|
10
10
|
* Lightweight DOM helpers bound to a window.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Plugin Manager
|
|
3
|
+
* Manages the lifecycle of enhanced plugins with synchronous initialization,
|
|
4
|
+
* preload support, and lifecycle hooks.
|
|
5
|
+
*/
|
|
6
|
+
export class EnhancedPluginManager {
|
|
7
|
+
/**
|
|
8
|
+
* @param {import('./jsmind.js').default} jm - jsMind instance
|
|
9
|
+
*/
|
|
10
|
+
constructor(jm: import("./jsmind.js").default);
|
|
11
|
+
jm: import("./jsmind.js").default;
|
|
12
|
+
/** @type {Map<string, EnhancedPlugin>} */
|
|
13
|
+
plugins: Map<string, EnhancedPlugin>;
|
|
14
|
+
/**
|
|
15
|
+
* Initialize preload plugins (before core modules)
|
|
16
|
+
*/
|
|
17
|
+
initPreloadPlugins(): void;
|
|
18
|
+
/**
|
|
19
|
+
* Initialize normal plugins (after core modules)
|
|
20
|
+
*/
|
|
21
|
+
initNormalPlugins(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Internal method: Initialize a single plugin
|
|
24
|
+
* @param {PluginDescriptor} descriptor
|
|
25
|
+
* @private
|
|
26
|
+
*/
|
|
27
|
+
private _initPlugin;
|
|
28
|
+
/**
|
|
29
|
+
* Remove a plugin
|
|
30
|
+
* @param {typeof EnhancedPlugin} PluginClass
|
|
31
|
+
*/
|
|
32
|
+
removePlugin(PluginClass: typeof EnhancedPlugin): void;
|
|
33
|
+
/**
|
|
34
|
+
* Destroy all plugins
|
|
35
|
+
*/
|
|
36
|
+
destroyAllPlugins(): void;
|
|
37
|
+
/**
|
|
38
|
+
* Get plugin instance by name
|
|
39
|
+
* @param {string} instanceName
|
|
40
|
+
* @returns {EnhancedPlugin | undefined}
|
|
41
|
+
*/
|
|
42
|
+
getPlugin(instanceName: string): EnhancedPlugin | undefined;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Enhanced Plugin Base Class
|
|
46
|
+
* Provides standard interface for enhanced plugins
|
|
47
|
+
*/
|
|
48
|
+
export class EnhancedPlugin {
|
|
49
|
+
/**
|
|
50
|
+
* Plugin instance name (must be defined by subclass)
|
|
51
|
+
* @type {string}
|
|
52
|
+
*/
|
|
53
|
+
static instanceName: string;
|
|
54
|
+
/**
|
|
55
|
+
* Whether to initialize before core modules
|
|
56
|
+
* @type {boolean}
|
|
57
|
+
*/
|
|
58
|
+
static preload: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* @param {{ jm: import('./jsmind.js').default, pluginOpt: object }} params
|
|
61
|
+
*/
|
|
62
|
+
constructor({ jm, pluginOpt }: {
|
|
63
|
+
jm: import("./jsmind.js").default;
|
|
64
|
+
pluginOpt: object;
|
|
65
|
+
});
|
|
66
|
+
jm: import("./jsmind.js").default;
|
|
67
|
+
options: any;
|
|
68
|
+
/**
|
|
69
|
+
* Called before plugin is removed
|
|
70
|
+
* Override this method to clean up resources
|
|
71
|
+
*/
|
|
72
|
+
beforePluginRemove(): void;
|
|
73
|
+
/**
|
|
74
|
+
* Called before jsMind instance is destroyed
|
|
75
|
+
* Override this method to clean up resources
|
|
76
|
+
*/
|
|
77
|
+
beforePluginDestroy(): void;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Plugin descriptor
|
|
81
|
+
*/
|
|
82
|
+
export type PluginDescriptor = {
|
|
83
|
+
/**
|
|
84
|
+
* - Plugin class
|
|
85
|
+
*/
|
|
86
|
+
PluginClass: typeof EnhancedPlugin;
|
|
87
|
+
/**
|
|
88
|
+
* - Plugin instance name
|
|
89
|
+
*/
|
|
90
|
+
instanceName: string;
|
|
91
|
+
/**
|
|
92
|
+
* - Whether to preload
|
|
93
|
+
*/
|
|
94
|
+
preload: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* - Plugin options
|
|
97
|
+
*/
|
|
98
|
+
pluginOpt: object;
|
|
99
|
+
/**
|
|
100
|
+
* - Plugin instance (after initialization)
|
|
101
|
+
*/
|
|
102
|
+
instance: EnhancedPlugin | null;
|
|
103
|
+
};
|
|
@@ -37,7 +37,6 @@ declare class SvgGraph {
|
|
|
37
37
|
};
|
|
38
38
|
custom_node_render?: Function;
|
|
39
39
|
expander_style: "char" | "number";
|
|
40
|
-
enable_multiline?: boolean;
|
|
41
40
|
};
|
|
42
41
|
e_svg: SVGElement;
|
|
43
42
|
size: {
|
|
@@ -122,7 +121,6 @@ declare class CanvasGraph {
|
|
|
122
121
|
};
|
|
123
122
|
custom_node_render?: Function;
|
|
124
123
|
expander_style: "char" | "number";
|
|
125
|
-
enable_multiline?: boolean;
|
|
126
124
|
};
|
|
127
125
|
e_canvas: HTMLElement;
|
|
128
126
|
canvas_ctx: CanvasRenderingContext2D;
|
|
@@ -10,8 +10,6 @@ export type JsMindRuntimeOptions = {
|
|
|
10
10
|
theme?: (string | null);
|
|
11
11
|
mode?: ("full" | "side");
|
|
12
12
|
support_html?: boolean;
|
|
13
|
-
enable_multiline?: boolean;
|
|
14
|
-
textAutoWrapWidth?: number;
|
|
15
13
|
log_level?: "debug" | "info" | "warn" | "error" | "disable";
|
|
16
14
|
view?: {
|
|
17
15
|
engine?: "canvas" | "svg";
|
|
@@ -16,8 +16,7 @@ export class ViewProvider {
|
|
|
16
16
|
* node_overflow: 'hidden'|'wrap',
|
|
17
17
|
* zoom: {min:number, max:number, step:number, mask_key:number},
|
|
18
18
|
* custom_node_render?: Function,
|
|
19
|
-
* expander_style: 'char'|'number'
|
|
20
|
-
* enable_multiline?: boolean
|
|
19
|
+
* expander_style: 'char'|'number'
|
|
21
20
|
* }} options - View configuration options
|
|
22
21
|
*/
|
|
23
22
|
constructor(jm: import("./jsmind.js").default, options: {
|
|
@@ -40,7 +39,6 @@ export class ViewProvider {
|
|
|
40
39
|
};
|
|
41
40
|
custom_node_render?: Function;
|
|
42
41
|
expander_style: "char" | "number";
|
|
43
|
-
enable_multiline?: boolean;
|
|
44
42
|
});
|
|
45
43
|
opts: {
|
|
46
44
|
engine: "canvas" | "svg";
|
|
@@ -62,7 +60,6 @@ export class ViewProvider {
|
|
|
62
60
|
};
|
|
63
61
|
custom_node_render?: Function;
|
|
64
62
|
expander_style: "char" | "number";
|
|
65
|
-
enable_multiline?: boolean;
|
|
66
63
|
};
|
|
67
64
|
jm: import("./jsmind.js").default;
|
|
68
65
|
layout: import("./jsmind.layout_provider.js").LayoutProvider;
|
|
@@ -97,7 +94,6 @@ export class ViewProvider {
|
|
|
97
94
|
};
|
|
98
95
|
custom_node_render?: Function;
|
|
99
96
|
expander_style: "char" | "number";
|
|
100
|
-
enable_multiline?: boolean;
|
|
101
97
|
};
|
|
102
98
|
e_svg: SVGElement;
|
|
103
99
|
size: {
|
|
@@ -148,7 +144,6 @@ export class ViewProvider {
|
|
|
148
144
|
};
|
|
149
145
|
custom_node_render?: Function;
|
|
150
146
|
expander_style: "char" | "number";
|
|
151
|
-
enable_multiline?: boolean;
|
|
152
147
|
};
|
|
153
148
|
e_canvas: HTMLElement;
|
|
154
149
|
canvas_ctx: CanvasRenderingContext2D;
|
|
@@ -223,11 +218,6 @@ export class ViewProvider {
|
|
|
223
218
|
* @param {import('./jsmind.node.js').Node} node - Target node
|
|
224
219
|
*/
|
|
225
220
|
init_nodes_size(node: import("./jsmind.node.js").Node): void;
|
|
226
|
-
/**
|
|
227
|
-
* Ensure correct size calculation for multiline nodes
|
|
228
|
-
* @param {HTMLElement} element - Node element
|
|
229
|
-
*/
|
|
230
|
-
_ensure_multiline_node_size(element: HTMLElement): void;
|
|
231
221
|
/** Initialize DOM elements for all nodes. */
|
|
232
222
|
init_nodes(): void;
|
|
233
223
|
/**
|
|
@@ -256,11 +246,6 @@ export class ViewProvider {
|
|
|
256
246
|
* @param {import('./jsmind.node.js').Node} node - Node to update
|
|
257
247
|
*/
|
|
258
248
|
update_node(node: import("./jsmind.node.js").Node): void;
|
|
259
|
-
/**
|
|
260
|
-
* Force recalculation of node size
|
|
261
|
-
* @param {import('./jsmind.node.js').Node} node - Node to recalculate
|
|
262
|
-
*/
|
|
263
|
-
_force_recalc_node_size(node: import("./jsmind.node.js").Node): void;
|
|
264
249
|
/**
|
|
265
250
|
* Select a node visually.
|
|
266
251
|
* @param {import('./jsmind.node.js').Node|null} node - Node to select
|
|
@@ -285,16 +270,6 @@ export class ViewProvider {
|
|
|
285
270
|
edit_node_begin(node: import("./jsmind.node.js").Node): void;
|
|
286
271
|
/** End editing current node. */
|
|
287
272
|
edit_node_end(): void;
|
|
288
|
-
_editor_input_handler: () => void;
|
|
289
|
-
/**
|
|
290
|
-
* Setup editor resize listener for multiline editing
|
|
291
|
-
* Reference TextEdit.js updateTextEditNode logic
|
|
292
|
-
*/
|
|
293
|
-
_setup_editor_resize_listener(): void;
|
|
294
|
-
/**
|
|
295
|
-
* Dynamically update CSS styles for multiline nodes to sync max-width with textAutoWrapWidth
|
|
296
|
-
*/
|
|
297
|
-
_update_multiline_css(): void;
|
|
298
273
|
/** @returns {{x:number,y:number}} */
|
|
299
274
|
get_view_offset(): {
|
|
300
275
|
x: number;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export default MultilineTextV2;
|
|
2
|
+
/**
|
|
3
|
+
* MultilineTextV2 Plugin
|
|
4
|
+
* Enhanced multiline text plugin using the new plugin system
|
|
5
|
+
*/
|
|
6
|
+
declare class MultilineTextV2 extends EnhancedPlugin {
|
|
7
|
+
/**
|
|
8
|
+
* @param {{ jm: import('../jsmind.js').default, pluginOpt: object }} params
|
|
9
|
+
*/
|
|
10
|
+
constructor({ jm, pluginOpt }: {
|
|
11
|
+
jm: import("../jsmind.js").default;
|
|
12
|
+
pluginOpt: object;
|
|
13
|
+
});
|
|
14
|
+
options: any;
|
|
15
|
+
editing_node: import("../jsmind.node.js").Node;
|
|
16
|
+
multiline_editor: HTMLElement;
|
|
17
|
+
_original_custom_render: Function | ((jm: import("../jsmind.js").default, ele: HTMLElement, node: import("../jsmind.node.js").Node) => void);
|
|
18
|
+
_original_edit_node_begin: any;
|
|
19
|
+
_original_edit_node_end: any;
|
|
20
|
+
/**
|
|
21
|
+
* Setup custom rendering in options (for preload plugins)
|
|
22
|
+
*/
|
|
23
|
+
setupCustomRenderInOptions(): void;
|
|
24
|
+
/**
|
|
25
|
+
* Setup custom rendering for multiline text (for normal plugins)
|
|
26
|
+
*/
|
|
27
|
+
setupCustomRender(): void;
|
|
28
|
+
/**
|
|
29
|
+
* Setup edit handlers (common for both preload and normal plugins)
|
|
30
|
+
*/
|
|
31
|
+
setupEditHandlers(): void;
|
|
32
|
+
/**
|
|
33
|
+
* Re-render all nodes to apply multiline styles and recalculate sizes.
|
|
34
|
+
* This is necessary because node sizes are calculated before custom styles are applied.
|
|
35
|
+
* @private
|
|
36
|
+
*/
|
|
37
|
+
private _rerender_all_nodes;
|
|
38
|
+
/**
|
|
39
|
+
* Begin editing a node
|
|
40
|
+
* @param {import('../jsmind.node.js').Node} node
|
|
41
|
+
*/
|
|
42
|
+
edit_node_begin(node: import("../jsmind.node.js").Node): void;
|
|
43
|
+
/**
|
|
44
|
+
* End editing and save changes.
|
|
45
|
+
*/
|
|
46
|
+
edit_node_end(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Cancel editing without saving changes.
|
|
49
|
+
*/
|
|
50
|
+
cancel_editing(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Clean up editor and reset state.
|
|
53
|
+
* @private
|
|
54
|
+
*/
|
|
55
|
+
private _cleanup_editor;
|
|
56
|
+
}
|
|
57
|
+
import { EnhancedPlugin } from '../jsmind.enhanced-plugin.js';
|
|
58
|
+
import jsMind from '../jsmind.js';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a custom node render function for multiline text
|
|
3
|
+
* @param {MultilineTextOptions} [options={}] - Plugin options
|
|
4
|
+
* @param {number} [options.text_width=200] - Maximum width for multiline text nodes
|
|
5
|
+
* @param {string} [options.line_height='1.5'] - Line height for text
|
|
6
|
+
* @returns {function(jsMind, HTMLElement, Node): boolean} Custom render function
|
|
7
|
+
* @example
|
|
8
|
+
* const options = {
|
|
9
|
+
* view: {
|
|
10
|
+
* custom_node_render: createMultilineRender({
|
|
11
|
+
* text_width: 250,
|
|
12
|
+
* line_height: '1.6',
|
|
13
|
+
* })
|
|
14
|
+
* }
|
|
15
|
+
* };
|
|
16
|
+
*/
|
|
17
|
+
export function createMultilineRender(options?: MultilineTextOptions): (arg0: jsMind, arg1: HTMLElement, arg2: Node) => boolean;
|
|
18
|
+
declare namespace _default {
|
|
19
|
+
export let name: string;
|
|
20
|
+
export { init };
|
|
21
|
+
export { createMultilineRender };
|
|
22
|
+
}
|
|
23
|
+
export default _default;
|
|
24
|
+
/**
|
|
25
|
+
* Default plugin options
|
|
26
|
+
*/
|
|
27
|
+
export type MultilineTextOptions = {
|
|
28
|
+
/**
|
|
29
|
+
* - Maximum width for multiline text nodes (default: 200)
|
|
30
|
+
*/
|
|
31
|
+
text_width: number;
|
|
32
|
+
/**
|
|
33
|
+
* - Line height for text (default: '1.5')
|
|
34
|
+
*/
|
|
35
|
+
line_height: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Plugin initialization function
|
|
39
|
+
* @param {import('../jsmind.js').default} jm - jsMind instance
|
|
40
|
+
* @param {MultilineTextOptions} options - Plugin options
|
|
41
|
+
* @private
|
|
42
|
+
*/
|
|
43
|
+
declare function init(jm: import("../jsmind.js").default, options: MultilineTextOptions): void;
|
package/es6/README-en.md
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# jsMind ES6 Version
|
|
2
|
-
|
|
3
|
-
[[中文](README.md)] | [English]
|
|
4
|
-
|
|
5
|
-
jsMind is now written in ES6 code, the source code is in `/src`, and the dist directory is `/es6`.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
### Compatibility
|
|
9
|
-
|
|
10
|
-
All features in the JS version are fully compatible in the ES6 version.
|
|
11
|
-
|
|
12
|
-
### Migration
|
|
13
|
-
|
|
14
|
-
If you use `jsMind` via `NPM`, no migration is required, we just changed the `main` file to `es6/jsmind.js`
|
|
15
|
-
|
|
16
|
-
If you reference `jsMind` from CDN, the only thing you need to do is change the url from `js/*.js` to `es6/*.js`.
|
|
17
|
-
|
|
18
|
-
E.g.
|
|
19
|
-
|
|
20
|
-
```html
|
|
21
|
-
<!-- before -->
|
|
22
|
-
<script type="text/javascript" src="//cdn.jsdelivr.net/npm/jsmind/js/jsmind.js"></script>
|
|
23
|
-
|
|
24
|
-
<!-- after -->
|
|
25
|
-
<script type="text/javascript" src="//cdn.jsdelivr.net/npm/jsmind/es6/jsmind.js"></script>
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
### Deprecation
|
|
29
|
-
|
|
30
|
-
The legacy version of jsmind and plugins have been moved to `/js-legacy`. we will no longer update them, and will remove them one day in the future. It's recommended that everyone use the latest version to follow continuous feature updates and bug fixes.
|
|
31
|
-
|
|
32
|
-
### Local build
|
|
33
|
-
|
|
34
|
-
You probably notice that there is no `js` files or `es6` files in the directory `/es6`. Yes, we publish the dist file to `NPM` but don't store them in the repo.
|
|
35
|
-
|
|
36
|
-
You can download the resources from CDN, or build them locally by running `npm run build` in the root dir of the project. Read [development doc](../docs/en/5.development.md) to get the detail about code building.
|
|
37
|
-
|