prosemirror-rs 0.3.0 → 0.3.5

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/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "prosemirror-rs-node"
3
- version = "0.3.0"
3
+ version = "0.3.5"
4
4
  authors = [
5
5
  "Johannes Wilm <johannes@fiduswriter.org>",
6
6
  "Daniel Seiler <me@dseiler.eu>",
package/copy-artifact.mjs CHANGED
@@ -4,23 +4,42 @@
4
4
  * The filename follows the napi-rs triple convention so that index.js can
5
5
  * load the correct binary at runtime. Called automatically by `npm run build`
6
6
  * after `cargo build --release`.
7
+ *
8
+ * Environment variables (optional, used in CI):
9
+ * RUST_TARGET – cross-compilation target, e.g. "aarch64-unknown-linux-gnu"
10
+ * When set, the binary is looked up under
11
+ * target/<RUST_TARGET>/release/ instead of target/release/.
7
12
  */
8
- import { cpSync } from 'fs';
13
+ import { cpSync, existsSync } from 'fs';
9
14
  import { platform, arch } from 'os';
10
15
  import { fileURLToPath } from 'url';
11
16
  import { join, dirname } from 'path';
12
17
 
13
18
  const __dirname = dirname(fileURLToPath(import.meta.url));
14
19
 
15
- // Map OS shared library extension
16
- function libExtension() {
20
+ // ── Per-platform helper: shared library filename (without path) ───────────
21
+ // Windows: prosemirror_rs.dll (no lib- prefix)
22
+ // macOS: libprosemirror_rs.dylib
23
+ // Linux: libprosemirror_rs.so
24
+ function libFilename() {
17
25
  const p = platform();
18
- if (p === 'win32') return '.dll';
19
- if (p === 'darwin') return '.dylib';
20
- return '.so';
26
+ if (p === 'win32') return 'prosemirror_rs.dll';
27
+ if (p === 'darwin') return 'libprosemirror_rs.dylib';
28
+ return 'libprosemirror_rs.so';
29
+ }
30
+
31
+ // ── Build directory ──────────────────────────────────────────────────────
32
+ // When cross-compiling, cargo places output under target/<rust_target>/release/
33
+ // instead of target/release/.
34
+ function buildDir() {
35
+ const rustTarget = process.env.RUST_TARGET;
36
+ if (rustTarget) {
37
+ return join(__dirname, '..', 'target', rustTarget, 'release');
38
+ }
39
+ return join(__dirname, '..', 'target', 'release');
21
40
  }
22
41
 
23
- // Map Node platform+arch napi-rs triple
42
+ // ── napi-rs platform triple for the output file ──────────────────────────
24
43
  function triple() {
25
44
  const p = platform();
26
45
  const a = arch();
@@ -32,8 +51,18 @@ function triple() {
32
51
  throw new Error(`Unsupported platform/arch: ${p}-${a}`);
33
52
  }
34
53
 
35
- const src = join(__dirname, '..', 'target', 'release', `libprosemirror_rs${libExtension()}`);
54
+ const src = join(buildDir(), libFilename());
36
55
  const dest = join(__dirname, `prosemirror-rs.${triple()}.node`);
37
56
 
57
+ if (!existsSync(src)) {
58
+ console.error(`ERROR: compiled artifact not found at ${src}`);
59
+ console.error('');
60
+ console.error('Make sure cargo build --release completed successfully first.');
61
+ if (process.env.RUST_TARGET) {
62
+ console.error(`(RUST_TARGET=${process.env.RUST_TARGET} is set)`);
63
+ }
64
+ process.exit(1);
65
+ }
66
+
38
67
  cpSync(src, dest);
39
- console.log(`Copied ${src} → ${dest}`);
68
+ console.log(`Copied ${src} → ${dest}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-rs",
3
- "version": "0.3.0",
3
+ "version": "0.3.5",
4
4
  "description": "Node.js bindings for prosemirror-rs: a Rust implementation of ProseMirror's document model and transform pipeline",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
Binary file
Binary file
package/src/lib.rs CHANGED
@@ -268,10 +268,15 @@ impl Editor {
268
268
  /// document directly to a database without creating any intermediate
269
269
  /// JS objects.
270
270
  ///
271
+ /// When `skipDefaults` is `true`, attributes whose value matches the
272
+ /// schema-defined default are omitted from the output ("mini" JSON).
273
+ ///
274
+ /// @param skipDefaults If true, omit attributes that have default values.
271
275
  /// @returns The document as a compact JSON string.
272
276
  #[napi]
273
- pub fn doc_json(&self) -> napi::Result<String> {
274
- serde_json::to_string(&self.doc)
277
+ pub fn doc_json(&self, skip_defaults: Option<bool>) -> napi::Result<String> {
278
+ let val = self.schema.with_types(|| self.doc.to_json(skip_defaults.unwrap_or(false)));
279
+ serde_json::to_string(&val)
275
280
  .map_err(|e| napi::Error::new(Status::GenericFailure, format!("Serialization error: {e}")))
276
281
  }
277
282
 
@@ -81,16 +81,96 @@ test('constructor throws when doc JSON is not a node object', () => {
81
81
  });
82
82
 
83
83
  // ---------------------------------------------------------------------------
84
- // docJson
84
+ // docJson with skipDefaults
85
85
  // ---------------------------------------------------------------------------
86
86
 
87
- test('docJson returns a valid JSON string containing the initial text', () => {
88
- const editor = new Editor(SCHEMA, DOC);
89
- const raw = editor.docJson();
90
- assert.equal(typeof raw, 'string');
91
- const doc = JSON.parse(raw);
92
- assert.equal(doc.type, 'doc');
93
- assert.ok(raw.includes('hello'), `Expected "hello" in: ${raw}`);
87
+ test('docJson() without argument includes all attributes', () => {
88
+ // Schema with attributes that have defaults
89
+ const schemaWithAttrs = JSON.stringify({
90
+ nodes: {
91
+ doc: { content: 'paragraph+' },
92
+ paragraph: {
93
+ content: 'text*',
94
+ group: 'block',
95
+ attrs: { align: { default: 'left' }, indent: { default: 0 } },
96
+ },
97
+ text: { group: 'inline' },
98
+ },
99
+ marks: { strong: { attrs: { level: { default: 1 } } }, em: {} },
100
+ });
101
+
102
+ // Document with default attributes (should be included in regular serialization)
103
+ const docDefault = JSON.stringify({
104
+ type: 'doc',
105
+ content: [{
106
+ type: 'paragraph',
107
+ attrs: { align: 'left', indent: 0 },
108
+ content: [{
109
+ type: 'text',
110
+ text: 'hello',
111
+ marks: [{ type: 'strong', attrs: { level: 1 } }],
112
+ }],
113
+ }],
114
+ });
115
+
116
+ const editorDefault = new Editor(schemaWithAttrs, docDefault);
117
+
118
+ // Regular serialization should include all attrs
119
+ const fullRaw = editorDefault.docJson();
120
+ const full = JSON.parse(fullRaw);
121
+ assert.ok(fullRaw.includes('"attrs"'), `Expected attrs in full serialization: ${fullRaw}`);
122
+ assert.equal(full.content[0].attrs.align, 'left');
123
+ assert.equal(full.content[0].attrs.indent, 0);
124
+ assert.equal(full.content[0].content[0].marks[0].attrs.level, 1);
125
+
126
+ // Mini serialization should skip all default attributes
127
+ const miniRaw = editorDefault.docJson(true);
128
+ const mini = JSON.parse(miniRaw);
129
+ assert.ok(!miniRaw.includes('"attrs"'), `Expected no attrs in mini serialization: ${miniRaw}`);
130
+ assert.ok(!('attrs' in mini.content[0]), 'paragraph should have no attrs');
131
+ assert.ok(!('attrs' in mini.content[0].content[0].marks[0]), 'mark should have no attrs');
132
+
133
+ // Document with non-default attributes
134
+ const docCustom = JSON.stringify({
135
+ type: 'doc',
136
+ content: [{
137
+ type: 'paragraph',
138
+ attrs: { align: 'right', indent: 2 },
139
+ content: [{ type: 'text', text: 'world' }],
140
+ }],
141
+ });
142
+
143
+ const editorCustom = new Editor(schemaWithAttrs, docCustom);
144
+ const miniCustomRaw = editorCustom.docJson(true);
145
+ const miniCustom = JSON.parse(miniCustomRaw);
146
+ // Non-default attrs should still appear
147
+ assert.ok(miniCustomRaw.includes('"attrs"'), `Expected attrs for non-default values: ${miniCustomRaw}`);
148
+ assert.equal(miniCustom.content[0].attrs.align, 'right');
149
+ assert.equal(miniCustom.content[0].attrs.indent, 2);
150
+
151
+ // Mix of default and non-default
152
+ const docMixed = JSON.stringify({
153
+ type: 'doc',
154
+ content: [{
155
+ type: 'paragraph',
156
+ attrs: { align: 'center', indent: 0 },
157
+ content: [{ type: 'text', text: 'mixed' }],
158
+ }],
159
+ });
160
+
161
+ const editorMixed = new Editor(schemaWithAttrs, docMixed);
162
+ const miniMixedRaw = editorMixed.docJson(true);
163
+ const miniMixed = JSON.parse(miniMixedRaw);
164
+ // Only 'align' should be present (indent is default 0)
165
+ assert.ok(miniMixedRaw.includes('"attrs"'), `Expected attrs for partial non-default: ${miniMixedRaw}`);
166
+ assert.equal(miniMixed.content[0].attrs.align, 'center');
167
+ assert.ok(!('indent' in miniMixed.content[0].attrs), 'indent should be omitted (default value)');
168
+
169
+ // Verify backwards compatibility: no argument == false
170
+ const editorBC = new Editor(SCHEMA, DOC);
171
+ const noArg = editorBC.docJson();
172
+ const falseArg = editorBC.docJson(false);
173
+ assert.equal(noArg, falseArg, 'docJson() and docJson(false) should be identical');
94
174
  });
95
175
 
96
176
  // ---------------------------------------------------------------------------