@pixerate/editor-react 0.15.0 → 0.16.0
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 +4 -0
- package/dist/canvas/index.cjs +219 -118
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +59 -3
- package/dist/canvas/index.d.ts +59 -3
- package/dist/canvas/index.js +103 -0
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-7SPRQZ6H.js +216 -0
- package/dist/chunk-7SPRQZ6H.js.map +1 -0
- package/dist/{chunk-QVQYYHJ5.js → chunk-FYIPXHAE.js} +21 -13
- package/dist/chunk-FYIPXHAE.js.map +1 -0
- package/dist/{chunk-LPFZMZ3L.js → chunk-XTFR3IMK.js} +676 -322
- package/dist/chunk-XTFR3IMK.js.map +1 -0
- package/dist/chunk-XWFU2CSG.js +167 -0
- package/dist/chunk-XWFU2CSG.js.map +1 -0
- package/dist/generation/index.cjs +235 -0
- package/dist/generation/index.cjs.map +1 -0
- package/dist/generation/index.d.cts +60 -0
- package/dist/generation/index.d.ts +60 -0
- package/dist/generation/index.js +13 -0
- package/dist/generation/index.js.map +1 -0
- package/dist/image-editor/index.cjs +684 -336
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +60 -2
- package/dist/image-editor/index.d.ts +60 -2
- package/dist/image-editor/index.js +13 -3
- package/dist/index.cjs +1881 -648
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +143 -4
- package/dist/index.d.ts +143 -4
- package/dist/index.js +626 -73
- package/dist/index.js.map +1 -1
- package/dist/persistence/index.cjs +191 -0
- package/dist/persistence/index.cjs.map +1 -0
- package/dist/persistence/index.d.cts +77 -0
- package/dist/persistence/index.d.ts +77 -0
- package/dist/persistence/index.js +9 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/spreadsheet/index.cjs +17 -10
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +2 -0
- package/dist/spreadsheet/index.d.ts +2 -0
- package/dist/spreadsheet/index.js +1 -1
- package/package.json +18 -2
- package/dist/chunk-LPFZMZ3L.js.map +0 -1
- package/dist/chunk-QVQYYHJ5.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -6,17 +6,32 @@ import {
|
|
|
6
6
|
useFillHandle,
|
|
7
7
|
useSpreadsheetClipboard,
|
|
8
8
|
useSpreadsheetEditor
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-FYIPXHAE.js";
|
|
10
10
|
import {
|
|
11
|
+
AdjustmentLayersPanel,
|
|
12
|
+
ControlSlider,
|
|
13
|
+
CurvesEditor,
|
|
14
|
+
Histogram,
|
|
11
15
|
ImageEditor,
|
|
12
|
-
useImageEditor
|
|
13
|
-
|
|
16
|
+
useImageEditor,
|
|
17
|
+
useImageHistogram
|
|
18
|
+
} from "./chunk-XTFR3IMK.js";
|
|
14
19
|
import {
|
|
15
20
|
CommandPalette,
|
|
16
21
|
useCommandPalette,
|
|
17
22
|
useCommandShortcuts,
|
|
18
23
|
useRegisterCommands
|
|
19
24
|
} from "./chunk-4ZC2WCUO.js";
|
|
25
|
+
import {
|
|
26
|
+
RecoveryPrompt,
|
|
27
|
+
useAutosave
|
|
28
|
+
} from "./chunk-XWFU2CSG.js";
|
|
29
|
+
import {
|
|
30
|
+
GenerationSettings,
|
|
31
|
+
JobProgress,
|
|
32
|
+
useGenerationPlan,
|
|
33
|
+
useJobProgress
|
|
34
|
+
} from "./chunk-7SPRQZ6H.js";
|
|
20
35
|
|
|
21
36
|
// src/hooks/usePromptEditor.ts
|
|
22
37
|
import { useMemo, useEffect, useRef } from "react";
|
|
@@ -660,11 +675,12 @@ var BubbleMenu = ({
|
|
|
660
675
|
// src/components/TemplateRenderer.tsx
|
|
661
676
|
import { useMemo as useMemo4 } from "react";
|
|
662
677
|
import {
|
|
678
|
+
describeConditionalTag,
|
|
663
679
|
evaluatePromptTokens,
|
|
680
|
+
formatConditionalTag,
|
|
664
681
|
resolveTemplates
|
|
665
682
|
} from "@pixerate/editor";
|
|
666
683
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
667
|
-
var CONDITIONAL_PREFIX = { if: "?", unless: "!", end: "/" };
|
|
668
684
|
var TemplateRenderer = ({
|
|
669
685
|
content,
|
|
670
686
|
templates = [],
|
|
@@ -747,8 +763,8 @@ var TemplateRenderer = ({
|
|
|
747
763
|
{
|
|
748
764
|
"data-conditional": token.conditional,
|
|
749
765
|
className: "inline-flex items-center px-1 py-0.5 mx-0.5 text-xs font-medium rounded-md border border-amber-400/40 bg-amber-400/15 text-amber-700 dark:text-amber-300",
|
|
750
|
-
title: token
|
|
751
|
-
children:
|
|
766
|
+
title: describeConditionalTag(token),
|
|
767
|
+
children: formatConditionalTag(token)
|
|
752
768
|
},
|
|
753
769
|
index
|
|
754
770
|
);
|
|
@@ -856,17 +872,312 @@ var VariableForm = ({
|
|
|
856
872
|
}) });
|
|
857
873
|
};
|
|
858
874
|
|
|
875
|
+
// src/components/MergePreview.tsx
|
|
876
|
+
import { useId as useId2, useState as useState7 } from "react";
|
|
877
|
+
|
|
878
|
+
// src/hooks/useMergePreview.ts
|
|
879
|
+
import { useCallback as useCallback5, useMemo as useMemo6, useState as useState6 } from "react";
|
|
880
|
+
import {
|
|
881
|
+
extractVariables,
|
|
882
|
+
matchFields,
|
|
883
|
+
mergeRecords,
|
|
884
|
+
parseDataSource,
|
|
885
|
+
recordToValues,
|
|
886
|
+
serializeMergeOutputs
|
|
887
|
+
} from "@pixerate/editor";
|
|
888
|
+
function useMergePreview(options) {
|
|
889
|
+
const { template, templates, bindings, parseOptions } = options;
|
|
890
|
+
const [sourceText, setSourceTextState] = useState6(options.initialSource ?? "");
|
|
891
|
+
const [overrides, setOverrides] = useState6({});
|
|
892
|
+
const [index, setIndexState] = useState6(0);
|
|
893
|
+
const [result, setResult] = useState6(null);
|
|
894
|
+
const source = useMemo6(
|
|
895
|
+
() => parseDataSource(sourceText, parseOptions),
|
|
896
|
+
[sourceText, parseOptions]
|
|
897
|
+
);
|
|
898
|
+
const variables = useMemo6(
|
|
899
|
+
() => extractVariables(template, { templates, bindings }).map((v) => v.name),
|
|
900
|
+
[template, templates, bindings]
|
|
901
|
+
);
|
|
902
|
+
const mapping = useMemo6(() => {
|
|
903
|
+
const merged = {
|
|
904
|
+
...matchFields(variables, source.fields).mapping
|
|
905
|
+
};
|
|
906
|
+
for (const [variable, field] of Object.entries(overrides)) {
|
|
907
|
+
if (variables.includes(variable) && (field === null || source.fields.includes(field))) {
|
|
908
|
+
merged[variable] = field;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
return merged;
|
|
912
|
+
}, [variables, source.fields, overrides]);
|
|
913
|
+
const count = source.records.length;
|
|
914
|
+
const current = count === 0 ? 0 : Math.min(index, count - 1);
|
|
915
|
+
const record = source.records[current];
|
|
916
|
+
const values = useMemo6(
|
|
917
|
+
() => record ? recordToValues(record, mapping) : {},
|
|
918
|
+
[record, mapping]
|
|
919
|
+
);
|
|
920
|
+
const setSourceText = useCallback5((text) => {
|
|
921
|
+
setSourceTextState(text);
|
|
922
|
+
setIndexState(0);
|
|
923
|
+
setResult(null);
|
|
924
|
+
}, []);
|
|
925
|
+
const loadFile = useCallback5(
|
|
926
|
+
async (file) => setSourceText(await file.text()),
|
|
927
|
+
[setSourceText]
|
|
928
|
+
);
|
|
929
|
+
const setIndex = useCallback5(
|
|
930
|
+
(next) => setIndexState(Math.max(0, Math.min(next, count - 1))),
|
|
931
|
+
[count]
|
|
932
|
+
);
|
|
933
|
+
const setFieldMapping = useCallback5((variable, field) => {
|
|
934
|
+
setOverrides((previous) => ({ ...previous, [variable]: field }));
|
|
935
|
+
setResult(null);
|
|
936
|
+
}, []);
|
|
937
|
+
const resetMapping = useCallback5(() => setOverrides({}), []);
|
|
938
|
+
const merge = useCallback5(() => {
|
|
939
|
+
const merged = mergeRecords(template, source, {
|
|
940
|
+
templates,
|
|
941
|
+
bindings,
|
|
942
|
+
mapping,
|
|
943
|
+
skipIf: options.skipIf,
|
|
944
|
+
separator: options.separator,
|
|
945
|
+
maxOutputs: options.maxOutputs
|
|
946
|
+
});
|
|
947
|
+
setResult(merged);
|
|
948
|
+
return merged;
|
|
949
|
+
}, [template, source, templates, bindings, mapping, options.skipIf, options.separator, options.maxOutputs]);
|
|
950
|
+
const serialize = useCallback5(
|
|
951
|
+
(format) => result ? serializeMergeOutputs(result.outputs, format, options.separator) : "",
|
|
952
|
+
[result, options.separator]
|
|
953
|
+
);
|
|
954
|
+
return {
|
|
955
|
+
sourceText,
|
|
956
|
+
setSourceText,
|
|
957
|
+
loadFile,
|
|
958
|
+
source,
|
|
959
|
+
variables,
|
|
960
|
+
mapping,
|
|
961
|
+
setFieldMapping,
|
|
962
|
+
resetMapping,
|
|
963
|
+
index: current,
|
|
964
|
+
count,
|
|
965
|
+
setIndex,
|
|
966
|
+
next: () => setIndex(current + 1),
|
|
967
|
+
prev: () => setIndex(current - 1),
|
|
968
|
+
record,
|
|
969
|
+
values,
|
|
970
|
+
merge,
|
|
971
|
+
result,
|
|
972
|
+
serialize
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// src/components/MergePreview.tsx
|
|
977
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
978
|
+
function downloadText(text, fileName, type) {
|
|
979
|
+
if (typeof document === "undefined" || typeof URL.createObjectURL !== "function") {
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
const url = URL.createObjectURL(new Blob([text], { type }));
|
|
983
|
+
const link = document.createElement("a");
|
|
984
|
+
link.href = url;
|
|
985
|
+
link.download = fileName;
|
|
986
|
+
link.click();
|
|
987
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
988
|
+
}
|
|
989
|
+
var MergePreview = ({
|
|
990
|
+
content,
|
|
991
|
+
templates,
|
|
992
|
+
bindings,
|
|
993
|
+
templateColorMap,
|
|
994
|
+
defaultSource,
|
|
995
|
+
parseOptions,
|
|
996
|
+
skipIf,
|
|
997
|
+
separator,
|
|
998
|
+
maxOutputs,
|
|
999
|
+
onMerge,
|
|
1000
|
+
downloadName = "merged",
|
|
1001
|
+
className = "flex flex-col gap-3 text-sm"
|
|
1002
|
+
}) => {
|
|
1003
|
+
const id = useId2();
|
|
1004
|
+
const merge = useMergePreview({
|
|
1005
|
+
template: content,
|
|
1006
|
+
templates,
|
|
1007
|
+
bindings,
|
|
1008
|
+
initialSource: defaultSource,
|
|
1009
|
+
parseOptions,
|
|
1010
|
+
skipIf,
|
|
1011
|
+
separator,
|
|
1012
|
+
maxOutputs
|
|
1013
|
+
});
|
|
1014
|
+
const [copied, setCopied] = useState7(false);
|
|
1015
|
+
const { source, result } = merge;
|
|
1016
|
+
const copy = async () => {
|
|
1017
|
+
try {
|
|
1018
|
+
await navigator.clipboard.writeText(merge.serialize("text"));
|
|
1019
|
+
setCopied(true);
|
|
1020
|
+
} catch {
|
|
1021
|
+
setCopied(false);
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
return /* @__PURE__ */ jsxs3("div", { className, "data-merge-preview": "", children: [
|
|
1025
|
+
/* @__PURE__ */ jsxs3("label", { htmlFor: `${id}-source`, className: "flex flex-col gap-1", children: [
|
|
1026
|
+
/* @__PURE__ */ jsx4("span", { className: "font-medium", children: "Data source (CSV, TSV or JSON)" }),
|
|
1027
|
+
/* @__PURE__ */ jsx4(
|
|
1028
|
+
"textarea",
|
|
1029
|
+
{
|
|
1030
|
+
id: `${id}-source`,
|
|
1031
|
+
"data-merge-source": "",
|
|
1032
|
+
value: merge.sourceText,
|
|
1033
|
+
rows: 5,
|
|
1034
|
+
spellCheck: false,
|
|
1035
|
+
onChange: (event) => merge.setSourceText(event.target.value),
|
|
1036
|
+
className: "px-2 py-1 font-mono text-xs rounded-md border border-violet-400/40 bg-transparent"
|
|
1037
|
+
}
|
|
1038
|
+
)
|
|
1039
|
+
] }),
|
|
1040
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1041
|
+
/* @__PURE__ */ jsx4(
|
|
1042
|
+
"input",
|
|
1043
|
+
{
|
|
1044
|
+
type: "file",
|
|
1045
|
+
accept: ".csv,.tsv,.txt,.json,text/csv,application/json",
|
|
1046
|
+
"aria-label": "Load data source file",
|
|
1047
|
+
"data-merge-file": "",
|
|
1048
|
+
onChange: (event) => {
|
|
1049
|
+
const file = event.target.files?.[0];
|
|
1050
|
+
if (file) void merge.loadFile(file);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
),
|
|
1054
|
+
/* @__PURE__ */ jsx4("span", { "data-merge-summary": "", className: "opacity-70", children: `${source.records.length} record${source.records.length === 1 ? "" : "s"} \xB7 ${source.fields.length} field${source.fields.length === 1 ? "" : "s"}` })
|
|
1055
|
+
] }),
|
|
1056
|
+
source.warnings.length > 0 && merge.sourceText.trim() !== "" && /* @__PURE__ */ jsx4("ul", { "data-merge-warnings": "", className: "text-amber-600 text-xs", children: source.warnings.map((warning, i) => /* @__PURE__ */ jsx4("li", { children: warning.message }, i)) }),
|
|
1057
|
+
merge.variables.length > 0 && /* @__PURE__ */ jsxs3("fieldset", { className: "flex flex-col gap-1", children: [
|
|
1058
|
+
/* @__PURE__ */ jsx4("legend", { className: "font-medium", children: "Field mapping" }),
|
|
1059
|
+
merge.variables.map((variable) => /* @__PURE__ */ jsxs3(
|
|
1060
|
+
"label",
|
|
1061
|
+
{
|
|
1062
|
+
className: "flex items-center gap-2",
|
|
1063
|
+
"data-merge-mapping": variable,
|
|
1064
|
+
children: [
|
|
1065
|
+
/* @__PURE__ */ jsx4("span", { className: "min-w-24 font-mono", children: `{${variable}}` }),
|
|
1066
|
+
/* @__PURE__ */ jsxs3(
|
|
1067
|
+
"select",
|
|
1068
|
+
{
|
|
1069
|
+
value: merge.mapping[variable] ?? "",
|
|
1070
|
+
"aria-label": `Field for ${variable}`,
|
|
1071
|
+
onChange: (event) => merge.setFieldMapping(variable, event.target.value || null),
|
|
1072
|
+
className: "px-1 py-0.5 rounded-md border border-violet-400/40 bg-transparent",
|
|
1073
|
+
children: [
|
|
1074
|
+
/* @__PURE__ */ jsx4("option", { value: "", children: "(none)" }),
|
|
1075
|
+
source.fields.map((field) => /* @__PURE__ */ jsx4("option", { value: field, children: field }, field))
|
|
1076
|
+
]
|
|
1077
|
+
}
|
|
1078
|
+
)
|
|
1079
|
+
]
|
|
1080
|
+
},
|
|
1081
|
+
variable
|
|
1082
|
+
))
|
|
1083
|
+
] }),
|
|
1084
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
|
|
1085
|
+
/* @__PURE__ */ jsx4(
|
|
1086
|
+
"button",
|
|
1087
|
+
{
|
|
1088
|
+
type: "button",
|
|
1089
|
+
"data-merge-prev": "",
|
|
1090
|
+
onClick: merge.prev,
|
|
1091
|
+
disabled: merge.index <= 0,
|
|
1092
|
+
children: "Prev"
|
|
1093
|
+
}
|
|
1094
|
+
),
|
|
1095
|
+
/* @__PURE__ */ jsx4("span", { "data-merge-position": "", children: merge.count === 0 ? "No records" : `Record ${merge.index + 1} of ${merge.count}` }),
|
|
1096
|
+
/* @__PURE__ */ jsx4(
|
|
1097
|
+
"button",
|
|
1098
|
+
{
|
|
1099
|
+
type: "button",
|
|
1100
|
+
"data-merge-next": "",
|
|
1101
|
+
onClick: merge.next,
|
|
1102
|
+
disabled: merge.index >= merge.count - 1,
|
|
1103
|
+
children: "Next"
|
|
1104
|
+
}
|
|
1105
|
+
)
|
|
1106
|
+
] }),
|
|
1107
|
+
/* @__PURE__ */ jsx4("div", { "data-merge-output": "", children: /* @__PURE__ */ jsx4(
|
|
1108
|
+
TemplateRenderer,
|
|
1109
|
+
{
|
|
1110
|
+
content,
|
|
1111
|
+
templates,
|
|
1112
|
+
bindings,
|
|
1113
|
+
templateColorMap,
|
|
1114
|
+
resolve: !!templates && templates.length > 0,
|
|
1115
|
+
values: merge.values
|
|
1116
|
+
}
|
|
1117
|
+
) }),
|
|
1118
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1119
|
+
/* @__PURE__ */ jsx4(
|
|
1120
|
+
"button",
|
|
1121
|
+
{
|
|
1122
|
+
type: "button",
|
|
1123
|
+
"data-merge-all": "",
|
|
1124
|
+
disabled: merge.count === 0,
|
|
1125
|
+
onClick: () => {
|
|
1126
|
+
const merged = merge.merge();
|
|
1127
|
+
setCopied(false);
|
|
1128
|
+
onMerge?.(merged);
|
|
1129
|
+
},
|
|
1130
|
+
children: "Merge all"
|
|
1131
|
+
}
|
|
1132
|
+
),
|
|
1133
|
+
result && /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
1134
|
+
/* @__PURE__ */ jsx4("span", { "data-merge-result": "", children: `${result.outputs.length} merged${result.skipped.length ? `, ${result.skipped.length} skipped` : ""}${result.truncated ? " (stopped early)" : ""}` }),
|
|
1135
|
+
/* @__PURE__ */ jsx4("button", { type: "button", "data-merge-copy": "", onClick: copy, children: copied ? "Copied" : "Copy" }),
|
|
1136
|
+
/* @__PURE__ */ jsx4(
|
|
1137
|
+
"button",
|
|
1138
|
+
{
|
|
1139
|
+
type: "button",
|
|
1140
|
+
"data-merge-download": "text",
|
|
1141
|
+
onClick: () => downloadText(merge.serialize("text"), `${downloadName}.txt`, "text/plain"),
|
|
1142
|
+
children: "Download .txt"
|
|
1143
|
+
}
|
|
1144
|
+
),
|
|
1145
|
+
/* @__PURE__ */ jsx4(
|
|
1146
|
+
"button",
|
|
1147
|
+
{
|
|
1148
|
+
type: "button",
|
|
1149
|
+
"data-merge-download": "json",
|
|
1150
|
+
onClick: () => downloadText(merge.serialize("json"), `${downloadName}.json`, "application/json"),
|
|
1151
|
+
children: "Download .json"
|
|
1152
|
+
}
|
|
1153
|
+
)
|
|
1154
|
+
] })
|
|
1155
|
+
] }),
|
|
1156
|
+
result && /* @__PURE__ */ jsx4(
|
|
1157
|
+
"textarea",
|
|
1158
|
+
{
|
|
1159
|
+
readOnly: true,
|
|
1160
|
+
"aria-label": "Merged output",
|
|
1161
|
+
"data-merge-text": "",
|
|
1162
|
+
rows: 6,
|
|
1163
|
+
value: merge.serialize("text"),
|
|
1164
|
+
className: "px-2 py-1 font-mono text-xs rounded-md border border-violet-400/40 bg-transparent"
|
|
1165
|
+
}
|
|
1166
|
+
)
|
|
1167
|
+
] });
|
|
1168
|
+
};
|
|
1169
|
+
|
|
859
1170
|
// src/hooks/useOutline.ts
|
|
860
|
-
import { useMemo as
|
|
1171
|
+
import { useMemo as useMemo7, useSyncExternalStore as useSyncExternalStore4 } from "react";
|
|
861
1172
|
import { createOutlineStore } from "@pixerate/editor";
|
|
862
1173
|
function useOutline(editor) {
|
|
863
|
-
const store =
|
|
1174
|
+
const store = useMemo7(() => createOutlineStore(editor), [editor]);
|
|
864
1175
|
return useSyncExternalStore4(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
865
1176
|
}
|
|
866
1177
|
|
|
867
1178
|
// src/components/OutlinePanel.tsx
|
|
868
1179
|
import { navigateToHeading } from "@pixerate/editor";
|
|
869
|
-
import { jsx as
|
|
1180
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
870
1181
|
var OutlinePanel = ({
|
|
871
1182
|
editor,
|
|
872
1183
|
title = "Outline",
|
|
@@ -880,11 +1191,11 @@ var OutlinePanel = ({
|
|
|
880
1191
|
}) => {
|
|
881
1192
|
const { headings, activeId } = useOutline(editor);
|
|
882
1193
|
const visible = headings.filter((h) => h.level <= maxLevel);
|
|
883
|
-
return /* @__PURE__ */
|
|
884
|
-
title != null && /* @__PURE__ */
|
|
885
|
-
visible.length === 0 ? /* @__PURE__ */
|
|
1194
|
+
return /* @__PURE__ */ jsxs4("nav", { className, "aria-label": "Document outline", "data-outline-panel": "", children: [
|
|
1195
|
+
title != null && /* @__PURE__ */ jsx5("div", { className: "pixerate-outline-title", children: title }),
|
|
1196
|
+
visible.length === 0 ? /* @__PURE__ */ jsx5("div", { className: "pixerate-outline-empty", children: emptyState }) : /* @__PURE__ */ jsx5("ul", { role: "list", style: { listStyle: "none", margin: 0, padding: 0 }, children: visible.map((heading) => {
|
|
886
1197
|
const active = heading.id === activeId;
|
|
887
|
-
return /* @__PURE__ */
|
|
1198
|
+
return /* @__PURE__ */ jsx5("li", { "data-level": heading.level, children: /* @__PURE__ */ jsx5(
|
|
888
1199
|
"button",
|
|
889
1200
|
{
|
|
890
1201
|
type: "button",
|
|
@@ -917,10 +1228,10 @@ import {
|
|
|
917
1228
|
} from "@pixerate/editor";
|
|
918
1229
|
|
|
919
1230
|
// src/slots.tsx
|
|
920
|
-
import { createContext, useContext, useMemo as
|
|
921
|
-
import { jsx as
|
|
1231
|
+
import { createContext, useContext, useMemo as useMemo8 } from "react";
|
|
1232
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
922
1233
|
var defaultSlots = {
|
|
923
|
-
Button: (props) => /* @__PURE__ */
|
|
1234
|
+
Button: (props) => /* @__PURE__ */ jsx6(
|
|
924
1235
|
"button",
|
|
925
1236
|
{
|
|
926
1237
|
type: "button",
|
|
@@ -930,26 +1241,26 @@ var defaultSlots = {
|
|
|
930
1241
|
children: props.children
|
|
931
1242
|
}
|
|
932
1243
|
),
|
|
933
|
-
Tooltip: (props) => /* @__PURE__ */
|
|
934
|
-
NodeHeader: (props) => /* @__PURE__ */
|
|
935
|
-
/* @__PURE__ */
|
|
1244
|
+
Tooltip: (props) => /* @__PURE__ */ jsx6("div", { className: `pixerate-tooltip-container ${props.className || ""}`, title: String(props.content), children: props.children }),
|
|
1245
|
+
NodeHeader: (props) => /* @__PURE__ */ jsxs5("div", { className: `pixerate-node-header flex items-center justify-between ${props.className || ""}`, children: [
|
|
1246
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
|
|
936
1247
|
props.icon,
|
|
937
|
-
/* @__PURE__ */
|
|
1248
|
+
/* @__PURE__ */ jsx6("span", { className: "font-semibold", children: props.title })
|
|
938
1249
|
] }),
|
|
939
|
-
props.onClose && /* @__PURE__ */
|
|
1250
|
+
props.onClose && /* @__PURE__ */ jsx6("button", { type: "button", onClick: props.onClose, className: "opacity-70 hover:opacity-100", children: "\xD7" })
|
|
940
1251
|
] })
|
|
941
1252
|
};
|
|
942
1253
|
var EditorSlotsContext = createContext(defaultSlots);
|
|
943
1254
|
function EditorSlotsProvider({ slots, children }) {
|
|
944
|
-
const mergedSlots =
|
|
945
|
-
return /* @__PURE__ */
|
|
1255
|
+
const mergedSlots = useMemo8(() => ({ ...defaultSlots, ...slots }), [slots]);
|
|
1256
|
+
return /* @__PURE__ */ jsx6(EditorSlotsContext.Provider, { value: mergedSlots, children });
|
|
946
1257
|
}
|
|
947
1258
|
function useEditorSlots() {
|
|
948
1259
|
return useContext(EditorSlotsContext);
|
|
949
1260
|
}
|
|
950
1261
|
|
|
951
1262
|
// src/review/useReview.ts
|
|
952
|
-
import { useCallback as
|
|
1263
|
+
import { useCallback as useCallback6, useMemo as useMemo9, useSyncExternalStore as useSyncExternalStore5 } from "react";
|
|
953
1264
|
import {
|
|
954
1265
|
getCommentStore,
|
|
955
1266
|
getTrackChangesStore
|
|
@@ -971,23 +1282,23 @@ function isCommentStore(value) {
|
|
|
971
1282
|
}
|
|
972
1283
|
function useComments(source) {
|
|
973
1284
|
const store = isCommentStore(source) ? source : getCommentStore(source);
|
|
974
|
-
const subscribe =
|
|
1285
|
+
const subscribe = useCallback6(
|
|
975
1286
|
(onChange) => store ? store.subscribe(() => onChange()) : noopUnsubscribe,
|
|
976
1287
|
[store]
|
|
977
1288
|
);
|
|
978
1289
|
const getSnapshot = () => store?.getSnapshot() ?? EMPTY_COMMENTS;
|
|
979
1290
|
const snapshot = useSyncExternalStore5(subscribe, getSnapshot, getSnapshot);
|
|
980
|
-
return
|
|
1291
|
+
return useMemo9(() => ({ ...snapshot, store }), [snapshot, store]);
|
|
981
1292
|
}
|
|
982
1293
|
function useTrackChanges(editor) {
|
|
983
1294
|
const store = getTrackChangesStore(editor ?? null);
|
|
984
|
-
const subscribe =
|
|
1295
|
+
const subscribe = useCallback6(
|
|
985
1296
|
(onChange) => store ? store.subscribe(() => onChange()) : noopUnsubscribe,
|
|
986
1297
|
[store]
|
|
987
1298
|
);
|
|
988
1299
|
const getSnapshot = () => store?.getSnapshot() ?? EMPTY_TRACK_CHANGES;
|
|
989
1300
|
const snapshot = useSyncExternalStore5(subscribe, getSnapshot, getSnapshot);
|
|
990
|
-
return
|
|
1301
|
+
return useMemo9(() => {
|
|
991
1302
|
const run = (fn) => () => {
|
|
992
1303
|
if (editor && !editor.isDestroyed) fn(editor);
|
|
993
1304
|
};
|
|
@@ -1007,12 +1318,12 @@ function useTrackChanges(editor) {
|
|
|
1007
1318
|
}
|
|
1008
1319
|
|
|
1009
1320
|
// src/review/CommentsPanel.tsx
|
|
1010
|
-
import { useState as
|
|
1321
|
+
import { useState as useState8 } from "react";
|
|
1011
1322
|
import {
|
|
1012
1323
|
formatReviewTime,
|
|
1013
1324
|
getCommentAuthor
|
|
1014
1325
|
} from "@pixerate/editor";
|
|
1015
|
-
import { jsx as
|
|
1326
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1016
1327
|
var buttonClass = "rounded px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-50";
|
|
1017
1328
|
var primaryButtonClass = "rounded px-2 py-1 text-xs bg-primary/10 text-primary hover:bg-primary/20 disabled:opacity-50";
|
|
1018
1329
|
var textareaClass = "w-full resize-y rounded-md border border-input bg-background p-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary";
|
|
@@ -1026,18 +1337,18 @@ var CommentsPanel = ({
|
|
|
1026
1337
|
emptyMessage = "No comments yet. Select text and add a comment."
|
|
1027
1338
|
}) => {
|
|
1028
1339
|
const { threads, activeThreadId, store } = useComments(storeProp ?? editor);
|
|
1029
|
-
const [showResolved, setShowResolved] =
|
|
1340
|
+
const [showResolved, setShowResolved] = useState8(showResolvedInitial);
|
|
1030
1341
|
const author = authorProp ?? getCommentAuthor(editor) ?? { id: "anonymous", name: "Anonymous" };
|
|
1031
1342
|
const visible = threads.filter((t) => showResolved || !t.resolved);
|
|
1032
1343
|
const resolvedCount = threads.filter((t) => t.resolved).length;
|
|
1033
|
-
return /* @__PURE__ */
|
|
1034
|
-
/* @__PURE__ */
|
|
1035
|
-
/* @__PURE__ */
|
|
1344
|
+
return /* @__PURE__ */ jsxs6("aside", { className, "aria-label": "Comments", children: [
|
|
1345
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between gap-2", children: [
|
|
1346
|
+
/* @__PURE__ */ jsxs6("span", { className: "text-xs font-medium text-muted-foreground", children: [
|
|
1036
1347
|
threads.length - resolvedCount,
|
|
1037
1348
|
" open",
|
|
1038
1349
|
resolvedCount > 0 ? ` \xB7 ${resolvedCount} resolved` : ""
|
|
1039
1350
|
] }),
|
|
1040
|
-
resolvedCount > 0 && /* @__PURE__ */
|
|
1351
|
+
resolvedCount > 0 && /* @__PURE__ */ jsx7(
|
|
1041
1352
|
"button",
|
|
1042
1353
|
{
|
|
1043
1354
|
type: "button",
|
|
@@ -1048,7 +1359,7 @@ var CommentsPanel = ({
|
|
|
1048
1359
|
}
|
|
1049
1360
|
)
|
|
1050
1361
|
] }),
|
|
1051
|
-
visible.length === 0 ? /* @__PURE__ */
|
|
1362
|
+
visible.length === 0 ? /* @__PURE__ */ jsx7("p", { className: "text-xs text-muted-foreground", children: emptyMessage }) : /* @__PURE__ */ jsx7("ul", { className: "flex flex-col gap-2", role: "list", children: visible.map((thread) => /* @__PURE__ */ jsx7(
|
|
1052
1363
|
CommentThreadCard,
|
|
1053
1364
|
{
|
|
1054
1365
|
thread,
|
|
@@ -1070,7 +1381,7 @@ var CommentThreadCard = ({
|
|
|
1070
1381
|
author,
|
|
1071
1382
|
formatTime
|
|
1072
1383
|
}) => {
|
|
1073
|
-
const [draft, setDraft] =
|
|
1384
|
+
const [draft, setDraft] = useState8("");
|
|
1074
1385
|
const isDraft = thread.body === "";
|
|
1075
1386
|
const quote = thread.anchor?.quote ?? thread.lastQuote ?? "";
|
|
1076
1387
|
const live = editor && !editor.isDestroyed ? editor : null;
|
|
@@ -1096,7 +1407,7 @@ var CommentThreadCard = ({
|
|
|
1096
1407
|
setDraft("");
|
|
1097
1408
|
};
|
|
1098
1409
|
const stop = (e) => e.stopPropagation();
|
|
1099
|
-
return /* @__PURE__ */
|
|
1410
|
+
return /* @__PURE__ */ jsxs6(
|
|
1100
1411
|
"li",
|
|
1101
1412
|
{
|
|
1102
1413
|
className: `flex flex-col gap-2 rounded-md border bg-background p-3 cursor-pointer ${active ? "border-primary ring-1 ring-primary" : "border-border"} ${thread.resolved ? "opacity-70" : ""}`,
|
|
@@ -1112,24 +1423,24 @@ var CommentThreadCard = ({
|
|
|
1112
1423
|
}
|
|
1113
1424
|
},
|
|
1114
1425
|
children: [
|
|
1115
|
-
/* @__PURE__ */
|
|
1116
|
-
/* @__PURE__ */
|
|
1117
|
-
/* @__PURE__ */
|
|
1426
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-baseline justify-between gap-2", children: [
|
|
1427
|
+
/* @__PURE__ */ jsx7("span", { className: "font-medium", children: thread.author.name }),
|
|
1428
|
+
/* @__PURE__ */ jsx7("span", { className: "text-xs text-muted-foreground", children: formatTime(thread.createdAt) })
|
|
1118
1429
|
] }),
|
|
1119
|
-
quote && /* @__PURE__ */
|
|
1120
|
-
thread.orphaned && /* @__PURE__ */
|
|
1121
|
-
thread.resolved && /* @__PURE__ */
|
|
1122
|
-
!isDraft && /* @__PURE__ */
|
|
1123
|
-
thread.replies.length > 0 && /* @__PURE__ */
|
|
1124
|
-
/* @__PURE__ */
|
|
1125
|
-
/* @__PURE__ */
|
|
1430
|
+
quote && /* @__PURE__ */ jsx7("blockquote", { className: "border-l-2 border-border pl-2 text-xs text-muted-foreground line-clamp-2", children: quote }),
|
|
1431
|
+
thread.orphaned && /* @__PURE__ */ jsx7("span", { className: "text-xs text-destructive", children: "The commented text was deleted." }),
|
|
1432
|
+
thread.resolved && /* @__PURE__ */ jsx7("span", { className: "text-xs text-muted-foreground", children: "Resolved" }),
|
|
1433
|
+
!isDraft && /* @__PURE__ */ jsx7("p", { className: "whitespace-pre-wrap break-words", children: thread.body }),
|
|
1434
|
+
thread.replies.length > 0 && /* @__PURE__ */ jsx7("ul", { className: "flex flex-col gap-2 border-l border-border pl-3", role: "list", children: thread.replies.map((reply) => /* @__PURE__ */ jsxs6("li", { className: "flex flex-col gap-0.5", "data-reply-id": reply.id, children: [
|
|
1435
|
+
/* @__PURE__ */ jsxs6("span", { className: "text-xs", children: [
|
|
1436
|
+
/* @__PURE__ */ jsx7("span", { className: "font-medium", children: reply.author.name }),
|
|
1126
1437
|
" ",
|
|
1127
|
-
/* @__PURE__ */
|
|
1438
|
+
/* @__PURE__ */ jsx7("span", { className: "text-muted-foreground", children: formatTime(reply.createdAt) })
|
|
1128
1439
|
] }),
|
|
1129
|
-
/* @__PURE__ */
|
|
1440
|
+
/* @__PURE__ */ jsx7("p", { className: "whitespace-pre-wrap break-words", children: reply.body })
|
|
1130
1441
|
] }, reply.id)) }),
|
|
1131
|
-
(active || isDraft) && !thread.resolved && /* @__PURE__ */
|
|
1132
|
-
/* @__PURE__ */
|
|
1442
|
+
(active || isDraft) && !thread.resolved && /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-1", onClick: stop, children: [
|
|
1443
|
+
/* @__PURE__ */ jsx7(
|
|
1133
1444
|
"textarea",
|
|
1134
1445
|
{
|
|
1135
1446
|
className: textareaClass,
|
|
@@ -1147,9 +1458,9 @@ var CommentThreadCard = ({
|
|
|
1147
1458
|
}
|
|
1148
1459
|
}
|
|
1149
1460
|
),
|
|
1150
|
-
/* @__PURE__ */
|
|
1151
|
-
isDraft && /* @__PURE__ */
|
|
1152
|
-
/* @__PURE__ */
|
|
1461
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex justify-end gap-1", children: [
|
|
1462
|
+
isDraft && /* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass, onClick: remove, children: "Cancel" }),
|
|
1463
|
+
/* @__PURE__ */ jsx7(
|
|
1153
1464
|
"button",
|
|
1154
1465
|
{
|
|
1155
1466
|
type: "button",
|
|
@@ -1161,9 +1472,9 @@ var CommentThreadCard = ({
|
|
|
1161
1472
|
)
|
|
1162
1473
|
] })
|
|
1163
1474
|
] }),
|
|
1164
|
-
!isDraft && /* @__PURE__ */
|
|
1165
|
-
/* @__PURE__ */
|
|
1166
|
-
/* @__PURE__ */
|
|
1475
|
+
!isDraft && /* @__PURE__ */ jsxs6("div", { className: "flex justify-end gap-1", onClick: stop, children: [
|
|
1476
|
+
/* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass, onClick: () => setResolved(!thread.resolved), children: thread.resolved ? "Reopen" : "Resolve" }),
|
|
1477
|
+
/* @__PURE__ */ jsx7(
|
|
1167
1478
|
"button",
|
|
1168
1479
|
{
|
|
1169
1480
|
type: "button",
|
|
@@ -1179,7 +1490,7 @@ var CommentThreadCard = ({
|
|
|
1179
1490
|
};
|
|
1180
1491
|
|
|
1181
1492
|
// src/review/ReviewToolbar.tsx
|
|
1182
|
-
import { jsx as
|
|
1493
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1183
1494
|
var defaultDescriptionClass = "max-w-[16rem] truncate px-1 text-xs text-muted-foreground";
|
|
1184
1495
|
var buttonClass2 = "rounded px-2 py-1 text-xs text-foreground hover:bg-muted disabled:opacity-40 disabled:hover:bg-transparent";
|
|
1185
1496
|
var ReviewToolbar = ({
|
|
@@ -1192,8 +1503,8 @@ var ReviewToolbar = ({
|
|
|
1192
1503
|
const count = review.changes.length;
|
|
1193
1504
|
const current = review.currentChange;
|
|
1194
1505
|
const index = current ? review.changes.findIndex((c) => c.id === current.id) + 1 : 0;
|
|
1195
|
-
return /* @__PURE__ */
|
|
1196
|
-
/* @__PURE__ */
|
|
1506
|
+
return /* @__PURE__ */ jsxs7("div", { className, role: "toolbar", "aria-label": "Review", children: [
|
|
1507
|
+
/* @__PURE__ */ jsx8(
|
|
1197
1508
|
"button",
|
|
1198
1509
|
{
|
|
1199
1510
|
type: "button",
|
|
@@ -1204,8 +1515,8 @@ var ReviewToolbar = ({
|
|
|
1204
1515
|
children: review.enabled ? "Tracking on" : "Track changes"
|
|
1205
1516
|
}
|
|
1206
1517
|
),
|
|
1207
|
-
/* @__PURE__ */
|
|
1208
|
-
/* @__PURE__ */
|
|
1518
|
+
/* @__PURE__ */ jsx8("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
|
|
1519
|
+
/* @__PURE__ */ jsx8(
|
|
1209
1520
|
"button",
|
|
1210
1521
|
{
|
|
1211
1522
|
type: "button",
|
|
@@ -1216,8 +1527,8 @@ var ReviewToolbar = ({
|
|
|
1216
1527
|
children: "\u2039"
|
|
1217
1528
|
}
|
|
1218
1529
|
),
|
|
1219
|
-
/* @__PURE__ */
|
|
1220
|
-
/* @__PURE__ */
|
|
1530
|
+
/* @__PURE__ */ jsx8("span", { className: "min-w-[4rem] text-center text-xs text-muted-foreground", "aria-live": "polite", children: count === 0 ? "No changes" : current ? `${index} of ${count}` : `${count} changes` }),
|
|
1531
|
+
/* @__PURE__ */ jsx8(
|
|
1221
1532
|
"button",
|
|
1222
1533
|
{
|
|
1223
1534
|
type: "button",
|
|
@@ -1228,7 +1539,7 @@ var ReviewToolbar = ({
|
|
|
1228
1539
|
children: "\u203A"
|
|
1229
1540
|
}
|
|
1230
1541
|
),
|
|
1231
|
-
showDescription && current && /* @__PURE__ */
|
|
1542
|
+
showDescription && current && /* @__PURE__ */ jsxs7(
|
|
1232
1543
|
"span",
|
|
1233
1544
|
{
|
|
1234
1545
|
className: descriptionClassName,
|
|
@@ -1240,8 +1551,8 @@ var ReviewToolbar = ({
|
|
|
1240
1551
|
]
|
|
1241
1552
|
}
|
|
1242
1553
|
),
|
|
1243
|
-
/* @__PURE__ */
|
|
1244
|
-
/* @__PURE__ */
|
|
1554
|
+
/* @__PURE__ */ jsx8("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
|
|
1555
|
+
/* @__PURE__ */ jsx8(
|
|
1245
1556
|
"button",
|
|
1246
1557
|
{
|
|
1247
1558
|
type: "button",
|
|
@@ -1251,7 +1562,7 @@ var ReviewToolbar = ({
|
|
|
1251
1562
|
children: "Accept"
|
|
1252
1563
|
}
|
|
1253
1564
|
),
|
|
1254
|
-
/* @__PURE__ */
|
|
1565
|
+
/* @__PURE__ */ jsx8(
|
|
1255
1566
|
"button",
|
|
1256
1567
|
{
|
|
1257
1568
|
type: "button",
|
|
@@ -1261,18 +1572,18 @@ var ReviewToolbar = ({
|
|
|
1261
1572
|
children: "Reject"
|
|
1262
1573
|
}
|
|
1263
1574
|
),
|
|
1264
|
-
/* @__PURE__ */
|
|
1265
|
-
/* @__PURE__ */
|
|
1575
|
+
/* @__PURE__ */ jsx8("button", { type: "button", className: buttonClass2, disabled: count === 0, onClick: review.acceptAll, children: "Accept all" }),
|
|
1576
|
+
/* @__PURE__ */ jsx8("button", { type: "button", className: buttonClass2, disabled: count === 0, onClick: review.rejectAll, children: "Reject all" })
|
|
1266
1577
|
] });
|
|
1267
1578
|
};
|
|
1268
1579
|
|
|
1269
1580
|
// src/review/CommentButton.tsx
|
|
1270
|
-
import { jsx as
|
|
1581
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
1271
1582
|
var CommentButton = ({
|
|
1272
1583
|
editor,
|
|
1273
1584
|
className = "rounded px-2 py-1 text-xs text-popover-foreground hover:bg-muted",
|
|
1274
1585
|
children = "Comment"
|
|
1275
|
-
}) => /* @__PURE__ */
|
|
1586
|
+
}) => /* @__PURE__ */ jsx9(
|
|
1276
1587
|
"button",
|
|
1277
1588
|
{
|
|
1278
1589
|
type: "button",
|
|
@@ -1282,18 +1593,253 @@ var CommentButton = ({
|
|
|
1282
1593
|
children
|
|
1283
1594
|
}
|
|
1284
1595
|
);
|
|
1596
|
+
|
|
1597
|
+
// src/review/useDocumentCompare.ts
|
|
1598
|
+
import { useCallback as useCallback7, useMemo as useMemo10, useRef as useRef8, useState as useState9 } from "react";
|
|
1599
|
+
import {
|
|
1600
|
+
applyDocumentComparison
|
|
1601
|
+
} from "@pixerate/editor";
|
|
1602
|
+
function useDocumentCompare(editor, options) {
|
|
1603
|
+
const [summary, setSummary] = useState9(null);
|
|
1604
|
+
const defaults = useRef8(options);
|
|
1605
|
+
defaults.current = options;
|
|
1606
|
+
const compare = useCallback7(
|
|
1607
|
+
(original, revised, overrides) => {
|
|
1608
|
+
if (!editor || editor.isDestroyed) return null;
|
|
1609
|
+
const result = applyDocumentComparison(editor, original, revised, {
|
|
1610
|
+
...defaults.current,
|
|
1611
|
+
...overrides
|
|
1612
|
+
});
|
|
1613
|
+
setSummary(result.summary);
|
|
1614
|
+
return result.summary;
|
|
1615
|
+
},
|
|
1616
|
+
[editor]
|
|
1617
|
+
);
|
|
1618
|
+
const clear = useCallback7(() => setSummary(null), []);
|
|
1619
|
+
return useMemo10(() => ({ summary, compare, clear }), [summary, compare, clear]);
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
// src/find-replace/useFindReplace.ts
|
|
1623
|
+
import { useCallback as useCallback8, useMemo as useMemo11, useSyncExternalStore as useSyncExternalStore6 } from "react";
|
|
1624
|
+
import {
|
|
1625
|
+
EMPTY_FIND_REPLACE_SNAPSHOT,
|
|
1626
|
+
getFindReplaceStore
|
|
1627
|
+
} from "@pixerate/editor";
|
|
1628
|
+
var noopUnsubscribe2 = () => {
|
|
1629
|
+
};
|
|
1630
|
+
function useFindReplace(editor) {
|
|
1631
|
+
const store = getFindReplaceStore(editor ?? null);
|
|
1632
|
+
const subscribe = useCallback8(
|
|
1633
|
+
(onChange) => store ? store.subscribe(() => onChange()) : noopUnsubscribe2,
|
|
1634
|
+
[store]
|
|
1635
|
+
);
|
|
1636
|
+
const getSnapshot = () => store?.getSnapshot() ?? EMPTY_FIND_REPLACE_SNAPSHOT;
|
|
1637
|
+
const snapshot = useSyncExternalStore6(subscribe, getSnapshot, getSnapshot);
|
|
1638
|
+
return useMemo11(() => {
|
|
1639
|
+
const run = (fn) => {
|
|
1640
|
+
if (editor && !editor.isDestroyed && store) fn(editor);
|
|
1641
|
+
};
|
|
1642
|
+
return {
|
|
1643
|
+
...snapshot,
|
|
1644
|
+
available: !!store,
|
|
1645
|
+
matchCount: snapshot.matches.length,
|
|
1646
|
+
show: (mode = "find") => run((e) => e.commands.openFindReplace({ mode })),
|
|
1647
|
+
close: () => run((e) => e.commands.closeFindReplace()),
|
|
1648
|
+
setQuery: (query) => run((e) => e.commands.setSearch({ query })),
|
|
1649
|
+
setReplacement: (replacement) => run((e) => e.commands.setSearch({ replacement })),
|
|
1650
|
+
setOptions: (options) => run((e) => e.commands.setSearch({ options })),
|
|
1651
|
+
toggleOption: (option) => run((e) => e.commands.setSearch({ options: { [option]: !snapshot.options[option] } })),
|
|
1652
|
+
next: () => run((e) => e.commands.findNext()),
|
|
1653
|
+
previous: () => run((e) => e.commands.findPrevious()),
|
|
1654
|
+
replace: () => run((e) => e.commands.replaceCurrent()),
|
|
1655
|
+
replaceAll: () => run((e) => e.commands.replaceAll()),
|
|
1656
|
+
clear: () => run((e) => e.commands.clearSearch())
|
|
1657
|
+
};
|
|
1658
|
+
}, [snapshot, editor, store]);
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
// src/find-replace/FindReplaceBar.tsx
|
|
1662
|
+
import { useEffect as useEffect8, useRef as useRef9, useState as useState10 } from "react";
|
|
1663
|
+
import { FIND_OPTION_TOGGLES, formatFindStatus } from "@pixerate/editor";
|
|
1664
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1665
|
+
var inputClass = "min-w-0 flex-1 rounded border border-input bg-background px-2 py-1 text-xs text-foreground focus:outline-none focus:ring-1 focus:ring-ring";
|
|
1666
|
+
var buttonClass3 = "rounded px-2 py-1 text-xs text-foreground hover:bg-muted disabled:opacity-40 disabled:hover:bg-transparent";
|
|
1667
|
+
var toggleClass = (on) => `${buttonClass3} font-mono ${on ? "bg-primary/10 text-primary" : "text-muted-foreground"}`;
|
|
1668
|
+
var isMod = (e) => e.metaKey || e.ctrlKey;
|
|
1669
|
+
var FindReplaceBar = ({
|
|
1670
|
+
editor,
|
|
1671
|
+
className = "flex flex-col gap-1 rounded-md border border-border bg-background p-1 text-sm shadow-sm",
|
|
1672
|
+
alwaysOpen = false,
|
|
1673
|
+
showMoreOptions = true,
|
|
1674
|
+
onClose
|
|
1675
|
+
}) => {
|
|
1676
|
+
const find = useFindReplace(editor);
|
|
1677
|
+
const findInput = useRef9(null);
|
|
1678
|
+
const [moreOpen, setMoreOpen] = useState10(false);
|
|
1679
|
+
const visible = find.available && (alwaysOpen || find.open);
|
|
1680
|
+
useEffect8(() => {
|
|
1681
|
+
if (!visible || find.focusRequest === 0) return;
|
|
1682
|
+
findInput.current?.focus();
|
|
1683
|
+
findInput.current?.select();
|
|
1684
|
+
}, [visible, find.focusRequest]);
|
|
1685
|
+
if (!visible) return null;
|
|
1686
|
+
const showReplace = find.mode === "replace";
|
|
1687
|
+
const status = formatFindStatus(find);
|
|
1688
|
+
const hasMatches = find.matchCount > 0;
|
|
1689
|
+
const close = () => {
|
|
1690
|
+
find.close();
|
|
1691
|
+
editor?.commands.focus();
|
|
1692
|
+
onClose?.();
|
|
1693
|
+
};
|
|
1694
|
+
const onBarKeyDown = (e) => {
|
|
1695
|
+
if (e.key === "Escape") {
|
|
1696
|
+
e.preventDefault();
|
|
1697
|
+
close();
|
|
1698
|
+
} else if (isMod(e) && (e.key === "f" || e.key === "F")) {
|
|
1699
|
+
e.preventDefault();
|
|
1700
|
+
find.show("find");
|
|
1701
|
+
} else if (isMod(e) && (e.key === "h" || e.key === "H")) {
|
|
1702
|
+
e.preventDefault();
|
|
1703
|
+
find.show("replace");
|
|
1704
|
+
}
|
|
1705
|
+
};
|
|
1706
|
+
const onFindKeyDown = (e) => {
|
|
1707
|
+
if (e.key !== "Enter") return;
|
|
1708
|
+
e.preventDefault();
|
|
1709
|
+
if (e.shiftKey) find.previous();
|
|
1710
|
+
else find.next();
|
|
1711
|
+
};
|
|
1712
|
+
const onReplaceKeyDown = (e) => {
|
|
1713
|
+
if (e.key !== "Enter") return;
|
|
1714
|
+
e.preventDefault();
|
|
1715
|
+
if (isMod(e)) find.replaceAll();
|
|
1716
|
+
else find.replace();
|
|
1717
|
+
};
|
|
1718
|
+
return /* @__PURE__ */ jsxs8("div", { className, role: "search", "aria-label": "Find and replace", onKeyDown: onBarKeyDown, children: [
|
|
1719
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1", children: [
|
|
1720
|
+
/* @__PURE__ */ jsx10(
|
|
1721
|
+
"button",
|
|
1722
|
+
{
|
|
1723
|
+
type: "button",
|
|
1724
|
+
className: buttonClass3,
|
|
1725
|
+
"aria-label": "Toggle replace",
|
|
1726
|
+
"aria-expanded": showReplace,
|
|
1727
|
+
onClick: () => find.show(showReplace ? "find" : "replace"),
|
|
1728
|
+
children: showReplace ? "\u25BE" : "\u25B8"
|
|
1729
|
+
}
|
|
1730
|
+
),
|
|
1731
|
+
/* @__PURE__ */ jsx10(
|
|
1732
|
+
"input",
|
|
1733
|
+
{
|
|
1734
|
+
ref: findInput,
|
|
1735
|
+
type: "text",
|
|
1736
|
+
className: inputClass,
|
|
1737
|
+
placeholder: "Find",
|
|
1738
|
+
"aria-label": "Find",
|
|
1739
|
+
"aria-invalid": !!find.error,
|
|
1740
|
+
value: find.query,
|
|
1741
|
+
onChange: (e) => find.setQuery(e.target.value),
|
|
1742
|
+
onKeyDown: onFindKeyDown
|
|
1743
|
+
}
|
|
1744
|
+
),
|
|
1745
|
+
FIND_OPTION_TOGGLES.filter((t) => !t.advanced).map((t) => /* @__PURE__ */ jsx10(
|
|
1746
|
+
"button",
|
|
1747
|
+
{
|
|
1748
|
+
type: "button",
|
|
1749
|
+
className: toggleClass(!!find.options[t.key]),
|
|
1750
|
+
"aria-label": t.label,
|
|
1751
|
+
title: t.label,
|
|
1752
|
+
"aria-pressed": !!find.options[t.key],
|
|
1753
|
+
onClick: () => find.toggleOption(t.key),
|
|
1754
|
+
children: t.short
|
|
1755
|
+
},
|
|
1756
|
+
t.key
|
|
1757
|
+
)),
|
|
1758
|
+
/* @__PURE__ */ jsx10(
|
|
1759
|
+
"span",
|
|
1760
|
+
{
|
|
1761
|
+
className: `min-w-[5rem] px-1 text-center text-xs ${find.error ? "text-destructive" : "text-muted-foreground"}`,
|
|
1762
|
+
"aria-live": "polite",
|
|
1763
|
+
role: find.error ? "alert" : "status",
|
|
1764
|
+
"data-testid": "find-status",
|
|
1765
|
+
children: status
|
|
1766
|
+
}
|
|
1767
|
+
),
|
|
1768
|
+
/* @__PURE__ */ jsx10("button", { type: "button", className: buttonClass3, "aria-label": "Previous match", disabled: !hasMatches, onClick: find.previous, children: "\u2191" }),
|
|
1769
|
+
/* @__PURE__ */ jsx10("button", { type: "button", className: buttonClass3, "aria-label": "Next match", disabled: !hasMatches, onClick: find.next, children: "\u2193" }),
|
|
1770
|
+
showMoreOptions && /* @__PURE__ */ jsx10(
|
|
1771
|
+
"button",
|
|
1772
|
+
{
|
|
1773
|
+
type: "button",
|
|
1774
|
+
className: buttonClass3,
|
|
1775
|
+
"aria-label": "More options",
|
|
1776
|
+
"aria-expanded": moreOpen,
|
|
1777
|
+
onClick: () => setMoreOpen((v) => !v),
|
|
1778
|
+
children: "\u2026"
|
|
1779
|
+
}
|
|
1780
|
+
),
|
|
1781
|
+
/* @__PURE__ */ jsx10("button", { type: "button", className: buttonClass3, "aria-label": "Close find", onClick: close, children: "\xD7" })
|
|
1782
|
+
] }),
|
|
1783
|
+
showReplace && /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1 pl-7", children: [
|
|
1784
|
+
/* @__PURE__ */ jsx10(
|
|
1785
|
+
"input",
|
|
1786
|
+
{
|
|
1787
|
+
type: "text",
|
|
1788
|
+
className: inputClass,
|
|
1789
|
+
placeholder: "Replace",
|
|
1790
|
+
"aria-label": "Replace",
|
|
1791
|
+
value: find.replacement,
|
|
1792
|
+
onChange: (e) => find.setReplacement(e.target.value),
|
|
1793
|
+
onKeyDown: onReplaceKeyDown
|
|
1794
|
+
}
|
|
1795
|
+
),
|
|
1796
|
+
/* @__PURE__ */ jsx10(
|
|
1797
|
+
"button",
|
|
1798
|
+
{
|
|
1799
|
+
type: "button",
|
|
1800
|
+
className: buttonClass3,
|
|
1801
|
+
disabled: !hasMatches || find.currentIndex < 0,
|
|
1802
|
+
onClick: find.replace,
|
|
1803
|
+
children: "Replace"
|
|
1804
|
+
}
|
|
1805
|
+
),
|
|
1806
|
+
/* @__PURE__ */ jsx10("button", { type: "button", className: buttonClass3, disabled: !hasMatches, onClick: find.replaceAll, children: "Replace all" }),
|
|
1807
|
+
find.lastReplaceCount !== null && /* @__PURE__ */ jsx10("span", { className: "px-1 text-xs text-muted-foreground", "aria-live": "polite", children: find.lastReplaceCount === 1 ? "1 replaced" : `${find.lastReplaceCount} replaced` })
|
|
1808
|
+
] }),
|
|
1809
|
+
showMoreOptions && moreOpen && /* @__PURE__ */ jsx10("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 pl-7 text-xs text-muted-foreground", children: FIND_OPTION_TOGGLES.filter((t) => t.advanced).map((t) => /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-1", children: [
|
|
1810
|
+
/* @__PURE__ */ jsx10(
|
|
1811
|
+
"input",
|
|
1812
|
+
{
|
|
1813
|
+
type: "checkbox",
|
|
1814
|
+
checked: !!find.options[t.key],
|
|
1815
|
+
onChange: () => find.toggleOption(t.key)
|
|
1816
|
+
}
|
|
1817
|
+
),
|
|
1818
|
+
t.label
|
|
1819
|
+
] }, t.key)) })
|
|
1820
|
+
] });
|
|
1821
|
+
};
|
|
1285
1822
|
export {
|
|
1823
|
+
AdjustmentLayersPanel,
|
|
1286
1824
|
BubbleMenu,
|
|
1287
1825
|
CommandPalette,
|
|
1288
1826
|
CommentButton,
|
|
1289
1827
|
CommentsPanel,
|
|
1828
|
+
ControlSlider,
|
|
1829
|
+
CurvesEditor,
|
|
1290
1830
|
DirtyTracker2 as DirtyTracker,
|
|
1291
1831
|
EditorContent,
|
|
1292
1832
|
EditorSlotsProvider,
|
|
1833
|
+
FindReplaceBar,
|
|
1293
1834
|
FormulaHints,
|
|
1835
|
+
GenerationSettings,
|
|
1836
|
+
Histogram,
|
|
1294
1837
|
HistoryManager2 as HistoryManager,
|
|
1295
1838
|
ImageEditor,
|
|
1839
|
+
JobProgress,
|
|
1840
|
+
MergePreview,
|
|
1296
1841
|
OutlinePanel,
|
|
1842
|
+
RecoveryPrompt,
|
|
1297
1843
|
ReviewToolbar,
|
|
1298
1844
|
SpreadsheetContextMenu,
|
|
1299
1845
|
SpreadsheetEditor,
|
|
@@ -1305,18 +1851,25 @@ export {
|
|
|
1305
1851
|
createHistoryManager2 as createHistoryManager,
|
|
1306
1852
|
defaultIsEqual,
|
|
1307
1853
|
useAgentToolProvider,
|
|
1854
|
+
useAutosave,
|
|
1308
1855
|
useCommandPalette,
|
|
1309
1856
|
useCommandShortcuts,
|
|
1310
1857
|
useComments,
|
|
1311
1858
|
useDismissGuard,
|
|
1859
|
+
useDocumentCompare,
|
|
1312
1860
|
useEditor2 as useEditor,
|
|
1313
1861
|
useEditorSlots,
|
|
1314
1862
|
useFileImport,
|
|
1315
1863
|
useFillHandle,
|
|
1864
|
+
useFindReplace,
|
|
1865
|
+
useGenerationPlan,
|
|
1316
1866
|
useHistory,
|
|
1317
1867
|
useHistoryDirty,
|
|
1318
1868
|
useHistoryShortcuts,
|
|
1319
1869
|
useImageEditor,
|
|
1870
|
+
useImageHistogram,
|
|
1871
|
+
useJobProgress,
|
|
1872
|
+
useMergePreview,
|
|
1320
1873
|
useNavigationGuard,
|
|
1321
1874
|
useOutline,
|
|
1322
1875
|
usePromptEditor,
|