@umbraci/jsmind 0.9.12 → 0.10.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umbraci/jsmind",
3
- "version": "0.9.12",
3
+ "version": "0.10.0",
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
5
  "main": "lib/jsmind.js",
6
6
  "module": "es/jsmind.js",
@@ -223,15 +223,16 @@ declare class jsMind {
223
223
  * Add multiple nodes to the mind map with optimized performance.
224
224
  * Supports standard jsMind formats: node_tree, node_array, and freemind with nested children structure.
225
225
  * @param {string | import('./jsmind.node.js').Node} parent_node - Parent node for all new nodes
226
- * @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
226
+ * @param {Array<{id?: string, topic?: string, data?: Record<string, any>, direction?: ('left'|'center'|'right'|'-1'|'0'|'1'|number), children?: Array, [key: string]: any}>} nodes_data - Array of node data objects with same format as add_node
227
227
  * @returns {Array<import('./jsmind.node.js').Node|null>} Array of created nodes (flattened from all levels)
228
228
  */
229
229
  add_nodes(parent_node: string | import("./jsmind.node.js").Node, nodes_data: Array<{
230
- id: string;
231
- topic: string;
230
+ id?: string;
231
+ topic?: string;
232
232
  data?: Record<string, any>;
233
233
  direction?: ("left" | "center" | "right" | "-1" | "0" | "1" | number);
234
234
  children?: any[];
235
+ [key: string]: any;
235
236
  }>): Array<import("./jsmind.node.js").Node | null>;
236
237
  /**
237
238
  * Recursively add nodes using existing format processors.
@@ -60,14 +60,20 @@ export type MindMapMeta = {
60
60
  };
61
61
  /**
62
62
  * Node tree data item
63
+ *
64
+ * Note: When using custom fieldNames configuration, the actual property names
65
+ * in your data may differ from these type definitions. For example, if you
66
+ * configure `fieldNames: { topic: 'name' }`, your data should use 'name'
67
+ * instead of 'topic'. The types shown here represent the default field names.
63
68
  */
64
69
  export type NodeTreeData = {
65
- id: string;
66
- topic: string;
70
+ id?: string;
71
+ topic?: string;
67
72
  data?: Record<string, any>;
68
73
  direction?: (number | string);
69
74
  expanded?: boolean;
70
75
  children?: NodeTreeData[];
76
+ [key: string]: any;
71
77
  };
72
78
  /**
73
79
  * Node tree formatted payload
@@ -79,15 +85,22 @@ export type NodeTreeFormat = {
79
85
  };
80
86
  /**
81
87
  * Node array data item
88
+ *
89
+ * Note: When using custom fieldNames configuration, the actual property names
90
+ * in your data may differ from these type definitions. For example, if you
91
+ * configure `fieldNames: { topic: 'name', parentid: 'parent' }`, your data
92
+ * should use 'name' and 'parent' instead of 'topic' and 'parentid'. The types
93
+ * shown here represent the default field names.
82
94
  */
83
95
  export type NodeArrayItem = {
84
- id: string;
85
- topic: string;
96
+ id?: string;
97
+ topic?: string;
86
98
  parentid?: string;
87
99
  data?: Record<string, any>;
88
100
  direction?: (number | string);
89
101
  expanded?: boolean;
90
102
  isroot?: boolean;
103
+ [key: string]: any;
91
104
  };
92
105
  /**
93
106
  * Node array formatted payload
@@ -60,5 +60,14 @@ export type JsMindRuntimeOptions = {
60
60
  mapping?: Record<string, number | number[]>;
61
61
  id_generator?: () => string;
62
62
  };
63
+ fieldNames?: {
64
+ id?: string;
65
+ topic?: string;
66
+ children?: string;
67
+ parentid?: string;
68
+ isroot?: string;
69
+ direction?: string;
70
+ expanded?: string;
71
+ };
63
72
  plugin?: Record<string, object>;
64
73
  };
@@ -1,55 +1,301 @@
1
1
  /**
2
- * Flatten a NodeTreeFormat/NodeTreeData by id
3
- * @param {NodeTreeFormat|NodeTreeData} tree
4
- * @param {{ fields?: string[], includeStructure?: boolean }=} opts
5
- * @returns {Record<string, any>}
2
+ * Flatten a tree into a Map of nodes keyed by id.
3
+ *
4
+ * Note: When using custom fieldNames, make sure to pass the correct field names in opts.fields.
5
+ * For example, if you configured fieldNames: { topic: 'name' }, you should pass fields: ['name', 'data', 'id'].
6
+ *
7
+ * @param {NodeTreeFormat|NodeTreeData} tree - The tree to flatten
8
+ * @param {FlattenOptions} [opts] - Flatten options
9
+ * @returns {Map<string, FlatNode>} Map of node id -> flattened node object
10
+ *
11
+ * @example
12
+ * // With default fieldNames
13
+ * const tree = { data: { id: 'root', topic: 'Root', children: [...] } };
14
+ * const flatMap = flatten(tree);
15
+ * const rootNode = flatMap.get('root'); // { id: 'root', topic: 'Root', data: {...}, _parentid: null, _order: 0 }
16
+ *
17
+ * @example
18
+ * // With custom fieldNames: { topic: 'name' }
19
+ * const tree = { data: { id: 'root', name: 'Root', children: [...] } };
20
+ * const flatMap = flatten(tree, { fields: ['name', 'data', 'id'] });
21
+ * const rootNode = flatMap.get('root'); // { id: 'root', name: 'Root', data: {...}, _parentid: null, _order: 0 }
6
22
  */
7
- export function flatten(tree: NodeTreeFormat | NodeTreeData, opts?: {
8
- fields?: string[];
9
- includeStructure?: boolean;
10
- } | undefined): Record<string, any>;
23
+ export function flatten(tree: NodeTreeFormat | NodeTreeData, opts?: FlattenOptions): Map<string, FlatNode>;
11
24
  /**
12
- * Compute diff between two snapshots
13
- * @param {NodeTreeFormat|NodeTreeData} a
14
- * @param {NodeTreeFormat|NodeTreeData} b
15
- * @param {{ fields?: string[], includeStructure?: boolean, maxSize?: number }=} opts
16
- * - fields: 可选。指定要参与 diff 的节点属性列表;默认比较所有属性(除 children)。与 includeStructure 组合使用时可同时比较 _parentid/_order。
17
- * @returns {{
18
- * created: any[],
19
- * updated: { id:string, before:any, after:any, changes: { key:string, before:any, after:any }[] }[],
20
- * deleted: any[],
21
- * truncated: boolean
22
- * }}
25
+ * Compute diff between two snapshots.
26
+ *
27
+ * Note: When using custom fieldNames, make sure to pass the correct field names in opts.fields.
28
+ * For example, if you configured fieldNames: { topic: 'name' }, you should pass fields: ['name', 'data', 'id'].
29
+ * When using jm.history.diff(), this is automatically handled for you.
30
+ *
31
+ * @param {NodeTreeFormat|NodeTreeData} a - First snapshot (before)
32
+ * @param {NodeTreeFormat|NodeTreeData} b - Second snapshot (after)
33
+ * @param {DiffOptions} [opts] - Diff options
34
+ * @returns {DiffResult} Diff result with created, updated, deleted nodes, and optionally categorized updates
35
+ *
36
+ * @example
37
+ * // Basic usage with default fieldNames
38
+ * const result = diff(snapshot1, snapshot2);
39
+ * console.log(result.created); // Newly created nodes
40
+ * console.log(result.updated); // Updated nodes with changes
41
+ * console.log(result.deleted); // Deleted nodes
42
+ *
43
+ * @example
44
+ * // With categorization
45
+ * const result = diff(snapshot1, snapshot2, { categorize: true });
46
+ * console.log(result.moved); // Nodes that were only moved
47
+ * console.log(result.modified); // Nodes that were only modified
48
+ * console.log(result.movedAndModified); // Nodes that were both moved and modified
49
+ *
50
+ * @example
51
+ * // With custom fieldNames: { topic: 'name' }
52
+ * const result = diff(snapshot1, snapshot2, { fields: ['name', 'data', 'id'] });
53
+ * // Now the diff will correctly detect changes in the 'name' field
54
+ *
55
+ * @example
56
+ * // Using jm.history.diff() (recommended - automatically handles fieldNames)
57
+ * const before = jm.get_data('node_tree');
58
+ * // ... make changes ...
59
+ * const after = jm.get_data('node_tree');
60
+ * const result = jm.history.diff(before, after);
61
+ * // fieldNames are automatically applied, no need to specify fields manually
23
62
  */
24
- export function diff(a: NodeTreeFormat | NodeTreeData, b: NodeTreeFormat | NodeTreeData, opts?: {
25
- fields?: string[];
26
- includeStructure?: boolean;
27
- maxSize?: number;
28
- } | undefined): {
29
- created: any[];
30
- updated: {
31
- id: string;
32
- before: any;
33
- after: any;
34
- changes: {
35
- key: string;
36
- before: any;
37
- after: any;
38
- }[];
39
- }[];
40
- deleted: any[];
41
- truncated: boolean;
42
- };
63
+ export function diff(a: NodeTreeFormat | NodeTreeData, b: NodeTreeFormat | NodeTreeData, opts?: DiffOptions): DiffResult;
43
64
  export type NodeTreeFormat = {
44
65
  meta?: any;
45
66
  format?: "node_tree";
46
67
  data: NodeTreeData;
47
68
  };
48
69
  export type NodeTreeData = {
49
- id: string;
70
+ id?: string;
50
71
  topic?: string;
51
72
  expanded?: boolean;
52
73
  direction?: "left" | "right";
53
74
  data?: Record<string, any>;
54
75
  children?: NodeTreeData[];
76
+ [key: string]: any;
77
+ };
78
+ export type FlatNode = {
79
+ /**
80
+ * - Node ID
81
+ */
82
+ id: string;
83
+ /**
84
+ * - Node topic/title
85
+ */
86
+ topic?: string;
87
+ /**
88
+ * - Node data
89
+ */
90
+ data?: Record<string, any>;
91
+ /**
92
+ * - Parent node ID (structure field)
93
+ */
94
+ _parentid?: string | null;
95
+ /**
96
+ * - Node order in parent's children (structure field)
97
+ */
98
+ _order?: number;
99
+ };
100
+ export type ChangeDetail = {
101
+ /**
102
+ * - The field name that changed
103
+ */
104
+ key: string;
105
+ /**
106
+ * - Value before change
107
+ */
108
+ before: any;
109
+ /**
110
+ * - Value after change
111
+ */
112
+ after: any;
113
+ };
114
+ export type UpdatedNode = {
115
+ /**
116
+ * - Node ID
117
+ */
118
+ id: string;
119
+ /**
120
+ * - Node state before change
121
+ */
122
+ before: FlatNode;
123
+ /**
124
+ * - Node state after change
125
+ */
126
+ after: FlatNode;
127
+ /**
128
+ * - Array of field changes
129
+ */
130
+ changes: ChangeDetail[];
131
+ };
132
+ export type MoveInfo = {
133
+ /**
134
+ * - Whether parent changed
135
+ */
136
+ parentChanged: boolean;
137
+ /**
138
+ * - Whether order changed
139
+ */
140
+ orderChanged: boolean;
141
+ /**
142
+ * - Original parent ID
143
+ */
144
+ fromParent: string | null;
145
+ /**
146
+ * - New parent ID
147
+ */
148
+ toParent: string | null;
149
+ /**
150
+ * - Original order
151
+ */
152
+ fromOrder: number;
153
+ /**
154
+ * - New order
155
+ */
156
+ toOrder: number;
157
+ };
158
+ export type MovedNode = {
159
+ /**
160
+ * - Node ID
161
+ */
162
+ id: string;
163
+ /**
164
+ * - Node state before move
165
+ */
166
+ before: FlatNode;
167
+ /**
168
+ * - Node state after move
169
+ */
170
+ after: FlatNode;
171
+ /**
172
+ * - Movement details
173
+ */
174
+ moveInfo: MoveInfo;
175
+ };
176
+ export type ModifiedNode = {
177
+ /**
178
+ * - Node ID
179
+ */
180
+ id: string;
181
+ /**
182
+ * - Node state before modification
183
+ */
184
+ before: FlatNode;
185
+ /**
186
+ * - Node state after modification
187
+ */
188
+ after: FlatNode;
189
+ /**
190
+ * - Array of field changes (excluding structure fields)
191
+ */
192
+ changes: ChangeDetail[];
193
+ };
194
+ export type MovedAndModifiedNode = {
195
+ /**
196
+ * - Node ID
197
+ */
198
+ id: string;
199
+ /**
200
+ * - Node state before change
201
+ */
202
+ before: FlatNode;
203
+ /**
204
+ * - Node state after change
205
+ */
206
+ after: FlatNode;
207
+ /**
208
+ * - Array of field changes (excluding structure fields)
209
+ */
210
+ changes: ChangeDetail[];
211
+ /**
212
+ * - Movement details
213
+ */
214
+ moveInfo: MoveInfo;
215
+ };
216
+ export type DiffResult = {
217
+ /**
218
+ * - Newly created nodes
219
+ */
220
+ created: FlatNode[];
221
+ /**
222
+ * - Updated nodes (all changes)
223
+ */
224
+ updated: UpdatedNode[];
225
+ /**
226
+ * - Deleted nodes
227
+ */
228
+ deleted: FlatNode[];
229
+ /**
230
+ * - Whether results were truncated due to maxSize
231
+ */
232
+ truncated: boolean;
233
+ /**
234
+ * - Nodes that were only moved (when categorize=true)
235
+ */
236
+ moved?: MovedNode[];
237
+ /**
238
+ * - Nodes that were only modified (when categorize=true)
239
+ */
240
+ modified?: ModifiedNode[];
241
+ /**
242
+ * - Nodes that were both moved and modified (when categorize=true)
243
+ */
244
+ movedAndModified?: MovedAndModifiedNode[];
245
+ };
246
+ export type FlattenOptions = {
247
+ /**
248
+ * - Array of field names to include. Defaults to ['topic', 'data', 'id'].
249
+ * When using custom fieldNames (e.g., { id: 'key', topic: 'name' }), this should be
250
+ * ['name', 'data', 'key'] to match the actual field names in the data.
251
+ */
252
+ fields?: string[];
253
+ /**
254
+ * - The field name to use as the node ID. Defaults to 'id'.
255
+ * When using custom fieldNames (e.g., { id: 'key' }), this should be 'key'.
256
+ */
257
+ idKey?: string;
258
+ /**
259
+ * - The field name to use for children array. Defaults to 'children'.
260
+ * When using custom fieldNames (e.g., { children: 'items' }), this should be 'items'.
261
+ */
262
+ childrenKey?: string;
263
+ /**
264
+ * - Whether to include _parentid and _order. Defaults to true
265
+ */
266
+ includeStructure?: boolean;
267
+ };
268
+ export type DiffOptions = {
269
+ /**
270
+ * - Array of field names to compare. Defaults to ['topic', 'data', 'id'].
271
+ * When using custom fieldNames (e.g., { id: 'key', topic: 'name' }), this should be
272
+ * ['name', 'data', 'key'] to match the actual field names in the data.
273
+ * Note: When using jm.history.diff(), this is automatically handled based
274
+ * on the configured fieldNames, so you don't need to specify it manually.
275
+ */
276
+ fields?: string[];
277
+ /**
278
+ * - The field name to use as the node ID. Defaults to 'id'.
279
+ * When using custom fieldNames (e.g., { id: 'key' }), this should be 'key'.
280
+ * Note: When using jm.history.diff(), this is automatically handled.
281
+ */
282
+ idKey?: string;
283
+ /**
284
+ * - The field name to use for children array. Defaults to 'children'.
285
+ * When using custom fieldNames (e.g., { children: 'items' }), this should be 'items'.
286
+ * Note: When using jm.history.diff(), this is automatically handled.
287
+ */
288
+ childrenKey?: string;
289
+ /**
290
+ * - Whether to include _parentid and _order in comparison. Defaults to true
291
+ */
292
+ includeStructure?: boolean;
293
+ /**
294
+ * - Maximum number of diff results. Defaults to 5000
295
+ */
296
+ maxSize?: number;
297
+ /**
298
+ * - Whether to categorize updates into moved/modified/movedAndModified. Defaults to false
299
+ */
300
+ categorize?: boolean;
55
301
  };
@@ -1,5 +1,7 @@
1
1
  export default HistoryPlugin;
2
2
  export type JsMind = import("../../jsmind.js").default;
3
+ export type DiffResult = import("./history-diff.js").DiffResult;
4
+ export type DiffOptions = import("./history-diff.js").DiffOptions;
3
5
  /**
4
6
  * HistoryPlugin skeleton (Task 1)
5
7
  */
@@ -55,6 +57,7 @@ declare class HistoryCore {
55
57
  _pending: boolean;
56
58
  _pendingMeta: any;
57
59
  _lastSig: any;
60
+ _lastRootId: any;
58
61
  add(reason: string, meta: any): void;
59
62
  pause(): void;
60
63
  resume(flush?: boolean): void;