@open-file-viewer/core 0.1.1 → 0.1.3
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 +33 -1
- package/dist/index.cjs +779 -239
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -1
- package/dist/index.d.ts +45 -1
- package/dist/index.js +779 -239
- package/dist/index.js.map +1 -1
- package/dist/style.css +481 -55
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -682,6 +682,7 @@ function createViewer(options) {
|
|
|
682
682
|
file,
|
|
683
683
|
size: getElementSize(viewport),
|
|
684
684
|
options: normalizedOptions,
|
|
685
|
+
toolbar: toolbar?.getContext(),
|
|
685
686
|
setLoading,
|
|
686
687
|
setError
|
|
687
688
|
});
|
|
@@ -855,18 +856,36 @@ function createToolbar(toolbar, viewport, queue) {
|
|
|
855
856
|
element.setAttribute("role", "toolbar");
|
|
856
857
|
element.setAttribute("aria-label", "File preview toolbar");
|
|
857
858
|
let file;
|
|
859
|
+
let currentIndex = 0;
|
|
860
|
+
let currentLength = queue.getLength();
|
|
858
861
|
let queueLabel;
|
|
859
862
|
let previousButton;
|
|
860
863
|
let nextButton;
|
|
864
|
+
let zoomResetButton;
|
|
865
|
+
let currentZoom;
|
|
861
866
|
const commandButtons = [];
|
|
867
|
+
const customButtons = [];
|
|
862
868
|
const disposers = [];
|
|
863
869
|
const search = createSearchController(viewport);
|
|
864
870
|
let searchInput;
|
|
865
871
|
let searchCount;
|
|
866
|
-
|
|
872
|
+
let canRunCommand = (_command) => false;
|
|
873
|
+
const getContext = () => createToolbarContext({
|
|
874
|
+
file,
|
|
875
|
+
index: currentIndex,
|
|
876
|
+
length: currentLength,
|
|
877
|
+
viewport,
|
|
878
|
+
queue,
|
|
879
|
+
element,
|
|
880
|
+
search,
|
|
881
|
+
canCommand: canRunCommand,
|
|
882
|
+
zoom: currentZoom,
|
|
883
|
+
setZoom
|
|
884
|
+
});
|
|
885
|
+
const addButton = (label, title, action, className, icon) => {
|
|
867
886
|
const button = document.createElement("button");
|
|
868
887
|
button.type = "button";
|
|
869
|
-
button
|
|
888
|
+
setToolbarButtonContent(button, label, icon);
|
|
870
889
|
button.title = title;
|
|
871
890
|
button.setAttribute("aria-label", title);
|
|
872
891
|
if (className) {
|
|
@@ -877,68 +896,119 @@ function createToolbar(toolbar, viewport, queue) {
|
|
|
877
896
|
disposers.push(() => button.removeEventListener("click", action));
|
|
878
897
|
return button;
|
|
879
898
|
};
|
|
880
|
-
const
|
|
881
|
-
const button = document.createElement("button");
|
|
882
|
-
button.type = "button";
|
|
883
|
-
button.textContent = label;
|
|
884
|
-
button.title = title;
|
|
885
|
-
button.setAttribute("aria-label", title);
|
|
886
|
-
const listener = () => {
|
|
887
|
-
void action();
|
|
888
|
-
};
|
|
889
|
-
button.addEventListener("click", listener);
|
|
890
|
-
element.append(button);
|
|
891
|
-
disposers.push(() => button.removeEventListener("click", listener));
|
|
892
|
-
return button;
|
|
893
|
-
};
|
|
894
|
-
if (queue.getLength() > 1) {
|
|
895
|
-
previousButton = addQueueButton("Prev", "Previous file", queue.previous);
|
|
896
|
-
nextButton = addQueueButton("Next", "Next file", queue.next);
|
|
897
|
-
queueLabel = document.createElement("span");
|
|
898
|
-
queueLabel.className = "ofv-toolbar-queue";
|
|
899
|
-
element.append(queueLabel);
|
|
900
|
-
}
|
|
901
|
-
const addCommandButton = (label, title, command) => {
|
|
899
|
+
const addCommandButton = (id, label, title, command) => {
|
|
902
900
|
const button = addButton(label, title, () => {
|
|
903
901
|
queue.command(command);
|
|
904
|
-
});
|
|
902
|
+
}, void 0, options.icons?.[id]);
|
|
905
903
|
button.disabled = true;
|
|
906
904
|
commandButtons.push({ button, command });
|
|
907
905
|
};
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
if (options.rotate) {
|
|
914
|
-
addCommandButton("Rotate", "Rotate right", "rotate-right");
|
|
915
|
-
}
|
|
916
|
-
if (options.download !== false) {
|
|
917
|
-
addButton("Download", "Download file", () => {
|
|
918
|
-
if (file) {
|
|
919
|
-
downloadFile(file);
|
|
906
|
+
const renderDefaultAction = (id) => {
|
|
907
|
+
if (!isBuiltInToolbarAction(id)) {
|
|
908
|
+
const customAction = options.actions?.find((action) => action.id === id);
|
|
909
|
+
if (customAction) {
|
|
910
|
+
renderCustomAction(customAction);
|
|
920
911
|
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
if (id === "previous" && queue.getLength() > 1) {
|
|
915
|
+
previousButton = addButton(
|
|
916
|
+
getToolbarLabel(options, "previous"),
|
|
917
|
+
getToolbarTitle(options, "previous"),
|
|
918
|
+
() => void queue.previous(),
|
|
919
|
+
void 0,
|
|
920
|
+
options.icons?.previous
|
|
921
|
+
);
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (id === "next" && queue.getLength() > 1) {
|
|
925
|
+
nextButton = addButton(
|
|
926
|
+
getToolbarLabel(options, "next"),
|
|
927
|
+
getToolbarTitle(options, "next"),
|
|
928
|
+
() => void queue.next(),
|
|
929
|
+
void 0,
|
|
930
|
+
options.icons?.next
|
|
931
|
+
);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
if (id === "queue" && queue.getLength() > 1) {
|
|
935
|
+
queueLabel = document.createElement("span");
|
|
936
|
+
queueLabel.className = "ofv-toolbar-queue";
|
|
937
|
+
element.append(queueLabel);
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
if (id === "zoom-out" && options.zoom) {
|
|
941
|
+
addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "zoom-out");
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
if (id === "zoom-in" && options.zoom) {
|
|
945
|
+
addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "zoom-in");
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
if (id === "zoom-reset" && options.zoom) {
|
|
949
|
+
addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "zoom-reset");
|
|
950
|
+
zoomResetButton = commandButtons[commandButtons.length - 1]?.button;
|
|
951
|
+
updateZoomLabel();
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
if (id === "rotate-right" && options.rotate) {
|
|
955
|
+
addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "rotate-right");
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
if (id === "download" && options.download !== false) {
|
|
959
|
+
addButton(
|
|
960
|
+
getToolbarLabel(options, id),
|
|
961
|
+
getToolbarTitle(options, id),
|
|
962
|
+
() => getContext().download(),
|
|
963
|
+
void 0,
|
|
964
|
+
options.icons?.download
|
|
965
|
+
);
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
if (id === "fullscreen" && options.fullscreen !== false) {
|
|
969
|
+
addButton(
|
|
970
|
+
getToolbarLabel(options, id),
|
|
971
|
+
getToolbarTitle(options, id),
|
|
972
|
+
() => getContext().fullscreen(),
|
|
973
|
+
void 0,
|
|
974
|
+
options.icons?.fullscreen
|
|
975
|
+
);
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
if (id === "print" && options.print) {
|
|
979
|
+
addButton(
|
|
980
|
+
getToolbarLabel(options, id),
|
|
981
|
+
getToolbarTitle(options, id),
|
|
982
|
+
() => getContext().print(),
|
|
983
|
+
void 0,
|
|
984
|
+
options.icons?.print
|
|
985
|
+
);
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
if (id === "search" && options.search !== false) {
|
|
989
|
+
renderSearchControl();
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
const renderCustomAction = (action) => {
|
|
994
|
+
const button = addButton(
|
|
995
|
+
action.label,
|
|
996
|
+
action.title || action.label,
|
|
997
|
+
() => void action.onClick(getContext()),
|
|
998
|
+
action.className,
|
|
999
|
+
action.icon
|
|
1000
|
+
);
|
|
1001
|
+
button.dataset.ofvToolbarAction = action.id;
|
|
1002
|
+
customButtons.push({ button, action });
|
|
1003
|
+
};
|
|
1004
|
+
const renderSearchControl = () => {
|
|
935
1005
|
const searchGroup = document.createElement("div");
|
|
936
1006
|
searchGroup.className = "ofv-toolbar-search";
|
|
937
|
-
searchGroup.title =
|
|
1007
|
+
searchGroup.title = getToolbarTitle(options, "search");
|
|
938
1008
|
const nextSearchInput = document.createElement("input");
|
|
939
1009
|
nextSearchInput.type = "search";
|
|
940
|
-
nextSearchInput.placeholder = "
|
|
941
|
-
nextSearchInput.setAttribute("aria-label",
|
|
1010
|
+
nextSearchInput.placeholder = getToolbarLabel(options, "search");
|
|
1011
|
+
nextSearchInput.setAttribute("aria-label", getToolbarTitle(options, "search"));
|
|
942
1012
|
const nextSearchCount = document.createElement("span");
|
|
943
1013
|
nextSearchCount.className = "ofv-toolbar-search-count";
|
|
944
1014
|
searchInput = nextSearchInput;
|
|
@@ -951,18 +1021,71 @@ function createToolbar(toolbar, viewport, queue) {
|
|
|
951
1021
|
searchGroup.append(nextSearchInput, nextSearchCount);
|
|
952
1022
|
element.append(searchGroup);
|
|
953
1023
|
disposers.push(() => nextSearchInput.removeEventListener("input", runSearch));
|
|
1024
|
+
};
|
|
1025
|
+
const renderToolbar = () => {
|
|
1026
|
+
if (options.render) {
|
|
1027
|
+
element.replaceChildren();
|
|
1028
|
+
const customElement = options.render(getContext());
|
|
1029
|
+
if (customElement) {
|
|
1030
|
+
element.append(customElement);
|
|
1031
|
+
}
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
getToolbarOrder(options, queue.getLength()).forEach(renderDefaultAction);
|
|
1035
|
+
getImplicitCustomActions(options).forEach(renderCustomAction);
|
|
1036
|
+
};
|
|
1037
|
+
renderToolbar();
|
|
1038
|
+
const updateCustomButtons = () => {
|
|
1039
|
+
const context = getContext();
|
|
1040
|
+
for (const { button, action } of customButtons) {
|
|
1041
|
+
button.disabled = evaluateToolbarFlag(action.disabled, context);
|
|
1042
|
+
button.hidden = evaluateToolbarFlag(action.hidden, context);
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
const resetSearch = () => {
|
|
1046
|
+
search.clear();
|
|
1047
|
+
if (searchInput) {
|
|
1048
|
+
searchInput.value = "";
|
|
1049
|
+
}
|
|
1050
|
+
if (searchCount) {
|
|
1051
|
+
searchCount.textContent = "";
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1054
|
+
function setZoom(zoom) {
|
|
1055
|
+
currentZoom = typeof zoom === "number" && Number.isFinite(zoom) && zoom > 0 ? zoom : void 0;
|
|
1056
|
+
updateZoomLabel();
|
|
1057
|
+
updateCustomButtons();
|
|
1058
|
+
refreshCustomRender();
|
|
1059
|
+
}
|
|
1060
|
+
function updateZoomLabel() {
|
|
1061
|
+
if (!zoomResetButton) {
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
setToolbarButtonContent(
|
|
1065
|
+
zoomResetButton,
|
|
1066
|
+
currentZoom === void 0 ? getToolbarLabel(options, "zoom-reset") : formatToolbarZoom(currentZoom),
|
|
1067
|
+
options.icons?.["zoom-reset"]
|
|
1068
|
+
);
|
|
954
1069
|
}
|
|
1070
|
+
const refreshCustomRender = () => {
|
|
1071
|
+
if (!options.render) {
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
element.replaceChildren();
|
|
1075
|
+
const customElement = options.render(getContext());
|
|
1076
|
+
if (customElement) {
|
|
1077
|
+
element.append(customElement);
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
955
1080
|
return {
|
|
956
1081
|
element,
|
|
957
1082
|
update(nextFile, index, length) {
|
|
958
1083
|
file = nextFile;
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
searchCount.textContent = "";
|
|
965
|
-
}
|
|
1084
|
+
currentIndex = index;
|
|
1085
|
+
currentLength = length;
|
|
1086
|
+
currentZoom = void 0;
|
|
1087
|
+
updateZoomLabel();
|
|
1088
|
+
resetSearch();
|
|
966
1089
|
commandButtons.forEach(({ button }) => {
|
|
967
1090
|
button.disabled = true;
|
|
968
1091
|
});
|
|
@@ -975,20 +1098,257 @@ function createToolbar(toolbar, viewport, queue) {
|
|
|
975
1098
|
if (nextButton) {
|
|
976
1099
|
nextButton.disabled = index >= length - 1;
|
|
977
1100
|
}
|
|
1101
|
+
updateCustomButtons();
|
|
1102
|
+
refreshCustomRender();
|
|
978
1103
|
},
|
|
979
1104
|
setCommandSupport(isSupported) {
|
|
1105
|
+
canRunCommand = isSupported;
|
|
980
1106
|
commandButtons.forEach(({ button, command }) => {
|
|
981
1107
|
button.disabled = !isSupported(command);
|
|
982
1108
|
});
|
|
1109
|
+
updateCustomButtons();
|
|
1110
|
+
refreshCustomRender();
|
|
983
1111
|
},
|
|
1112
|
+
getContext,
|
|
1113
|
+
setZoom,
|
|
984
1114
|
destroy() {
|
|
985
1115
|
search.clear();
|
|
986
1116
|
for (const dispose of disposers) {
|
|
987
1117
|
dispose();
|
|
988
1118
|
}
|
|
1119
|
+
element.replaceChildren();
|
|
989
1120
|
}
|
|
990
1121
|
};
|
|
991
1122
|
}
|
|
1123
|
+
function createToolbarContext({
|
|
1124
|
+
file,
|
|
1125
|
+
index,
|
|
1126
|
+
length,
|
|
1127
|
+
viewport,
|
|
1128
|
+
queue,
|
|
1129
|
+
element,
|
|
1130
|
+
search,
|
|
1131
|
+
canCommand,
|
|
1132
|
+
zoom,
|
|
1133
|
+
setZoom
|
|
1134
|
+
}) {
|
|
1135
|
+
return {
|
|
1136
|
+
file,
|
|
1137
|
+
index,
|
|
1138
|
+
length,
|
|
1139
|
+
viewport,
|
|
1140
|
+
canPrevious: index > 0,
|
|
1141
|
+
canNext: index < length - 1,
|
|
1142
|
+
zoom,
|
|
1143
|
+
zoomLabel: zoom === void 0 ? void 0 : formatToolbarZoom(zoom),
|
|
1144
|
+
async previous() {
|
|
1145
|
+
await queue.previous();
|
|
1146
|
+
},
|
|
1147
|
+
async next() {
|
|
1148
|
+
await queue.next();
|
|
1149
|
+
},
|
|
1150
|
+
command: queue.command,
|
|
1151
|
+
canCommand,
|
|
1152
|
+
setZoom,
|
|
1153
|
+
download() {
|
|
1154
|
+
if (file) {
|
|
1155
|
+
downloadFile(file);
|
|
1156
|
+
}
|
|
1157
|
+
},
|
|
1158
|
+
fullscreen() {
|
|
1159
|
+
void element.parentElement?.requestFullscreen?.();
|
|
1160
|
+
},
|
|
1161
|
+
print() {
|
|
1162
|
+
printPreview(viewport);
|
|
1163
|
+
},
|
|
1164
|
+
search: search.search,
|
|
1165
|
+
clearSearch: search.clear
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
var defaultToolbarLabels = {
|
|
1169
|
+
previous: "Prev",
|
|
1170
|
+
next: "Next",
|
|
1171
|
+
queue: "",
|
|
1172
|
+
"zoom-out": "-",
|
|
1173
|
+
"zoom-in": "+",
|
|
1174
|
+
"zoom-reset": "100%",
|
|
1175
|
+
"rotate-right": "Rotate",
|
|
1176
|
+
download: "Download",
|
|
1177
|
+
fullscreen: "Fullscreen",
|
|
1178
|
+
print: "Print",
|
|
1179
|
+
search: "Search"
|
|
1180
|
+
};
|
|
1181
|
+
var defaultToolbarTitles = {
|
|
1182
|
+
previous: "Previous file",
|
|
1183
|
+
next: "Next file",
|
|
1184
|
+
queue: "Current file position",
|
|
1185
|
+
"zoom-out": "Zoom out",
|
|
1186
|
+
"zoom-in": "Zoom in",
|
|
1187
|
+
"zoom-reset": "Reset zoom",
|
|
1188
|
+
"rotate-right": "Rotate right",
|
|
1189
|
+
download: "Download file",
|
|
1190
|
+
fullscreen: "Open preview fullscreen",
|
|
1191
|
+
print: "Print preview",
|
|
1192
|
+
search: "Search preview text"
|
|
1193
|
+
};
|
|
1194
|
+
function getToolbarLabel(options, id) {
|
|
1195
|
+
return options.labels?.[id] ?? defaultToolbarLabels[id];
|
|
1196
|
+
}
|
|
1197
|
+
function getToolbarTitle(options, id) {
|
|
1198
|
+
return options.titles?.[id] ?? options.labels?.[id] ?? defaultToolbarTitles[id];
|
|
1199
|
+
}
|
|
1200
|
+
function formatToolbarZoom(zoom) {
|
|
1201
|
+
return `${Math.round(zoom * 100)}%`;
|
|
1202
|
+
}
|
|
1203
|
+
function getToolbarOrder(options, queueLength) {
|
|
1204
|
+
if (options.order) {
|
|
1205
|
+
return options.order;
|
|
1206
|
+
}
|
|
1207
|
+
const actions = [];
|
|
1208
|
+
if (queueLength > 1) {
|
|
1209
|
+
actions.push("previous", "next", "queue");
|
|
1210
|
+
}
|
|
1211
|
+
if (options.zoom) {
|
|
1212
|
+
actions.push("zoom-out", "zoom-in", "zoom-reset");
|
|
1213
|
+
}
|
|
1214
|
+
if (options.rotate) {
|
|
1215
|
+
actions.push("rotate-right");
|
|
1216
|
+
}
|
|
1217
|
+
if (options.download !== false) {
|
|
1218
|
+
actions.push("download");
|
|
1219
|
+
}
|
|
1220
|
+
if (options.fullscreen !== false) {
|
|
1221
|
+
actions.push("fullscreen");
|
|
1222
|
+
}
|
|
1223
|
+
if (options.print) {
|
|
1224
|
+
actions.push("print");
|
|
1225
|
+
}
|
|
1226
|
+
if (options.search !== false) {
|
|
1227
|
+
actions.push("search");
|
|
1228
|
+
}
|
|
1229
|
+
return actions;
|
|
1230
|
+
}
|
|
1231
|
+
function getImplicitCustomActions(options) {
|
|
1232
|
+
if (options.order || !options.actions) {
|
|
1233
|
+
return [];
|
|
1234
|
+
}
|
|
1235
|
+
return [...options.actions].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
1236
|
+
}
|
|
1237
|
+
function evaluateToolbarFlag(value, context) {
|
|
1238
|
+
return typeof value === "function" ? value(context) : Boolean(value);
|
|
1239
|
+
}
|
|
1240
|
+
function setToolbarButtonContent(button, label, icon) {
|
|
1241
|
+
button.replaceChildren();
|
|
1242
|
+
if (!icon) {
|
|
1243
|
+
button.textContent = label;
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
const iconElement = document.createElement("span");
|
|
1247
|
+
iconElement.className = "ofv-toolbar-icon";
|
|
1248
|
+
iconElement.setAttribute("aria-hidden", "true");
|
|
1249
|
+
if (typeof icon === "string") {
|
|
1250
|
+
iconElement.append(sanitizeToolbarIcon(icon));
|
|
1251
|
+
} else {
|
|
1252
|
+
iconElement.append(icon.cloneNode(true));
|
|
1253
|
+
}
|
|
1254
|
+
const labelElement = document.createElement("span");
|
|
1255
|
+
labelElement.className = "ofv-toolbar-label";
|
|
1256
|
+
labelElement.textContent = label;
|
|
1257
|
+
button.append(iconElement, labelElement);
|
|
1258
|
+
}
|
|
1259
|
+
var allowedToolbarIconTags = /* @__PURE__ */ new Set([
|
|
1260
|
+
"svg",
|
|
1261
|
+
"g",
|
|
1262
|
+
"path",
|
|
1263
|
+
"circle",
|
|
1264
|
+
"rect",
|
|
1265
|
+
"line",
|
|
1266
|
+
"polyline",
|
|
1267
|
+
"polygon",
|
|
1268
|
+
"ellipse",
|
|
1269
|
+
"defs",
|
|
1270
|
+
"title",
|
|
1271
|
+
"desc"
|
|
1272
|
+
]);
|
|
1273
|
+
var allowedToolbarIconAttrs = /* @__PURE__ */ new Set([
|
|
1274
|
+
"aria-hidden",
|
|
1275
|
+
"class",
|
|
1276
|
+
"cx",
|
|
1277
|
+
"cy",
|
|
1278
|
+
"d",
|
|
1279
|
+
"fill",
|
|
1280
|
+
"focusable",
|
|
1281
|
+
"height",
|
|
1282
|
+
"id",
|
|
1283
|
+
"points",
|
|
1284
|
+
"r",
|
|
1285
|
+
"rx",
|
|
1286
|
+
"ry",
|
|
1287
|
+
"stroke",
|
|
1288
|
+
"stroke-linecap",
|
|
1289
|
+
"stroke-linejoin",
|
|
1290
|
+
"stroke-width",
|
|
1291
|
+
"transform",
|
|
1292
|
+
"viewBox",
|
|
1293
|
+
"width",
|
|
1294
|
+
"x",
|
|
1295
|
+
"x1",
|
|
1296
|
+
"x2",
|
|
1297
|
+
"y",
|
|
1298
|
+
"y1",
|
|
1299
|
+
"y2"
|
|
1300
|
+
]);
|
|
1301
|
+
function sanitizeToolbarIcon(icon) {
|
|
1302
|
+
const template = document.createElement("template");
|
|
1303
|
+
template.innerHTML = icon.trim();
|
|
1304
|
+
const fragment = document.createDocumentFragment();
|
|
1305
|
+
for (const child of Array.from(template.content.childNodes)) {
|
|
1306
|
+
const sanitized = sanitizeToolbarIconNode(child);
|
|
1307
|
+
if (sanitized) {
|
|
1308
|
+
fragment.append(sanitized);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
return fragment;
|
|
1312
|
+
}
|
|
1313
|
+
function sanitizeToolbarIconNode(node) {
|
|
1314
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
1315
|
+
const text = node.textContent || "";
|
|
1316
|
+
return text.trim() ? document.createTextNode(text) : null;
|
|
1317
|
+
}
|
|
1318
|
+
if (!(node instanceof Element)) {
|
|
1319
|
+
return null;
|
|
1320
|
+
}
|
|
1321
|
+
const tagName = node.tagName.toLowerCase();
|
|
1322
|
+
if (!allowedToolbarIconTags.has(tagName)) {
|
|
1323
|
+
return null;
|
|
1324
|
+
}
|
|
1325
|
+
const sanitized = document.createElementNS("http://www.w3.org/2000/svg", tagName);
|
|
1326
|
+
for (const attr of Array.from(node.attributes)) {
|
|
1327
|
+
if (isSafeToolbarIconAttribute(attr.name, attr.value)) {
|
|
1328
|
+
sanitized.setAttribute(attr.name, attr.value);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
for (const child of Array.from(node.childNodes)) {
|
|
1332
|
+
const sanitizedChild = sanitizeToolbarIconNode(child);
|
|
1333
|
+
if (sanitizedChild) {
|
|
1334
|
+
sanitized.append(sanitizedChild);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
return sanitized;
|
|
1338
|
+
}
|
|
1339
|
+
function isSafeToolbarIconAttribute(name, value) {
|
|
1340
|
+
const attrName = name.toLowerCase();
|
|
1341
|
+
if (attrName.startsWith("on") || attrName.includes(":")) {
|
|
1342
|
+
return false;
|
|
1343
|
+
}
|
|
1344
|
+
if (!allowedToolbarIconAttrs.has(name) && !allowedToolbarIconAttrs.has(attrName) && !attrName.startsWith("data-")) {
|
|
1345
|
+
return false;
|
|
1346
|
+
}
|
|
1347
|
+
return !/^\s*(?:javascript|data:text\/html|vbscript):/i.test(value);
|
|
1348
|
+
}
|
|
1349
|
+
function isBuiltInToolbarAction(id) {
|
|
1350
|
+
return id in defaultToolbarLabels;
|
|
1351
|
+
}
|
|
992
1352
|
function createSearchController(root) {
|
|
993
1353
|
const markerClass = "ofv-search-match";
|
|
994
1354
|
const clear = () => {
|
|
@@ -1397,6 +1757,7 @@ function imagePlugin() {
|
|
|
1397
1757
|
const updateTransform = () => {
|
|
1398
1758
|
image.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale}) rotate(${rotation}deg)`;
|
|
1399
1759
|
zoomLabel.textContent = `${Math.round(scale * 100)}%`;
|
|
1760
|
+
ctx.toolbar?.setZoom(scale);
|
|
1400
1761
|
};
|
|
1401
1762
|
const showImageFallback = () => {
|
|
1402
1763
|
stage.replaceChildren(createImageFallback(ctx.file.name, url));
|
|
@@ -1512,6 +1873,7 @@ function imagePlugin() {
|
|
|
1512
1873
|
image.style.maxHeight = `${Math.max(0, size.height - controls.offsetHeight)}px`;
|
|
1513
1874
|
},
|
|
1514
1875
|
destroy() {
|
|
1876
|
+
ctx.toolbar?.setZoom(void 0);
|
|
1515
1877
|
for (const dispose of disposers) {
|
|
1516
1878
|
dispose();
|
|
1517
1879
|
}
|
|
@@ -3070,7 +3432,6 @@ var mimeLangMap = {
|
|
|
3070
3432
|
};
|
|
3071
3433
|
var MAX_HIGHLIGHT_CHARS = 18e4;
|
|
3072
3434
|
var MAX_RENDER_CHARS = 6e5;
|
|
3073
|
-
var MONACO_LOADER_KEY = "__OFV_MONACO_LOADER__";
|
|
3074
3435
|
function loadPrismCss(theme) {
|
|
3075
3436
|
const lightId = "ofv-prism-css-light";
|
|
3076
3437
|
const darkId = "ofv-prism-css-dark";
|
|
@@ -3294,10 +3655,6 @@ function textPlugin() {
|
|
|
3294
3655
|
wrapButton.addEventListener("click", () => {
|
|
3295
3656
|
const wrapped = wrapper.classList.toggle("is-wrapped");
|
|
3296
3657
|
wrapButton.setAttribute("aria-pressed", String(wrapped));
|
|
3297
|
-
monacoEditor?.updateOptions?.({ wordWrap: wrapped ? "on" : "off" });
|
|
3298
|
-
if (fallbackEditor) {
|
|
3299
|
-
fallbackEditor.wrap = wrapped ? "soft" : "off";
|
|
3300
|
-
}
|
|
3301
3658
|
});
|
|
3302
3659
|
const copyButton = document.createElement("button");
|
|
3303
3660
|
copyButton.type = "button";
|
|
@@ -3322,12 +3679,7 @@ function textPlugin() {
|
|
|
3322
3679
|
downloadText(ctx.file.name, text);
|
|
3323
3680
|
status.textContent = "Download ready";
|
|
3324
3681
|
});
|
|
3325
|
-
|
|
3326
|
-
editorButton.type = "button";
|
|
3327
|
-
editorButton.className = "ofv-code-action";
|
|
3328
|
-
editorButton.textContent = "Editor";
|
|
3329
|
-
editorButton.setAttribute("aria-pressed", "false");
|
|
3330
|
-
actions.append(status, editorButton, wrapButton, copyButton, downloadButton);
|
|
3682
|
+
actions.append(wrapButton, copyButton, downloadButton, status);
|
|
3331
3683
|
header.append(title, actions);
|
|
3332
3684
|
const structureSummary = createTextStructureSummary(text, ext, lang, ctx.file.mimeType);
|
|
3333
3685
|
const body = document.createElement("div");
|
|
@@ -3343,93 +3695,6 @@ function textPlugin() {
|
|
|
3343
3695
|
code.textContent = codeText;
|
|
3344
3696
|
pre.appendChild(code);
|
|
3345
3697
|
body.append(gutter, pre);
|
|
3346
|
-
const editorHost = document.createElement("div");
|
|
3347
|
-
editorHost.className = "ofv-code-editor";
|
|
3348
|
-
editorHost.hidden = true;
|
|
3349
|
-
let monacoEditor;
|
|
3350
|
-
let monacoModel;
|
|
3351
|
-
let fallbackEditor;
|
|
3352
|
-
let editorReady = false;
|
|
3353
|
-
let editorLoading;
|
|
3354
|
-
const showReader = () => {
|
|
3355
|
-
editorHost.hidden = true;
|
|
3356
|
-
body.hidden = false;
|
|
3357
|
-
wrapper.classList.remove("is-editor");
|
|
3358
|
-
editorButton.textContent = "Editor";
|
|
3359
|
-
editorButton.setAttribute("aria-pressed", "false");
|
|
3360
|
-
};
|
|
3361
|
-
const showEditor = async () => {
|
|
3362
|
-
if (text.length > MAX_RENDER_CHARS) {
|
|
3363
|
-
status.textContent = "Editor skipped for large file";
|
|
3364
|
-
return;
|
|
3365
|
-
}
|
|
3366
|
-
if (editorReady) {
|
|
3367
|
-
body.hidden = true;
|
|
3368
|
-
editorHost.hidden = false;
|
|
3369
|
-
wrapper.classList.add("is-editor");
|
|
3370
|
-
editorButton.textContent = "Reader";
|
|
3371
|
-
editorButton.setAttribute("aria-pressed", "true");
|
|
3372
|
-
monacoEditor?.layout?.();
|
|
3373
|
-
return;
|
|
3374
|
-
}
|
|
3375
|
-
if (!editorLoading) {
|
|
3376
|
-
editorLoading = (async () => {
|
|
3377
|
-
editorButton.disabled = true;
|
|
3378
|
-
status.textContent = "Loading editor";
|
|
3379
|
-
try {
|
|
3380
|
-
const monaco = await loadMonaco();
|
|
3381
|
-
if (!monaco.editor?.create) {
|
|
3382
|
-
throw new Error("Monaco editor API is unavailable.");
|
|
3383
|
-
}
|
|
3384
|
-
const monacoLanguage = toMonacoLanguage(ext, lang);
|
|
3385
|
-
monaco.editor.setTheme?.(isDark ? "vs-dark" : "vs");
|
|
3386
|
-
monacoModel = monaco.editor.createModel?.(text, monacoLanguage);
|
|
3387
|
-
monacoEditor = monaco.editor.create(editorHost, {
|
|
3388
|
-
...monacoModel ? { model: monacoModel } : { value: text, language: monacoLanguage },
|
|
3389
|
-
automaticLayout: true,
|
|
3390
|
-
fontFamily: "var(--ofv-font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace)",
|
|
3391
|
-
fontSize: 13,
|
|
3392
|
-
lineNumbers: "on",
|
|
3393
|
-
minimap: { enabled: text.length <= 8e4 },
|
|
3394
|
-
readOnly: true,
|
|
3395
|
-
renderLineHighlight: "line",
|
|
3396
|
-
scrollBeyondLastLine: false,
|
|
3397
|
-
wordWrap: wrapper.classList.contains("is-wrapped") ? "on" : "off"
|
|
3398
|
-
});
|
|
3399
|
-
editorReady = true;
|
|
3400
|
-
status.textContent = "Editor ready";
|
|
3401
|
-
body.hidden = true;
|
|
3402
|
-
editorHost.hidden = false;
|
|
3403
|
-
wrapper.classList.add("is-editor");
|
|
3404
|
-
editorButton.textContent = "Reader";
|
|
3405
|
-
editorButton.setAttribute("aria-pressed", "true");
|
|
3406
|
-
monacoEditor.layout?.();
|
|
3407
|
-
} catch (error) {
|
|
3408
|
-
console.warn("Monaco editor failed to load; using built-in code editor fallback:", error);
|
|
3409
|
-
fallbackEditor = createBasicCodeEditor(text, wrapper.classList.contains("is-wrapped"));
|
|
3410
|
-
editorHost.replaceChildren(fallbackEditor);
|
|
3411
|
-
editorReady = true;
|
|
3412
|
-
status.textContent = "Basic editor";
|
|
3413
|
-
body.hidden = true;
|
|
3414
|
-
editorHost.hidden = false;
|
|
3415
|
-
wrapper.classList.add("is-editor");
|
|
3416
|
-
editorButton.textContent = "Reader";
|
|
3417
|
-
editorButton.setAttribute("aria-pressed", "true");
|
|
3418
|
-
} finally {
|
|
3419
|
-
editorButton.disabled = false;
|
|
3420
|
-
}
|
|
3421
|
-
})();
|
|
3422
|
-
}
|
|
3423
|
-
await editorLoading;
|
|
3424
|
-
};
|
|
3425
|
-
editorButton.addEventListener("click", () => {
|
|
3426
|
-
if (editorHost.hidden) {
|
|
3427
|
-
void showEditor();
|
|
3428
|
-
} else {
|
|
3429
|
-
showReader();
|
|
3430
|
-
status.textContent = "Reader mode";
|
|
3431
|
-
}
|
|
3432
|
-
});
|
|
3433
3698
|
wrapper.append(header);
|
|
3434
3699
|
if (structureSummary) {
|
|
3435
3700
|
wrapper.append(structureSummary);
|
|
@@ -3447,7 +3712,6 @@ function textPlugin() {
|
|
|
3447
3712
|
wrapper.append(notice);
|
|
3448
3713
|
}
|
|
3449
3714
|
wrapper.appendChild(body);
|
|
3450
|
-
wrapper.appendChild(editorHost);
|
|
3451
3715
|
ctx.viewport.appendChild(wrapper);
|
|
3452
3716
|
if (shouldHighlight) {
|
|
3453
3717
|
try {
|
|
@@ -3457,12 +3721,7 @@ function textPlugin() {
|
|
|
3457
3721
|
}
|
|
3458
3722
|
}
|
|
3459
3723
|
return {
|
|
3460
|
-
resize() {
|
|
3461
|
-
monacoEditor?.layout?.();
|
|
3462
|
-
},
|
|
3463
3724
|
destroy() {
|
|
3464
|
-
monacoEditor?.dispose?.();
|
|
3465
|
-
monacoModel?.dispose?.();
|
|
3466
3725
|
wrapper.remove();
|
|
3467
3726
|
}
|
|
3468
3727
|
};
|
|
@@ -3477,45 +3736,6 @@ function normalizeFileName2(name) {
|
|
|
3477
3736
|
const baseName = name.split(/[\\/]/).pop() || name;
|
|
3478
3737
|
return baseName.toLowerCase();
|
|
3479
3738
|
}
|
|
3480
|
-
async function loadMonaco() {
|
|
3481
|
-
const injectedLoader = globalThis[MONACO_LOADER_KEY];
|
|
3482
|
-
if (typeof injectedLoader === "function") {
|
|
3483
|
-
return await injectedLoader();
|
|
3484
|
-
}
|
|
3485
|
-
const importer = new Function("specifier", "return import(specifier)");
|
|
3486
|
-
return importer("monaco-editor");
|
|
3487
|
-
}
|
|
3488
|
-
function toMonacoLanguage(ext, prismLanguage) {
|
|
3489
|
-
if (ext === "vue") {
|
|
3490
|
-
return "html";
|
|
3491
|
-
}
|
|
3492
|
-
if (ext === "tsx") {
|
|
3493
|
-
return "typescript";
|
|
3494
|
-
}
|
|
3495
|
-
if (ext === "jsx") {
|
|
3496
|
-
return "javascript";
|
|
3497
|
-
}
|
|
3498
|
-
if (prismLanguage === "markup") {
|
|
3499
|
-
return ext === "xml" ? "xml" : "html";
|
|
3500
|
-
}
|
|
3501
|
-
if (prismLanguage === "bash") {
|
|
3502
|
-
return "shell";
|
|
3503
|
-
}
|
|
3504
|
-
if (prismLanguage === "none") {
|
|
3505
|
-
return "plaintext";
|
|
3506
|
-
}
|
|
3507
|
-
return prismLanguage;
|
|
3508
|
-
}
|
|
3509
|
-
function createBasicCodeEditor(text, wrapped) {
|
|
3510
|
-
const textarea = document.createElement("textarea");
|
|
3511
|
-
textarea.className = "ofv-code-editor-fallback";
|
|
3512
|
-
textarea.readOnly = true;
|
|
3513
|
-
textarea.spellcheck = false;
|
|
3514
|
-
textarea.wrap = wrapped ? "soft" : "off";
|
|
3515
|
-
textarea.value = text;
|
|
3516
|
-
textarea.setAttribute("aria-label", "Code editor preview");
|
|
3517
|
-
return textarea;
|
|
3518
|
-
}
|
|
3519
3739
|
function createTextFallback(fileName, url) {
|
|
3520
3740
|
const fallback = document.createElement("div");
|
|
3521
3741
|
fallback.className = "ofv-fallback";
|
|
@@ -3753,7 +3973,13 @@ function pdfPlugin(options = {}) {
|
|
|
3753
3973
|
scroller.className = "ofv-pdf ofv-pdf-pages";
|
|
3754
3974
|
viewer.append(summary, scroller);
|
|
3755
3975
|
ctx.viewport.append(viewer);
|
|
3756
|
-
const documentTask = pdf.getDocument(
|
|
3976
|
+
const documentTask = pdf.getDocument({
|
|
3977
|
+
url,
|
|
3978
|
+
cMapUrl: normalizedOptions.cMapUrl ?? `https://cdn.jsdelivr.net/npm/pdfjs-dist@${pdf.version}/cmaps/`,
|
|
3979
|
+
cMapPacked: normalizedOptions.cMapPacked ?? true,
|
|
3980
|
+
standardFontDataUrl: normalizedOptions.standardFontDataUrl ?? `https://cdn.jsdelivr.net/npm/pdfjs-dist@${pdf.version}/standard_fonts/`,
|
|
3981
|
+
useSystemFonts: normalizedOptions.useSystemFonts ?? true
|
|
3982
|
+
});
|
|
3757
3983
|
const doc = await documentTask.promise.catch((error) => {
|
|
3758
3984
|
viewer.remove();
|
|
3759
3985
|
ctx.viewport.classList.add("ofv-center");
|
|
@@ -3789,6 +4015,7 @@ function pdfPlugin(options = {}) {
|
|
|
3789
4015
|
let zoomFactor = 1;
|
|
3790
4016
|
const updateSummary = () => {
|
|
3791
4017
|
renderPdfSummary(summary, doc.numPages, pagesMeta, ctx.options.fit, zoomFactor);
|
|
4018
|
+
ctx.toolbar?.setZoom(zoomFactor);
|
|
3792
4019
|
};
|
|
3793
4020
|
const clearPage = (pageIdx) => {
|
|
3794
4021
|
const state = pageStates[pageIdx];
|
|
@@ -3812,12 +4039,17 @@ function pdfPlugin(options = {}) {
|
|
|
3812
4039
|
try {
|
|
3813
4040
|
const page = await doc.getPage(pageIdx + 1);
|
|
3814
4041
|
const meta = pagesMeta[pageIdx];
|
|
3815
|
-
const scale = ctx.options.fit === "actual" ? zoomFactor : Math.max(0.
|
|
4042
|
+
const scale = ctx.options.fit === "actual" ? zoomFactor : Math.max(0.05, Math.min(5, getPdfAvailableWidth(size.width) / meta.width * zoomFactor));
|
|
3816
4043
|
const viewport = page.getViewport({ scale });
|
|
4044
|
+
const outputScale = getPdfOutputScale();
|
|
4045
|
+
const cssWidth = Math.floor(viewport.width);
|
|
4046
|
+
const cssHeight = Math.floor(viewport.height);
|
|
3817
4047
|
const canvas = document.createElement("canvas");
|
|
3818
4048
|
canvas.className = "ofv-pdf-page";
|
|
3819
|
-
canvas.width = Math.floor(
|
|
3820
|
-
canvas.height = Math.floor(
|
|
4049
|
+
canvas.width = Math.floor(cssWidth * outputScale);
|
|
4050
|
+
canvas.height = Math.floor(cssHeight * outputScale);
|
|
4051
|
+
canvas.style.width = `${cssWidth}px`;
|
|
4052
|
+
canvas.style.height = `${cssHeight}px`;
|
|
3821
4053
|
const context = canvas.getContext("2d");
|
|
3822
4054
|
if (!context) {
|
|
3823
4055
|
throw new Error("Canvas 2D context is not available.");
|
|
@@ -3826,7 +4058,8 @@ function pdfPlugin(options = {}) {
|
|
|
3826
4058
|
state.canvas = canvas;
|
|
3827
4059
|
const renderTask = page.render({
|
|
3828
4060
|
canvasContext: context,
|
|
3829
|
-
viewport
|
|
4061
|
+
viewport,
|
|
4062
|
+
transform: outputScale === 1 ? void 0 : [outputScale, 0, 0, outputScale, 0, 0]
|
|
3830
4063
|
});
|
|
3831
4064
|
state.renderTask = renderTask;
|
|
3832
4065
|
await renderTask.promise;
|
|
@@ -3834,8 +4067,8 @@ function pdfPlugin(options = {}) {
|
|
|
3834
4067
|
const textContent = await page.getTextContent();
|
|
3835
4068
|
const textLayer = document.createElement("div");
|
|
3836
4069
|
textLayer.className = "ofv-pdf-text-layer";
|
|
3837
|
-
textLayer.style.width = `${
|
|
3838
|
-
textLayer.style.height = `${
|
|
4070
|
+
textLayer.style.width = `${cssWidth}px`;
|
|
4071
|
+
textLayer.style.height = `${cssHeight}px`;
|
|
3839
4072
|
state.wrapper.appendChild(textLayer);
|
|
3840
4073
|
for (const item of textContent.items) {
|
|
3841
4074
|
if (!("str" in item)) continue;
|
|
@@ -3896,7 +4129,7 @@ function pdfPlugin(options = {}) {
|
|
|
3896
4129
|
}
|
|
3897
4130
|
for (let i = 0; i < doc.numPages; i++) {
|
|
3898
4131
|
const meta = pagesMeta[i];
|
|
3899
|
-
const scale = ctx.options.fit === "actual" ? zoomFactor : Math.max(0.
|
|
4132
|
+
const scale = ctx.options.fit === "actual" ? zoomFactor : Math.max(0.05, Math.min(5, getPdfAvailableWidth(size.width) / meta.width * zoomFactor));
|
|
3900
4133
|
const w = Math.floor(meta.width * scale);
|
|
3901
4134
|
const h = Math.floor(meta.height * scale);
|
|
3902
4135
|
const wrapper = document.createElement("div");
|
|
@@ -3951,6 +4184,7 @@ function pdfPlugin(options = {}) {
|
|
|
3951
4184
|
}, 120);
|
|
3952
4185
|
},
|
|
3953
4186
|
destroy() {
|
|
4187
|
+
ctx.toolbar?.setZoom(void 0);
|
|
3954
4188
|
window.clearTimeout(resizeTimer);
|
|
3955
4189
|
observer?.disconnect();
|
|
3956
4190
|
pageStates.forEach((state) => {
|
|
@@ -3969,6 +4203,19 @@ function pdfPlugin(options = {}) {
|
|
|
3969
4203
|
}
|
|
3970
4204
|
};
|
|
3971
4205
|
}
|
|
4206
|
+
function getPdfOutputScale() {
|
|
4207
|
+
if (typeof window === "undefined") {
|
|
4208
|
+
return 1;
|
|
4209
|
+
}
|
|
4210
|
+
return Math.max(1, Math.min(window.devicePixelRatio || 1, 2.5));
|
|
4211
|
+
}
|
|
4212
|
+
function getPdfAvailableWidth(width) {
|
|
4213
|
+
if (!Number.isFinite(width) || width <= 0) {
|
|
4214
|
+
return 1;
|
|
4215
|
+
}
|
|
4216
|
+
const gutter = width < 160 ? 16 : 32;
|
|
4217
|
+
return Math.max(1, width - gutter);
|
|
4218
|
+
}
|
|
3972
4219
|
function renderPdfSummary(summary, pages, pagesMeta, fit, zoomFactor) {
|
|
3973
4220
|
summary.replaceChildren();
|
|
3974
4221
|
appendPdfSummary(summary, "\u9875\u6570", String(pages));
|
|
@@ -4728,8 +4975,9 @@ function officePlugin() {
|
|
|
4728
4975
|
ctx.viewport.append(panel);
|
|
4729
4976
|
const extension = resolveFormat(ctx.file, officeMimeFormatMap);
|
|
4730
4977
|
const arrayBuffer = await readArrayBuffer(ctx.file);
|
|
4978
|
+
let disposeDocxFit;
|
|
4731
4979
|
if (fileIsDocx(extension)) {
|
|
4732
|
-
await renderDocx(panel, arrayBuffer);
|
|
4980
|
+
disposeDocxFit = await renderDocx(panel, arrayBuffer);
|
|
4733
4981
|
} else if (extension === "rtf") {
|
|
4734
4982
|
renderPlainDocument(panel, "RTF \u6587\u6863", rtfToText(await readTextFromBuffer(arrayBuffer)));
|
|
4735
4983
|
} else if (extension === "odt") {
|
|
@@ -4754,6 +5002,7 @@ function officePlugin() {
|
|
|
4754
5002
|
}
|
|
4755
5003
|
return {
|
|
4756
5004
|
destroy() {
|
|
5005
|
+
disposeDocxFit?.();
|
|
4757
5006
|
panel.remove();
|
|
4758
5007
|
}
|
|
4759
5008
|
};
|
|
@@ -4787,6 +5036,7 @@ async function renderDocx(panel, arrayBuffer) {
|
|
|
4787
5036
|
experimental: true,
|
|
4788
5037
|
useBase64URL: true
|
|
4789
5038
|
});
|
|
5039
|
+
return fitDocxPages(content);
|
|
4790
5040
|
} catch (error) {
|
|
4791
5041
|
content.replaceChildren();
|
|
4792
5042
|
const fallbackNote = document.createElement("div");
|
|
@@ -4801,6 +5051,74 @@ async function renderDocx(panel, arrayBuffer) {
|
|
|
4801
5051
|
}
|
|
4802
5052
|
console.warn("DOCX layout preview failed, fell back to Mammoth:", error);
|
|
4803
5053
|
}
|
|
5054
|
+
return () => void 0;
|
|
5055
|
+
}
|
|
5056
|
+
function fitDocxPages(container) {
|
|
5057
|
+
const wrapper = container.querySelector(".ofv-docx-wrapper");
|
|
5058
|
+
if (!wrapper) {
|
|
5059
|
+
return () => void 0;
|
|
5060
|
+
}
|
|
5061
|
+
const update = () => {
|
|
5062
|
+
const frames = ensureDocxPageFrames(wrapper);
|
|
5063
|
+
if (frames.length === 0) {
|
|
5064
|
+
wrapper.style.removeProperty("--ofv-docx-scale");
|
|
5065
|
+
return;
|
|
5066
|
+
}
|
|
5067
|
+
const availableWidth = Math.max(1, container.clientWidth - 48);
|
|
5068
|
+
const pageWidth = Math.max(
|
|
5069
|
+
1,
|
|
5070
|
+
...frames.map(({ page }) => {
|
|
5071
|
+
const rectWidth = page.getBoundingClientRect().width;
|
|
5072
|
+
return page.offsetWidth || rectWidth || parseCssPixelValue(page.style.width) || 794;
|
|
5073
|
+
})
|
|
5074
|
+
);
|
|
5075
|
+
const scale = Math.min(1, Math.max(0.35, availableWidth / pageWidth));
|
|
5076
|
+
wrapper.style.setProperty("--ofv-docx-scale", formatCssNumber(scale));
|
|
5077
|
+
wrapper.style.setProperty("--ofv-docx-page-width", `${pageWidth}px`);
|
|
5078
|
+
for (const { frame, page } of frames) {
|
|
5079
|
+
const pageHeight = page.offsetHeight || page.getBoundingClientRect().height || parseCssPixelValue(page.style.height);
|
|
5080
|
+
if (pageHeight > 0) {
|
|
5081
|
+
frame.style.height = `${Math.ceil(pageHeight * scale)}px`;
|
|
5082
|
+
}
|
|
5083
|
+
}
|
|
5084
|
+
};
|
|
5085
|
+
update();
|
|
5086
|
+
const timers = [0, 80, 240].map((delay) => window.setTimeout(update, delay));
|
|
5087
|
+
if (typeof ResizeObserver === "undefined") {
|
|
5088
|
+
window.addEventListener("resize", update);
|
|
5089
|
+
return () => {
|
|
5090
|
+
timers.forEach((timer) => window.clearTimeout(timer));
|
|
5091
|
+
window.removeEventListener("resize", update);
|
|
5092
|
+
};
|
|
5093
|
+
}
|
|
5094
|
+
const observer = new ResizeObserver(update);
|
|
5095
|
+
observer.observe(container);
|
|
5096
|
+
observer.observe(wrapper);
|
|
5097
|
+
return () => {
|
|
5098
|
+
timers.forEach((timer) => window.clearTimeout(timer));
|
|
5099
|
+
observer.disconnect();
|
|
5100
|
+
};
|
|
5101
|
+
}
|
|
5102
|
+
function ensureDocxPageFrames(wrapper) {
|
|
5103
|
+
const pages = Array.from(wrapper.querySelectorAll("section.ofv-docx"));
|
|
5104
|
+
return pages.map((page) => {
|
|
5105
|
+
const parent = page.parentElement;
|
|
5106
|
+
if (parent?.classList.contains("ofv-docx-page-frame")) {
|
|
5107
|
+
return { frame: parent, page };
|
|
5108
|
+
}
|
|
5109
|
+
const frame = document.createElement("div");
|
|
5110
|
+
frame.className = "ofv-docx-page-frame";
|
|
5111
|
+
page.before(frame);
|
|
5112
|
+
frame.append(page);
|
|
5113
|
+
return { frame, page };
|
|
5114
|
+
});
|
|
5115
|
+
}
|
|
5116
|
+
function parseCssPixelValue(value) {
|
|
5117
|
+
const parsed = Number.parseFloat(value);
|
|
5118
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
5119
|
+
}
|
|
5120
|
+
function formatCssNumber(value) {
|
|
5121
|
+
return Number.isFinite(value) ? value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "") : "1";
|
|
4804
5122
|
}
|
|
4805
5123
|
async function renderDocxTextFallback(container, arrayBuffer) {
|
|
4806
5124
|
const article = document.createElement("article");
|
|
@@ -4899,7 +5217,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
|
|
|
4899
5217
|
workbook = xlsx.read(arrayBuffer, { type: "array" });
|
|
4900
5218
|
} catch (error) {
|
|
4901
5219
|
if (isLegacyOfficeBinary(extension)) {
|
|
4902
|
-
renderLegacyOfficeBinary(panel, extension, arrayBuffer, normalizeOfficeError(error));
|
|
5220
|
+
renderLegacyOfficeBinary(panel, extension, arrayBuffer, `\u8868\u683C\u89E3\u6790\u5931\u8D25\uFF1A${normalizeOfficeError(error)}`);
|
|
4903
5221
|
return;
|
|
4904
5222
|
}
|
|
4905
5223
|
renderSheetFallback(panel, extension, normalizeOfficeError(error));
|
|
@@ -6004,14 +6322,15 @@ function legacyOfficeFormatLabel(extension) {
|
|
|
6004
6322
|
function renderLegacyOfficeBinary(panel, extension, arrayBuffer, parseError) {
|
|
6005
6323
|
const fragments = extractLegacyOfficeText(arrayBuffer);
|
|
6006
6324
|
panel.replaceChildren();
|
|
6007
|
-
const section = createSection("Office \
|
|
6325
|
+
const section = createSection("Office \u8F6C\u6362\u63D0\u793A");
|
|
6326
|
+
section.classList.add("ofv-office-conversion");
|
|
6008
6327
|
const format = document.createElement("p");
|
|
6009
6328
|
const strong = document.createElement("strong");
|
|
6010
6329
|
strong.textContent = `.${extension}`;
|
|
6011
6330
|
format.append(
|
|
6012
6331
|
strong,
|
|
6013
6332
|
document.createTextNode(
|
|
6014
|
-
" \u5C5E\u4E8E\u65E7\u7248 Microsoft Office \u4E8C\u8FDB\u5236\u683C\u5F0F\uFF0C\u6D4F\u89C8\u5668\u5185\u65E0\u6CD5\u9AD8\u4FDD\u771F\u89E3\u6790\uFF1B\u5F53\u524D\
|
|
6333
|
+
" \u5C5E\u4E8E\u65E7\u7248 Microsoft Office \u4E8C\u8FDB\u5236\u683C\u5F0F\uFF0C\u6D4F\u89C8\u5668\u5185\u65E0\u6CD5\u9AD8\u4FDD\u771F\u89E3\u6790\uFF1B\u5F53\u524D\u4EC5\u5C55\u793A\u53EF\u4FE1\u6587\u672C\u7247\u6BB5\u548C\u7ED3\u6784\u6307\u7EB9\uFF0C\u5B8C\u6574\u6392\u7248\u5EFA\u8BAE\u63A5\u5165 LibreOffice/OnlyOffice \u670D\u52A1\u7AEF\u8F6C\u6362\u4E3A PDF/HTML\u3002"
|
|
6015
6334
|
)
|
|
6016
6335
|
);
|
|
6017
6336
|
const meta = document.createElement("dl");
|
|
@@ -6020,12 +6339,15 @@ function renderLegacyOfficeBinary(panel, extension, arrayBuffer, parseError) {
|
|
|
6020
6339
|
appendOfficeBinaryMeta(meta, "\u6587\u4EF6\u7ED3\u6784", hasOleSignature(arrayBuffer) ? "\u68C0\u6D4B\u5230 OLE Compound File \u7B7E\u540D" : "\u672A\u68C0\u6D4B\u5230\u6807\u51C6 OLE \u7B7E\u540D\uFF0C\u6309\u539F\u59CB\u4E8C\u8FDB\u5236\u5C1D\u8BD5\u63D0\u53D6");
|
|
6021
6340
|
appendOfficeBinaryMeta(meta, "\u6587\u672C\u7247\u6BB5", `${fragments.length} \u6BB5`);
|
|
6022
6341
|
if (parseError) {
|
|
6023
|
-
appendOfficeBinaryMeta(meta, "\
|
|
6342
|
+
appendOfficeBinaryMeta(meta, "\u89E3\u6790\u72B6\u6001", parseError);
|
|
6024
6343
|
}
|
|
6025
6344
|
section.append(format, meta);
|
|
6026
6345
|
if (fragments.length > 0) {
|
|
6027
6346
|
const article = document.createElement("article");
|
|
6028
|
-
article.className = "ofv-document";
|
|
6347
|
+
article.className = "ofv-document ofv-office-binary-fragments";
|
|
6348
|
+
const heading = document.createElement("h4");
|
|
6349
|
+
heading.textContent = "\u53EF\u8BFB\u6587\u672C\u7247\u6BB5";
|
|
6350
|
+
article.append(heading);
|
|
6029
6351
|
for (const fragment of fragments.slice(0, 80)) {
|
|
6030
6352
|
const paragraph = document.createElement("p");
|
|
6031
6353
|
paragraph.textContent = fragment;
|
|
@@ -6034,7 +6356,8 @@ function renderLegacyOfficeBinary(panel, extension, arrayBuffer, parseError) {
|
|
|
6034
6356
|
section.append(article);
|
|
6035
6357
|
} else {
|
|
6036
6358
|
const empty = document.createElement("p");
|
|
6037
|
-
empty.
|
|
6359
|
+
empty.className = "ofv-office-binary-empty";
|
|
6360
|
+
empty.textContent = "\u672A\u63D0\u53D6\u5230\u7A33\u5B9A\u53EF\u8BFB\u6587\u672C\u3002\u8BE5\u6587\u4EF6\u53EF\u80FD\u7ECF\u8FC7\u538B\u7F29\u3001\u52A0\u5BC6\uFF0C\u6216\u6587\u672C\u7F16\u7801\u65E0\u6CD5\u5728\u6D4F\u89C8\u5668\u7AEF\u53EF\u9760\u8BC6\u522B\uFF1B\u8BF7\u4F7F\u7528\u670D\u52A1\u7AEF LibreOffice/OnlyOffice \u8F6C\u6362\u540E\u9884\u89C8\u3002";
|
|
6038
6361
|
section.append(empty);
|
|
6039
6362
|
}
|
|
6040
6363
|
panel.append(section);
|
|
@@ -6067,12 +6390,12 @@ function normalizeOfficeError(error) {
|
|
|
6067
6390
|
function extractLegacyOfficeText(arrayBuffer) {
|
|
6068
6391
|
const bytes = new Uint8Array(arrayBuffer);
|
|
6069
6392
|
const fragments = [
|
|
6070
|
-
...extractPrintableRuns(bytes),
|
|
6071
|
-
...extractUtf16Runs(bytes)
|
|
6072
|
-
].map((
|
|
6393
|
+
...extractPrintableRuns(bytes).map((text) => ({ text, source: "ascii" })),
|
|
6394
|
+
...extractUtf16Runs(bytes).map((text) => ({ text, source: "utf16" }))
|
|
6395
|
+
].map(({ text, source }) => ({ text: normalizeLegacyText(text), source })).filter(({ text, source }) => isReadableLegacyTextFragment(text, source));
|
|
6073
6396
|
const unique = [];
|
|
6074
6397
|
const seen = /* @__PURE__ */ new Set();
|
|
6075
|
-
for (const fragment of fragments) {
|
|
6398
|
+
for (const { text: fragment } of fragments) {
|
|
6076
6399
|
const key = fragment.toLowerCase();
|
|
6077
6400
|
if (!seen.has(key)) {
|
|
6078
6401
|
seen.add(key);
|
|
@@ -6081,6 +6404,104 @@ function extractLegacyOfficeText(arrayBuffer) {
|
|
|
6081
6404
|
}
|
|
6082
6405
|
return unique.slice(0, 160);
|
|
6083
6406
|
}
|
|
6407
|
+
function isReadableLegacyTextFragment(fragment, source) {
|
|
6408
|
+
if (fragment.length > 600) {
|
|
6409
|
+
return false;
|
|
6410
|
+
}
|
|
6411
|
+
if (isLegacyOfficeMetadataNoise(fragment)) {
|
|
6412
|
+
return false;
|
|
6413
|
+
}
|
|
6414
|
+
if (!/[\p{L}\p{N}]/u.test(fragment)) {
|
|
6415
|
+
return false;
|
|
6416
|
+
}
|
|
6417
|
+
const chars = Array.from(fragment);
|
|
6418
|
+
const letters = chars.filter((char) => /\p{L}/u.test(char)).length;
|
|
6419
|
+
const digits = chars.filter((char) => /\p{N}/u.test(char)).length;
|
|
6420
|
+
const spaces = chars.filter((char) => /\s/u.test(char)).length;
|
|
6421
|
+
const asciiLetters = chars.filter((char) => /[A-Za-z]/.test(char)).length;
|
|
6422
|
+
const cjkLetters = chars.filter((char) => /[\u3400-\u9fff]/u.test(char)).length;
|
|
6423
|
+
const punctuation = chars.filter((char) => /[^\p{L}\p{N}\s]/u.test(char)).length;
|
|
6424
|
+
const alphaNumeric = letters + digits;
|
|
6425
|
+
const readableRatio = alphaNumeric / chars.length;
|
|
6426
|
+
const punctuationRatio = punctuation / chars.length;
|
|
6427
|
+
if (fragment.length < 4 || readableRatio < 0.55 || punctuationRatio > 0.24) {
|
|
6428
|
+
return false;
|
|
6429
|
+
}
|
|
6430
|
+
if (/([\p{L}\p{N}])\1{4,}/u.test(fragment)) {
|
|
6431
|
+
return false;
|
|
6432
|
+
}
|
|
6433
|
+
if (cjkLetters >= 2) {
|
|
6434
|
+
const suspiciousCjk = chars.filter((char) => isAsciiBytePairCjk(char)).length;
|
|
6435
|
+
if (suspiciousCjk / cjkLetters > 0.65) {
|
|
6436
|
+
return false;
|
|
6437
|
+
}
|
|
6438
|
+
if (isLikelyCjkHeading(fragment)) {
|
|
6439
|
+
return true;
|
|
6440
|
+
}
|
|
6441
|
+
if (punctuation > 0 && fragment.length < 12) {
|
|
6442
|
+
return false;
|
|
6443
|
+
}
|
|
6444
|
+
return cjkLetters >= 8 || cjkLetters >= 4 && spaces > 0;
|
|
6445
|
+
}
|
|
6446
|
+
if (asciiLetters >= 4) {
|
|
6447
|
+
if (punctuation > 0 && spaces === 0) {
|
|
6448
|
+
return false;
|
|
6449
|
+
}
|
|
6450
|
+
if (source === "ascii" && /^[A-Z]{2,8}$/.test(fragment)) {
|
|
6451
|
+
return false;
|
|
6452
|
+
}
|
|
6453
|
+
if (spaces > 0) {
|
|
6454
|
+
return letters >= 3;
|
|
6455
|
+
}
|
|
6456
|
+
return fragment.length >= 6;
|
|
6457
|
+
}
|
|
6458
|
+
if (spaces > 0 && letters >= 3) {
|
|
6459
|
+
return true;
|
|
6460
|
+
}
|
|
6461
|
+
return false;
|
|
6462
|
+
}
|
|
6463
|
+
function isLegacyOfficeMetadataNoise(fragment) {
|
|
6464
|
+
if (/[$�\uFFFD]/u.test(fragment)) {
|
|
6465
|
+
return true;
|
|
6466
|
+
}
|
|
6467
|
+
if (/^(?:Root Entry|WordDocument|Workbook|Book|SummaryInformation|DocumentSummaryInformation|CompObj|ObjectPool|Data|PowerPoint Document|Pictures)$/i.test(fragment)) {
|
|
6468
|
+
return true;
|
|
6469
|
+
}
|
|
6470
|
+
if (/\.(dotm?|docm?|pptx?|ppsx?|xlsm?|xlsx?)\b/i.test(fragment)) {
|
|
6471
|
+
return true;
|
|
6472
|
+
}
|
|
6473
|
+
if (/^(?:默认段落字体|普通表格|正文|标题|副标题|目录|页眉|页脚|批注|超链接)(?:\s*\d+)?$/.test(fragment)) {
|
|
6474
|
+
return true;
|
|
6475
|
+
}
|
|
6476
|
+
if (/\b(?:Normal|Default|Calibri|Times New Roman|WPS Office|Microsoft Office|KSOP?ProductBuildVer)\b/i.test(fragment)) {
|
|
6477
|
+
return true;
|
|
6478
|
+
}
|
|
6479
|
+
if (/^\d+(?:Table|List|Heading|Title|Style)$/i.test(fragment)) {
|
|
6480
|
+
return true;
|
|
6481
|
+
}
|
|
6482
|
+
if (/[{(]?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[})]?/i.test(fragment)) {
|
|
6483
|
+
return true;
|
|
6484
|
+
}
|
|
6485
|
+
if (/^[A-Z_]{3,}$/.test(fragment) || /^[A-Za-z]+(?:Information|Document|Storage|Stream|Table|Data|Pool|Obj|Props)$/i.test(fragment)) {
|
|
6486
|
+
return true;
|
|
6487
|
+
}
|
|
6488
|
+
return false;
|
|
6489
|
+
}
|
|
6490
|
+
function isLikelyCjkHeading(fragment) {
|
|
6491
|
+
return /^(?:标题|第[一二三四五六七八九十\d]+[章节条]|[一二三四五六七八九十\d]+[、..])\s*[\p{L}\p{N}\s-]*$/u.test(fragment);
|
|
6492
|
+
}
|
|
6493
|
+
function isAsciiBytePairCjk(char) {
|
|
6494
|
+
const code = char.codePointAt(0) || 0;
|
|
6495
|
+
if (code < 13312 || code > 40959) {
|
|
6496
|
+
return false;
|
|
6497
|
+
}
|
|
6498
|
+
const low = code & 255;
|
|
6499
|
+
const high = code >> 8;
|
|
6500
|
+
return isPrintableAsciiByte(low) && isPrintableAsciiByte(high);
|
|
6501
|
+
}
|
|
6502
|
+
function isPrintableAsciiByte(value) {
|
|
6503
|
+
return value >= 32 && value <= 126;
|
|
6504
|
+
}
|
|
6084
6505
|
function extractPrintableRuns(bytes) {
|
|
6085
6506
|
const fragments = [];
|
|
6086
6507
|
let current = "";
|
|
@@ -6103,6 +6524,13 @@ function extractUtf16Runs(bytes) {
|
|
|
6103
6524
|
const fragments = [];
|
|
6104
6525
|
let current = "";
|
|
6105
6526
|
for (let index = 0; index < bytes.length - 1; index += 2) {
|
|
6527
|
+
if (looksLikeMisalignedAsciiUtf16(bytes[index], bytes[index + 1])) {
|
|
6528
|
+
if (current.length >= 3) {
|
|
6529
|
+
fragments.push(current);
|
|
6530
|
+
}
|
|
6531
|
+
current = "";
|
|
6532
|
+
continue;
|
|
6533
|
+
}
|
|
6106
6534
|
const code = bytes[index] | bytes[index + 1] << 8;
|
|
6107
6535
|
if (code >= 32 && code <= 55295 || code === 9) {
|
|
6108
6536
|
current += String.fromCharCode(code);
|
|
@@ -6118,6 +6546,9 @@ function extractUtf16Runs(bytes) {
|
|
|
6118
6546
|
}
|
|
6119
6547
|
return fragments;
|
|
6120
6548
|
}
|
|
6549
|
+
function looksLikeMisalignedAsciiUtf16(lowByte, highByte) {
|
|
6550
|
+
return lowByte === 0 && (highByte >= 48 && highByte <= 57 || highByte >= 65 && highByte <= 90 || highByte >= 97 && highByte <= 122);
|
|
6551
|
+
}
|
|
6121
6552
|
function normalizeLegacyText(value) {
|
|
6122
6553
|
return value.replace(/[\u0000-\u001f]+/g, " ").replace(/\s+/g, " ").trim();
|
|
6123
6554
|
}
|
|
@@ -6755,18 +7186,45 @@ function archivePlugin() {
|
|
|
6755
7186
|
layout.className = "ofv-archive-layout";
|
|
6756
7187
|
const sidebar = document.createElement("div");
|
|
6757
7188
|
sidebar.className = "ofv-archive-sidebar";
|
|
7189
|
+
const sidebarPanel = document.createElement("div");
|
|
7190
|
+
sidebarPanel.className = "ofv-archive-sidebar-panel";
|
|
6758
7191
|
const header = document.createElement("div");
|
|
6759
7192
|
header.className = "ofv-archive-header";
|
|
6760
|
-
|
|
6761
|
-
|
|
7193
|
+
const sidebarTitle = document.createElement("span");
|
|
7194
|
+
sidebarTitle.className = "ofv-archive-header-title";
|
|
7195
|
+
sidebarTitle.textContent = `\u6587\u4EF6\u5217\u8868 (${archiveEntries.filter((e) => !e.dir).length})`;
|
|
7196
|
+
const sidebarToggle = document.createElement("button");
|
|
7197
|
+
sidebarToggle.className = "ofv-archive-sidebar-toggle";
|
|
7198
|
+
sidebarToggle.type = "button";
|
|
7199
|
+
sidebarToggle.setAttribute("aria-label", "\u5C55\u5F00\u6587\u4EF6\u5217\u8868");
|
|
7200
|
+
sidebarToggle.setAttribute("aria-expanded", "false");
|
|
7201
|
+
sidebarToggle.title = "\u5C55\u5F00\u6587\u4EF6\u5217\u8868";
|
|
7202
|
+
sidebarToggle.textContent = "\u2039";
|
|
7203
|
+
header.append(sidebarToggle, sidebarTitle);
|
|
7204
|
+
sidebarPanel.append(header);
|
|
6762
7205
|
const tree = document.createElement("div");
|
|
6763
7206
|
tree.className = "ofv-archive-tree";
|
|
6764
|
-
|
|
7207
|
+
sidebarPanel.append(tree);
|
|
7208
|
+
sidebar.append(sidebarPanel);
|
|
6765
7209
|
const mainPanel = document.createElement("div");
|
|
6766
7210
|
mainPanel.className = "ofv-archive-main";
|
|
6767
7211
|
layout.append(sidebar, mainPanel);
|
|
6768
7212
|
panel.append(layout);
|
|
6769
7213
|
let currentSubInstance = null;
|
|
7214
|
+
const getSidebarViewportWidth = () => ctx.viewport.clientWidth || ctx.size.width;
|
|
7215
|
+
const shouldAutoCollapseSidebar = () => getSidebarViewportWidth() <= 520;
|
|
7216
|
+
const setSidebarCollapsed = (collapsed) => {
|
|
7217
|
+
layout.classList.toggle("is-sidebar-collapsed", collapsed);
|
|
7218
|
+
sidebarToggle.setAttribute("aria-expanded", String(!collapsed));
|
|
7219
|
+
const label = collapsed ? "\u5C55\u5F00\u6587\u4EF6\u5217\u8868" : "\u6536\u8D77\u6587\u4EF6\u5217\u8868";
|
|
7220
|
+
sidebarToggle.setAttribute("aria-label", label);
|
|
7221
|
+
sidebarToggle.title = label;
|
|
7222
|
+
sidebarToggle.textContent = collapsed ? "\u203A" : "\u2039";
|
|
7223
|
+
};
|
|
7224
|
+
setSidebarCollapsed(false);
|
|
7225
|
+
sidebarToggle.addEventListener("click", () => {
|
|
7226
|
+
setSidebarCollapsed(!layout.classList.contains("is-sidebar-collapsed"));
|
|
7227
|
+
});
|
|
6770
7228
|
const showDefaultSummary = () => {
|
|
6771
7229
|
mainPanel.replaceChildren();
|
|
6772
7230
|
const summary = document.createElement("div");
|
|
@@ -6806,6 +7264,9 @@ function archivePlugin() {
|
|
|
6806
7264
|
if (destroyed) {
|
|
6807
7265
|
return;
|
|
6808
7266
|
}
|
|
7267
|
+
if (shouldAutoCollapseSidebar()) {
|
|
7268
|
+
setSidebarCollapsed(true);
|
|
7269
|
+
}
|
|
6809
7270
|
const token = ++renderToken;
|
|
6810
7271
|
sidebar.querySelectorAll(".ofv-archive-item").forEach((el) => {
|
|
6811
7272
|
el.classList.remove("is-active");
|
|
@@ -9088,7 +9549,7 @@ function cadPlugin() {
|
|
|
9088
9549
|
return { destroy: () => panel.remove() };
|
|
9089
9550
|
}
|
|
9090
9551
|
const dxf = await readTextFile(ctx.file);
|
|
9091
|
-
const viewer = renderDxf(panel, dxf);
|
|
9552
|
+
const viewer = renderDxf(panel, dxf, ctx);
|
|
9092
9553
|
return {
|
|
9093
9554
|
canCommand(command) {
|
|
9094
9555
|
return viewer.canCommand(command);
|
|
@@ -9557,7 +10018,7 @@ function hexPreview(bytes) {
|
|
|
9557
10018
|
}
|
|
9558
10019
|
return rows.join("\n") || "\u65E0\u53EF\u5C55\u793A\u5B57\u8282\u3002";
|
|
9559
10020
|
}
|
|
9560
|
-
function renderDxf(panel, dxf) {
|
|
10021
|
+
function renderDxf(panel, dxf, ctx) {
|
|
9561
10022
|
const pairs = dxf.split(/\r?\n/).map((line) => line.trim());
|
|
9562
10023
|
const lines = [];
|
|
9563
10024
|
const circles = [];
|
|
@@ -9735,6 +10196,10 @@ function renderDxf(panel, dxf) {
|
|
|
9735
10196
|
note.textContent = `\u5DF2\u63D0\u53D6 LINE ${lines.length} \u4E2A\u3001CIRCLE ${circles.length} \u4E2A\u3001ARC ${arcs.length} \u4E2A\u3001POLYLINE ${polylines.length} \u4E2A\u3001POINT ${points.length} \u4E2A\u3001TEXT ${texts.length} \u4E2A\u3002`;
|
|
9736
10197
|
section.append(note, svg);
|
|
9737
10198
|
panel.append(section);
|
|
10199
|
+
const updateToolbarZoom = () => {
|
|
10200
|
+
ctx.toolbar?.setZoom(initialViewBox.width / currentViewBox.width);
|
|
10201
|
+
};
|
|
10202
|
+
updateToolbarZoom();
|
|
9738
10203
|
return {
|
|
9739
10204
|
canCommand(command) {
|
|
9740
10205
|
return command === "zoom-in" || command === "zoom-out" || command === "zoom-reset";
|
|
@@ -9749,14 +10214,19 @@ function renderDxf(panel, dxf) {
|
|
|
9749
10214
|
currentViewBox.x = centerX - currentViewBox.width / 2;
|
|
9750
10215
|
currentViewBox.y = centerY - currentViewBox.height / 2;
|
|
9751
10216
|
applyViewBox();
|
|
10217
|
+
updateToolbarZoom();
|
|
9752
10218
|
return true;
|
|
9753
10219
|
}
|
|
9754
10220
|
if (command === "zoom-reset") {
|
|
9755
10221
|
currentViewBox = { ...initialViewBox };
|
|
9756
10222
|
applyViewBox();
|
|
10223
|
+
updateToolbarZoom();
|
|
9757
10224
|
return true;
|
|
9758
10225
|
}
|
|
9759
10226
|
return false;
|
|
10227
|
+
},
|
|
10228
|
+
destroy() {
|
|
10229
|
+
ctx.toolbar?.setZoom(void 0);
|
|
9760
10230
|
}
|
|
9761
10231
|
};
|
|
9762
10232
|
}
|
|
@@ -10032,6 +10502,12 @@ function model3dPlugin() {
|
|
|
10032
10502
|
renderer.setSize(width, height, false);
|
|
10033
10503
|
};
|
|
10034
10504
|
resize(ctx.size);
|
|
10505
|
+
const updateToolbarZoom = () => {
|
|
10506
|
+
const currentDistance = vectorDistance(camera.position, controls.target);
|
|
10507
|
+
const initialDistance = vectorDistance(initialFrame.cameraPosition, initialFrame.target);
|
|
10508
|
+
ctx.toolbar?.setZoom(initialDistance > 0 ? initialDistance / currentDistance : void 0);
|
|
10509
|
+
};
|
|
10510
|
+
updateToolbarZoom();
|
|
10035
10511
|
return {
|
|
10036
10512
|
canCommand(command) {
|
|
10037
10513
|
return command === "zoom-in" || command === "zoom-out" || command === "zoom-reset" || command === "rotate-right" || command === "rotate-left";
|
|
@@ -10042,6 +10518,7 @@ function model3dPlugin() {
|
|
|
10042
10518
|
camera.position.sub(controls.target).multiplyScalar(factor).add(controls.target);
|
|
10043
10519
|
camera.updateProjectionMatrix();
|
|
10044
10520
|
controls.update();
|
|
10521
|
+
updateToolbarZoom();
|
|
10045
10522
|
return true;
|
|
10046
10523
|
}
|
|
10047
10524
|
if (command === "zoom-reset") {
|
|
@@ -10051,6 +10528,7 @@ function model3dPlugin() {
|
|
|
10051
10528
|
camera.far = initialFrame.far;
|
|
10052
10529
|
camera.updateProjectionMatrix();
|
|
10053
10530
|
controls.update();
|
|
10531
|
+
updateToolbarZoom();
|
|
10054
10532
|
return true;
|
|
10055
10533
|
}
|
|
10056
10534
|
if (command === "rotate-right" || command === "rotate-left") {
|
|
@@ -10061,6 +10539,7 @@ function model3dPlugin() {
|
|
|
10061
10539
|
},
|
|
10062
10540
|
resize,
|
|
10063
10541
|
destroy() {
|
|
10542
|
+
ctx.toolbar?.setZoom(void 0);
|
|
10064
10543
|
window.cancelAnimationFrame(animationFrame);
|
|
10065
10544
|
controls.dispose();
|
|
10066
10545
|
renderer.dispose();
|
|
@@ -10072,6 +10551,12 @@ function model3dPlugin() {
|
|
|
10072
10551
|
}
|
|
10073
10552
|
};
|
|
10074
10553
|
}
|
|
10554
|
+
function vectorDistance(a, b) {
|
|
10555
|
+
if (typeof a.distanceTo === "function") {
|
|
10556
|
+
return a.distanceTo(b);
|
|
10557
|
+
}
|
|
10558
|
+
return Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z);
|
|
10559
|
+
}
|
|
10075
10560
|
function renderModelFallback(ctx, url, isExternal, message) {
|
|
10076
10561
|
const panel = document.createElement("div");
|
|
10077
10562
|
panel.className = "ofv-fallback";
|
|
@@ -10428,34 +10913,65 @@ function gisPlugin() {
|
|
|
10428
10913
|
}
|
|
10429
10914
|
const wrapper = document.createElement("div");
|
|
10430
10915
|
wrapper.className = "ofv-gis-viewer";
|
|
10431
|
-
|
|
10916
|
+
const summary = summarizeGeoJson(geojson);
|
|
10917
|
+
wrapper.append(createGisSummary(summary));
|
|
10432
10918
|
ctx.viewport.appendChild(wrapper);
|
|
10433
10919
|
const mapContainer = document.createElement("div");
|
|
10434
10920
|
mapContainer.className = "ofv-map-stage";
|
|
10435
10921
|
wrapper.appendChild(mapContainer);
|
|
10922
|
+
if (summary.features === 0) {
|
|
10923
|
+
mapContainer.append(createEmptyMapState());
|
|
10924
|
+
} else {
|
|
10925
|
+
mapContainer.append(createMapLegend(summary));
|
|
10926
|
+
}
|
|
10436
10927
|
const map = Leaflet.map(mapContainer).setView([0, 0], 2);
|
|
10437
10928
|
Leaflet.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
|
10438
10929
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
|
10439
10930
|
}).addTo(map);
|
|
10440
10931
|
const geojsonLayer = Leaflet.geoJSON(geojson, {
|
|
10441
10932
|
style: () => ({
|
|
10442
|
-
|
|
10933
|
+
className: "ofv-map-feature",
|
|
10934
|
+
color: "#e11d48",
|
|
10443
10935
|
weight: 2,
|
|
10444
|
-
opacity: 0.
|
|
10445
|
-
fillColor: "#
|
|
10446
|
-
fillOpacity: 0.
|
|
10936
|
+
opacity: 0.92,
|
|
10937
|
+
fillColor: "#fb923c",
|
|
10938
|
+
fillOpacity: 0.3,
|
|
10939
|
+
lineCap: "round",
|
|
10940
|
+
lineJoin: "round"
|
|
10447
10941
|
}),
|
|
10448
10942
|
pointToLayer: (feature, latlng) => {
|
|
10449
10943
|
return Leaflet.circleMarker(latlng, {
|
|
10450
|
-
|
|
10451
|
-
|
|
10944
|
+
className: "ofv-map-feature ofv-map-point",
|
|
10945
|
+
radius: 7,
|
|
10946
|
+
fillColor: "#e11d48",
|
|
10452
10947
|
color: "#ffffff",
|
|
10453
|
-
weight:
|
|
10948
|
+
weight: 2.5,
|
|
10454
10949
|
opacity: 1,
|
|
10455
|
-
fillOpacity: 0.
|
|
10950
|
+
fillOpacity: 0.9
|
|
10456
10951
|
});
|
|
10457
10952
|
},
|
|
10458
10953
|
onEachFeature: (feature, layer) => {
|
|
10954
|
+
const label = feature.properties?.name || feature.properties?.title || feature.properties?.label;
|
|
10955
|
+
if (label) {
|
|
10956
|
+
layer.bindTooltip?.(String(label), {
|
|
10957
|
+
className: "ofv-map-tooltip",
|
|
10958
|
+
direction: "top",
|
|
10959
|
+
sticky: true
|
|
10960
|
+
});
|
|
10961
|
+
}
|
|
10962
|
+
layer.on?.({
|
|
10963
|
+
mouseover(event) {
|
|
10964
|
+
event.target?.setStyle?.({
|
|
10965
|
+
weight: 4,
|
|
10966
|
+
opacity: 1,
|
|
10967
|
+
fillOpacity: 0.4
|
|
10968
|
+
});
|
|
10969
|
+
event.target?.bringToFront?.();
|
|
10970
|
+
},
|
|
10971
|
+
mouseout(event) {
|
|
10972
|
+
geojsonLayer.resetStyle?.(event.target);
|
|
10973
|
+
}
|
|
10974
|
+
});
|
|
10459
10975
|
if (feature.properties) {
|
|
10460
10976
|
const props = feature.properties;
|
|
10461
10977
|
const keys = Object.keys(props);
|
|
@@ -10490,15 +11006,20 @@ function gisPlugin() {
|
|
|
10490
11006
|
const bounds = geojsonLayer.getBounds();
|
|
10491
11007
|
if (bounds.isValid()) {
|
|
10492
11008
|
map.fitBounds(bounds, { padding: [20, 20] });
|
|
11009
|
+
map.invalidateSize();
|
|
10493
11010
|
}
|
|
10494
11011
|
} catch (e) {
|
|
10495
11012
|
console.warn("Could not fit bounds for GeoJSON data:", e);
|
|
10496
11013
|
}
|
|
11014
|
+
const resizeTimers = [0, 80, 240].map((delay) => window.setTimeout(() => {
|
|
11015
|
+
map.invalidateSize();
|
|
11016
|
+
}, delay));
|
|
10497
11017
|
return {
|
|
10498
11018
|
resize() {
|
|
10499
11019
|
map.invalidateSize();
|
|
10500
11020
|
},
|
|
10501
11021
|
destroy() {
|
|
11022
|
+
resizeTimers.forEach((timer) => window.clearTimeout(timer));
|
|
10502
11023
|
map.remove();
|
|
10503
11024
|
wrapper.remove();
|
|
10504
11025
|
}
|
|
@@ -10523,8 +11044,7 @@ function normalizeGisError(error, fileName) {
|
|
|
10523
11044
|
function isGisFallback(value) {
|
|
10524
11045
|
return typeof value === "object" && value !== null && "fallback" in value;
|
|
10525
11046
|
}
|
|
10526
|
-
function createGisSummary(
|
|
10527
|
-
const summary = summarizeGeoJson(geojson);
|
|
11047
|
+
function createGisSummary(summary) {
|
|
10528
11048
|
const bar = document.createElement("div");
|
|
10529
11049
|
bar.className = "ofv-gis-summary";
|
|
10530
11050
|
appendSummaryItem(bar, "\u8981\u7D20", String(summary.features));
|
|
@@ -10538,6 +11058,26 @@ function createGisSummary(geojson) {
|
|
|
10538
11058
|
}
|
|
10539
11059
|
return bar;
|
|
10540
11060
|
}
|
|
11061
|
+
function createEmptyMapState() {
|
|
11062
|
+
const empty = document.createElement("div");
|
|
11063
|
+
empty.className = "ofv-map-empty";
|
|
11064
|
+
const title = document.createElement("strong");
|
|
11065
|
+
title.textContent = "\u6682\u65E0\u53EF\u5C55\u793A\u7684\u5730\u56FE\u8981\u7D20";
|
|
11066
|
+
const detail = document.createElement("span");
|
|
11067
|
+
detail.textContent = "GeoJSON \u5DF2\u8BC6\u522B\uFF0C\u4F46 features \u4E3A\u7A7A\u3002";
|
|
11068
|
+
empty.append(title, detail);
|
|
11069
|
+
return empty;
|
|
11070
|
+
}
|
|
11071
|
+
function createMapLegend(summary) {
|
|
11072
|
+
const legend = document.createElement("div");
|
|
11073
|
+
legend.className = "ofv-map-legend";
|
|
11074
|
+
const title = document.createElement("strong");
|
|
11075
|
+
title.textContent = "GeoJSON";
|
|
11076
|
+
const detail = document.createElement("span");
|
|
11077
|
+
detail.textContent = `${summary.features} \u4E2A\u8981\u7D20 \xB7 ${formatGeometryCounts(summary.geometryCounts)}`;
|
|
11078
|
+
legend.append(title, detail);
|
|
11079
|
+
return legend;
|
|
11080
|
+
}
|
|
10541
11081
|
function appendSummaryItem(parent, label, value) {
|
|
10542
11082
|
const item = document.createElement("span");
|
|
10543
11083
|
const key = document.createElement("span");
|