diff --git a/packages/element/src/index.ts b/packages/element/src/index.ts index 4fc1ef5579..d677859ad5 100644 --- a/packages/element/src/index.ts +++ b/packages/element/src/index.ts @@ -29,6 +29,9 @@ export const hashElementsVersion = (elements: ElementsMapOrArray): number => { // string hash function (using djb2). Not cryptographically secure, use only // for versioning and such. +// note: hashes individual code units (not code points), +// but for hashing purposes this is fine as it iterates through every code unit +// (as such, no need to encode to byte string first) export const hashString = (s: string): number => { let hash: number = 5381; for (let i = 0; i < s.length; i++) { diff --git a/packages/excalidraw/components/PublishLibrary.tsx b/packages/excalidraw/components/PublishLibrary.tsx index 076b303d70..cdc038dac3 100644 --- a/packages/excalidraw/components/PublishLibrary.tsx +++ b/packages/excalidraw/components/PublishLibrary.tsx @@ -518,7 +518,7 @@ const PublishLibrary = ({
diff --git a/packages/excalidraw/data/library.ts b/packages/excalidraw/data/library.ts index 429ba1046c..abe2fec853 100644 --- a/packages/excalidraw/data/library.ts +++ b/packages/excalidraw/data/library.ts @@ -62,6 +62,7 @@ type LibraryUpdate = { deletedItems: Map; /** newly added items in the library */ addedItems: Map; + updatedItems: Map; }; // an object so that we can later add more properties to it without breaking, @@ -170,6 +171,7 @@ const createLibraryUpdate = ( const update: LibraryUpdate = { deletedItems: new Map(), addedItems: new Map(), + updatedItems: new Map(), }; for (const item of prevLibraryItems) { @@ -181,8 +183,11 @@ const createLibraryUpdate = ( const prevItemsMap = arrayToMap(prevLibraryItems); for (const item of nextLibraryItems) { - if (!prevItemsMap.has(item.id)) { + const prevItem = prevItemsMap.get(item.id); + if (!prevItem) { update.addedItems.set(item.id, item); + } else if (getLibraryItemHash(prevItem) !== getLibraryItemHash(item)) { + update.updatedItems.set(item.id, item); } } @@ -586,12 +591,14 @@ class AdapterTransaction { let lastSavedLibraryItemsHash = 0; let librarySaveCounter = 0; +const getLibraryItemHash = (item: LibraryItem) => { + return `${item.id}:${item.name || ""}:${hashElementsVersion(item.elements)}`; +}; + export const getLibraryItemsHash = (items: LibraryItems) => { return hashString( items - .map((item) => { - return `${item.id}:${hashElementsVersion(item.elements)}`; - }) + .map((item) => getLibraryItemHash(item)) .sort() .join(), ); @@ -641,6 +648,13 @@ const persistLibraryUpdate = async ( } } + // replace existing items with their updated versions + if (update.updatedItems) { + for (const [id, item] of update.updatedItems) { + nextLibraryItemsMap.set(id, item); + } + } + const nextLibraryItems = addedItems.concat( Array.from(nextLibraryItemsMap.values()), ); diff --git a/packages/excalidraw/locales/en.json b/packages/excalidraw/locales/en.json index 8279cb4344..4bd76fe876 100644 --- a/packages/excalidraw/locales/en.json +++ b/packages/excalidraw/locales/en.json @@ -230,10 +230,11 @@ "objectsSnapMode": "Snap to objects", "exitZenMode": "Exit zen mode", "cancel": "Cancel", + "saveLibNames": "Save name(s) and exit", "clear": "Clear", "remove": "Remove", "embed": "Toggle embedding", - "publishLibrary": "Publish selected", + "publishLibrary": "Rename or publish", "submit": "Submit", "confirm": "Confirm", "embeddableInteractionButton": "Click to interact"