kritzel-stencil 0.3.30 → 0.3.32
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/dist/cjs/index.cjs.js +1 -1
- package/dist/cjs/kritzel-active-users_45.cjs.entry.js +256 -113
- package/dist/cjs/{schema.constants-TiseVL4a.js → schema.constants-DItwOm2z.js} +32 -2
- package/dist/collection/classes/handlers/line-handle.handler.js +4 -2
- package/dist/collection/classes/managers/browser-permission.manager.js +4 -4
- package/dist/collection/classes/managers/clipboard.manager.js +36 -104
- package/dist/collection/classes/managers/clipboard.types.js +1 -0
- package/dist/collection/classes/objects/base-object.class.js +7 -0
- package/dist/collection/classes/objects/line.class.js +21 -0
- package/dist/collection/classes/providers/paste-intent/event-paste-intent.provider.js +48 -0
- package/dist/collection/classes/providers/paste-intent/ios-paste-intent.provider.js +16 -0
- package/dist/collection/classes/providers/paste-intent/navigator-paste-intent.provider.js +86 -0
- package/dist/collection/classes/registries/paste-intent-provider.registry.js +26 -0
- package/dist/collection/classes/structures/object-map.structure.js +29 -5
- package/dist/collection/classes/structures/quadtree.structure.js +14 -0
- package/dist/collection/constants/version.js +1 -1
- package/dist/collection/interfaces/paste-intent-provider.interface.js +1 -0
- package/dist/components/index.js +1 -1
- package/dist/components/kritzel-awareness-cursors.js +1 -1
- package/dist/components/kritzel-controls.js +1 -1
- package/dist/components/kritzel-editor.js +1 -1
- package/dist/components/kritzel-engine.js +1 -1
- package/dist/components/kritzel-settings.js +1 -1
- package/dist/components/kritzel-tool-config.js +1 -1
- package/dist/components/{p-BC7jWR_G.js → p-14NPx-_P.js} +1 -1
- package/dist/components/{p-B4WfPDj0.js → p-BmX2Vb4Z.js} +1 -1
- package/dist/components/{p-pCB0JKlD.js → p-COhjyuRa.js} +1 -1
- package/dist/components/{p-E4tWs1Ga.js → p-DK2ZByiU.js} +2 -2
- package/dist/components/p-DfIknZRl.js +1 -0
- package/dist/components/{p-CPnjRgkl.js → p-Dl3MwBL1.js} +1 -1
- package/dist/components/{p-CRQEFKjb.js → p-FjazwgrY.js} +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/kritzel-active-users_45.entry.js +256 -113
- package/dist/esm/{schema.constants-7ayQfjzt.js → schema.constants-CH2FWTVr.js} +32 -2
- package/dist/stencil/index.esm.js +1 -1
- package/dist/stencil/p-CH2FWTVr.js +1 -0
- package/dist/stencil/p-be38d770.entry.js +9 -0
- package/dist/stencil/stencil.esm.js +1 -1
- package/dist/types/classes/managers/clipboard.manager.d.ts +21 -37
- package/dist/types/classes/managers/clipboard.types.d.ts +23 -0
- package/dist/types/classes/objects/line.class.d.ts +11 -0
- package/dist/types/classes/providers/paste-intent/event-paste-intent.provider.d.ts +8 -0
- package/dist/types/classes/providers/paste-intent/ios-paste-intent.provider.d.ts +6 -0
- package/dist/types/classes/providers/paste-intent/navigator-paste-intent.provider.d.ts +9 -0
- package/dist/types/classes/registries/paste-intent-provider.registry.d.ts +10 -0
- package/dist/types/classes/structures/object-map.structure.d.ts +1 -0
- package/dist/types/classes/structures/quadtree.structure.d.ts +8 -0
- package/dist/types/constants/version.d.ts +1 -1
- package/dist/types/interfaces/paste-intent-provider.interface.d.ts +10 -0
- package/package.json +1 -1
- package/dist/components/p-BNu3Akp5.js +0 -1
- package/dist/stencil/p-0adfecf4.entry.js +0 -9
- package/dist/stencil/p-7ayQfjzt.js +0 -1
package/dist/cjs/index.cjs.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var index = require('./index-Xav9JFHg.js');
|
|
4
|
-
var schema_constants = require('./schema.constants-
|
|
4
|
+
var schema_constants = require('./schema.constants-DItwOm2z.js');
|
|
5
5
|
var Y = require('yjs');
|
|
6
6
|
require('y-indexeddb');
|
|
7
7
|
require('y-websocket');
|
|
@@ -21918,6 +21918,20 @@ class KritzelQuadtree {
|
|
|
21918
21918
|
}
|
|
21919
21919
|
return false;
|
|
21920
21920
|
}
|
|
21921
|
+
/**
|
|
21922
|
+
* Reindexes an existing object by removing it from its current node and
|
|
21923
|
+
* reinserting it based on its latest bounding box.
|
|
21924
|
+
*
|
|
21925
|
+
* @param object - The updated object to reindex
|
|
21926
|
+
* @returns `true` if the object was found and reinserted, `false` otherwise
|
|
21927
|
+
*/
|
|
21928
|
+
reindex(object) {
|
|
21929
|
+
const removed = this.remove(o => o.id === object.id);
|
|
21930
|
+
if (removed === 0) {
|
|
21931
|
+
return false;
|
|
21932
|
+
}
|
|
21933
|
+
return this.insert(object);
|
|
21934
|
+
}
|
|
21921
21935
|
/**
|
|
21922
21936
|
* Removes objects from the quadtree that match the given predicate.
|
|
21923
21937
|
* Recursively searches through all nodes and removes matching objects.
|
|
@@ -22489,10 +22503,19 @@ class KritzelObjectMap {
|
|
|
22489
22503
|
const objectsToUpdate = [];
|
|
22490
22504
|
const selectionGroupsToUpdate = [];
|
|
22491
22505
|
const objectsToDelete = [];
|
|
22506
|
+
// The reverse anchor index (targetObjectId -> anchored lines) is derived runtime
|
|
22507
|
+
// state that is NOT synced via Yjs. When line anchor connections arrive through a
|
|
22508
|
+
// remote change it must be rebuilt, otherwise getLinesAnchoredTo/updateAnchorsForObject
|
|
22509
|
+
// return stale/empty results on this client (e.g. anchored lines fail to follow a
|
|
22510
|
+
// locally-moved target, leaving selection UI and anchors visually offset).
|
|
22511
|
+
let anchorIndexNeedsRebuild = false;
|
|
22492
22512
|
changedKeys.forEach(key => {
|
|
22493
22513
|
const change = event.changes.keys.get(key);
|
|
22494
22514
|
if (change && change.action === 'delete') {
|
|
22495
22515
|
objectsToDelete.push(key);
|
|
22516
|
+
if (this._idMap.get(key) instanceof schema_constants.KritzelLine) {
|
|
22517
|
+
anchorIndexNeedsRebuild = true;
|
|
22518
|
+
}
|
|
22496
22519
|
}
|
|
22497
22520
|
else {
|
|
22498
22521
|
const serialized = this._objectsMap?.get(key);
|
|
@@ -22504,6 +22527,9 @@ class KritzelObjectMap {
|
|
|
22504
22527
|
}
|
|
22505
22528
|
else {
|
|
22506
22529
|
objectsToUpdate.push(object);
|
|
22530
|
+
if (object instanceof schema_constants.KritzelLine) {
|
|
22531
|
+
anchorIndexNeedsRebuild = true;
|
|
22532
|
+
}
|
|
22507
22533
|
}
|
|
22508
22534
|
}
|
|
22509
22535
|
}
|
|
@@ -22530,7 +22556,7 @@ class KritzelObjectMap {
|
|
|
22530
22556
|
if (previous && typeof object.adoptTransientStateFrom === 'function') {
|
|
22531
22557
|
object.adoptTransientStateFrom(previous);
|
|
22532
22558
|
}
|
|
22533
|
-
this.quadtree.
|
|
22559
|
+
this.quadtree.reindex(object);
|
|
22534
22560
|
this._idMap.set(object.id, object);
|
|
22535
22561
|
updatedObjects.push(object);
|
|
22536
22562
|
}
|
|
@@ -22549,7 +22575,7 @@ class KritzelObjectMap {
|
|
|
22549
22575
|
if (previous && typeof object.adoptTransientStateFrom === 'function') {
|
|
22550
22576
|
object.adoptTransientStateFrom(previous);
|
|
22551
22577
|
}
|
|
22552
|
-
this.quadtree.
|
|
22578
|
+
this.quadtree.reindex(object);
|
|
22553
22579
|
this._idMap.set(object.id, object);
|
|
22554
22580
|
}
|
|
22555
22581
|
else {
|
|
@@ -22566,7 +22592,7 @@ class KritzelObjectMap {
|
|
|
22566
22592
|
object.refreshObjectDimensions(undefined, true);
|
|
22567
22593
|
object.captureUnchangedSnapshots();
|
|
22568
22594
|
// Update quadtree again with corrected dimensions
|
|
22569
|
-
this.quadtree.
|
|
22595
|
+
this.quadtree.reindex(object);
|
|
22570
22596
|
});
|
|
22571
22597
|
// If regular objects were changed but their SelectionGroup wasn't explicitly updated,
|
|
22572
22598
|
// we still need to refresh that SelectionGroup's dimensions
|
|
@@ -22584,10 +22610,16 @@ class KritzelObjectMap {
|
|
|
22584
22610
|
selectionGroup.refreshObjectDimensions(undefined, true);
|
|
22585
22611
|
selectionGroup.captureUnchangedSnapshots();
|
|
22586
22612
|
// Update quadtree with corrected dimensions
|
|
22587
|
-
this.quadtree.
|
|
22613
|
+
this.quadtree.reindex(selectionGroup);
|
|
22588
22614
|
}
|
|
22589
22615
|
}
|
|
22590
22616
|
}
|
|
22617
|
+
// Rebuild the anchor index if any line anchor connections may have changed, so that
|
|
22618
|
+
// subsequent local operations (moving a target, refreshing selection UI) resolve the
|
|
22619
|
+
// correct anchored lines on this client.
|
|
22620
|
+
if (anchorIndexNeedsRebuild) {
|
|
22621
|
+
this._core?.anchorManager.rebuildIndex();
|
|
22622
|
+
}
|
|
22591
22623
|
// Invalidate the store's selection cache so it fetches the updated SelectionGroup
|
|
22592
22624
|
this._core?.store.invalidateSelectionCache();
|
|
22593
22625
|
this._core?.rerender();
|
|
@@ -22829,7 +22861,12 @@ class KritzelObjectMap {
|
|
|
22829
22861
|
if (!existed) {
|
|
22830
22862
|
return false;
|
|
22831
22863
|
}
|
|
22832
|
-
this.
|
|
22864
|
+
if (this._localOnlyMode || options.skipReindex) {
|
|
22865
|
+
this.quadtree.update(object);
|
|
22866
|
+
}
|
|
22867
|
+
else {
|
|
22868
|
+
this.quadtree.reindex(object);
|
|
22869
|
+
}
|
|
22833
22870
|
this._idMap.set(object.id, object);
|
|
22834
22871
|
if (!this._localOnlyMode && this._objectsMap && this.isPersistable(object)) {
|
|
22835
22872
|
const serialized = object.serialize();
|
|
@@ -24018,17 +24055,17 @@ class KritzelBrowserPermissionManager {
|
|
|
24018
24055
|
*/
|
|
24019
24056
|
constructor(_core) {
|
|
24020
24057
|
this.registerRequestPermissionHandler('clipboard-read', async () => {
|
|
24021
|
-
// Firefox and Safari open native paste menus when
|
|
24058
|
+
// Firefox and Safari open native paste menus when the clipboard is read
|
|
24022
24059
|
// proactively, so rely on the permission state instead of an eager read.
|
|
24023
24060
|
if (this._isFirefox || this._isSafari) {
|
|
24024
24061
|
return 'granted';
|
|
24025
24062
|
}
|
|
24026
24063
|
try {
|
|
24027
|
-
await navigator.
|
|
24028
|
-
return 'granted';
|
|
24064
|
+
const status = await navigator.permissions.query({ name: 'clipboard-read' });
|
|
24065
|
+
return status.state === 'denied' ? 'denied' : 'granted';
|
|
24029
24066
|
}
|
|
24030
24067
|
catch {
|
|
24031
|
-
return '
|
|
24068
|
+
return 'granted';
|
|
24032
24069
|
}
|
|
24033
24070
|
});
|
|
24034
24071
|
}
|
|
@@ -24067,37 +24104,141 @@ class KritzelBrowserPermissionManager {
|
|
|
24067
24104
|
}
|
|
24068
24105
|
}
|
|
24069
24106
|
|
|
24070
|
-
|
|
24071
|
-
/**
|
|
24072
|
-
* Manages clipboard operations (copy/cut/paste) for a core instance.
|
|
24073
|
-
*
|
|
24074
|
-
* The system clipboard is the single source of truth: copy serializes the current
|
|
24075
|
-
* selection into a Kritzel payload, paste reads + revives it. No copied objects are
|
|
24076
|
-
* cached on the engine state.
|
|
24077
|
-
*/
|
|
24078
|
-
class KritzelClipboardManager {
|
|
24107
|
+
class KritzelPasteIntentProviderRegistry {
|
|
24079
24108
|
_core;
|
|
24080
|
-
|
|
24081
|
-
|
|
24082
|
-
|
|
24083
|
-
*/
|
|
24084
|
-
_pasteOffset = 0;
|
|
24085
|
-
constructor(core) {
|
|
24086
|
-
this._core = core;
|
|
24109
|
+
_providers = [];
|
|
24110
|
+
constructor(_core) {
|
|
24111
|
+
this._core = _core;
|
|
24087
24112
|
}
|
|
24088
|
-
|
|
24089
|
-
|
|
24090
|
-
|
|
24091
|
-
|
|
24092
|
-
|
|
24093
|
-
|
|
24094
|
-
|
|
24095
|
-
|
|
24113
|
+
register(provider) {
|
|
24114
|
+
this._providers.push(provider);
|
|
24115
|
+
}
|
|
24116
|
+
async resolve(event) {
|
|
24117
|
+
const context = {
|
|
24118
|
+
core: this._core,
|
|
24119
|
+
event,
|
|
24120
|
+
};
|
|
24121
|
+
for (const provider of this._providers) {
|
|
24122
|
+
if (!provider.canHandle(context)) {
|
|
24123
|
+
continue;
|
|
24124
|
+
}
|
|
24125
|
+
const intent = await provider.getPasteIntent(context);
|
|
24126
|
+
if (intent.type !== 'unknown') {
|
|
24127
|
+
return intent;
|
|
24128
|
+
}
|
|
24096
24129
|
}
|
|
24097
|
-
return
|
|
24130
|
+
return { type: 'unknown' };
|
|
24098
24131
|
}
|
|
24099
|
-
|
|
24100
|
-
|
|
24132
|
+
}
|
|
24133
|
+
|
|
24134
|
+
const CLIPBOARD_PAYLOAD_PREFIX = 'kritzel-clipboard/v1:';
|
|
24135
|
+
|
|
24136
|
+
class KritzelEventPasteIntentProvider {
|
|
24137
|
+
canHandle(context) {
|
|
24138
|
+
return !!context.event?.clipboardData;
|
|
24139
|
+
}
|
|
24140
|
+
async getPasteIntent(context) {
|
|
24141
|
+
const data = context.event?.clipboardData;
|
|
24142
|
+
if (!data) {
|
|
24143
|
+
return { type: 'unknown' };
|
|
24144
|
+
}
|
|
24145
|
+
const imageFiles = Array.from(data.files ?? []).filter(file => file.type.startsWith('image/'));
|
|
24146
|
+
if (imageFiles.length > 0) {
|
|
24147
|
+
return { type: 'image_files', files: imageFiles };
|
|
24148
|
+
}
|
|
24149
|
+
if (data.types.includes('text/plain')) {
|
|
24150
|
+
const textIntent = this.determineTextPasteIntent(data.getData('text/plain'));
|
|
24151
|
+
if (textIntent) {
|
|
24152
|
+
return textIntent;
|
|
24153
|
+
}
|
|
24154
|
+
}
|
|
24155
|
+
return { type: 'unknown' };
|
|
24156
|
+
}
|
|
24157
|
+
parseClipboardPayload(text) {
|
|
24158
|
+
if (!text || !text.startsWith(CLIPBOARD_PAYLOAD_PREFIX)) {
|
|
24159
|
+
return null;
|
|
24160
|
+
}
|
|
24161
|
+
try {
|
|
24162
|
+
const parsed = JSON.parse(text.slice(CLIPBOARD_PAYLOAD_PREFIX.length));
|
|
24163
|
+
if (parsed?.version !== 1 || !Array.isArray(parsed.objects)) {
|
|
24164
|
+
return null;
|
|
24165
|
+
}
|
|
24166
|
+
return parsed;
|
|
24167
|
+
}
|
|
24168
|
+
catch {
|
|
24169
|
+
return null;
|
|
24170
|
+
}
|
|
24171
|
+
}
|
|
24172
|
+
determineTextPasteIntent(text) {
|
|
24173
|
+
const payload = this.parseClipboardPayload(text);
|
|
24174
|
+
if (payload) {
|
|
24175
|
+
return { type: 'kritzel_objects', payload };
|
|
24176
|
+
}
|
|
24177
|
+
if (text && text.trim().length > 0) {
|
|
24178
|
+
return { type: 'plain_text', text };
|
|
24179
|
+
}
|
|
24180
|
+
return null;
|
|
24181
|
+
}
|
|
24182
|
+
}
|
|
24183
|
+
|
|
24184
|
+
class KritzelNavigatorPasteIntentProvider {
|
|
24185
|
+
canHandle(context) {
|
|
24186
|
+
return !context.event?.clipboardData && typeof navigator !== 'undefined' && !!navigator.clipboard;
|
|
24187
|
+
}
|
|
24188
|
+
async getPasteIntent(context) {
|
|
24189
|
+
if (typeof navigator === 'undefined' || !navigator.clipboard) {
|
|
24190
|
+
return { type: 'unknown' };
|
|
24191
|
+
}
|
|
24192
|
+
const { clipboard } = navigator;
|
|
24193
|
+
const permissionState = await context.core.permissionManager.checkPermission('clipboard-read');
|
|
24194
|
+
if (permissionState !== 'granted') {
|
|
24195
|
+
const requestedState = await context.core.permissionManager.requestPermission('clipboard-read');
|
|
24196
|
+
if (requestedState !== 'granted') {
|
|
24197
|
+
return { type: 'unknown' };
|
|
24198
|
+
}
|
|
24199
|
+
}
|
|
24200
|
+
if (clipboard.read) {
|
|
24201
|
+
try {
|
|
24202
|
+
const items = await clipboard.read();
|
|
24203
|
+
const { imageFiles, text } = await this.processClipboardItems(items);
|
|
24204
|
+
if (imageFiles.length > 0) {
|
|
24205
|
+
return { type: 'image_files', files: imageFiles };
|
|
24206
|
+
}
|
|
24207
|
+
if (text !== null) {
|
|
24208
|
+
const textIntent = this.determineTextPasteIntent(text);
|
|
24209
|
+
if (textIntent) {
|
|
24210
|
+
return textIntent;
|
|
24211
|
+
}
|
|
24212
|
+
}
|
|
24213
|
+
}
|
|
24214
|
+
catch {
|
|
24215
|
+
// Fall back to readText below when read() is denied or unavailable at runtime.
|
|
24216
|
+
}
|
|
24217
|
+
}
|
|
24218
|
+
if (clipboard.readText) {
|
|
24219
|
+
const textIntent = this.determineTextPasteIntent(await clipboard.readText());
|
|
24220
|
+
if (textIntent) {
|
|
24221
|
+
return textIntent;
|
|
24222
|
+
}
|
|
24223
|
+
}
|
|
24224
|
+
return { type: 'unknown' };
|
|
24225
|
+
}
|
|
24226
|
+
async processClipboardItems(items) {
|
|
24227
|
+
const imageFiles = [];
|
|
24228
|
+
let text = null;
|
|
24229
|
+
await Promise.all(items.map(async (item) => {
|
|
24230
|
+
const imageType = item.types.find(type => type.startsWith('image/'));
|
|
24231
|
+
if (imageType) {
|
|
24232
|
+
const blob = await item.getType(imageType);
|
|
24233
|
+
imageFiles.push(new File([blob], 'pasted-image', { type: imageType }));
|
|
24234
|
+
return;
|
|
24235
|
+
}
|
|
24236
|
+
if (text === null && item.types.includes('text/plain')) {
|
|
24237
|
+
const blob = await item.getType('text/plain');
|
|
24238
|
+
text = await blob.text();
|
|
24239
|
+
}
|
|
24240
|
+
}));
|
|
24241
|
+
return { imageFiles, text };
|
|
24101
24242
|
}
|
|
24102
24243
|
parseClipboardPayload(text) {
|
|
24103
24244
|
if (!text || !text.startsWith(CLIPBOARD_PAYLOAD_PREFIX)) {
|
|
@@ -24124,6 +24265,79 @@ class KritzelClipboardManager {
|
|
|
24124
24265
|
}
|
|
24125
24266
|
return null;
|
|
24126
24267
|
}
|
|
24268
|
+
}
|
|
24269
|
+
|
|
24270
|
+
class KritzelIosPasteIntentProvider {
|
|
24271
|
+
canHandle(context) {
|
|
24272
|
+
return !context.event?.clipboardData && schema_constants.KritzelDevicesHelper.isIOS();
|
|
24273
|
+
}
|
|
24274
|
+
async getPasteIntent(context) {
|
|
24275
|
+
// On iOS Safari, eventless programmatic clipboard reads are commonly unavailable
|
|
24276
|
+
// (e.g. the context-menu paste calls engine.paste() without a native ClipboardEvent).
|
|
24277
|
+
// Fall back to the in-memory session buffer so internal object copy/paste still works.
|
|
24278
|
+
const payload = context.core.clipboardManager.sessionClipboardPayload;
|
|
24279
|
+
if (payload) {
|
|
24280
|
+
return { type: 'kritzel_objects', payload };
|
|
24281
|
+
}
|
|
24282
|
+
return { type: 'unknown' };
|
|
24283
|
+
}
|
|
24284
|
+
}
|
|
24285
|
+
|
|
24286
|
+
/**
|
|
24287
|
+
* Manages clipboard operations (copy/cut/paste) for a core instance.
|
|
24288
|
+
*
|
|
24289
|
+
* The system clipboard is the preferred source of truth: copy serializes the current
|
|
24290
|
+
* selection into a Kritzel payload, paste reads + revives it.
|
|
24291
|
+
*
|
|
24292
|
+
* A copy also mirrors the payload into an in-memory session buffer. On platforms where
|
|
24293
|
+
* eventless programmatic clipboard access is unavailable (notably iOS Safari, where the
|
|
24294
|
+
* context menu triggers copy/paste without a native ClipboardEvent), the session buffer
|
|
24295
|
+
* is used as the fallback source so internal object copy/paste keeps working.
|
|
24296
|
+
*/
|
|
24297
|
+
class KritzelClipboardManager {
|
|
24298
|
+
_core;
|
|
24299
|
+
_pasteIntentRegistry;
|
|
24300
|
+
/**
|
|
24301
|
+
* In-memory mirror of the most recent copy payload. Acts as the fallback clipboard
|
|
24302
|
+
* source when the system clipboard cannot be read programmatically (e.g. iOS Safari
|
|
24303
|
+
* context-menu paste). Reset is not required; a new copy overwrites it.
|
|
24304
|
+
*/
|
|
24305
|
+
_sessionClipboardPayload = null;
|
|
24306
|
+
/**
|
|
24307
|
+
* In-memory cascade offset (world units) applied to successive pastes of the same
|
|
24308
|
+
* clipboard content so they don't stack exactly on top of each other. Reset on copy/cut.
|
|
24309
|
+
*/
|
|
24310
|
+
_pasteOffset = 0;
|
|
24311
|
+
constructor(core) {
|
|
24312
|
+
this._core = core;
|
|
24313
|
+
this._pasteIntentRegistry = new KritzelPasteIntentProviderRegistry(this._core);
|
|
24314
|
+
// Provider order is intentional: iOS eventless path should short-circuit,
|
|
24315
|
+
// native paste events remain preferred, and async navigator APIs are fallback.
|
|
24316
|
+
this._pasteIntentRegistry.register(new KritzelIosPasteIntentProvider());
|
|
24317
|
+
this._pasteIntentRegistry.register(new KritzelEventPasteIntentProvider());
|
|
24318
|
+
this._pasteIntentRegistry.register(new KritzelNavigatorPasteIntentProvider());
|
|
24319
|
+
}
|
|
24320
|
+
/**
|
|
24321
|
+
* The most recent copy payload mirrored in memory, used as a fallback clipboard
|
|
24322
|
+
* source when the system clipboard cannot be read programmatically.
|
|
24323
|
+
*/
|
|
24324
|
+
get sessionClipboardPayload() {
|
|
24325
|
+
return this._sessionClipboardPayload;
|
|
24326
|
+
}
|
|
24327
|
+
/**
|
|
24328
|
+
* Serializes an object for the clipboard payload. Groups additionally embed their
|
|
24329
|
+
* (recursively serialized) children under `__children__` so the payload is self-contained.
|
|
24330
|
+
*/
|
|
24331
|
+
serializeForClipboard(object) {
|
|
24332
|
+
const serialized = (typeof object.serialize === 'function' ? object.serialize() : object);
|
|
24333
|
+
if (schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelGroup')) {
|
|
24334
|
+
serialized.__children__ = object.children.map(child => this.serializeForClipboard(child));
|
|
24335
|
+
}
|
|
24336
|
+
return serialized;
|
|
24337
|
+
}
|
|
24338
|
+
serializeClipboardPayload(payload) {
|
|
24339
|
+
return `${CLIPBOARD_PAYLOAD_PREFIX}${JSON.stringify(payload)}`;
|
|
24340
|
+
}
|
|
24127
24341
|
async writeBrowserClipboardPayload(payload, event) {
|
|
24128
24342
|
const serialized = this.serializeClipboardPayload(payload);
|
|
24129
24343
|
if (event?.clipboardData) {
|
|
@@ -24203,80 +24417,6 @@ class KritzelClipboardManager {
|
|
|
24203
24417
|
}
|
|
24204
24418
|
}
|
|
24205
24419
|
}
|
|
24206
|
-
/**
|
|
24207
|
-
* Evaluates the clipboard event to determine exactly what the user is trying to paste.
|
|
24208
|
-
* Returns a PasteIntent covering the 3 main cases: Kritzel objects, Images, or Plain text.
|
|
24209
|
-
*/
|
|
24210
|
-
async determinePasteIntent(event) {
|
|
24211
|
-
if (event?.clipboardData) {
|
|
24212
|
-
const data = event.clipboardData;
|
|
24213
|
-
const imageFiles = Array.from(data.files ?? []).filter(file => file.type.startsWith('image/'));
|
|
24214
|
-
if (imageFiles.length > 0) {
|
|
24215
|
-
return { type: 'image_files', files: imageFiles };
|
|
24216
|
-
}
|
|
24217
|
-
if (data.types.includes('text/plain')) {
|
|
24218
|
-
const textIntent = this.determineTextPasteIntent(data.getData('text/plain'));
|
|
24219
|
-
if (textIntent)
|
|
24220
|
-
return textIntent;
|
|
24221
|
-
}
|
|
24222
|
-
return { type: 'unknown' };
|
|
24223
|
-
}
|
|
24224
|
-
if (typeof navigator === 'undefined' || !navigator.clipboard) {
|
|
24225
|
-
return { type: 'unknown' };
|
|
24226
|
-
}
|
|
24227
|
-
const { clipboard } = navigator;
|
|
24228
|
-
const permissionState = await this._core.permissionManager.checkPermission('clipboard-read');
|
|
24229
|
-
if (permissionState !== 'granted') {
|
|
24230
|
-
const requestedState = await this._core.permissionManager.requestPermission('clipboard-read');
|
|
24231
|
-
if (requestedState !== 'granted') {
|
|
24232
|
-
return { type: 'unknown' };
|
|
24233
|
-
}
|
|
24234
|
-
}
|
|
24235
|
-
if (clipboard.read) {
|
|
24236
|
-
try {
|
|
24237
|
-
const items = await clipboard.read();
|
|
24238
|
-
const { imageFiles, text } = await this.processClipboardItems(items);
|
|
24239
|
-
if (imageFiles.length > 0) {
|
|
24240
|
-
return { type: 'image_files', files: imageFiles };
|
|
24241
|
-
}
|
|
24242
|
-
if (text !== null) {
|
|
24243
|
-
const textIntent = this.determineTextPasteIntent(text);
|
|
24244
|
-
if (textIntent)
|
|
24245
|
-
return textIntent;
|
|
24246
|
-
}
|
|
24247
|
-
}
|
|
24248
|
-
catch {
|
|
24249
|
-
// Fall back to readText below when read() is denied or unavailable at runtime.
|
|
24250
|
-
}
|
|
24251
|
-
}
|
|
24252
|
-
if (clipboard.readText) {
|
|
24253
|
-
const textIntent = this.determineTextPasteIntent(await clipboard.readText());
|
|
24254
|
-
if (textIntent)
|
|
24255
|
-
return textIntent;
|
|
24256
|
-
}
|
|
24257
|
-
return { type: 'unknown' };
|
|
24258
|
-
}
|
|
24259
|
-
/**
|
|
24260
|
-
* Helper to parse clipboard items in parallel
|
|
24261
|
-
*/
|
|
24262
|
-
async processClipboardItems(items) {
|
|
24263
|
-
const imageFiles = [];
|
|
24264
|
-
let text = null;
|
|
24265
|
-
// Process items concurrently
|
|
24266
|
-
await Promise.all(items.map(async (item) => {
|
|
24267
|
-
const imageType = item.types.find(type => type.startsWith('image/'));
|
|
24268
|
-
if (imageType) {
|
|
24269
|
-
const blob = await item.getType(imageType);
|
|
24270
|
-
imageFiles.push(new File([blob], 'pasted-image', { type: imageType }));
|
|
24271
|
-
return;
|
|
24272
|
-
}
|
|
24273
|
-
if (text === null && item.types.includes('text/plain')) {
|
|
24274
|
-
const blob = await item.getType('text/plain');
|
|
24275
|
-
text = await blob.text();
|
|
24276
|
-
}
|
|
24277
|
-
}));
|
|
24278
|
-
return { imageFiles, text };
|
|
24279
|
-
}
|
|
24280
24420
|
/**
|
|
24281
24421
|
* Copies currently selected objects into internal clipboard state.
|
|
24282
24422
|
*/
|
|
@@ -24292,6 +24432,9 @@ class KritzelClipboardManager {
|
|
|
24292
24432
|
};
|
|
24293
24433
|
// A fresh copy resets the cascade so the first paste lands at the default offset.
|
|
24294
24434
|
this._pasteOffset = 0;
|
|
24435
|
+
// Mirror the payload in memory so eventless paste still works where the system
|
|
24436
|
+
// clipboard is not programmatically accessible (e.g. iOS Safari context menu).
|
|
24437
|
+
this._sessionClipboardPayload = payload;
|
|
24295
24438
|
void this.writeBrowserClipboardPayload(payload, event);
|
|
24296
24439
|
}
|
|
24297
24440
|
/**
|
|
@@ -24350,7 +24493,7 @@ class KritzelClipboardManager {
|
|
|
24350
24493
|
* Pastes objects from clipboard with optional explicit world coordinates.
|
|
24351
24494
|
*/
|
|
24352
24495
|
async paste(x, y, event) {
|
|
24353
|
-
const pasteIntent = await this.
|
|
24496
|
+
const pasteIntent = await this._pasteIntentRegistry.resolve(event);
|
|
24354
24497
|
let objects = null;
|
|
24355
24498
|
if (pasteIntent.type === 'kritzel_objects') {
|
|
24356
24499
|
objects = this.materializeKritzelObjects(pasteIntent);
|
|
@@ -31259,7 +31402,7 @@ const KritzelPortal = class {
|
|
|
31259
31402
|
* This file is auto-generated by the version bump scripts.
|
|
31260
31403
|
* Do not modify manually.
|
|
31261
31404
|
*/
|
|
31262
|
-
const KRITZEL_VERSION = '0.3.
|
|
31405
|
+
const KRITZEL_VERSION = '0.3.32';
|
|
31263
31406
|
|
|
31264
31407
|
const kritzelSettingsCss = () => `:host{display:contents}kritzel-dialog{--kritzel-dialog-body-padding:0;--kritzel-dialog-width-large:800px;--kritzel-dialog-height-large:500px}.footer-button{padding:8px 16px;border-radius:6px;cursor:pointer;font-size:14px}.cancel-button{border:1px solid #ebebeb;background:#fff;color:inherit}.cancel-button:hover{background:#f5f5f5}.settings-content{padding:0}.settings-content h3{margin:0 0 16px 0;font-size:18px;font-weight:600;color:var(--kritzel-settings-content-heading-color, #333333)}.settings-content p{margin:0;font-size:14px;color:var(--kritzel-settings-content-text-color, #666666);line-height:1.5}.settings-group{display:flex;flex-direction:column;gap:24px}.settings-item{display:flex;flex-direction:column;gap:8px}.settings-row{display:flex;align-items:center;justify-content:space-between;gap:16px}.settings-label{font-size:14px;font-weight:600;color:var(--kritzel-settings-label-color, #333333);margin:0 0 4px 0}.settings-description{font-size:12px;color:var(--kritzel-settings-description-color, #888888);margin:0;line-height:1.4}.shortcuts-list{display:flex;flex-direction:column;gap:24px}.shortcuts-category{display:flex;flex-direction:column;gap:8px}.shortcuts-category-title{font-size:14px;font-weight:600;color:var(--kritzel-settings-label-color, #333333);margin:0 0 4px 0}.shortcuts-group{display:flex;flex-direction:column;gap:4px}.shortcut-item{display:flex;justify-content:space-between;align-items:center;padding:6px 8px;border-radius:4px;background:var(--kritzel-settings-shortcut-item-bg, rgba(0, 0, 0, 0.02))}.shortcut-label{font-size:14px;color:var(--kritzel-settings-content-text-color, #666666)}.shortcut-key{font-family:monospace;font-size:12px;padding:2px 8px;border-radius:4px;background:var(--kritzel-settings-shortcut-key-bg, #f0f0f0);color:var(--kritzel-settings-shortcut-key-color, #333333);border:1px solid var(--kritzel-settings-shortcut-key-border, #ddd)}`;
|
|
31265
31408
|
|
|
@@ -571,6 +571,13 @@ class KritzelBaseObject {
|
|
|
571
571
|
this.resizing = false;
|
|
572
572
|
this.markedForRemoval = false;
|
|
573
573
|
this.isVisible = true;
|
|
574
|
+
// Reset known transient geometry caches if present on subclasses.
|
|
575
|
+
if ('_adjustedPoints' in this) {
|
|
576
|
+
this._adjustedPoints = null;
|
|
577
|
+
}
|
|
578
|
+
if ('_clipInfo' in this) {
|
|
579
|
+
this._clipInfo = null;
|
|
580
|
+
}
|
|
574
581
|
return this;
|
|
575
582
|
}
|
|
576
583
|
/**
|
|
@@ -18393,6 +18400,27 @@ class KritzelLine extends KritzelBaseObject {
|
|
|
18393
18400
|
calculateBoundingBox() {
|
|
18394
18401
|
return KritzelLine.calculateBoundingBox(this.startX, this.startY, this.endX, this.endY, this.controlX, this.controlY, this.strokeWidth);
|
|
18395
18402
|
}
|
|
18403
|
+
/**
|
|
18404
|
+
* Serializes the line while excluding transient cache fields.
|
|
18405
|
+
* @returns A serializable line object without runtime caches
|
|
18406
|
+
*/
|
|
18407
|
+
serialize() {
|
|
18408
|
+
const serialized = super.serialize();
|
|
18409
|
+
delete serialized._adjustedPoints;
|
|
18410
|
+
delete serialized._clipInfo;
|
|
18411
|
+
return serialized;
|
|
18412
|
+
}
|
|
18413
|
+
/**
|
|
18414
|
+
* Deserializes and resets transient caches so geometry is recomputed lazily.
|
|
18415
|
+
* @param object - The serialized line object
|
|
18416
|
+
* @returns This instance cast to type T
|
|
18417
|
+
*/
|
|
18418
|
+
deserialize(object) {
|
|
18419
|
+
const result = super.deserialize(object);
|
|
18420
|
+
this._adjustedPoints = null;
|
|
18421
|
+
this._clipInfo = null;
|
|
18422
|
+
return result;
|
|
18423
|
+
}
|
|
18396
18424
|
/**
|
|
18397
18425
|
* Recalculates and updates the width, height, x, y, and translation properties
|
|
18398
18426
|
* based on the current endpoint positions and stroke width.
|
|
@@ -24327,8 +24355,10 @@ class KritzelLineHandleHandler extends KritzelBaseHandler {
|
|
|
24327
24355
|
// Adjust translateX/Y to compensate for the bounding box shift
|
|
24328
24356
|
line.translateX = newTranslateX;
|
|
24329
24357
|
line.translateY = newTranslateY;
|
|
24330
|
-
// Clear
|
|
24331
|
-
|
|
24358
|
+
// Clear transient caches so geometry and anchor clipping recompute on demand.
|
|
24359
|
+
const linePrivate = line;
|
|
24360
|
+
linePrivate._adjustedPoints = null;
|
|
24361
|
+
linePrivate._clipInfo = null;
|
|
24332
24362
|
this._core.store.objects.update(line);
|
|
24333
24363
|
}
|
|
24334
24364
|
/**
|
|
@@ -395,8 +395,10 @@ export class KritzelLineHandleHandler extends KritzelBaseHandler {
|
|
|
395
395
|
// Adjust translateX/Y to compensate for the bounding box shift
|
|
396
396
|
line.translateX = newTranslateX;
|
|
397
397
|
line.translateY = newTranslateY;
|
|
398
|
-
// Clear
|
|
399
|
-
|
|
398
|
+
// Clear transient caches so geometry and anchor clipping recompute on demand.
|
|
399
|
+
const linePrivate = line;
|
|
400
|
+
linePrivate._adjustedPoints = null;
|
|
401
|
+
linePrivate._clipInfo = null;
|
|
400
402
|
this._core.store.objects.update(line);
|
|
401
403
|
}
|
|
402
404
|
/**
|
|
@@ -14,17 +14,17 @@ export class KritzelBrowserPermissionManager {
|
|
|
14
14
|
*/
|
|
15
15
|
constructor(_core) {
|
|
16
16
|
this.registerRequestPermissionHandler('clipboard-read', async () => {
|
|
17
|
-
// Firefox and Safari open native paste menus when
|
|
17
|
+
// Firefox and Safari open native paste menus when the clipboard is read
|
|
18
18
|
// proactively, so rely on the permission state instead of an eager read.
|
|
19
19
|
if (this._isFirefox || this._isSafari) {
|
|
20
20
|
return 'granted';
|
|
21
21
|
}
|
|
22
22
|
try {
|
|
23
|
-
await navigator.
|
|
24
|
-
return 'granted';
|
|
23
|
+
const status = await navigator.permissions.query({ name: 'clipboard-read' });
|
|
24
|
+
return status.state === 'denied' ? 'denied' : 'granted';
|
|
25
25
|
}
|
|
26
26
|
catch {
|
|
27
|
-
return '
|
|
27
|
+
return 'granted';
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
30
|
}
|