@ixo/editor 2.0.0 → 2.0.1

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.
@@ -12672,6 +12672,155 @@ function useCollaborativeYDoc(_options) {
12672
12672
 
12673
12673
  // src/mantine/hooks/useCollaborativeIxoEditor.ts
12674
12674
  import { useMemo as useMemo44, useEffect as useEffect37 } from "react";
12675
+
12676
+ // src/core/lib/matrixMetadata.ts
12677
+ var COVER_IMAGE_EVENT_TYPE = "ixo.page.cover_image";
12678
+ var COVER_ICON_EVENT_TYPE = "ixo.page.cover_icon";
12679
+ var MatrixMetadataManager = class {
12680
+ constructor(matrixClient, roomId) {
12681
+ this.subscribers = /* @__PURE__ */ new Set();
12682
+ this.eventHandler = null;
12683
+ this.matrixClient = matrixClient;
12684
+ this.roomId = roomId;
12685
+ this.setupEventListener();
12686
+ }
12687
+ /**
12688
+ * Set up Matrix event listener for real-time updates
12689
+ */
12690
+ setupEventListener() {
12691
+ this.eventHandler = (event) => {
12692
+ if (event.getRoomId() === this.roomId) {
12693
+ const eventType = event.getType();
12694
+ if (eventType === COVER_IMAGE_EVENT_TYPE || eventType === COVER_ICON_EVENT_TYPE) {
12695
+ const metadata = this.getMetadata();
12696
+ if (metadata) {
12697
+ this.notifySubscribers(metadata);
12698
+ }
12699
+ }
12700
+ }
12701
+ };
12702
+ this.matrixClient.on("RoomState.events", this.eventHandler);
12703
+ }
12704
+ /**
12705
+ * Notify all subscribers of metadata changes
12706
+ */
12707
+ notifySubscribers(metadata) {
12708
+ this.subscribers.forEach((callback) => {
12709
+ try {
12710
+ callback(metadata);
12711
+ } catch (error) {
12712
+ console.error("Error in metadata subscriber callback:", error);
12713
+ }
12714
+ });
12715
+ }
12716
+ /**
12717
+ * Send metadata to Matrix state events (separate events for cover and icon)
12718
+ *
12719
+ * @param metadata - The metadata to store (only URL strings)
12720
+ * @param permissions - Permission check object
12721
+ * @returns Promise that resolves when state events are sent
12722
+ */
12723
+ async setMetadata(metadata, permissions) {
12724
+ if (!permissions.write) {
12725
+ console.warn("Cannot set metadata: write permission denied");
12726
+ return;
12727
+ }
12728
+ try {
12729
+ const promises = [];
12730
+ if (metadata.cover !== void 0) {
12731
+ promises.push(
12732
+ this.matrixClient.sendStateEvent(
12733
+ this.roomId,
12734
+ COVER_IMAGE_EVENT_TYPE,
12735
+ { url: metadata.cover || null },
12736
+ // Wrap in object - Matrix requires JSON content
12737
+ ""
12738
+ // Empty state key
12739
+ )
12740
+ );
12741
+ }
12742
+ if (metadata.icon !== void 0) {
12743
+ promises.push(
12744
+ this.matrixClient.sendStateEvent(
12745
+ this.roomId,
12746
+ COVER_ICON_EVENT_TYPE,
12747
+ { url: metadata.icon || null },
12748
+ // Wrap in object - Matrix requires JSON content
12749
+ ""
12750
+ // Empty state key
12751
+ )
12752
+ );
12753
+ }
12754
+ await Promise.all(promises);
12755
+ } catch (error) {
12756
+ console.error("Failed to set page metadata:", error);
12757
+ throw error;
12758
+ }
12759
+ }
12760
+ /**
12761
+ * Get current metadata from Matrix state (reads from separate events)
12762
+ *
12763
+ * @returns Current metadata or null if not set
12764
+ */
12765
+ getMetadata() {
12766
+ try {
12767
+ const room = this.matrixClient.getRoom(this.roomId);
12768
+ if (!room) {
12769
+ console.warn(`Room ${this.roomId} not found`);
12770
+ return null;
12771
+ }
12772
+ const coverEvent = room.currentState.getStateEvents(
12773
+ COVER_IMAGE_EVENT_TYPE,
12774
+ ""
12775
+ // Empty state key
12776
+ );
12777
+ const coverContent = coverEvent ? coverEvent.getContent() : null;
12778
+ const coverUrl = coverContent?.url || null;
12779
+ const iconEvent = room.currentState.getStateEvents(
12780
+ COVER_ICON_EVENT_TYPE,
12781
+ ""
12782
+ // Empty state key
12783
+ );
12784
+ const iconContent = iconEvent ? iconEvent.getContent() : null;
12785
+ const iconUrl = iconContent?.url || null;
12786
+ if (!coverUrl && !iconUrl) {
12787
+ return null;
12788
+ }
12789
+ return {
12790
+ cover: coverUrl || void 0,
12791
+ icon: iconUrl || void 0
12792
+ };
12793
+ } catch (error) {
12794
+ console.error("Failed to get page metadata:", error);
12795
+ return null;
12796
+ }
12797
+ }
12798
+ /**
12799
+ * Subscribe to metadata changes
12800
+ *
12801
+ * @param callback - Function to call when metadata changes
12802
+ * @returns Unsubscribe function
12803
+ */
12804
+ subscribe(callback) {
12805
+ this.subscribers.add(callback);
12806
+ return () => {
12807
+ this.subscribers.delete(callback);
12808
+ };
12809
+ }
12810
+ /**
12811
+ * Clean up event listeners and subscriptions
12812
+ * Call this when the manager is no longer needed
12813
+ */
12814
+ dispose() {
12815
+ if (this.eventHandler) {
12816
+ this.matrixClient.off("RoomState.events", this.eventHandler);
12817
+ this.eventHandler = null;
12818
+ }
12819
+ this.subscribers.clear();
12820
+ }
12821
+ };
12822
+
12823
+ // src/mantine/hooks/useCollaborativeIxoEditor.ts
12675
12824
  function useCreateCollaborativeIxoEditor(options) {
12676
12825
  const yDoc = useCollaborativeYDoc(options);
12677
12826
  const {
@@ -12705,6 +12854,12 @@ function useCreateCollaborativeIxoEditor(options) {
12705
12854
  matrixClient,
12706
12855
  roomId: options.roomId
12707
12856
  });
12857
+ const metadataManager = useMemo44(() => new MatrixMetadataManager(matrixClient, options.roomId), [matrixClient, options.roomId]);
12858
+ useEffect37(() => {
12859
+ return () => {
12860
+ metadataManager.dispose();
12861
+ };
12862
+ }, [metadataManager]);
12708
12863
  const defaultUploadFile = useMemo44(
12709
12864
  () => uploadFile || (async (file) => {
12710
12865
  return new Promise((resolve, reject) => {
@@ -12816,31 +12971,43 @@ function useCreateCollaborativeIxoEditor(options) {
12816
12971
  ixoEditor.getFlow = () => {
12817
12972
  return flowArray.toArray();
12818
12973
  };
12819
- ixoEditor.setCoverImage = (imageData) => {
12974
+ ixoEditor._metadataManager = metadataManager;
12975
+ ixoEditor.setPageMetadata = async (updates) => {
12820
12976
  if (!permissions.write) {
12977
+ console.warn("Cannot set page metadata: write permission denied");
12821
12978
  return;
12822
12979
  }
12823
- if (imageData === void 0 || imageData === null) {
12824
- root.delete("coverImage");
12825
- } else {
12826
- root.set("coverImage", imageData);
12980
+ const current = metadataManager.getMetadata() || {};
12981
+ await metadataManager.setMetadata({ ...current, ...updates }, permissions);
12982
+ };
12983
+ ixoEditor.getPageMetadata = () => {
12984
+ return metadataManager.getMetadata();
12985
+ };
12986
+ ixoEditor.setCoverImage = (imageData) => {
12987
+ if (!permissions.write) {
12988
+ return;
12827
12989
  }
12990
+ const current = metadataManager.getMetadata() || {};
12991
+ const coverUrl = imageData?.url;
12992
+ metadataManager.setMetadata({ ...current, cover: coverUrl }, permissions).catch((error) => {
12993
+ console.error("Failed to set cover image:", error);
12994
+ });
12828
12995
  };
12829
12996
  ixoEditor.getCoverImage = () => {
12830
- return root.get("coverImage");
12997
+ return void 0;
12831
12998
  };
12832
12999
  ixoEditor.setLogo = (imageData) => {
12833
13000
  if (!permissions.write) {
12834
13001
  return;
12835
13002
  }
12836
- if (imageData === void 0 || imageData === null) {
12837
- root.delete("logo");
12838
- } else {
12839
- root.set("logo", imageData);
12840
- }
13003
+ const current = metadataManager.getMetadata() || {};
13004
+ const iconUrl = imageData?.url;
13005
+ metadataManager.setMetadata({ ...current, icon: iconUrl }, permissions).catch((error) => {
13006
+ console.error("Failed to set logo:", error);
13007
+ });
12841
13008
  };
12842
13009
  ixoEditor.getLogo = () => {
12843
- return root.get("logo");
13010
+ return void 0;
12844
13011
  };
12845
13012
  }
12846
13013
  useEffect37(() => {
@@ -12889,27 +13056,20 @@ function CoverImage({ coverImageUrl, logoUrl }) {
12889
13056
  const [coverPosition, setCoverPosition] = useState48(50);
12890
13057
  const coverFileInputRef = useRef9(null);
12891
13058
  const logoFileInputRef = useRef9(null);
12892
- const [coverImageData, setCoverImageData] = useState48(() => editor?.getCoverImage?.());
12893
- const [logoData, setLogoData] = useState48(() => editor?.getLogo?.());
13059
+ const [metadata, setMetadata] = useState48(() => editor?.getPageMetadata?.() || null);
12894
13060
  useEffect38(() => {
12895
- if (!editor?._yRoot) {
13061
+ if (!editor?._metadataManager) {
12896
13062
  return;
12897
13063
  }
12898
- const root = editor._yRoot;
12899
- const observer = () => {
12900
- const cover = root.get("coverImage");
12901
- const logo = root.get("logo");
12902
- setCoverImageData(cover);
12903
- setLogoData(logo);
12904
- };
12905
- observer();
12906
- root.observe(observer);
12907
- return () => {
12908
- root.unobserve(observer);
12909
- };
13064
+ const initialMetadata = editor._metadataManager.getMetadata();
13065
+ setMetadata(initialMetadata);
13066
+ const unsubscribe = editor._metadataManager.subscribe((newMetadata) => {
13067
+ setMetadata(newMetadata);
13068
+ });
13069
+ return unsubscribe;
12910
13070
  }, [editor]);
12911
- const coverUrl = coverImageData?.url || coverImageUrl;
12912
- const logoSrc = logoData?.url || logoUrl;
13071
+ const coverUrl = metadata?.cover || coverImageUrl;
13072
+ const logoSrc = metadata?.icon || logoUrl;
12913
13073
  const hasCover = !!coverUrl;
12914
13074
  const hasLogo = !!logoSrc;
12915
13075
  const handleFileSelect = async (event, type) => {
@@ -12919,12 +13079,21 @@ function CoverImage({ coverImageUrl, logoUrl }) {
12919
13079
  }
12920
13080
  try {
12921
13081
  const uploadedData = await handlers.publicFileUpload(file);
13082
+ const imageUrl = uploadedData.url;
13083
+ setMetadata((prev) => ({
13084
+ ...prev,
13085
+ [type === "cover" ? "cover" : "icon"]: imageUrl
13086
+ }));
12922
13087
  if (type === "cover") {
12923
- editor.setCoverImage?.(uploadedData);
13088
+ await editor.setPageMetadata?.({ cover: imageUrl });
12924
13089
  } else {
12925
- editor.setLogo?.(uploadedData);
13090
+ await editor.setPageMetadata?.({ icon: imageUrl });
12926
13091
  }
12927
13092
  } catch (error) {
13093
+ const currentMetadata = editor?.getPageMetadata?.();
13094
+ if (currentMetadata) {
13095
+ setMetadata(currentMetadata);
13096
+ }
12928
13097
  }
12929
13098
  };
12930
13099
  const handleAddCover = () => {
@@ -12936,9 +13105,17 @@ function CoverImage({ coverImageUrl, logoUrl }) {
12936
13105
  const handleReposition = () => {
12937
13106
  setIsRepositioning(!isRepositioning);
12938
13107
  };
12939
- const handleRemoveCover = () => {
13108
+ const handleRemoveCover = async () => {
12940
13109
  if (!editor) return;
12941
- editor.setCoverImage?.(void 0);
13110
+ setMetadata((prev) => ({ ...prev, cover: void 0 }));
13111
+ try {
13112
+ await editor.setPageMetadata?.({ cover: void 0 });
13113
+ } catch (error) {
13114
+ const currentMetadata = editor?.getPageMetadata?.();
13115
+ if (currentMetadata) {
13116
+ setMetadata(currentMetadata);
13117
+ }
13118
+ }
12942
13119
  };
12943
13120
  const handleAddLogo = () => {
12944
13121
  logoFileInputRef.current?.click();
@@ -12946,9 +13123,17 @@ function CoverImage({ coverImageUrl, logoUrl }) {
12946
13123
  const handleChangeLogo = () => {
12947
13124
  logoFileInputRef.current?.click();
12948
13125
  };
12949
- const handleRemoveLogo = () => {
13126
+ const handleRemoveLogo = async () => {
12950
13127
  if (!editor) return;
12951
- editor.setLogo?.(void 0);
13128
+ setMetadata((prev) => ({ ...prev, icon: void 0 }));
13129
+ try {
13130
+ await editor.setPageMetadata?.({ icon: void 0 });
13131
+ } catch (error) {
13132
+ const currentMetadata = editor?.getPageMetadata?.();
13133
+ if (currentMetadata) {
13134
+ setMetadata(currentMetadata);
13135
+ }
13136
+ }
12952
13137
  };
12953
13138
  const handleMouseMove = (e) => {
12954
13139
  if (!isRepositioning) return;
@@ -12974,16 +13159,14 @@ function CoverImage({ coverImageUrl, logoUrl }) {
12974
13159
  onMouseEnter: () => editable && setIsHovering(true),
12975
13160
  onMouseLeave: () => editable && setIsHovering(false)
12976
13161
  },
12977
- /* @__PURE__ */ React151.createElement("input", { ref: coverFileInputRef, type: "file", accept: "image/*", style: { display: "none" }, onChange: (e) => handleFileSelect(e, "cover") }),
12978
- /* @__PURE__ */ React151.createElement("input", { ref: logoFileInputRef, type: "file", accept: "image/*", style: { display: "none" }, onChange: (e) => handleFileSelect(e, "logo") }),
12979
- editable && isHovering && !logoSrc && /* @__PURE__ */ React151.createElement(
13162
+ /* @__PURE__ */ React151.createElement("div", { style: { maxWidth: "900px", margin: "0 auto", position: "relative", height: "100%" } }, /* @__PURE__ */ React151.createElement("input", { ref: coverFileInputRef, type: "file", accept: "image/*", style: { display: "none" }, onChange: (e) => handleFileSelect(e, "cover") }), /* @__PURE__ */ React151.createElement("input", { ref: logoFileInputRef, type: "file", accept: "image/*", style: { display: "none" }, onChange: (e) => handleFileSelect(e, "logo") }), editable && isHovering && !logoSrc && /* @__PURE__ */ React151.createElement(
12980
13163
  Group45,
12981
13164
  {
12982
13165
  gap: "xs",
12983
13166
  style: {
12984
13167
  position: "absolute",
12985
13168
  top: "12px",
12986
- left: "96px",
13169
+ left: "0",
12987
13170
  zIndex: 10
12988
13171
  }
12989
13172
  },
@@ -13021,13 +13204,12 @@ function CoverImage({ coverImageUrl, logoUrl }) {
13021
13204
  },
13022
13205
  "Add cover"
13023
13206
  )
13024
- ),
13025
- logoSrc && /* @__PURE__ */ React151.createElement(
13207
+ ), logoSrc && /* @__PURE__ */ React151.createElement(
13026
13208
  Box24,
13027
13209
  {
13028
13210
  style: {
13029
13211
  position: "relative",
13030
- insetInlineStart: "96px",
13212
+ insetInlineStart: "0",
13031
13213
  width: "120px",
13032
13214
  height: "120px",
13033
13215
  marginTop: "16px",
@@ -13050,14 +13232,18 @@ function CoverImage({ coverImageUrl, logoUrl }) {
13050
13232
  }
13051
13233
  ),
13052
13234
  editable && isHovering && /* @__PURE__ */ React151.createElement(
13053
- Group45,
13235
+ "div",
13054
13236
  {
13055
- gap: "xs",
13056
13237
  style: {
13057
13238
  position: "absolute",
13058
- top: "0",
13059
- left: "130px",
13060
- zIndex: 12
13239
+ bottom: "-32px",
13240
+ left: "50%",
13241
+ transform: "translateX(-50%)",
13242
+ zIndex: 12,
13243
+ display: "flex",
13244
+ flexDirection: "row",
13245
+ gap: "4px",
13246
+ alignItems: "center"
13061
13247
  }
13062
13248
  },
13063
13249
  /* @__PURE__ */ React151.createElement(
@@ -13093,9 +13279,26 @@ function CoverImage({ coverImageUrl, logoUrl }) {
13093
13279
  }
13094
13280
  },
13095
13281
  "Remove"
13282
+ ),
13283
+ /* @__PURE__ */ React151.createElement(
13284
+ Button32,
13285
+ {
13286
+ variant: "filled",
13287
+ size: "xs",
13288
+ onClick: handleAddCover,
13289
+ style: {
13290
+ backgroundColor: "rgba(255, 255, 255, 0.9)",
13291
+ color: "#37352f",
13292
+ fontSize: "12px",
13293
+ fontWeight: 500,
13294
+ padding: "4px 8px",
13295
+ height: "auto"
13296
+ }
13297
+ },
13298
+ "Add cover"
13096
13299
  )
13097
13300
  )
13098
- )
13301
+ ))
13099
13302
  );
13100
13303
  }
13101
13304
  return /* @__PURE__ */ React151.createElement(
@@ -13200,13 +13403,13 @@ function CoverImage({ coverImageUrl, logoUrl }) {
13200
13403
  "Remove"
13201
13404
  )
13202
13405
  ),
13203
- /* @__PURE__ */ React151.createElement(
13406
+ /* @__PURE__ */ React151.createElement("div", { style: { maxWidth: "900px", margin: "0 auto", position: "absolute", bottom: 0, left: 0, right: 0, height: "70px" } }, /* @__PURE__ */ React151.createElement(
13204
13407
  Box24,
13205
13408
  {
13206
13409
  style: {
13207
13410
  position: "absolute",
13208
13411
  bottom: "4px",
13209
- insetInlineStart: "96px",
13412
+ insetInlineStart: "0",
13210
13413
  width: "120px",
13211
13414
  height: "120px",
13212
13415
  zIndex: 11
@@ -13300,7 +13503,7 @@ function CoverImage({ coverImageUrl, logoUrl }) {
13300
13503
  "Add icon"
13301
13504
  )
13302
13505
  ))
13303
- ),
13506
+ )),
13304
13507
  /* @__PURE__ */ React151.createElement("input", { ref: coverFileInputRef, type: "file", accept: "image/*", style: { display: "none" }, onChange: (e) => handleFileSelect(e, "cover") }),
13305
13508
  /* @__PURE__ */ React151.createElement("input", { ref: logoFileInputRef, type: "file", accept: "image/*", style: { display: "none" }, onChange: (e) => handleFileSelect(e, "logo") })
13306
13509
  );
@@ -13548,4 +13751,4 @@ export {
13548
13751
  ixoGraphQLClient,
13549
13752
  getEntity
13550
13753
  };
13551
- //# sourceMappingURL=chunk-3BZ6OPAK.mjs.map
13754
+ //# sourceMappingURL=chunk-5JO27QWP.mjs.map