@scrawl-board/board 0.1.0-beta.7 → 0.1.0-beta.8
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 +11 -7
- package/dist/browser.d.ts +340 -21
- package/dist/browser.js +1536 -35811
- package/dist/core.d.ts +115 -30
- package/dist/core.js +260 -124
- package/dist/index.d.ts +454 -35
- package/dist/index.js +1627 -35810
- package/dist/local.d.ts +469 -0
- package/dist/local.js +234 -0
- package/dist/react.d.ts +369 -21
- package/dist/react.js +1377 -35736
- package/package.json +17 -3
package/dist/core.js
CHANGED
|
@@ -48,6 +48,37 @@ function strokeId(value) {
|
|
|
48
48
|
return value;
|
|
49
49
|
}
|
|
50
50
|
//#endregion
|
|
51
|
+
//#region src/core/document/migrations/registry.ts
|
|
52
|
+
/** Schema versions this build can load. An unrecognized version fails safely (`DOCUMENT_VERSION_UNSUPPORTED`) rather than being silently treated as current. */
|
|
53
|
+
var SUPPORTED_SCHEMA_VERSIONS = /* @__PURE__ */ new Set([0, 1]);
|
|
54
|
+
function isSupportedSchemaVersion(version) {
|
|
55
|
+
return typeof version === "number" && SUPPORTED_SCHEMA_VERSIONS.has(version);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Document collection fields introduced after schemaVersion 1 first
|
|
59
|
+
* shipped real documents. A document stored before a field's introduction
|
|
60
|
+
* can never retroactively gain it, so requiring it at load would fail
|
|
61
|
+
* every pre-existing document — these stay optional even at the current
|
|
62
|
+
* version. New entries only ever get added here, never removed (an old
|
|
63
|
+
* stored document's absence of a since-introduced field must keep loading
|
|
64
|
+
* for as long as that document might still exist).
|
|
65
|
+
*/
|
|
66
|
+
var FIELDS_OPTIONAL_AT_CURRENT_VERSION = /* @__PURE__ */ new Set([
|
|
67
|
+
"customObjects",
|
|
68
|
+
"rectangles",
|
|
69
|
+
"ellipses",
|
|
70
|
+
"groups",
|
|
71
|
+
"lines",
|
|
72
|
+
"arrows",
|
|
73
|
+
"polygons",
|
|
74
|
+
"stars",
|
|
75
|
+
"hearts"
|
|
76
|
+
]);
|
|
77
|
+
/** Whether `field` must be present on a document already at the current schema version (never required on an older, migrating-forward document). */
|
|
78
|
+
function isFieldRequiredAtCurrentVersion(field) {
|
|
79
|
+
return !FIELDS_OPTIONAL_AT_CURRENT_VERSION.has(field);
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
51
82
|
//#region src/core/document/document-schema.ts
|
|
52
83
|
var CURRENT_DOCUMENT_SCHEMA_VERSION = 1;
|
|
53
84
|
var DocumentRecoveryError = class extends Error {
|
|
@@ -76,7 +107,7 @@ function loadDocumentBytes(originalBytes) {
|
|
|
76
107
|
function migrateDocument(raw) {
|
|
77
108
|
if (!isRecord(raw)) return failure("DOCUMENT_VALIDATION_FAILED", "Document must be an object");
|
|
78
109
|
const version = raw.schemaVersion ?? 0;
|
|
79
|
-
if (version
|
|
110
|
+
if (!isSupportedSchemaVersion(version)) return failure("DOCUMENT_VERSION_UNSUPPORTED", `Document schema version ${String(version)} is unsupported`, void 0, "schemaVersion");
|
|
80
111
|
if (!isJsonValue(raw)) return failure("DOCUMENT_VALIDATION_FAILED", "Document must contain JSON values only");
|
|
81
112
|
const strokes = validateStrokes(raw.strokes);
|
|
82
113
|
if (strokes instanceof DocumentRecoveryError) return {
|
|
@@ -89,15 +120,15 @@ function migrateDocument(raw) {
|
|
|
89
120
|
tables: validateCollection(raw, "tables", version, validateTable),
|
|
90
121
|
images: validateCollection(raw, "images", version, validateImage),
|
|
91
122
|
timers: validateCollection(raw, "timers", version, validateTimer),
|
|
92
|
-
customObjects: validateCollection(raw, "customObjects", version, validateCustomObject
|
|
93
|
-
rectangles: validateCollection(raw, "rectangles", version, validateRectangle
|
|
94
|
-
ellipses: validateCollection(raw, "ellipses", version, validateEllipse
|
|
95
|
-
groups: validateCollection(raw, "groups", version, validateGroup
|
|
96
|
-
lines: validateCollection(raw, "lines", version, validateLine
|
|
97
|
-
arrows: validateCollection(raw, "arrows", version, validateArrow
|
|
98
|
-
polygons: validateCollection(raw, "polygons", version, validatePolygon
|
|
99
|
-
stars: validateCollection(raw, "stars", version, validateStar
|
|
100
|
-
hearts: validateCollection(raw, "hearts", version, validateHeart
|
|
123
|
+
customObjects: validateCollection(raw, "customObjects", version, validateCustomObject),
|
|
124
|
+
rectangles: validateCollection(raw, "rectangles", version, validateRectangle),
|
|
125
|
+
ellipses: validateCollection(raw, "ellipses", version, validateEllipse),
|
|
126
|
+
groups: validateCollection(raw, "groups", version, validateGroup),
|
|
127
|
+
lines: validateCollection(raw, "lines", version, validateLine),
|
|
128
|
+
arrows: validateCollection(raw, "arrows", version, validateArrow),
|
|
129
|
+
polygons: validateCollection(raw, "polygons", version, validatePolygon),
|
|
130
|
+
stars: validateCollection(raw, "stars", version, validateStar),
|
|
131
|
+
hearts: validateCollection(raw, "hearts", version, validateHeart)
|
|
101
132
|
};
|
|
102
133
|
if (collections.notes instanceof DocumentRecoveryError) return {
|
|
103
134
|
ok: false,
|
|
@@ -189,8 +220,19 @@ function serializeDocument(document) {
|
|
|
189
220
|
if (!result.ok || result.migratedFrom !== 1) throw result.ok ? new DocumentRecoveryError("DOCUMENT_VALIDATION_FAILED", "Saving requires the current Document schema") : result.error;
|
|
190
221
|
return JSON.stringify(result.document);
|
|
191
222
|
}
|
|
192
|
-
|
|
193
|
-
|
|
223
|
+
/**
|
|
224
|
+
* A Document's serialized size in bytes (Phase 5) — UTF-8, not UTF-16
|
|
225
|
+
* `string.length`, since a Document with non-ASCII note/text content (most
|
|
226
|
+
* of them, eventually) would otherwise under-report. Useful for a Host
|
|
227
|
+
* deciding when to warn about an unusually large board, or for logging/
|
|
228
|
+
* telemetry around save size — not consulted by anything inside this
|
|
229
|
+
* package itself, which has no size limit of its own.
|
|
230
|
+
*/
|
|
231
|
+
function documentSize(document) {
|
|
232
|
+
return new TextEncoder().encode(JSON.stringify(document)).length;
|
|
233
|
+
}
|
|
234
|
+
function validateCollection(raw, name, version, validator) {
|
|
235
|
+
if (isFieldRequiredAtCurrentVersion(name) && version === 1 && !(name in raw)) return validation(`${name} is required`, name);
|
|
194
236
|
const value = raw[name] ?? [];
|
|
195
237
|
if (!Array.isArray(value)) return validation(`${name} must be an array`, name);
|
|
196
238
|
for (let index = 0; index < value.length; index += 1) if (!isRecord(value[index]) || !validator(value[index])) return validation(`invalid ${name} entry`, `${name}[${index}]`);
|
|
@@ -202,11 +244,20 @@ function validateStrokes(raw) {
|
|
|
202
244
|
for (let index = 0; index < raw.length; index += 1) {
|
|
203
245
|
const value = raw[index];
|
|
204
246
|
const path = `strokes[${index}]`;
|
|
205
|
-
if (!isRecord(value)
|
|
247
|
+
if (!isRecord(value)) return validation("stroke must be an object", path);
|
|
248
|
+
if (!nonempty(value.id)) return validation("stroke.id must be a non-empty string", `${path}.id`);
|
|
249
|
+
if (!nonempty(value.color)) return validation("stroke.color must be a non-empty string", `${path}.color`);
|
|
250
|
+
if (!positive(value.baseWidth)) return validation("stroke.baseWidth must be a positive number", `${path}.baseWidth`);
|
|
251
|
+
if (!optionalEnum(value.tool, [
|
|
206
252
|
"marker",
|
|
207
253
|
"highlighter",
|
|
208
254
|
"shape"
|
|
209
|
-
])
|
|
255
|
+
])) return validation("stroke.tool must be \"marker\", \"highlighter\", or \"shape\"", `${path}.tool`);
|
|
256
|
+
if (!validLock(value)) return validation("invalid stroke.lock", `${path}.lock`);
|
|
257
|
+
if (!validHidden(value)) return validation("invalid stroke.hidden", `${path}.hidden`);
|
|
258
|
+
if (!optionalString(value.clusterId)) return validation("stroke.clusterId must be a string", `${path}.clusterId`);
|
|
259
|
+
if (!validMatrix(value.matrix)) return validation("invalid stroke.matrix", `${path}.matrix`);
|
|
260
|
+
if (!Array.isArray(value.points)) return validation("stroke.points must be an array", `${path}.points`);
|
|
210
261
|
const points = [];
|
|
211
262
|
for (let pointIndex = 0; pointIndex < value.points.length; pointIndex += 1) {
|
|
212
263
|
const point = normalizePoint(value.points[pointIndex]);
|
|
@@ -232,47 +283,47 @@ function normalizePoint(raw) {
|
|
|
232
283
|
return values;
|
|
233
284
|
}
|
|
234
285
|
function validateNote(v) {
|
|
235
|
-
return basePosition(v) && positive(v.size) && finite(v.zOffset) && nonempty(v.color) && typeof v.text === "string" && validLock(v) && (v.votes === void 0 || Array.isArray(v.votes) && v.votes.every((vote) => isRecord(vote) && nonempty(vote.userId) && nonempty(vote.name) && nonempty(vote.color)));
|
|
286
|
+
return basePosition(v) && positive(v.size) && finite(v.zOffset) && nonempty(v.color) && typeof v.text === "string" && validLock(v) && validHidden(v) && (v.votes === void 0 || Array.isArray(v.votes) && v.votes.every((vote) => isRecord(vote) && nonempty(vote.userId) && nonempty(vote.name) && nonempty(vote.color)));
|
|
236
287
|
}
|
|
237
288
|
function validateText(v) {
|
|
238
|
-
return basePosition(v) && typeof v.text === "string" && positive(v.fontSize) && nonempty(v.color) && validLock(v) && optionalString(v.clusterId);
|
|
289
|
+
return basePosition(v) && typeof v.text === "string" && positive(v.fontSize) && nonempty(v.color) && validLock(v) && validHidden(v) && optionalString(v.clusterId);
|
|
239
290
|
}
|
|
240
291
|
function validateTable(v) {
|
|
241
|
-
return basePosition(v) && integer(v.rows) && integer(v.cols) && numberArray(v.colWidths, v.cols) && numberArray(v.rowHeights, v.rows) && isStringRecord(v.cells) && validLock(v) && optionalString(v.color) && optionalString(v.backgroundColor) && optionalString(v.clusterId);
|
|
292
|
+
return basePosition(v) && integer(v.rows) && integer(v.cols) && numberArray(v.colWidths, v.cols) && numberArray(v.rowHeights, v.rows) && isStringRecord(v.cells) && validLock(v) && validHidden(v) && optionalString(v.color) && optionalString(v.backgroundColor) && optionalString(v.clusterId);
|
|
242
293
|
}
|
|
243
294
|
function validateImage(v) {
|
|
244
295
|
const identifiesAnImage = v.ref !== void 0 ? isAssetRef(v.ref) : nonempty(v.src);
|
|
245
|
-
return basePosition(v) && identifiesAnImage && positive(v.width) && positive(v.height) && positive(v.aspectRatio) && validLock(v) && optionalString(v.name) && optionalString(v.createdAt) && optionalString(v.stamp);
|
|
296
|
+
return basePosition(v) && identifiesAnImage && positive(v.width) && positive(v.height) && positive(v.aspectRatio) && validLock(v) && validHidden(v) && optionalString(v.name) && optionalString(v.createdAt) && optionalString(v.stamp);
|
|
246
297
|
}
|
|
247
298
|
function validateShapeStyle(v) {
|
|
248
299
|
return optionalString(v.fill) && optionalString(v.stroke) && (v.strokeWidth === void 0 || positive(v.strokeWidth)) && (v.opacity === void 0 || finite(v.opacity) && v.opacity >= 0 && v.opacity <= 1);
|
|
249
300
|
}
|
|
250
301
|
function validateRectangle(v) {
|
|
251
|
-
return basePosition(v) && positive(v.width) && positive(v.height) && validLock(v) && validateShapeStyle(v) && (v.rotation === void 0 || finite(v.rotation)) && (v.cornerRadius === void 0 || finite(v.cornerRadius) && v.cornerRadius >= 0);
|
|
302
|
+
return basePosition(v) && positive(v.width) && positive(v.height) && validLock(v) && validHidden(v) && validateShapeStyle(v) && (v.rotation === void 0 || finite(v.rotation)) && (v.cornerRadius === void 0 || finite(v.cornerRadius) && v.cornerRadius >= 0);
|
|
252
303
|
}
|
|
253
304
|
function validateEllipse(v) {
|
|
254
|
-
return basePosition(v) && positive(v.width) && positive(v.height) && validLock(v) && validateShapeStyle(v) && (v.rotation === void 0 || finite(v.rotation));
|
|
305
|
+
return basePosition(v) && positive(v.width) && positive(v.height) && validLock(v) && validHidden(v) && validateShapeStyle(v) && (v.rotation === void 0 || finite(v.rotation));
|
|
255
306
|
}
|
|
256
307
|
function validatePolygon(v) {
|
|
257
|
-
return basePosition(v) && positive(v.width) && positive(v.height) && validLock(v) && validateShapeStyle(v) && (v.rotation === void 0 || finite(v.rotation)) && (v.sides === 3 || v.sides === 4 || v.sides === 5 || v.sides === 6 || v.sides === 8);
|
|
308
|
+
return basePosition(v) && positive(v.width) && positive(v.height) && validLock(v) && validHidden(v) && validateShapeStyle(v) && (v.rotation === void 0 || finite(v.rotation)) && (v.sides === 3 || v.sides === 4 || v.sides === 5 || v.sides === 6 || v.sides === 8);
|
|
258
309
|
}
|
|
259
310
|
function validateStar(v) {
|
|
260
|
-
return basePosition(v) && positive(v.width) && positive(v.height) && validLock(v) && validateShapeStyle(v) && (v.rotation === void 0 || finite(v.rotation)) && finite(v.points) && v.points >= 3 && finite(v.innerRadiusRatio) && v.innerRadiusRatio > 0 && v.innerRadiusRatio < 1;
|
|
311
|
+
return basePosition(v) && positive(v.width) && positive(v.height) && validLock(v) && validHidden(v) && validateShapeStyle(v) && (v.rotation === void 0 || finite(v.rotation)) && finite(v.points) && v.points >= 3 && finite(v.innerRadiusRatio) && v.innerRadiusRatio > 0 && v.innerRadiusRatio < 1;
|
|
261
312
|
}
|
|
262
313
|
function validateHeart(v) {
|
|
263
|
-
return basePosition(v) && positive(v.width) && positive(v.height) && validLock(v) && validateShapeStyle(v) && (v.rotation === void 0 || finite(v.rotation));
|
|
314
|
+
return basePosition(v) && positive(v.width) && positive(v.height) && validLock(v) && validHidden(v) && validateShapeStyle(v) && (v.rotation === void 0 || finite(v.rotation));
|
|
264
315
|
}
|
|
265
316
|
function validPoint(v) {
|
|
266
317
|
return isRecord(v) && finite(v.x) && finite(v.y);
|
|
267
318
|
}
|
|
268
319
|
function validateLine(v) {
|
|
269
|
-
return nonempty(v.id) && validPoint(v.start) && validPoint(v.end) && validLock(v) && validateShapeStyle(v);
|
|
320
|
+
return nonempty(v.id) && validPoint(v.start) && validPoint(v.end) && validLock(v) && validHidden(v) && validateShapeStyle(v);
|
|
270
321
|
}
|
|
271
322
|
function validateArrow(v) {
|
|
272
|
-
return nonempty(v.id) && validPoint(v.start) && validPoint(v.end) && validLock(v) && validateShapeStyle(v) && (v.head === void 0 || v.head === "triangle" || v.head === "none");
|
|
323
|
+
return nonempty(v.id) && validPoint(v.start) && validPoint(v.end) && validLock(v) && validHidden(v) && validateShapeStyle(v) && (v.head === void 0 || v.head === "triangle" || v.head === "none");
|
|
273
324
|
}
|
|
274
325
|
function validateGroup(v) {
|
|
275
|
-
return nonempty(v.id) && Array.isArray(v.children) && v.children.every((c) => nonempty(c)) && validLock(v);
|
|
326
|
+
return nonempty(v.id) && Array.isArray(v.children) && v.children.every((c) => nonempty(c)) && validLock(v) && validHidden(v);
|
|
276
327
|
}
|
|
277
328
|
function validateObjectOrder(v) {
|
|
278
329
|
if (v === void 0) return [];
|
|
@@ -280,7 +331,7 @@ function validateObjectOrder(v) {
|
|
|
280
331
|
return structuredClone(v);
|
|
281
332
|
}
|
|
282
333
|
function validateTimer(v) {
|
|
283
|
-
return basePosition(v) && positive(v.size) && positive(v.durationMs) && finite(v.remainingMs) && v.remainingMs >= 0 && (v.runningSince === void 0 || finite(v.runningSince)) && validLock(v);
|
|
334
|
+
return basePosition(v) && positive(v.size) && positive(v.durationMs) && finite(v.remainingMs) && v.remainingMs >= 0 && (v.runningSince === void 0 || finite(v.runningSince)) && validLock(v) && validHidden(v);
|
|
284
335
|
}
|
|
285
336
|
function validateCustomObject(v) {
|
|
286
337
|
return nonempty(v.id) && nonempty(v.type) && integer(v.schemaVersion) && validMatrix(v.transform) && Array.isArray(v.transform) && validCustomFallback(v.fallback) && (v.lock === void 0 || validCustomLock(v.lock)) && isJsonValue(v.props);
|
|
@@ -299,6 +350,9 @@ function basePosition(v) {
|
|
|
299
350
|
function validLock(v) {
|
|
300
351
|
return (v.locked === void 0 || typeof v.locked === "boolean") && optionalString(v.lockedBy) && optionalString(v.lockedByName);
|
|
301
352
|
}
|
|
353
|
+
function validHidden(v) {
|
|
354
|
+
return v.hidden === void 0 || typeof v.hidden === "boolean";
|
|
355
|
+
}
|
|
302
356
|
function validMatrix(v) {
|
|
303
357
|
return v === void 0 || Array.isArray(v) && v.length === 6 && v.every(finite);
|
|
304
358
|
}
|
|
@@ -863,8 +917,51 @@ function serializeLock(item) {
|
|
|
863
917
|
//#endregion
|
|
864
918
|
//#region src/core/shapes/shapeObjects.ts
|
|
865
919
|
var SHAPE_MIN_SIZE = .5;
|
|
866
|
-
|
|
867
|
-
|
|
920
|
+
/**
|
|
921
|
+
* Centralized shape style defaults (Phase 8) — one object a Host can read
|
|
922
|
+
* to know (or, by not relying on the standalone constants below, override
|
|
923
|
+
* via its own UI state) what a newly drawn Rectangle/Ellipse/Line/Arrow/
|
|
924
|
+
* Polygon/Star/Heart starts with when the user hasn't picked a stroke/width
|
|
925
|
+
* yet. `shapeTool.ts` (commit-time), `renderer/shapes/lines.ts` (render-time
|
|
926
|
+
* fallback for an object missing these fields), and
|
|
927
|
+
* `persistence/serialization/svg.ts` (export-time fallback) all read from
|
|
928
|
+
* here — the two standalone constants below are kept for source
|
|
929
|
+
* compatibility and simply mirror this object's values, not a second
|
|
930
|
+
* source of truth.
|
|
931
|
+
*/
|
|
932
|
+
var SHAPE_STYLE_DEFAULTS = {
|
|
933
|
+
stroke: "#1C1C1E",
|
|
934
|
+
strokeWidth: .12
|
|
935
|
+
};
|
|
936
|
+
var SHAPE_DEFAULT_STROKE = SHAPE_STYLE_DEFAULTS.stroke;
|
|
937
|
+
var SHAPE_DEFAULT_STROKE_WIDTH = SHAPE_STYLE_DEFAULTS.strokeWidth;
|
|
938
|
+
/**
|
|
939
|
+
* Normalizes a box-shaped object's `x`/`y`/`width`/`height` to non-negative
|
|
940
|
+
* dimensions in place of whatever an arbitrary caller supplied (Phase 8) —
|
|
941
|
+
* the interactive drag tools (`shapeTool.ts`) already produce
|
|
942
|
+
* always-non-negative geometry by scanning min/max points before ever
|
|
943
|
+
* constructing a shape, but a raw `content.add`/`content.update` call (Host
|
|
944
|
+
* code, a Custom tool, or a remote collaboration Op) has no such guarantee.
|
|
945
|
+
* `BoardDocument` calls this at every entry point so "dragging from
|
|
946
|
+
* bottom-right toward top-left" and "a negative `width`/`height` patch"
|
|
947
|
+
* produce the same valid geometry regardless of source. A no-op (returns
|
|
948
|
+
* the same reference) when both dimensions are already non-negative, so
|
|
949
|
+
* this never allocates on the common path.
|
|
950
|
+
*/
|
|
951
|
+
function normalizeBoxGeometry(box) {
|
|
952
|
+
if (box.width >= 0 && box.height >= 0) return box;
|
|
953
|
+
const left = Math.min(box.x, box.x + box.width);
|
|
954
|
+
const right = Math.max(box.x, box.x + box.width);
|
|
955
|
+
const top = Math.max(box.y, box.y - box.height);
|
|
956
|
+
const bottom = Math.min(box.y, box.y - box.height);
|
|
957
|
+
return {
|
|
958
|
+
...box,
|
|
959
|
+
x: left,
|
|
960
|
+
y: top,
|
|
961
|
+
width: right - left,
|
|
962
|
+
height: top - bottom
|
|
963
|
+
};
|
|
964
|
+
}
|
|
868
965
|
function cloneRectangle(rect) {
|
|
869
966
|
return { ...rect };
|
|
870
967
|
}
|
|
@@ -1233,31 +1330,33 @@ var BoardDocument = class {
|
|
|
1233
1330
|
const i = this.orderIndex.get(id);
|
|
1234
1331
|
if (i === void 0 || i >= this.objectOrder.length - 1) return;
|
|
1235
1332
|
[this.objectOrder[i], this.objectOrder[i + 1]] = [this.objectOrder[i + 1], this.objectOrder[i]];
|
|
1236
|
-
this.
|
|
1237
|
-
this.
|
|
1333
|
+
this.orderIndex.set(this.objectOrder[i], i);
|
|
1334
|
+
this.orderIndex.set(this.objectOrder[i + 1], i + 1);
|
|
1335
|
+
this.emit({ orderChanged: [this.objectOrder[i], this.objectOrder[i + 1]] });
|
|
1238
1336
|
}
|
|
1239
1337
|
sendBackward(id) {
|
|
1240
1338
|
const i = this.orderIndex.get(id);
|
|
1241
1339
|
if (i === void 0 || i <= 0) return;
|
|
1242
1340
|
[this.objectOrder[i], this.objectOrder[i - 1]] = [this.objectOrder[i - 1], this.objectOrder[i]];
|
|
1243
|
-
this.
|
|
1244
|
-
this.
|
|
1341
|
+
this.orderIndex.set(this.objectOrder[i], i);
|
|
1342
|
+
this.orderIndex.set(this.objectOrder[i - 1], i - 1);
|
|
1343
|
+
this.emit({ orderChanged: [this.objectOrder[i - 1], this.objectOrder[i]] });
|
|
1245
1344
|
}
|
|
1246
1345
|
bringToFront(id) {
|
|
1247
1346
|
const i = this.orderIndex.get(id);
|
|
1248
1347
|
if (i === void 0 || i >= this.objectOrder.length - 1) return;
|
|
1249
1348
|
this.objectOrder.splice(i, 1);
|
|
1250
1349
|
this.objectOrder.push(id);
|
|
1251
|
-
this.
|
|
1252
|
-
this.emit({ orderChanged:
|
|
1350
|
+
for (let k = i; k < this.objectOrder.length; k++) this.orderIndex.set(this.objectOrder[k], k);
|
|
1351
|
+
this.emit({ orderChanged: this.objectOrder.slice(i) });
|
|
1253
1352
|
}
|
|
1254
1353
|
sendToBack(id) {
|
|
1255
1354
|
const i = this.orderIndex.get(id);
|
|
1256
1355
|
if (i === void 0 || i <= 0) return;
|
|
1257
1356
|
this.objectOrder.splice(i, 1);
|
|
1258
1357
|
this.objectOrder.unshift(id);
|
|
1259
|
-
this.
|
|
1260
|
-
this.emit({ orderChanged:
|
|
1358
|
+
for (let k = 0; k <= i; k++) this.orderIndex.set(this.objectOrder[k], k);
|
|
1359
|
+
this.emit({ orderChanged: this.objectOrder.slice(0, i + 1) });
|
|
1261
1360
|
}
|
|
1262
1361
|
/** Reorders `id` relative to its current neighbors. A no-op for an id that doesn't participate in paint order (see `orderRank`). */
|
|
1263
1362
|
reorder(id, direction) {
|
|
@@ -1347,6 +1446,18 @@ var BoardDocument = class {
|
|
|
1347
1446
|
isLocked(id) {
|
|
1348
1447
|
return !!this.strokes.get(id)?.locked || !!this.notes.get(id)?.locked || !!this.texts.get(id)?.locked || !!this.tables.get(id)?.locked || !!this.images.get(id)?.locked || !!this.timers.get(id)?.locked || !!this.rectangles.get(id)?.locked || !!this.ellipses.get(id)?.locked || !!this.groups.get(id)?.locked || !!this.lines.get(id)?.locked || !!this.arrows.get(id)?.locked || !!this.polygons.get(id)?.locked || !!this.stars.get(id)?.locked || !!this.hearts.get(id)?.locked;
|
|
1349
1448
|
}
|
|
1449
|
+
/**
|
|
1450
|
+
* True if `id` exists and is hidden, for any type — the same per-type
|
|
1451
|
+
* probe pattern as {@link isLocked} (Phase 8). Custom objects are
|
|
1452
|
+
* excluded for the same reason `isLocked` excludes them: they have no
|
|
1453
|
+
* `Hideable` field at all, so "hidden" isn't a concept that applies to
|
|
1454
|
+
* them yet. Used by marquee selection (`selectTool.ts`) to keep a hidden
|
|
1455
|
+
* object out of a rubber-band selection even for object types whose own
|
|
1456
|
+
* renderer doesn't yet suppress click-based hit-testing.
|
|
1457
|
+
*/
|
|
1458
|
+
isHidden(id) {
|
|
1459
|
+
return !!this.strokes.get(id)?.hidden || !!this.notes.get(id)?.hidden || !!this.texts.get(id)?.hidden || !!this.tables.get(id)?.hidden || !!this.images.get(id)?.hidden || !!this.timers.get(id)?.hidden || !!this.rectangles.get(id)?.hidden || !!this.ellipses.get(id)?.hidden || !!this.groups.get(id)?.hidden || !!this.lines.get(id)?.hidden || !!this.arrows.get(id)?.hidden || !!this.polygons.get(id)?.hidden || !!this.stars.get(id)?.hidden || !!this.hearts.get(id)?.hidden;
|
|
1460
|
+
}
|
|
1350
1461
|
subscribe(listener) {
|
|
1351
1462
|
this.listeners.add(listener);
|
|
1352
1463
|
return () => this.listeners.delete(listener);
|
|
@@ -1501,8 +1612,9 @@ var BoardDocument = class {
|
|
|
1501
1612
|
return this.rectangles.values();
|
|
1502
1613
|
}
|
|
1503
1614
|
addRectangles(rectangles) {
|
|
1504
|
-
|
|
1505
|
-
|
|
1615
|
+
const normalized = rectangles.map(normalizeBoxGeometry);
|
|
1616
|
+
for (const rect of normalized) this.rectangles.set(rect.id, rect);
|
|
1617
|
+
this.emit({ rectanglesAdded: normalized });
|
|
1506
1618
|
}
|
|
1507
1619
|
removeRectangles(ids) {
|
|
1508
1620
|
const rectanglesRemoved = [];
|
|
@@ -1511,8 +1623,9 @@ var BoardDocument = class {
|
|
|
1511
1623
|
}
|
|
1512
1624
|
/** Replace a rectangle's contents (move, resize, restyle) under the same id. */
|
|
1513
1625
|
setRectangle(rect) {
|
|
1514
|
-
|
|
1515
|
-
this.
|
|
1626
|
+
const normalized = normalizeBoxGeometry(rect);
|
|
1627
|
+
this.rectangles.set(normalized.id, normalized);
|
|
1628
|
+
this.emit({ rectanglesUpdated: [normalized] });
|
|
1516
1629
|
}
|
|
1517
1630
|
getEllipse(id) {
|
|
1518
1631
|
return this.ellipses.get(id);
|
|
@@ -1521,8 +1634,9 @@ var BoardDocument = class {
|
|
|
1521
1634
|
return this.ellipses.values();
|
|
1522
1635
|
}
|
|
1523
1636
|
addEllipses(ellipses) {
|
|
1524
|
-
|
|
1525
|
-
|
|
1637
|
+
const normalized = ellipses.map(normalizeBoxGeometry);
|
|
1638
|
+
for (const ellipse of normalized) this.ellipses.set(ellipse.id, ellipse);
|
|
1639
|
+
this.emit({ ellipsesAdded: normalized });
|
|
1526
1640
|
}
|
|
1527
1641
|
removeEllipses(ids) {
|
|
1528
1642
|
const ellipsesRemoved = [];
|
|
@@ -1531,8 +1645,9 @@ var BoardDocument = class {
|
|
|
1531
1645
|
}
|
|
1532
1646
|
/** Replace an ellipse's contents (move, resize, restyle) under the same id. */
|
|
1533
1647
|
setEllipse(ellipse) {
|
|
1534
|
-
|
|
1535
|
-
this.
|
|
1648
|
+
const normalized = normalizeBoxGeometry(ellipse);
|
|
1649
|
+
this.ellipses.set(normalized.id, normalized);
|
|
1650
|
+
this.emit({ ellipsesUpdated: [normalized] });
|
|
1536
1651
|
}
|
|
1537
1652
|
getGroup(id) {
|
|
1538
1653
|
return this.groups.get(id);
|
|
@@ -1601,8 +1716,9 @@ var BoardDocument = class {
|
|
|
1601
1716
|
return this.polygons.values();
|
|
1602
1717
|
}
|
|
1603
1718
|
addPolygons(polygons) {
|
|
1604
|
-
|
|
1605
|
-
|
|
1719
|
+
const normalized = polygons.map(normalizeBoxGeometry);
|
|
1720
|
+
for (const polygon of normalized) this.polygons.set(polygon.id, polygon);
|
|
1721
|
+
this.emit({ polygonsAdded: normalized });
|
|
1606
1722
|
}
|
|
1607
1723
|
removePolygons(ids) {
|
|
1608
1724
|
const polygonsRemoved = [];
|
|
@@ -1611,8 +1727,9 @@ var BoardDocument = class {
|
|
|
1611
1727
|
}
|
|
1612
1728
|
/** Replace a polygon's contents (move, resize, rotate, restyle) under the same id. */
|
|
1613
1729
|
setPolygon(polygon) {
|
|
1614
|
-
|
|
1615
|
-
this.
|
|
1730
|
+
const normalized = normalizeBoxGeometry(polygon);
|
|
1731
|
+
this.polygons.set(normalized.id, normalized);
|
|
1732
|
+
this.emit({ polygonsUpdated: [normalized] });
|
|
1616
1733
|
}
|
|
1617
1734
|
getStar(id) {
|
|
1618
1735
|
return this.stars.get(id);
|
|
@@ -1621,8 +1738,9 @@ var BoardDocument = class {
|
|
|
1621
1738
|
return this.stars.values();
|
|
1622
1739
|
}
|
|
1623
1740
|
addStars(stars) {
|
|
1624
|
-
|
|
1625
|
-
|
|
1741
|
+
const normalized = stars.map(normalizeBoxGeometry);
|
|
1742
|
+
for (const star of normalized) this.stars.set(star.id, star);
|
|
1743
|
+
this.emit({ starsAdded: normalized });
|
|
1626
1744
|
}
|
|
1627
1745
|
removeStars(ids) {
|
|
1628
1746
|
const starsRemoved = [];
|
|
@@ -1631,8 +1749,9 @@ var BoardDocument = class {
|
|
|
1631
1749
|
}
|
|
1632
1750
|
/** Replace a star's contents (move, resize, rotate, restyle) under the same id. */
|
|
1633
1751
|
setStar(star) {
|
|
1634
|
-
|
|
1635
|
-
this.
|
|
1752
|
+
const normalized = normalizeBoxGeometry(star);
|
|
1753
|
+
this.stars.set(normalized.id, normalized);
|
|
1754
|
+
this.emit({ starsUpdated: [normalized] });
|
|
1636
1755
|
}
|
|
1637
1756
|
getHeart(id) {
|
|
1638
1757
|
return this.hearts.get(id);
|
|
@@ -1641,8 +1760,9 @@ var BoardDocument = class {
|
|
|
1641
1760
|
return this.hearts.values();
|
|
1642
1761
|
}
|
|
1643
1762
|
addHearts(hearts) {
|
|
1644
|
-
|
|
1645
|
-
|
|
1763
|
+
const normalized = hearts.map(normalizeBoxGeometry);
|
|
1764
|
+
for (const heart of normalized) this.hearts.set(heart.id, heart);
|
|
1765
|
+
this.emit({ heartsAdded: normalized });
|
|
1646
1766
|
}
|
|
1647
1767
|
removeHearts(ids) {
|
|
1648
1768
|
const heartsRemoved = [];
|
|
@@ -1651,8 +1771,9 @@ var BoardDocument = class {
|
|
|
1651
1771
|
}
|
|
1652
1772
|
/** Replace a heart's contents (move, resize, rotate, restyle) under the same id. */
|
|
1653
1773
|
setHeart(heart) {
|
|
1654
|
-
|
|
1655
|
-
this.
|
|
1774
|
+
const normalized = normalizeBoxGeometry(heart);
|
|
1775
|
+
this.hearts.set(normalized.id, normalized);
|
|
1776
|
+
this.emit({ heartsUpdated: [normalized] });
|
|
1656
1777
|
}
|
|
1657
1778
|
setStrokeLocked(id, locked, by) {
|
|
1658
1779
|
const stroke = this.strokes.get(id);
|
|
@@ -2117,7 +2238,17 @@ var BoardDocument = class {
|
|
|
2117
2238
|
static deserializeTables(data) {
|
|
2118
2239
|
return (data.tables ?? []).map(cloneTable);
|
|
2119
2240
|
}
|
|
2120
|
-
|
|
2241
|
+
/**
|
|
2242
|
+
* `onSkip` (Phase 9) replaces an unconditional `console.error` — `core`
|
|
2243
|
+
* must never do raw console I/O (no dev-gate, no way for a Host to
|
|
2244
|
+
* suppress or redirect it), so a skipped stroke is now reported only if
|
|
2245
|
+
* the caller asks for it, via whatever diagnostic channel it already
|
|
2246
|
+
* has (e.g. `controller-internal.ts` routes this into the same typed
|
|
2247
|
+
* `"error"` event every other diagnostic already uses). Silent by
|
|
2248
|
+
* default, matching how every other `deserialize*` method here already
|
|
2249
|
+
* behaves (no diagnostics at all).
|
|
2250
|
+
*/
|
|
2251
|
+
static deserializeStrokes(data, onSkip) {
|
|
2121
2252
|
const strokes = [];
|
|
2122
2253
|
for (const s of data.strokes) try {
|
|
2123
2254
|
strokes.push({
|
|
@@ -2131,7 +2262,7 @@ var BoardDocument = class {
|
|
|
2131
2262
|
...serializeLock(s)
|
|
2132
2263
|
});
|
|
2133
2264
|
} catch (err) {
|
|
2134
|
-
|
|
2265
|
+
onSkip?.(s?.id ?? "(no id)", err);
|
|
2135
2266
|
}
|
|
2136
2267
|
return strokes;
|
|
2137
2268
|
}
|
|
@@ -2145,46 +2276,51 @@ var BoardDocument = class {
|
|
|
2145
2276
|
* ids already tracked (idempotent by construction: `orderIndex.has` gates
|
|
2146
2277
|
* every append).
|
|
2147
2278
|
*
|
|
2148
|
-
* `orderChanged`
|
|
2149
|
-
* changed
|
|
2150
|
-
*
|
|
2151
|
-
*
|
|
2152
|
-
*
|
|
2153
|
-
*
|
|
2154
|
-
*
|
|
2155
|
-
*
|
|
2156
|
-
*
|
|
2157
|
-
*
|
|
2158
|
-
*
|
|
2279
|
+
* `orderChanged` (Phase 9) reports exactly the ids whose rank actually
|
|
2280
|
+
* changed, using `orderIndex` throughout instead of `indexOf` — a pure
|
|
2281
|
+
* append never shifts any existing id's rank (new ids land at the tail,
|
|
2282
|
+
* already covered by this same change's own `Added` field, so
|
|
2283
|
+
* `orderChanged` stays unset), while a removal shifts every id at-or-
|
|
2284
|
+
* after the lowest removed rank down by one, computed in a single O(n)
|
|
2285
|
+
* filter pass (not one `indexOf`+`splice` per removed id) regardless of
|
|
2286
|
+
* how many ids this one change removes. Every renderer's `onChange` now
|
|
2287
|
+
* looks up only the ids actually in `orderChanged` instead of walking
|
|
2288
|
+
* its entire mesh map on any order-touching change — a broad, unfiltered
|
|
2289
|
+
* `orderChanged` here would silently defeat that fix, not just waste
|
|
2290
|
+
* cycles here.
|
|
2159
2291
|
*/
|
|
2160
2292
|
emit(change) {
|
|
2161
|
-
|
|
2293
|
+
const newIds = [];
|
|
2162
2294
|
for (const field of ORDERED_ADDED_FIELDS) {
|
|
2163
2295
|
const objects = change[field];
|
|
2164
2296
|
if (!objects) continue;
|
|
2165
|
-
for (const object of objects) if (!this.orderIndex.has(object.id))
|
|
2166
|
-
this.objectOrder.push(object.id);
|
|
2167
|
-
orderTouched = true;
|
|
2168
|
-
}
|
|
2297
|
+
for (const object of objects) if (!this.orderIndex.has(object.id)) newIds.push(object.id);
|
|
2169
2298
|
}
|
|
2299
|
+
const removedIds = /* @__PURE__ */ new Set();
|
|
2170
2300
|
for (const field of ORDERED_REMOVED_FIELDS) {
|
|
2171
2301
|
const ids = change[field];
|
|
2172
2302
|
if (!ids) continue;
|
|
2173
|
-
for (const id of ids)
|
|
2174
|
-
const index = this.objectOrder.indexOf(id);
|
|
2175
|
-
if (index !== -1) {
|
|
2176
|
-
this.objectOrder.splice(index, 1);
|
|
2177
|
-
orderTouched = true;
|
|
2178
|
-
}
|
|
2179
|
-
}
|
|
2303
|
+
for (const id of ids) if (this.orderIndex.has(id)) removedIds.add(id);
|
|
2180
2304
|
}
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2305
|
+
let orderChanged;
|
|
2306
|
+
if (removedIds.size > 0) {
|
|
2307
|
+
let minIndex = this.objectOrder.length;
|
|
2308
|
+
for (const id of removedIds) minIndex = Math.min(minIndex, this.orderIndex.get(id));
|
|
2309
|
+
const next = [];
|
|
2310
|
+
for (const id of this.objectOrder) if (!removedIds.has(id)) next.push(id);
|
|
2311
|
+
this.objectOrder = next;
|
|
2312
|
+
for (let k = minIndex; k < this.objectOrder.length; k++) this.orderIndex.set(this.objectOrder[k], k);
|
|
2313
|
+
for (const id of removedIds) this.orderIndex.delete(id);
|
|
2314
|
+
orderChanged = this.objectOrder.slice(minIndex);
|
|
2187
2315
|
}
|
|
2316
|
+
if (newIds.length > 0) for (const id of newIds) {
|
|
2317
|
+
this.orderIndex.set(id, this.objectOrder.length);
|
|
2318
|
+
this.objectOrder.push(id);
|
|
2319
|
+
}
|
|
2320
|
+
if (orderChanged) change = {
|
|
2321
|
+
...change,
|
|
2322
|
+
orderChanged
|
|
2323
|
+
};
|
|
2188
2324
|
const full = {
|
|
2189
2325
|
...EMPTY_CHANGE,
|
|
2190
2326
|
...change
|
|
@@ -3695,7 +3831,7 @@ function documentToSVG(doc, now = 0, registry, backgroundColor = DEFAULT_BACKGRO
|
|
|
3695
3831
|
const highlights = doc.strokes.filter((s) => s.tool === "highlighter");
|
|
3696
3832
|
const ink = doc.strokes.filter((s) => s.tool !== "highlighter");
|
|
3697
3833
|
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${view}">\n<defs>\n${defs}\n</defs>\n${[
|
|
3698
|
-
`<rect x="${fmt(bounds.minX)}" y="${fmt(bounds.minY)}" width="${fmt(bounds.width)}" height="${fmt(bounds.height)}" fill="${backgroundColor}"/>`,
|
|
3834
|
+
`<rect x="${fmt(bounds.minX)}" y="${fmt(bounds.minY)}" width="${fmt(bounds.width)}" height="${fmt(bounds.height)}" fill="${escapeXML(backgroundColor)}"/>`,
|
|
3699
3835
|
...images.map((image) => imageToElement(image, resolvedAssets)),
|
|
3700
3836
|
...tables.map(tableToGroup),
|
|
3701
3837
|
...rectangles.map(rectangleToElement),
|
|
@@ -3723,7 +3859,7 @@ function customObjectToGroup(object, registry, resolvedAssets) {
|
|
|
3723
3859
|
const definition = registry?.objectType(object.type);
|
|
3724
3860
|
if (definition) try {
|
|
3725
3861
|
const scene = definition.describe(object, { selected: false });
|
|
3726
|
-
return `<g data-id="${object.id}">${sceneNodeToSVG(scene, object.transform, resolvedAssets)}</g>`;
|
|
3862
|
+
return `<g data-id="${escapeXML(object.id)}">${sceneNodeToSVG(scene, object.transform, resolvedAssets)}</g>`;
|
|
3727
3863
|
} catch {}
|
|
3728
3864
|
return fallbackToGroup(object);
|
|
3729
3865
|
}
|
|
@@ -3747,17 +3883,17 @@ function sceneNodeToSVG(node, parent, resolvedAssets) {
|
|
|
3747
3883
|
case "rect": {
|
|
3748
3884
|
const svgY = -node.y - node.height;
|
|
3749
3885
|
const rx = node.cornerRadius ? ` rx="${fmt(node.cornerRadius)}"` : "";
|
|
3750
|
-
const stroke = node.stroke ? ` stroke="${node.stroke}" stroke-width="${fmt(node.strokeWidth ?? .1)}"` : "";
|
|
3751
|
-
return `<rect x="${fmt(node.x)}" y="${fmt(svgY)}" width="${fmt(node.width)}" height="${fmt(node.height)}" fill="${node.fill ?? "none"}"${rx}${stroke}${opacityAttr}${transformAttr}/>`;
|
|
3886
|
+
const stroke = node.stroke ? ` stroke="${escapeXML(node.stroke)}" stroke-width="${fmt(node.strokeWidth ?? .1)}"` : "";
|
|
3887
|
+
return `<rect x="${fmt(node.x)}" y="${fmt(svgY)}" width="${fmt(node.width)}" height="${fmt(node.height)}" fill="${escapeXML(node.fill ?? "none")}"${rx}${stroke}${opacityAttr}${transformAttr}/>`;
|
|
3752
3888
|
}
|
|
3753
3889
|
case "text": {
|
|
3754
3890
|
const anchor = node.align === "center" ? ` text-anchor="middle"` : node.align === "end" ? ` text-anchor="end"` : "";
|
|
3755
3891
|
const tspans = node.text.split("\n").map((line, i) => `<tspan x="${fmt(node.x)}" y="${fmt(-node.y + node.fontSize * (.8 + 1.25 * i))}">${escapeXML(line)}</tspan>`).join("");
|
|
3756
|
-
return `<text font-family="Inter, 'Segoe UI', sans-serif" font-size="${fmt(node.fontSize)}" font-weight="500" fill="${node.color}"${anchor}${opacityAttr}${transformAttr}>${tspans}</text>`;
|
|
3892
|
+
return `<text font-family="Inter, 'Segoe UI', sans-serif" font-size="${fmt(node.fontSize)}" font-weight="500" fill="${escapeXML(node.color)}"${anchor}${opacityAttr}${transformAttr}>${tspans}</text>`;
|
|
3757
3893
|
}
|
|
3758
3894
|
case "ellipse": {
|
|
3759
|
-
const stroke = node.stroke ? ` stroke="${node.stroke}" stroke-width="${fmt(node.strokeWidth ?? .1)}"` : "";
|
|
3760
|
-
return `<ellipse cx="${fmt(node.cx)}" cy="${fmt(-node.cy)}" rx="${fmt(node.rx)}" ry="${fmt(node.ry)}" fill="${node.fill ?? "none"}"${stroke}${opacityAttr}${transformAttr}/>`;
|
|
3895
|
+
const stroke = node.stroke ? ` stroke="${escapeXML(node.stroke)}" stroke-width="${fmt(node.strokeWidth ?? .1)}"` : "";
|
|
3896
|
+
return `<ellipse cx="${fmt(node.cx)}" cy="${fmt(-node.cy)}" rx="${fmt(node.rx)}" ry="${fmt(node.ry)}" fill="${escapeXML(node.fill ?? "none")}"${stroke}${opacityAttr}${transformAttr}/>`;
|
|
3761
3897
|
}
|
|
3762
3898
|
case "image": {
|
|
3763
3899
|
const svgY = -node.y - node.height;
|
|
@@ -3788,7 +3924,7 @@ function fallbackToGroup(object) {
|
|
|
3788
3924
|
const label = object.fallback.label;
|
|
3789
3925
|
const labelEl = label ? `<text x="${fmt(b.x + Math.min(.3, b.width * .1))}" y="${fmt(svgY + Math.min(1.2, b.height * .4))}" font-family="Inter, 'Segoe UI', sans-serif" font-size="${fmt(Math.min(1.1, b.height * .3))}" fill="#6B7280">${escapeXML(label)}</text>` : "";
|
|
3790
3926
|
return [
|
|
3791
|
-
`<g data-id="${object.id}" data-fallback="true"${svgTransformAttr(object.transform)}>`,
|
|
3927
|
+
`<g data-id="${escapeXML(object.id)}" data-fallback="true"${svgTransformAttr(object.transform)}>`,
|
|
3792
3928
|
`<rect x="${fmt(b.x)}" y="${fmt(svgY)}" width="${fmt(b.width)}" height="${fmt(b.height)}" fill="#F3F4F6" stroke="#9CA3AF" stroke-width="0.15" stroke-dasharray="0.3 0.3"/>`,
|
|
3793
3929
|
labelEl,
|
|
3794
3930
|
`</g>`
|
|
@@ -3812,7 +3948,7 @@ function strokeToPaths(stroke) {
|
|
|
3812
3948
|
const d = `M ${left.join(" L ")} L ${right.join(" L ")} Z`;
|
|
3813
3949
|
const opacity = run.opacity * baseOpacity;
|
|
3814
3950
|
const opacityAttr = opacity < 1 ? ` fill-opacity="${fmt(opacity)}"` : "";
|
|
3815
|
-
paths.push(`<path data-id="${stroke.id}" d="${d}" fill="${stroke.color}"${opacityAttr}${transform}/>`);
|
|
3951
|
+
paths.push(`<path data-id="${escapeXML(stroke.id)}" d="${d}" fill="${escapeXML(stroke.color)}"${opacityAttr}${transform}/>`);
|
|
3816
3952
|
}
|
|
3817
3953
|
return paths;
|
|
3818
3954
|
}
|
|
@@ -3834,14 +3970,14 @@ function splitByOpacity(edges) {
|
|
|
3834
3970
|
function textToElement(block) {
|
|
3835
3971
|
const top = -block.y;
|
|
3836
3972
|
const tspans = block.text.split("\n").map((line, i) => `<tspan x="${fmt(block.x)}" y="${fmt(top + block.fontSize * (.8 + 1.25 * i))}">${escapeXML(line)}</tspan>`).join("");
|
|
3837
|
-
return `<text font-family="Inter, 'Segoe UI', sans-serif" font-size="${fmt(block.fontSize)}" font-weight="500" fill="${block.color}">${tspans}</text>`;
|
|
3973
|
+
return `<text font-family="Inter, 'Segoe UI', sans-serif" font-size="${fmt(block.fontSize)}" font-weight="500" fill="${escapeXML(block.color)}">${tspans}</text>`;
|
|
3838
3974
|
}
|
|
3839
3975
|
function noteShadowFilter(note) {
|
|
3840
3976
|
const dx = -LIGHT.x / LIGHT.z * note.zOffset;
|
|
3841
3977
|
const dy = LIGHT.y / LIGHT.z * note.zOffset;
|
|
3842
3978
|
const blur = (.35 + note.zOffset * .55) / 2;
|
|
3843
3979
|
const opacity = Math.max(.05, .26 - note.zOffset * .028);
|
|
3844
|
-
return `<filter id="sh-${note.id}" x="-50%" y="-50%" width="200%" height="200%"><feDropShadow dx="${fmt(dx)}" dy="${fmt(dy)}" stdDeviation="${fmt(blur)}" flood-opacity="${fmt(opacity)}"/></filter>`;
|
|
3980
|
+
return `<filter id="sh-${escapeXML(note.id)}" x="-50%" y="-50%" width="200%" height="200%"><feDropShadow dx="${fmt(dx)}" dy="${fmt(dy)}" stdDeviation="${fmt(blur)}" flood-opacity="${fmt(opacity)}"/></filter>`;
|
|
3845
3981
|
}
|
|
3846
3982
|
function noteToGroup(note) {
|
|
3847
3983
|
const left = note.x - note.size / 2;
|
|
@@ -3851,7 +3987,7 @@ function noteToGroup(note) {
|
|
|
3851
3987
|
const tspans = wrapText(note.text, note.size - pad * 2, fontSize).map((line, i) => `<tspan x="${fmt(left + pad)}" y="${fmt(top + pad + fontSize * (.8 + 1.25 * i))}">${escapeXML(line)}</tspan>`).join("");
|
|
3852
3988
|
return [
|
|
3853
3989
|
`<g>`,
|
|
3854
|
-
`<rect x="${fmt(left)}" y="${fmt(top)}" width="${fmt(note.size)}" height="${fmt(note.size)}" fill="${note.color}" filter="url(#sh-${note.id})"/>`,
|
|
3990
|
+
`<rect x="${fmt(left)}" y="${fmt(top)}" width="${fmt(note.size)}" height="${fmt(note.size)}" fill="${escapeXML(note.color)}" filter="url(#sh-${escapeXML(note.id)})"/>`,
|
|
3855
3991
|
`<text font-family="ui-rounded, 'Segoe UI', sans-serif" font-size="${fmt(fontSize)}" font-weight="500" fill="#37352F">${tspans}</text>`,
|
|
3856
3992
|
`</g>`
|
|
3857
3993
|
].join("");
|
|
@@ -3882,8 +4018,8 @@ function tableToGroup(table) {
|
|
|
3882
4018
|
const left = table.x;
|
|
3883
4019
|
const top = -table.y;
|
|
3884
4020
|
const headerH = table.rowHeights[0] ?? height / table.rows;
|
|
3885
|
-
const borderColor = table.color || "#D1D5DB";
|
|
3886
|
-
const bgColor = table.backgroundColor || "#FFFFFF";
|
|
4021
|
+
const borderColor = escapeXML(table.color || "#D1D5DB");
|
|
4022
|
+
const bgColor = escapeXML(table.backgroundColor || "#FFFFFF");
|
|
3887
4023
|
const elements = [];
|
|
3888
4024
|
elements.push(`<rect x="${fmt(left)}" y="${fmt(top)}" width="${fmt(width)}" height="${fmt(height)}" fill="${bgColor}" rx="0.5"/>`);
|
|
3889
4025
|
elements.push(`<rect x="${fmt(left)}" y="${fmt(top)}" width="${fmt(width)}" height="${fmt(headerH)}" fill="#F3F4F6" rx="0.5"/>`);
|
|
@@ -3911,14 +4047,14 @@ function tableToGroup(table) {
|
|
|
3911
4047
|
const padY = Math.min(.3, rowH * .12);
|
|
3912
4048
|
const fontSize = Math.min(1.25, rowH * .45);
|
|
3913
4049
|
const weight = r === 0 ? "bold" : "500";
|
|
3914
|
-
const color = r === 0 ? "#111827" : table.color ?? "#374151";
|
|
4050
|
+
const color = escapeXML(r === 0 ? "#111827" : table.color ?? "#374151");
|
|
3915
4051
|
elements.push(`<text x="${fmt(left + cellLeft + padX)}" y="${fmt(top + cellTop + padY + fontSize * .8)}" font-family="Inter, 'Segoe UI', sans-serif" font-size="${fmt(fontSize)}" font-weight="${weight}" fill="${color}">${escapeXML(text)}</text>`);
|
|
3916
4052
|
}
|
|
3917
4053
|
cellLeft += colW;
|
|
3918
4054
|
}
|
|
3919
4055
|
cellTop += rowH;
|
|
3920
4056
|
}
|
|
3921
|
-
return `<g data-id="${table.id}">\n${elements.join("\n")}\n</g>`;
|
|
4057
|
+
return `<g data-id="${escapeXML(table.id)}">\n${elements.join("\n")}\n</g>`;
|
|
3922
4058
|
}
|
|
3923
4059
|
function timerToGroup(timer, now) {
|
|
3924
4060
|
const r = timer.size / 2;
|
|
@@ -3926,7 +4062,7 @@ function timerToGroup(timer, now) {
|
|
|
3926
4062
|
const cy = -timer.y;
|
|
3927
4063
|
const label = formatTimer(timerRemaining(timer, now));
|
|
3928
4064
|
return [
|
|
3929
|
-
`<g data-id="${timer.id}">`,
|
|
4065
|
+
`<g data-id="${escapeXML(timer.id)}">`,
|
|
3930
4066
|
`<circle cx="${fmt(cx + .4)}" cy="${fmt(cy + .4)}" r="${fmt(r)}" fill="#000" fill-opacity="0.12"/>`,
|
|
3931
4067
|
`<circle cx="${fmt(cx)}" cy="${fmt(cy)}" r="${fmt(r)}" fill="#E11D48"/>`,
|
|
3932
4068
|
`<circle cx="${fmt(cx)}" cy="${fmt(cy)}" r="${fmt(r * .72)}" fill="#FAFAF9"/>`,
|
|
@@ -3939,8 +4075,8 @@ function imageToElement(image, resolvedAssets) {
|
|
|
3939
4075
|
const top = -image.y - image.height / 2;
|
|
3940
4076
|
if (image.ref) {
|
|
3941
4077
|
const dataUri = resolvedAssets?.get(image.ref);
|
|
3942
|
-
if (!dataUri) return `<g data-id="${image.id}" data-fallback="true">${imagePlaceholderElement(left, top, image.width, image.height, image.name)}</g>`;
|
|
3943
|
-
return `<image data-id="${image.id}" href="${escapeXML(dataUri)}" x="${fmt(left)}" y="${fmt(top)}" width="${fmt(image.width)}" height="${fmt(image.height)}" preserveAspectRatio="none"/>`;
|
|
4078
|
+
if (!dataUri) return `<g data-id="${escapeXML(image.id)}" data-fallback="true">${imagePlaceholderElement(left, top, image.width, image.height, image.name)}</g>`;
|
|
4079
|
+
return `<image data-id="${escapeXML(image.id)}" href="${escapeXML(dataUri)}" x="${fmt(left)}" y="${fmt(top)}" width="${fmt(image.width)}" height="${fmt(image.height)}" preserveAspectRatio="none"/>`;
|
|
3944
4080
|
}
|
|
3945
4081
|
return `<image href="${escapeXML(image.src)}" x="${fmt(left)}" y="${fmt(top)}" width="${fmt(image.width)}" height="${fmt(image.height)}" preserveAspectRatio="none"/>`;
|
|
3946
4082
|
}
|
|
@@ -3962,19 +4098,19 @@ function opacityAttrOf(opacity) {
|
|
|
3962
4098
|
}
|
|
3963
4099
|
function rectangleToElement(rect) {
|
|
3964
4100
|
const svgY = -rect.y;
|
|
3965
|
-
const stroke = rect.stroke ? ` stroke="${rect.stroke}" stroke-width="${fmt(rect.strokeWidth ?? .12)}"` : "";
|
|
4101
|
+
const stroke = rect.stroke ? ` stroke="${escapeXML(rect.stroke)}" stroke-width="${fmt(rect.strokeWidth ?? .12)}"` : "";
|
|
3966
4102
|
const rx = rect.cornerRadius ? ` rx="${fmt(rect.cornerRadius)}"` : "";
|
|
3967
4103
|
const transform = shapeRotationAttr(rect.x, rect.y, rect.width, rect.height, rect.rotation);
|
|
3968
4104
|
const opacity = opacityAttrOf(rect.opacity);
|
|
3969
|
-
return `<rect data-id="${rect.id}" x="${fmt(rect.x)}" y="${fmt(svgY)}" width="${fmt(rect.width)}" height="${fmt(rect.height)}" fill="${rect.fill ?? "none"}"${rx}${stroke}${opacity}${transform}/>`;
|
|
4105
|
+
return `<rect data-id="${escapeXML(rect.id)}" x="${fmt(rect.x)}" y="${fmt(svgY)}" width="${fmt(rect.width)}" height="${fmt(rect.height)}" fill="${escapeXML(rect.fill ?? "none")}"${rx}${stroke}${opacity}${transform}/>`;
|
|
3970
4106
|
}
|
|
3971
4107
|
function ellipseToElement(ellipse) {
|
|
3972
4108
|
const cx = ellipse.x + ellipse.width / 2;
|
|
3973
4109
|
const cy = -ellipse.y + ellipse.height / 2;
|
|
3974
|
-
const stroke = ellipse.stroke ? ` stroke="${ellipse.stroke}" stroke-width="${fmt(ellipse.strokeWidth ?? .12)}"` : "";
|
|
4110
|
+
const stroke = ellipse.stroke ? ` stroke="${escapeXML(ellipse.stroke)}" stroke-width="${fmt(ellipse.strokeWidth ?? .12)}"` : "";
|
|
3975
4111
|
const transform = shapeRotationAttr(ellipse.x, ellipse.y, ellipse.width, ellipse.height, ellipse.rotation);
|
|
3976
4112
|
const opacity = opacityAttrOf(ellipse.opacity);
|
|
3977
|
-
return `<ellipse data-id="${ellipse.id}" cx="${fmt(cx)}" cy="${fmt(cy)}" rx="${fmt(ellipse.width / 2)}" ry="${fmt(ellipse.height / 2)}" fill="${ellipse.fill ?? "none"}"${stroke}${opacity}${transform}/>`;
|
|
4113
|
+
return `<ellipse data-id="${escapeXML(ellipse.id)}" cx="${fmt(cx)}" cy="${fmt(cy)}" rx="${fmt(ellipse.width / 2)}" ry="${fmt(ellipse.height / 2)}" fill="${escapeXML(ellipse.fill ?? "none")}"${stroke}${opacity}${transform}/>`;
|
|
3978
4114
|
}
|
|
3979
4115
|
/**
|
|
3980
4116
|
* A regular N-gon inscribed in the same top-edge/Y-up bounding box
|
|
@@ -3987,46 +4123,46 @@ function polygonToElement(polygon) {
|
|
|
3987
4123
|
const cx = polygon.x + polygon.width / 2;
|
|
3988
4124
|
const cy = polygon.y - polygon.height / 2;
|
|
3989
4125
|
const svgPoints = regularPolygonPoints(polygon.sides, polygon.width, polygon.height, polygonStartAngle(polygon.sides)).map((p) => `${fmt(cx + p.x)},${fmt(-(cy + p.y))}`).join(" ");
|
|
3990
|
-
const stroke = polygon.stroke ? ` stroke="${polygon.stroke}" stroke-width="${fmt(polygon.strokeWidth ??
|
|
4126
|
+
const stroke = polygon.stroke ? ` stroke="${escapeXML(polygon.stroke)}" stroke-width="${fmt(polygon.strokeWidth ?? SHAPE_DEFAULT_STROKE_WIDTH)}"` : "";
|
|
3991
4127
|
const transform = shapeRotationAttr(polygon.x, polygon.y, polygon.width, polygon.height, polygon.rotation);
|
|
3992
4128
|
const opacity = opacityAttrOf(polygon.opacity);
|
|
3993
|
-
return `<polygon data-id="${polygon.id}" points="${svgPoints}" fill="${polygon.fill ?? "none"}"${stroke}${opacity}${transform}/>`;
|
|
4129
|
+
return `<polygon data-id="${escapeXML(polygon.id)}" points="${svgPoints}" fill="${escapeXML(polygon.fill ?? "none")}"${stroke}${opacity}${transform}/>`;
|
|
3994
4130
|
}
|
|
3995
4131
|
/** Same convention/pattern as `polygonToElement` — a `points`-vertex star with `innerRadiusRatio` (`polygonGeometry.ts`'s `starPoints`). */
|
|
3996
4132
|
function starToElement(star) {
|
|
3997
4133
|
const cx = star.x + star.width / 2;
|
|
3998
4134
|
const cy = star.y - star.height / 2;
|
|
3999
4135
|
const svgPoints = starPoints(star.points, star.innerRadiusRatio, star.width, star.height).map((p) => `${fmt(cx + p.x)},${fmt(-(cy + p.y))}`).join(" ");
|
|
4000
|
-
const stroke = star.stroke ? ` stroke="${star.stroke}" stroke-width="${fmt(star.strokeWidth ??
|
|
4136
|
+
const stroke = star.stroke ? ` stroke="${escapeXML(star.stroke)}" stroke-width="${fmt(star.strokeWidth ?? SHAPE_DEFAULT_STROKE_WIDTH)}"` : "";
|
|
4001
4137
|
const transform = shapeRotationAttr(star.x, star.y, star.width, star.height, star.rotation);
|
|
4002
4138
|
const opacity = opacityAttrOf(star.opacity);
|
|
4003
|
-
return `<polygon data-id="${star.id}" points="${svgPoints}" fill="${star.fill ?? "none"}"${stroke}${opacity}${transform}/>`;
|
|
4139
|
+
return `<polygon data-id="${escapeXML(star.id)}" points="${svgPoints}" fill="${escapeXML(star.fill ?? "none")}"${stroke}${opacity}${transform}/>`;
|
|
4004
4140
|
}
|
|
4005
4141
|
/** Same convention/pattern as `polygonToElement` — the standard parametric heart curve (`polygonGeometry.ts`'s `heartPoints`), emitted as a closed `<polygon>` (dense enough at `HEART_SEGMENTS` to read as a smooth curve). */
|
|
4006
4142
|
function heartToElement(heart) {
|
|
4007
4143
|
const cx = heart.x + heart.width / 2;
|
|
4008
4144
|
const cy = heart.y - heart.height / 2;
|
|
4009
4145
|
const svgPoints = heartPoints(heart.width, heart.height).map((p) => `${fmt(cx + p.x)},${fmt(-(cy + p.y))}`).join(" ");
|
|
4010
|
-
const stroke = heart.stroke ? ` stroke="${heart.stroke}" stroke-width="${fmt(heart.strokeWidth ??
|
|
4146
|
+
const stroke = heart.stroke ? ` stroke="${escapeXML(heart.stroke)}" stroke-width="${fmt(heart.strokeWidth ?? SHAPE_DEFAULT_STROKE_WIDTH)}"` : "";
|
|
4011
4147
|
const transform = shapeRotationAttr(heart.x, heart.y, heart.width, heart.height, heart.rotation);
|
|
4012
4148
|
const opacity = opacityAttrOf(heart.opacity);
|
|
4013
|
-
return `<polygon data-id="${heart.id}" points="${svgPoints}" fill="${heart.fill ?? "none"}"${stroke}${opacity}${transform}/>`;
|
|
4149
|
+
return `<polygon data-id="${escapeXML(heart.id)}" points="${svgPoints}" fill="${escapeXML(heart.fill ?? "none")}"${stroke}${opacity}${transform}/>`;
|
|
4014
4150
|
}
|
|
4015
4151
|
/** No rotation/box concept (see shapeObjects.ts's header) — the two endpoints are emitted directly, y-flipped like every other coordinate here. */
|
|
4016
4152
|
function lineToElement(line) {
|
|
4017
|
-
const stroke = line.stroke ??
|
|
4018
|
-
const width = line.strokeWidth ??
|
|
4153
|
+
const stroke = escapeXML(line.stroke ?? SHAPE_DEFAULT_STROKE);
|
|
4154
|
+
const width = line.strokeWidth ?? SHAPE_DEFAULT_STROKE_WIDTH;
|
|
4019
4155
|
const opacity = opacityAttrOf(line.opacity);
|
|
4020
|
-
return `<line data-id="${line.id}" x1="${fmt(line.start.x)}" y1="${fmt(-line.start.y)}" x2="${fmt(line.end.x)}" y2="${fmt(-line.end.y)}" stroke="${stroke}" stroke-width="${fmt(width)}"${opacity}/>`;
|
|
4156
|
+
return `<line data-id="${escapeXML(line.id)}" x1="${fmt(line.start.x)}" y1="${fmt(-line.start.y)}" x2="${fmt(line.end.x)}" y2="${fmt(-line.end.y)}" stroke="${stroke}" stroke-width="${fmt(width)}"${opacity}/>`;
|
|
4021
4157
|
}
|
|
4022
4158
|
/** Shaft plus head barbs — same `arrowHeadBarbs` the canvas renderer uses, so the two never drift. */
|
|
4023
4159
|
function arrowToGroup(arrow) {
|
|
4024
|
-
const stroke = arrow.stroke ??
|
|
4025
|
-
const width = arrow.strokeWidth ??
|
|
4160
|
+
const stroke = escapeXML(arrow.stroke ?? SHAPE_DEFAULT_STROKE);
|
|
4161
|
+
const width = arrow.strokeWidth ?? SHAPE_DEFAULT_STROKE_WIDTH;
|
|
4026
4162
|
const opacity = opacityAttrOf(arrow.opacity);
|
|
4027
4163
|
const segment = (a, b) => `<line x1="${fmt(a.x)}" y1="${fmt(-a.y)}" x2="${fmt(b.x)}" y2="${fmt(-b.y)}" stroke="${stroke}" stroke-width="${fmt(width)}"/>`;
|
|
4028
4164
|
const segments = [segment(arrow.start, arrow.end), ...arrowHeadBarbs(arrow.start, arrow.end, arrow.head).map(([a, b]) => segment(a, b))];
|
|
4029
|
-
return `<g data-id="${arrow.id}"${opacity}>${segments.join("")}</g>`;
|
|
4165
|
+
return `<g data-id="${escapeXML(arrow.id)}"${opacity}>${segments.join("")}</g>`;
|
|
4030
4166
|
}
|
|
4031
4167
|
function contentBounds(strokes, notes, texts, tables = [], images = [], timers = [], customObjects = [], rectangles = [], ellipses = [], lines = [], arrows = [], polygons = [], stars = [], hearts = []) {
|
|
4032
4168
|
let minX = Infinity;
|
|
@@ -4142,4 +4278,4 @@ function fmt(n) {
|
|
|
4142
4278
|
var SDK_PACKAGE_NAME = "@scrawl-board/board";
|
|
4143
4279
|
var SDK_DEVELOPMENT_VERSION = "0.0.0-development";
|
|
4144
4280
|
//#endregion
|
|
4145
|
-
export { ASSET_CACHE_BYTES_DEFAULT, ASSET_CACHE_BYTES_MAX, ASSET_CACHE_BYTES_MIN, ASSET_EXPORT_MAX_DECODED_MEGAPIXELS, ASSET_EXPORT_MAX_ENCODED_BYTES, ASSET_MAX_CONCURRENT_RESOLUTIONS, ASSET_MAX_DECODED_MEGAPIXELS, ASSET_MAX_DIMENSION_PX, ASSET_MAX_ENCODED_BYTES, ASSET_REF_MAX_BYTES, ASSET_REF_PATTERN, AddArrowCommand, AddEllipseCommand, AddGroupCommand, AddHeartCommand, AddImageCommand, AddLineCommand, AddNoteCommand, AddPolygonCommand, AddRectangleCommand, AddStarCommand, AddStrokesCommand, AddTableCommand, AddTextCommand, AddTimerCommand, AssetResolutionError, BEACON_INSET, BoardDocument, CURRENT_DOCUMENT_SCHEMA_VERSION, ClusterStore, CommandBatch, DeleteArrowCommand, DeleteEllipseCommand, DeleteGroupCommand, DeleteHeartCommand, DeleteImageCommand, DeleteLineCommand, DeleteNoteCommand, DeletePolygonCommand, DeleteRectangleCommand, DeleteStarCommand, DeleteStrokesCommand, DeleteTableCommand, DeleteTextCommand, DeleteTimerCommand, DocumentRecoveryError, END_TAPER, ERASE_THRESHOLD, EraseCommand, FOG_COLOR, HIGHLIGHT_COLORS, History, IDENTITY, INK_COLORS, LockItemsCommand, MIN_WIDTH_FACTOR, NOTE_COLORS, NOTE_DEFAULT_SIZE, NOTE_DEFAULT_Z, NOTE_MAX_Z, NOTE_MIN_Z, NOTE_PEEL_STEP, ReorderObjectCommand, SDK_DEVELOPMENT_VERSION, SDK_PACKAGE_NAME, SHAPE_DEFAULT_STROKE, SHAPE_DEFAULT_STROKE_WIDTH, SHAPE_MIN_SIZE, STAMPS, STAMP_SIZE, SUPPORTED_ASSET_MEDIA_TYPES, SpatialIndex, TABLE_DEFAULT_CELL_HEIGHT, TABLE_DEFAULT_CELL_WIDTH, TABLE_DEFAULT_FONT_SIZE, TEXT_DEFAULT_SIZE, TIMER_DEFAULT_DURATION_MS, TIMER_DEFAULT_SIZE, TIMER_PRESETS_MS, TransformCommand, TransformObjectsCommand, UpdateArrowCommand, UpdateEllipseCommand, UpdateGroupCommand, UpdateHeartCommand, UpdateImageCommand, UpdateLineCommand, UpdateNoteCommand, UpdatePolygonCommand, UpdateRectangleCommand, UpdateStarCommand, UpdateTableCommand, UpdateTextCommand, UpdateTimerCommand, apply, applyItemLock, assetRef, avgScale, canUnlockItem, changeToOps, clampAssetCacheBytes, cloneArrow, cloneCustomObject, cloneEllipse, cloneGroup, cloneHeart, cloneImage, cloneLine, cloneNote, clonePolygon, cloneRectangle, cloneStar, cloneStroke, cloneTable, cloneText, cloneTimer, documentId, documentToSVG, formatTimer, invert, isAssetRef, isIdentity, isStampKind, loadDocumentBytes, measureTable, measureTextBlock, migrateDocument, mul, pauseTimer, placePresenceBeacon, ribbonEdges, rotationAbout, scalingAbout, searchBoard, serializeDocument, serializeLock, serializeStroke, setTimerDuration, stampDataUrl, startTimer, strokeId, timerExpired, timerRemaining, toggleTimer, translation };
|
|
4281
|
+
export { ASSET_CACHE_BYTES_DEFAULT, ASSET_CACHE_BYTES_MAX, ASSET_CACHE_BYTES_MIN, ASSET_EXPORT_MAX_DECODED_MEGAPIXELS, ASSET_EXPORT_MAX_ENCODED_BYTES, ASSET_MAX_CONCURRENT_RESOLUTIONS, ASSET_MAX_DECODED_MEGAPIXELS, ASSET_MAX_DIMENSION_PX, ASSET_MAX_ENCODED_BYTES, ASSET_REF_MAX_BYTES, ASSET_REF_PATTERN, AddArrowCommand, AddEllipseCommand, AddGroupCommand, AddHeartCommand, AddImageCommand, AddLineCommand, AddNoteCommand, AddPolygonCommand, AddRectangleCommand, AddStarCommand, AddStrokesCommand, AddTableCommand, AddTextCommand, AddTimerCommand, AssetResolutionError, BEACON_INSET, BoardDocument, CURRENT_DOCUMENT_SCHEMA_VERSION, ClusterStore, CommandBatch, DeleteArrowCommand, DeleteEllipseCommand, DeleteGroupCommand, DeleteHeartCommand, DeleteImageCommand, DeleteLineCommand, DeleteNoteCommand, DeletePolygonCommand, DeleteRectangleCommand, DeleteStarCommand, DeleteStrokesCommand, DeleteTableCommand, DeleteTextCommand, DeleteTimerCommand, DocumentRecoveryError, END_TAPER, ERASE_THRESHOLD, EraseCommand, FOG_COLOR, HIGHLIGHT_COLORS, History, IDENTITY, INK_COLORS, LockItemsCommand, MIN_WIDTH_FACTOR, NOTE_COLORS, NOTE_DEFAULT_SIZE, NOTE_DEFAULT_Z, NOTE_MAX_Z, NOTE_MIN_Z, NOTE_PEEL_STEP, ReorderObjectCommand, SDK_DEVELOPMENT_VERSION, SDK_PACKAGE_NAME, SHAPE_DEFAULT_STROKE, SHAPE_DEFAULT_STROKE_WIDTH, SHAPE_MIN_SIZE, SHAPE_STYLE_DEFAULTS, STAMPS, STAMP_SIZE, SUPPORTED_ASSET_MEDIA_TYPES, SpatialIndex, TABLE_DEFAULT_CELL_HEIGHT, TABLE_DEFAULT_CELL_WIDTH, TABLE_DEFAULT_FONT_SIZE, TEXT_DEFAULT_SIZE, TIMER_DEFAULT_DURATION_MS, TIMER_DEFAULT_SIZE, TIMER_PRESETS_MS, TransformCommand, TransformObjectsCommand, UpdateArrowCommand, UpdateEllipseCommand, UpdateGroupCommand, UpdateHeartCommand, UpdateImageCommand, UpdateLineCommand, UpdateNoteCommand, UpdatePolygonCommand, UpdateRectangleCommand, UpdateStarCommand, UpdateTableCommand, UpdateTextCommand, UpdateTimerCommand, apply, applyItemLock, assetRef, avgScale, canUnlockItem, changeToOps, clampAssetCacheBytes, cloneArrow, cloneCustomObject, cloneEllipse, cloneGroup, cloneHeart, cloneImage, cloneLine, cloneNote, clonePolygon, cloneRectangle, cloneStar, cloneStroke, cloneTable, cloneText, cloneTimer, documentId, documentSize, documentToSVG, formatTimer, invert, isAssetRef, isIdentity, isStampKind, loadDocumentBytes, measureTable, measureTextBlock, migrateDocument, mul, pauseTimer, placePresenceBeacon, ribbonEdges, rotationAbout, scalingAbout, searchBoard, serializeDocument, serializeLock, serializeStroke, setTimerDuration, stampDataUrl, startTimer, strokeId, timerExpired, timerRemaining, toggleTimer, translation };
|