@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.cjs
CHANGED
|
@@ -735,6 +735,7 @@ function createViewer(options) {
|
|
|
735
735
|
file,
|
|
736
736
|
size: getElementSize(viewport),
|
|
737
737
|
options: normalizedOptions,
|
|
738
|
+
toolbar: toolbar?.getContext(),
|
|
738
739
|
setLoading,
|
|
739
740
|
setError
|
|
740
741
|
});
|
|
@@ -908,18 +909,36 @@ function createToolbar(toolbar, viewport, queue) {
|
|
|
908
909
|
element.setAttribute("role", "toolbar");
|
|
909
910
|
element.setAttribute("aria-label", "File preview toolbar");
|
|
910
911
|
let file;
|
|
912
|
+
let currentIndex = 0;
|
|
913
|
+
let currentLength = queue.getLength();
|
|
911
914
|
let queueLabel;
|
|
912
915
|
let previousButton;
|
|
913
916
|
let nextButton;
|
|
917
|
+
let zoomResetButton;
|
|
918
|
+
let currentZoom;
|
|
914
919
|
const commandButtons = [];
|
|
920
|
+
const customButtons = [];
|
|
915
921
|
const disposers = [];
|
|
916
922
|
const search = createSearchController(viewport);
|
|
917
923
|
let searchInput;
|
|
918
924
|
let searchCount;
|
|
919
|
-
|
|
925
|
+
let canRunCommand = (_command) => false;
|
|
926
|
+
const getContext = () => createToolbarContext({
|
|
927
|
+
file,
|
|
928
|
+
index: currentIndex,
|
|
929
|
+
length: currentLength,
|
|
930
|
+
viewport,
|
|
931
|
+
queue,
|
|
932
|
+
element,
|
|
933
|
+
search,
|
|
934
|
+
canCommand: canRunCommand,
|
|
935
|
+
zoom: currentZoom,
|
|
936
|
+
setZoom
|
|
937
|
+
});
|
|
938
|
+
const addButton = (label, title, action, className, icon) => {
|
|
920
939
|
const button = document.createElement("button");
|
|
921
940
|
button.type = "button";
|
|
922
|
-
button
|
|
941
|
+
setToolbarButtonContent(button, label, icon);
|
|
923
942
|
button.title = title;
|
|
924
943
|
button.setAttribute("aria-label", title);
|
|
925
944
|
if (className) {
|
|
@@ -930,68 +949,119 @@ function createToolbar(toolbar, viewport, queue) {
|
|
|
930
949
|
disposers.push(() => button.removeEventListener("click", action));
|
|
931
950
|
return button;
|
|
932
951
|
};
|
|
933
|
-
const
|
|
934
|
-
const button = document.createElement("button");
|
|
935
|
-
button.type = "button";
|
|
936
|
-
button.textContent = label;
|
|
937
|
-
button.title = title;
|
|
938
|
-
button.setAttribute("aria-label", title);
|
|
939
|
-
const listener = () => {
|
|
940
|
-
void action();
|
|
941
|
-
};
|
|
942
|
-
button.addEventListener("click", listener);
|
|
943
|
-
element.append(button);
|
|
944
|
-
disposers.push(() => button.removeEventListener("click", listener));
|
|
945
|
-
return button;
|
|
946
|
-
};
|
|
947
|
-
if (queue.getLength() > 1) {
|
|
948
|
-
previousButton = addQueueButton("Prev", "Previous file", queue.previous);
|
|
949
|
-
nextButton = addQueueButton("Next", "Next file", queue.next);
|
|
950
|
-
queueLabel = document.createElement("span");
|
|
951
|
-
queueLabel.className = "ofv-toolbar-queue";
|
|
952
|
-
element.append(queueLabel);
|
|
953
|
-
}
|
|
954
|
-
const addCommandButton = (label, title, command) => {
|
|
952
|
+
const addCommandButton = (id, label, title, command) => {
|
|
955
953
|
const button = addButton(label, title, () => {
|
|
956
954
|
queue.command(command);
|
|
957
|
-
});
|
|
955
|
+
}, void 0, options.icons?.[id]);
|
|
958
956
|
button.disabled = true;
|
|
959
957
|
commandButtons.push({ button, command });
|
|
960
958
|
};
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
if (options.rotate) {
|
|
967
|
-
addCommandButton("Rotate", "Rotate right", "rotate-right");
|
|
968
|
-
}
|
|
969
|
-
if (options.download !== false) {
|
|
970
|
-
addButton("Download", "Download file", () => {
|
|
971
|
-
if (file) {
|
|
972
|
-
downloadFile(file);
|
|
959
|
+
const renderDefaultAction = (id) => {
|
|
960
|
+
if (!isBuiltInToolbarAction(id)) {
|
|
961
|
+
const customAction = options.actions?.find((action) => action.id === id);
|
|
962
|
+
if (customAction) {
|
|
963
|
+
renderCustomAction(customAction);
|
|
973
964
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
if (id === "previous" && queue.getLength() > 1) {
|
|
968
|
+
previousButton = addButton(
|
|
969
|
+
getToolbarLabel(options, "previous"),
|
|
970
|
+
getToolbarTitle(options, "previous"),
|
|
971
|
+
() => void queue.previous(),
|
|
972
|
+
void 0,
|
|
973
|
+
options.icons?.previous
|
|
974
|
+
);
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
if (id === "next" && queue.getLength() > 1) {
|
|
978
|
+
nextButton = addButton(
|
|
979
|
+
getToolbarLabel(options, "next"),
|
|
980
|
+
getToolbarTitle(options, "next"),
|
|
981
|
+
() => void queue.next(),
|
|
982
|
+
void 0,
|
|
983
|
+
options.icons?.next
|
|
984
|
+
);
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
if (id === "queue" && queue.getLength() > 1) {
|
|
988
|
+
queueLabel = document.createElement("span");
|
|
989
|
+
queueLabel.className = "ofv-toolbar-queue";
|
|
990
|
+
element.append(queueLabel);
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
if (id === "zoom-out" && options.zoom) {
|
|
994
|
+
addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "zoom-out");
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
if (id === "zoom-in" && options.zoom) {
|
|
998
|
+
addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "zoom-in");
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
if (id === "zoom-reset" && options.zoom) {
|
|
1002
|
+
addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "zoom-reset");
|
|
1003
|
+
zoomResetButton = commandButtons[commandButtons.length - 1]?.button;
|
|
1004
|
+
updateZoomLabel();
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
if (id === "rotate-right" && options.rotate) {
|
|
1008
|
+
addCommandButton(id, getToolbarLabel(options, id), getToolbarTitle(options, id), "rotate-right");
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
if (id === "download" && options.download !== false) {
|
|
1012
|
+
addButton(
|
|
1013
|
+
getToolbarLabel(options, id),
|
|
1014
|
+
getToolbarTitle(options, id),
|
|
1015
|
+
() => getContext().download(),
|
|
1016
|
+
void 0,
|
|
1017
|
+
options.icons?.download
|
|
1018
|
+
);
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
if (id === "fullscreen" && options.fullscreen !== false) {
|
|
1022
|
+
addButton(
|
|
1023
|
+
getToolbarLabel(options, id),
|
|
1024
|
+
getToolbarTitle(options, id),
|
|
1025
|
+
() => getContext().fullscreen(),
|
|
1026
|
+
void 0,
|
|
1027
|
+
options.icons?.fullscreen
|
|
1028
|
+
);
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
if (id === "print" && options.print) {
|
|
1032
|
+
addButton(
|
|
1033
|
+
getToolbarLabel(options, id),
|
|
1034
|
+
getToolbarTitle(options, id),
|
|
1035
|
+
() => getContext().print(),
|
|
1036
|
+
void 0,
|
|
1037
|
+
options.icons?.print
|
|
1038
|
+
);
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
if (id === "search" && options.search !== false) {
|
|
1042
|
+
renderSearchControl();
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
const renderCustomAction = (action) => {
|
|
1047
|
+
const button = addButton(
|
|
1048
|
+
action.label,
|
|
1049
|
+
action.title || action.label,
|
|
1050
|
+
() => void action.onClick(getContext()),
|
|
1051
|
+
action.className,
|
|
1052
|
+
action.icon
|
|
1053
|
+
);
|
|
1054
|
+
button.dataset.ofvToolbarAction = action.id;
|
|
1055
|
+
customButtons.push({ button, action });
|
|
1056
|
+
};
|
|
1057
|
+
const renderSearchControl = () => {
|
|
988
1058
|
const searchGroup = document.createElement("div");
|
|
989
1059
|
searchGroup.className = "ofv-toolbar-search";
|
|
990
|
-
searchGroup.title =
|
|
1060
|
+
searchGroup.title = getToolbarTitle(options, "search");
|
|
991
1061
|
const nextSearchInput = document.createElement("input");
|
|
992
1062
|
nextSearchInput.type = "search";
|
|
993
|
-
nextSearchInput.placeholder = "
|
|
994
|
-
nextSearchInput.setAttribute("aria-label",
|
|
1063
|
+
nextSearchInput.placeholder = getToolbarLabel(options, "search");
|
|
1064
|
+
nextSearchInput.setAttribute("aria-label", getToolbarTitle(options, "search"));
|
|
995
1065
|
const nextSearchCount = document.createElement("span");
|
|
996
1066
|
nextSearchCount.className = "ofv-toolbar-search-count";
|
|
997
1067
|
searchInput = nextSearchInput;
|
|
@@ -1004,18 +1074,71 @@ function createToolbar(toolbar, viewport, queue) {
|
|
|
1004
1074
|
searchGroup.append(nextSearchInput, nextSearchCount);
|
|
1005
1075
|
element.append(searchGroup);
|
|
1006
1076
|
disposers.push(() => nextSearchInput.removeEventListener("input", runSearch));
|
|
1077
|
+
};
|
|
1078
|
+
const renderToolbar = () => {
|
|
1079
|
+
if (options.render) {
|
|
1080
|
+
element.replaceChildren();
|
|
1081
|
+
const customElement = options.render(getContext());
|
|
1082
|
+
if (customElement) {
|
|
1083
|
+
element.append(customElement);
|
|
1084
|
+
}
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
getToolbarOrder(options, queue.getLength()).forEach(renderDefaultAction);
|
|
1088
|
+
getImplicitCustomActions(options).forEach(renderCustomAction);
|
|
1089
|
+
};
|
|
1090
|
+
renderToolbar();
|
|
1091
|
+
const updateCustomButtons = () => {
|
|
1092
|
+
const context = getContext();
|
|
1093
|
+
for (const { button, action } of customButtons) {
|
|
1094
|
+
button.disabled = evaluateToolbarFlag(action.disabled, context);
|
|
1095
|
+
button.hidden = evaluateToolbarFlag(action.hidden, context);
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
const resetSearch = () => {
|
|
1099
|
+
search.clear();
|
|
1100
|
+
if (searchInput) {
|
|
1101
|
+
searchInput.value = "";
|
|
1102
|
+
}
|
|
1103
|
+
if (searchCount) {
|
|
1104
|
+
searchCount.textContent = "";
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1107
|
+
function setZoom(zoom) {
|
|
1108
|
+
currentZoom = typeof zoom === "number" && Number.isFinite(zoom) && zoom > 0 ? zoom : void 0;
|
|
1109
|
+
updateZoomLabel();
|
|
1110
|
+
updateCustomButtons();
|
|
1111
|
+
refreshCustomRender();
|
|
1112
|
+
}
|
|
1113
|
+
function updateZoomLabel() {
|
|
1114
|
+
if (!zoomResetButton) {
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
setToolbarButtonContent(
|
|
1118
|
+
zoomResetButton,
|
|
1119
|
+
currentZoom === void 0 ? getToolbarLabel(options, "zoom-reset") : formatToolbarZoom(currentZoom),
|
|
1120
|
+
options.icons?.["zoom-reset"]
|
|
1121
|
+
);
|
|
1007
1122
|
}
|
|
1123
|
+
const refreshCustomRender = () => {
|
|
1124
|
+
if (!options.render) {
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
element.replaceChildren();
|
|
1128
|
+
const customElement = options.render(getContext());
|
|
1129
|
+
if (customElement) {
|
|
1130
|
+
element.append(customElement);
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1008
1133
|
return {
|
|
1009
1134
|
element,
|
|
1010
1135
|
update(nextFile, index, length) {
|
|
1011
1136
|
file = nextFile;
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
searchCount.textContent = "";
|
|
1018
|
-
}
|
|
1137
|
+
currentIndex = index;
|
|
1138
|
+
currentLength = length;
|
|
1139
|
+
currentZoom = void 0;
|
|
1140
|
+
updateZoomLabel();
|
|
1141
|
+
resetSearch();
|
|
1019
1142
|
commandButtons.forEach(({ button }) => {
|
|
1020
1143
|
button.disabled = true;
|
|
1021
1144
|
});
|
|
@@ -1028,20 +1151,257 @@ function createToolbar(toolbar, viewport, queue) {
|
|
|
1028
1151
|
if (nextButton) {
|
|
1029
1152
|
nextButton.disabled = index >= length - 1;
|
|
1030
1153
|
}
|
|
1154
|
+
updateCustomButtons();
|
|
1155
|
+
refreshCustomRender();
|
|
1031
1156
|
},
|
|
1032
1157
|
setCommandSupport(isSupported) {
|
|
1158
|
+
canRunCommand = isSupported;
|
|
1033
1159
|
commandButtons.forEach(({ button, command }) => {
|
|
1034
1160
|
button.disabled = !isSupported(command);
|
|
1035
1161
|
});
|
|
1162
|
+
updateCustomButtons();
|
|
1163
|
+
refreshCustomRender();
|
|
1036
1164
|
},
|
|
1165
|
+
getContext,
|
|
1166
|
+
setZoom,
|
|
1037
1167
|
destroy() {
|
|
1038
1168
|
search.clear();
|
|
1039
1169
|
for (const dispose of disposers) {
|
|
1040
1170
|
dispose();
|
|
1041
1171
|
}
|
|
1172
|
+
element.replaceChildren();
|
|
1042
1173
|
}
|
|
1043
1174
|
};
|
|
1044
1175
|
}
|
|
1176
|
+
function createToolbarContext({
|
|
1177
|
+
file,
|
|
1178
|
+
index,
|
|
1179
|
+
length,
|
|
1180
|
+
viewport,
|
|
1181
|
+
queue,
|
|
1182
|
+
element,
|
|
1183
|
+
search,
|
|
1184
|
+
canCommand,
|
|
1185
|
+
zoom,
|
|
1186
|
+
setZoom
|
|
1187
|
+
}) {
|
|
1188
|
+
return {
|
|
1189
|
+
file,
|
|
1190
|
+
index,
|
|
1191
|
+
length,
|
|
1192
|
+
viewport,
|
|
1193
|
+
canPrevious: index > 0,
|
|
1194
|
+
canNext: index < length - 1,
|
|
1195
|
+
zoom,
|
|
1196
|
+
zoomLabel: zoom === void 0 ? void 0 : formatToolbarZoom(zoom),
|
|
1197
|
+
async previous() {
|
|
1198
|
+
await queue.previous();
|
|
1199
|
+
},
|
|
1200
|
+
async next() {
|
|
1201
|
+
await queue.next();
|
|
1202
|
+
},
|
|
1203
|
+
command: queue.command,
|
|
1204
|
+
canCommand,
|
|
1205
|
+
setZoom,
|
|
1206
|
+
download() {
|
|
1207
|
+
if (file) {
|
|
1208
|
+
downloadFile(file);
|
|
1209
|
+
}
|
|
1210
|
+
},
|
|
1211
|
+
fullscreen() {
|
|
1212
|
+
void element.parentElement?.requestFullscreen?.();
|
|
1213
|
+
},
|
|
1214
|
+
print() {
|
|
1215
|
+
printPreview(viewport);
|
|
1216
|
+
},
|
|
1217
|
+
search: search.search,
|
|
1218
|
+
clearSearch: search.clear
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
var defaultToolbarLabels = {
|
|
1222
|
+
previous: "Prev",
|
|
1223
|
+
next: "Next",
|
|
1224
|
+
queue: "",
|
|
1225
|
+
"zoom-out": "-",
|
|
1226
|
+
"zoom-in": "+",
|
|
1227
|
+
"zoom-reset": "100%",
|
|
1228
|
+
"rotate-right": "Rotate",
|
|
1229
|
+
download: "Download",
|
|
1230
|
+
fullscreen: "Fullscreen",
|
|
1231
|
+
print: "Print",
|
|
1232
|
+
search: "Search"
|
|
1233
|
+
};
|
|
1234
|
+
var defaultToolbarTitles = {
|
|
1235
|
+
previous: "Previous file",
|
|
1236
|
+
next: "Next file",
|
|
1237
|
+
queue: "Current file position",
|
|
1238
|
+
"zoom-out": "Zoom out",
|
|
1239
|
+
"zoom-in": "Zoom in",
|
|
1240
|
+
"zoom-reset": "Reset zoom",
|
|
1241
|
+
"rotate-right": "Rotate right",
|
|
1242
|
+
download: "Download file",
|
|
1243
|
+
fullscreen: "Open preview fullscreen",
|
|
1244
|
+
print: "Print preview",
|
|
1245
|
+
search: "Search preview text"
|
|
1246
|
+
};
|
|
1247
|
+
function getToolbarLabel(options, id) {
|
|
1248
|
+
return options.labels?.[id] ?? defaultToolbarLabels[id];
|
|
1249
|
+
}
|
|
1250
|
+
function getToolbarTitle(options, id) {
|
|
1251
|
+
return options.titles?.[id] ?? options.labels?.[id] ?? defaultToolbarTitles[id];
|
|
1252
|
+
}
|
|
1253
|
+
function formatToolbarZoom(zoom) {
|
|
1254
|
+
return `${Math.round(zoom * 100)}%`;
|
|
1255
|
+
}
|
|
1256
|
+
function getToolbarOrder(options, queueLength) {
|
|
1257
|
+
if (options.order) {
|
|
1258
|
+
return options.order;
|
|
1259
|
+
}
|
|
1260
|
+
const actions = [];
|
|
1261
|
+
if (queueLength > 1) {
|
|
1262
|
+
actions.push("previous", "next", "queue");
|
|
1263
|
+
}
|
|
1264
|
+
if (options.zoom) {
|
|
1265
|
+
actions.push("zoom-out", "zoom-in", "zoom-reset");
|
|
1266
|
+
}
|
|
1267
|
+
if (options.rotate) {
|
|
1268
|
+
actions.push("rotate-right");
|
|
1269
|
+
}
|
|
1270
|
+
if (options.download !== false) {
|
|
1271
|
+
actions.push("download");
|
|
1272
|
+
}
|
|
1273
|
+
if (options.fullscreen !== false) {
|
|
1274
|
+
actions.push("fullscreen");
|
|
1275
|
+
}
|
|
1276
|
+
if (options.print) {
|
|
1277
|
+
actions.push("print");
|
|
1278
|
+
}
|
|
1279
|
+
if (options.search !== false) {
|
|
1280
|
+
actions.push("search");
|
|
1281
|
+
}
|
|
1282
|
+
return actions;
|
|
1283
|
+
}
|
|
1284
|
+
function getImplicitCustomActions(options) {
|
|
1285
|
+
if (options.order || !options.actions) {
|
|
1286
|
+
return [];
|
|
1287
|
+
}
|
|
1288
|
+
return [...options.actions].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
1289
|
+
}
|
|
1290
|
+
function evaluateToolbarFlag(value, context) {
|
|
1291
|
+
return typeof value === "function" ? value(context) : Boolean(value);
|
|
1292
|
+
}
|
|
1293
|
+
function setToolbarButtonContent(button, label, icon) {
|
|
1294
|
+
button.replaceChildren();
|
|
1295
|
+
if (!icon) {
|
|
1296
|
+
button.textContent = label;
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
const iconElement = document.createElement("span");
|
|
1300
|
+
iconElement.className = "ofv-toolbar-icon";
|
|
1301
|
+
iconElement.setAttribute("aria-hidden", "true");
|
|
1302
|
+
if (typeof icon === "string") {
|
|
1303
|
+
iconElement.append(sanitizeToolbarIcon(icon));
|
|
1304
|
+
} else {
|
|
1305
|
+
iconElement.append(icon.cloneNode(true));
|
|
1306
|
+
}
|
|
1307
|
+
const labelElement = document.createElement("span");
|
|
1308
|
+
labelElement.className = "ofv-toolbar-label";
|
|
1309
|
+
labelElement.textContent = label;
|
|
1310
|
+
button.append(iconElement, labelElement);
|
|
1311
|
+
}
|
|
1312
|
+
var allowedToolbarIconTags = /* @__PURE__ */ new Set([
|
|
1313
|
+
"svg",
|
|
1314
|
+
"g",
|
|
1315
|
+
"path",
|
|
1316
|
+
"circle",
|
|
1317
|
+
"rect",
|
|
1318
|
+
"line",
|
|
1319
|
+
"polyline",
|
|
1320
|
+
"polygon",
|
|
1321
|
+
"ellipse",
|
|
1322
|
+
"defs",
|
|
1323
|
+
"title",
|
|
1324
|
+
"desc"
|
|
1325
|
+
]);
|
|
1326
|
+
var allowedToolbarIconAttrs = /* @__PURE__ */ new Set([
|
|
1327
|
+
"aria-hidden",
|
|
1328
|
+
"class",
|
|
1329
|
+
"cx",
|
|
1330
|
+
"cy",
|
|
1331
|
+
"d",
|
|
1332
|
+
"fill",
|
|
1333
|
+
"focusable",
|
|
1334
|
+
"height",
|
|
1335
|
+
"id",
|
|
1336
|
+
"points",
|
|
1337
|
+
"r",
|
|
1338
|
+
"rx",
|
|
1339
|
+
"ry",
|
|
1340
|
+
"stroke",
|
|
1341
|
+
"stroke-linecap",
|
|
1342
|
+
"stroke-linejoin",
|
|
1343
|
+
"stroke-width",
|
|
1344
|
+
"transform",
|
|
1345
|
+
"viewBox",
|
|
1346
|
+
"width",
|
|
1347
|
+
"x",
|
|
1348
|
+
"x1",
|
|
1349
|
+
"x2",
|
|
1350
|
+
"y",
|
|
1351
|
+
"y1",
|
|
1352
|
+
"y2"
|
|
1353
|
+
]);
|
|
1354
|
+
function sanitizeToolbarIcon(icon) {
|
|
1355
|
+
const template = document.createElement("template");
|
|
1356
|
+
template.innerHTML = icon.trim();
|
|
1357
|
+
const fragment = document.createDocumentFragment();
|
|
1358
|
+
for (const child of Array.from(template.content.childNodes)) {
|
|
1359
|
+
const sanitized = sanitizeToolbarIconNode(child);
|
|
1360
|
+
if (sanitized) {
|
|
1361
|
+
fragment.append(sanitized);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
return fragment;
|
|
1365
|
+
}
|
|
1366
|
+
function sanitizeToolbarIconNode(node) {
|
|
1367
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
1368
|
+
const text = node.textContent || "";
|
|
1369
|
+
return text.trim() ? document.createTextNode(text) : null;
|
|
1370
|
+
}
|
|
1371
|
+
if (!(node instanceof Element)) {
|
|
1372
|
+
return null;
|
|
1373
|
+
}
|
|
1374
|
+
const tagName = node.tagName.toLowerCase();
|
|
1375
|
+
if (!allowedToolbarIconTags.has(tagName)) {
|
|
1376
|
+
return null;
|
|
1377
|
+
}
|
|
1378
|
+
const sanitized = document.createElementNS("http://www.w3.org/2000/svg", tagName);
|
|
1379
|
+
for (const attr of Array.from(node.attributes)) {
|
|
1380
|
+
if (isSafeToolbarIconAttribute(attr.name, attr.value)) {
|
|
1381
|
+
sanitized.setAttribute(attr.name, attr.value);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
for (const child of Array.from(node.childNodes)) {
|
|
1385
|
+
const sanitizedChild = sanitizeToolbarIconNode(child);
|
|
1386
|
+
if (sanitizedChild) {
|
|
1387
|
+
sanitized.append(sanitizedChild);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
return sanitized;
|
|
1391
|
+
}
|
|
1392
|
+
function isSafeToolbarIconAttribute(name, value) {
|
|
1393
|
+
const attrName = name.toLowerCase();
|
|
1394
|
+
if (attrName.startsWith("on") || attrName.includes(":")) {
|
|
1395
|
+
return false;
|
|
1396
|
+
}
|
|
1397
|
+
if (!allowedToolbarIconAttrs.has(name) && !allowedToolbarIconAttrs.has(attrName) && !attrName.startsWith("data-")) {
|
|
1398
|
+
return false;
|
|
1399
|
+
}
|
|
1400
|
+
return !/^\s*(?:javascript|data:text\/html|vbscript):/i.test(value);
|
|
1401
|
+
}
|
|
1402
|
+
function isBuiltInToolbarAction(id) {
|
|
1403
|
+
return id in defaultToolbarLabels;
|
|
1404
|
+
}
|
|
1045
1405
|
function createSearchController(root) {
|
|
1046
1406
|
const markerClass = "ofv-search-match";
|
|
1047
1407
|
const clear = () => {
|
|
@@ -1450,6 +1810,7 @@ function imagePlugin() {
|
|
|
1450
1810
|
const updateTransform = () => {
|
|
1451
1811
|
image.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale}) rotate(${rotation}deg)`;
|
|
1452
1812
|
zoomLabel.textContent = `${Math.round(scale * 100)}%`;
|
|
1813
|
+
ctx.toolbar?.setZoom(scale);
|
|
1453
1814
|
};
|
|
1454
1815
|
const showImageFallback = () => {
|
|
1455
1816
|
stage.replaceChildren(createImageFallback(ctx.file.name, url));
|
|
@@ -1565,6 +1926,7 @@ function imagePlugin() {
|
|
|
1565
1926
|
image.style.maxHeight = `${Math.max(0, size.height - controls.offsetHeight)}px`;
|
|
1566
1927
|
},
|
|
1567
1928
|
destroy() {
|
|
1929
|
+
ctx.toolbar?.setZoom(void 0);
|
|
1568
1930
|
for (const dispose of disposers) {
|
|
1569
1931
|
dispose();
|
|
1570
1932
|
}
|
|
@@ -3123,7 +3485,6 @@ var mimeLangMap = {
|
|
|
3123
3485
|
};
|
|
3124
3486
|
var MAX_HIGHLIGHT_CHARS = 18e4;
|
|
3125
3487
|
var MAX_RENDER_CHARS = 6e5;
|
|
3126
|
-
var MONACO_LOADER_KEY = "__OFV_MONACO_LOADER__";
|
|
3127
3488
|
function loadPrismCss(theme) {
|
|
3128
3489
|
const lightId = "ofv-prism-css-light";
|
|
3129
3490
|
const darkId = "ofv-prism-css-dark";
|
|
@@ -3347,10 +3708,6 @@ function textPlugin() {
|
|
|
3347
3708
|
wrapButton.addEventListener("click", () => {
|
|
3348
3709
|
const wrapped = wrapper.classList.toggle("is-wrapped");
|
|
3349
3710
|
wrapButton.setAttribute("aria-pressed", String(wrapped));
|
|
3350
|
-
monacoEditor?.updateOptions?.({ wordWrap: wrapped ? "on" : "off" });
|
|
3351
|
-
if (fallbackEditor) {
|
|
3352
|
-
fallbackEditor.wrap = wrapped ? "soft" : "off";
|
|
3353
|
-
}
|
|
3354
3711
|
});
|
|
3355
3712
|
const copyButton = document.createElement("button");
|
|
3356
3713
|
copyButton.type = "button";
|
|
@@ -3375,12 +3732,7 @@ function textPlugin() {
|
|
|
3375
3732
|
downloadText(ctx.file.name, text);
|
|
3376
3733
|
status.textContent = "Download ready";
|
|
3377
3734
|
});
|
|
3378
|
-
|
|
3379
|
-
editorButton.type = "button";
|
|
3380
|
-
editorButton.className = "ofv-code-action";
|
|
3381
|
-
editorButton.textContent = "Editor";
|
|
3382
|
-
editorButton.setAttribute("aria-pressed", "false");
|
|
3383
|
-
actions.append(status, editorButton, wrapButton, copyButton, downloadButton);
|
|
3735
|
+
actions.append(wrapButton, copyButton, downloadButton, status);
|
|
3384
3736
|
header.append(title, actions);
|
|
3385
3737
|
const structureSummary = createTextStructureSummary(text, ext, lang, ctx.file.mimeType);
|
|
3386
3738
|
const body = document.createElement("div");
|
|
@@ -3396,93 +3748,6 @@ function textPlugin() {
|
|
|
3396
3748
|
code.textContent = codeText;
|
|
3397
3749
|
pre.appendChild(code);
|
|
3398
3750
|
body.append(gutter, pre);
|
|
3399
|
-
const editorHost = document.createElement("div");
|
|
3400
|
-
editorHost.className = "ofv-code-editor";
|
|
3401
|
-
editorHost.hidden = true;
|
|
3402
|
-
let monacoEditor;
|
|
3403
|
-
let monacoModel;
|
|
3404
|
-
let fallbackEditor;
|
|
3405
|
-
let editorReady = false;
|
|
3406
|
-
let editorLoading;
|
|
3407
|
-
const showReader = () => {
|
|
3408
|
-
editorHost.hidden = true;
|
|
3409
|
-
body.hidden = false;
|
|
3410
|
-
wrapper.classList.remove("is-editor");
|
|
3411
|
-
editorButton.textContent = "Editor";
|
|
3412
|
-
editorButton.setAttribute("aria-pressed", "false");
|
|
3413
|
-
};
|
|
3414
|
-
const showEditor = async () => {
|
|
3415
|
-
if (text.length > MAX_RENDER_CHARS) {
|
|
3416
|
-
status.textContent = "Editor skipped for large file";
|
|
3417
|
-
return;
|
|
3418
|
-
}
|
|
3419
|
-
if (editorReady) {
|
|
3420
|
-
body.hidden = true;
|
|
3421
|
-
editorHost.hidden = false;
|
|
3422
|
-
wrapper.classList.add("is-editor");
|
|
3423
|
-
editorButton.textContent = "Reader";
|
|
3424
|
-
editorButton.setAttribute("aria-pressed", "true");
|
|
3425
|
-
monacoEditor?.layout?.();
|
|
3426
|
-
return;
|
|
3427
|
-
}
|
|
3428
|
-
if (!editorLoading) {
|
|
3429
|
-
editorLoading = (async () => {
|
|
3430
|
-
editorButton.disabled = true;
|
|
3431
|
-
status.textContent = "Loading editor";
|
|
3432
|
-
try {
|
|
3433
|
-
const monaco = await loadMonaco();
|
|
3434
|
-
if (!monaco.editor?.create) {
|
|
3435
|
-
throw new Error("Monaco editor API is unavailable.");
|
|
3436
|
-
}
|
|
3437
|
-
const monacoLanguage = toMonacoLanguage(ext, lang);
|
|
3438
|
-
monaco.editor.setTheme?.(isDark ? "vs-dark" : "vs");
|
|
3439
|
-
monacoModel = monaco.editor.createModel?.(text, monacoLanguage);
|
|
3440
|
-
monacoEditor = monaco.editor.create(editorHost, {
|
|
3441
|
-
...monacoModel ? { model: monacoModel } : { value: text, language: monacoLanguage },
|
|
3442
|
-
automaticLayout: true,
|
|
3443
|
-
fontFamily: "var(--ofv-font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace)",
|
|
3444
|
-
fontSize: 13,
|
|
3445
|
-
lineNumbers: "on",
|
|
3446
|
-
minimap: { enabled: text.length <= 8e4 },
|
|
3447
|
-
readOnly: true,
|
|
3448
|
-
renderLineHighlight: "line",
|
|
3449
|
-
scrollBeyondLastLine: false,
|
|
3450
|
-
wordWrap: wrapper.classList.contains("is-wrapped") ? "on" : "off"
|
|
3451
|
-
});
|
|
3452
|
-
editorReady = true;
|
|
3453
|
-
status.textContent = "Editor ready";
|
|
3454
|
-
body.hidden = true;
|
|
3455
|
-
editorHost.hidden = false;
|
|
3456
|
-
wrapper.classList.add("is-editor");
|
|
3457
|
-
editorButton.textContent = "Reader";
|
|
3458
|
-
editorButton.setAttribute("aria-pressed", "true");
|
|
3459
|
-
monacoEditor.layout?.();
|
|
3460
|
-
} catch (error) {
|
|
3461
|
-
console.warn("Monaco editor failed to load; using built-in code editor fallback:", error);
|
|
3462
|
-
fallbackEditor = createBasicCodeEditor(text, wrapper.classList.contains("is-wrapped"));
|
|
3463
|
-
editorHost.replaceChildren(fallbackEditor);
|
|
3464
|
-
editorReady = true;
|
|
3465
|
-
status.textContent = "Basic editor";
|
|
3466
|
-
body.hidden = true;
|
|
3467
|
-
editorHost.hidden = false;
|
|
3468
|
-
wrapper.classList.add("is-editor");
|
|
3469
|
-
editorButton.textContent = "Reader";
|
|
3470
|
-
editorButton.setAttribute("aria-pressed", "true");
|
|
3471
|
-
} finally {
|
|
3472
|
-
editorButton.disabled = false;
|
|
3473
|
-
}
|
|
3474
|
-
})();
|
|
3475
|
-
}
|
|
3476
|
-
await editorLoading;
|
|
3477
|
-
};
|
|
3478
|
-
editorButton.addEventListener("click", () => {
|
|
3479
|
-
if (editorHost.hidden) {
|
|
3480
|
-
void showEditor();
|
|
3481
|
-
} else {
|
|
3482
|
-
showReader();
|
|
3483
|
-
status.textContent = "Reader mode";
|
|
3484
|
-
}
|
|
3485
|
-
});
|
|
3486
3751
|
wrapper.append(header);
|
|
3487
3752
|
if (structureSummary) {
|
|
3488
3753
|
wrapper.append(structureSummary);
|
|
@@ -3500,7 +3765,6 @@ function textPlugin() {
|
|
|
3500
3765
|
wrapper.append(notice);
|
|
3501
3766
|
}
|
|
3502
3767
|
wrapper.appendChild(body);
|
|
3503
|
-
wrapper.appendChild(editorHost);
|
|
3504
3768
|
ctx.viewport.appendChild(wrapper);
|
|
3505
3769
|
if (shouldHighlight) {
|
|
3506
3770
|
try {
|
|
@@ -3510,12 +3774,7 @@ function textPlugin() {
|
|
|
3510
3774
|
}
|
|
3511
3775
|
}
|
|
3512
3776
|
return {
|
|
3513
|
-
resize() {
|
|
3514
|
-
monacoEditor?.layout?.();
|
|
3515
|
-
},
|
|
3516
3777
|
destroy() {
|
|
3517
|
-
monacoEditor?.dispose?.();
|
|
3518
|
-
monacoModel?.dispose?.();
|
|
3519
3778
|
wrapper.remove();
|
|
3520
3779
|
}
|
|
3521
3780
|
};
|
|
@@ -3530,45 +3789,6 @@ function normalizeFileName2(name) {
|
|
|
3530
3789
|
const baseName = name.split(/[\\/]/).pop() || name;
|
|
3531
3790
|
return baseName.toLowerCase();
|
|
3532
3791
|
}
|
|
3533
|
-
async function loadMonaco() {
|
|
3534
|
-
const injectedLoader = globalThis[MONACO_LOADER_KEY];
|
|
3535
|
-
if (typeof injectedLoader === "function") {
|
|
3536
|
-
return await injectedLoader();
|
|
3537
|
-
}
|
|
3538
|
-
const importer = new Function("specifier", "return import(specifier)");
|
|
3539
|
-
return importer("monaco-editor");
|
|
3540
|
-
}
|
|
3541
|
-
function toMonacoLanguage(ext, prismLanguage) {
|
|
3542
|
-
if (ext === "vue") {
|
|
3543
|
-
return "html";
|
|
3544
|
-
}
|
|
3545
|
-
if (ext === "tsx") {
|
|
3546
|
-
return "typescript";
|
|
3547
|
-
}
|
|
3548
|
-
if (ext === "jsx") {
|
|
3549
|
-
return "javascript";
|
|
3550
|
-
}
|
|
3551
|
-
if (prismLanguage === "markup") {
|
|
3552
|
-
return ext === "xml" ? "xml" : "html";
|
|
3553
|
-
}
|
|
3554
|
-
if (prismLanguage === "bash") {
|
|
3555
|
-
return "shell";
|
|
3556
|
-
}
|
|
3557
|
-
if (prismLanguage === "none") {
|
|
3558
|
-
return "plaintext";
|
|
3559
|
-
}
|
|
3560
|
-
return prismLanguage;
|
|
3561
|
-
}
|
|
3562
|
-
function createBasicCodeEditor(text, wrapped) {
|
|
3563
|
-
const textarea = document.createElement("textarea");
|
|
3564
|
-
textarea.className = "ofv-code-editor-fallback";
|
|
3565
|
-
textarea.readOnly = true;
|
|
3566
|
-
textarea.spellcheck = false;
|
|
3567
|
-
textarea.wrap = wrapped ? "soft" : "off";
|
|
3568
|
-
textarea.value = text;
|
|
3569
|
-
textarea.setAttribute("aria-label", "Code editor preview");
|
|
3570
|
-
return textarea;
|
|
3571
|
-
}
|
|
3572
3792
|
function createTextFallback(fileName, url) {
|
|
3573
3793
|
const fallback = document.createElement("div");
|
|
3574
3794
|
fallback.className = "ofv-fallback";
|
|
@@ -3806,7 +4026,13 @@ function pdfPlugin(options = {}) {
|
|
|
3806
4026
|
scroller.className = "ofv-pdf ofv-pdf-pages";
|
|
3807
4027
|
viewer.append(summary, scroller);
|
|
3808
4028
|
ctx.viewport.append(viewer);
|
|
3809
|
-
const documentTask = pdf.getDocument(
|
|
4029
|
+
const documentTask = pdf.getDocument({
|
|
4030
|
+
url,
|
|
4031
|
+
cMapUrl: normalizedOptions.cMapUrl ?? `https://cdn.jsdelivr.net/npm/pdfjs-dist@${pdf.version}/cmaps/`,
|
|
4032
|
+
cMapPacked: normalizedOptions.cMapPacked ?? true,
|
|
4033
|
+
standardFontDataUrl: normalizedOptions.standardFontDataUrl ?? `https://cdn.jsdelivr.net/npm/pdfjs-dist@${pdf.version}/standard_fonts/`,
|
|
4034
|
+
useSystemFonts: normalizedOptions.useSystemFonts ?? true
|
|
4035
|
+
});
|
|
3810
4036
|
const doc = await documentTask.promise.catch((error) => {
|
|
3811
4037
|
viewer.remove();
|
|
3812
4038
|
ctx.viewport.classList.add("ofv-center");
|
|
@@ -3842,6 +4068,7 @@ function pdfPlugin(options = {}) {
|
|
|
3842
4068
|
let zoomFactor = 1;
|
|
3843
4069
|
const updateSummary = () => {
|
|
3844
4070
|
renderPdfSummary(summary, doc.numPages, pagesMeta, ctx.options.fit, zoomFactor);
|
|
4071
|
+
ctx.toolbar?.setZoom(zoomFactor);
|
|
3845
4072
|
};
|
|
3846
4073
|
const clearPage = (pageIdx) => {
|
|
3847
4074
|
const state = pageStates[pageIdx];
|
|
@@ -3865,12 +4092,17 @@ function pdfPlugin(options = {}) {
|
|
|
3865
4092
|
try {
|
|
3866
4093
|
const page = await doc.getPage(pageIdx + 1);
|
|
3867
4094
|
const meta = pagesMeta[pageIdx];
|
|
3868
|
-
const scale = ctx.options.fit === "actual" ? zoomFactor : Math.max(0.
|
|
4095
|
+
const scale = ctx.options.fit === "actual" ? zoomFactor : Math.max(0.05, Math.min(5, getPdfAvailableWidth(size.width) / meta.width * zoomFactor));
|
|
3869
4096
|
const viewport = page.getViewport({ scale });
|
|
4097
|
+
const outputScale = getPdfOutputScale();
|
|
4098
|
+
const cssWidth = Math.floor(viewport.width);
|
|
4099
|
+
const cssHeight = Math.floor(viewport.height);
|
|
3870
4100
|
const canvas = document.createElement("canvas");
|
|
3871
4101
|
canvas.className = "ofv-pdf-page";
|
|
3872
|
-
canvas.width = Math.floor(
|
|
3873
|
-
canvas.height = Math.floor(
|
|
4102
|
+
canvas.width = Math.floor(cssWidth * outputScale);
|
|
4103
|
+
canvas.height = Math.floor(cssHeight * outputScale);
|
|
4104
|
+
canvas.style.width = `${cssWidth}px`;
|
|
4105
|
+
canvas.style.height = `${cssHeight}px`;
|
|
3874
4106
|
const context = canvas.getContext("2d");
|
|
3875
4107
|
if (!context) {
|
|
3876
4108
|
throw new Error("Canvas 2D context is not available.");
|
|
@@ -3879,7 +4111,8 @@ function pdfPlugin(options = {}) {
|
|
|
3879
4111
|
state.canvas = canvas;
|
|
3880
4112
|
const renderTask = page.render({
|
|
3881
4113
|
canvasContext: context,
|
|
3882
|
-
viewport
|
|
4114
|
+
viewport,
|
|
4115
|
+
transform: outputScale === 1 ? void 0 : [outputScale, 0, 0, outputScale, 0, 0]
|
|
3883
4116
|
});
|
|
3884
4117
|
state.renderTask = renderTask;
|
|
3885
4118
|
await renderTask.promise;
|
|
@@ -3887,8 +4120,8 @@ function pdfPlugin(options = {}) {
|
|
|
3887
4120
|
const textContent = await page.getTextContent();
|
|
3888
4121
|
const textLayer = document.createElement("div");
|
|
3889
4122
|
textLayer.className = "ofv-pdf-text-layer";
|
|
3890
|
-
textLayer.style.width = `${
|
|
3891
|
-
textLayer.style.height = `${
|
|
4123
|
+
textLayer.style.width = `${cssWidth}px`;
|
|
4124
|
+
textLayer.style.height = `${cssHeight}px`;
|
|
3892
4125
|
state.wrapper.appendChild(textLayer);
|
|
3893
4126
|
for (const item of textContent.items) {
|
|
3894
4127
|
if (!("str" in item)) continue;
|
|
@@ -3949,7 +4182,7 @@ function pdfPlugin(options = {}) {
|
|
|
3949
4182
|
}
|
|
3950
4183
|
for (let i = 0; i < doc.numPages; i++) {
|
|
3951
4184
|
const meta = pagesMeta[i];
|
|
3952
|
-
const scale = ctx.options.fit === "actual" ? zoomFactor : Math.max(0.
|
|
4185
|
+
const scale = ctx.options.fit === "actual" ? zoomFactor : Math.max(0.05, Math.min(5, getPdfAvailableWidth(size.width) / meta.width * zoomFactor));
|
|
3953
4186
|
const w = Math.floor(meta.width * scale);
|
|
3954
4187
|
const h = Math.floor(meta.height * scale);
|
|
3955
4188
|
const wrapper = document.createElement("div");
|
|
@@ -4004,6 +4237,7 @@ function pdfPlugin(options = {}) {
|
|
|
4004
4237
|
}, 120);
|
|
4005
4238
|
},
|
|
4006
4239
|
destroy() {
|
|
4240
|
+
ctx.toolbar?.setZoom(void 0);
|
|
4007
4241
|
window.clearTimeout(resizeTimer);
|
|
4008
4242
|
observer?.disconnect();
|
|
4009
4243
|
pageStates.forEach((state) => {
|
|
@@ -4022,6 +4256,19 @@ function pdfPlugin(options = {}) {
|
|
|
4022
4256
|
}
|
|
4023
4257
|
};
|
|
4024
4258
|
}
|
|
4259
|
+
function getPdfOutputScale() {
|
|
4260
|
+
if (typeof window === "undefined") {
|
|
4261
|
+
return 1;
|
|
4262
|
+
}
|
|
4263
|
+
return Math.max(1, Math.min(window.devicePixelRatio || 1, 2.5));
|
|
4264
|
+
}
|
|
4265
|
+
function getPdfAvailableWidth(width) {
|
|
4266
|
+
if (!Number.isFinite(width) || width <= 0) {
|
|
4267
|
+
return 1;
|
|
4268
|
+
}
|
|
4269
|
+
const gutter = width < 160 ? 16 : 32;
|
|
4270
|
+
return Math.max(1, width - gutter);
|
|
4271
|
+
}
|
|
4025
4272
|
function renderPdfSummary(summary, pages, pagesMeta, fit, zoomFactor) {
|
|
4026
4273
|
summary.replaceChildren();
|
|
4027
4274
|
appendPdfSummary(summary, "\u9875\u6570", String(pages));
|
|
@@ -4781,8 +5028,9 @@ function officePlugin() {
|
|
|
4781
5028
|
ctx.viewport.append(panel);
|
|
4782
5029
|
const extension = resolveFormat(ctx.file, officeMimeFormatMap);
|
|
4783
5030
|
const arrayBuffer = await readArrayBuffer(ctx.file);
|
|
5031
|
+
let disposeDocxFit;
|
|
4784
5032
|
if (fileIsDocx(extension)) {
|
|
4785
|
-
await renderDocx(panel, arrayBuffer);
|
|
5033
|
+
disposeDocxFit = await renderDocx(panel, arrayBuffer);
|
|
4786
5034
|
} else if (extension === "rtf") {
|
|
4787
5035
|
renderPlainDocument(panel, "RTF \u6587\u6863", rtfToText(await readTextFromBuffer(arrayBuffer)));
|
|
4788
5036
|
} else if (extension === "odt") {
|
|
@@ -4807,6 +5055,7 @@ function officePlugin() {
|
|
|
4807
5055
|
}
|
|
4808
5056
|
return {
|
|
4809
5057
|
destroy() {
|
|
5058
|
+
disposeDocxFit?.();
|
|
4810
5059
|
panel.remove();
|
|
4811
5060
|
}
|
|
4812
5061
|
};
|
|
@@ -4840,6 +5089,7 @@ async function renderDocx(panel, arrayBuffer) {
|
|
|
4840
5089
|
experimental: true,
|
|
4841
5090
|
useBase64URL: true
|
|
4842
5091
|
});
|
|
5092
|
+
return fitDocxPages(content);
|
|
4843
5093
|
} catch (error) {
|
|
4844
5094
|
content.replaceChildren();
|
|
4845
5095
|
const fallbackNote = document.createElement("div");
|
|
@@ -4854,6 +5104,74 @@ async function renderDocx(panel, arrayBuffer) {
|
|
|
4854
5104
|
}
|
|
4855
5105
|
console.warn("DOCX layout preview failed, fell back to Mammoth:", error);
|
|
4856
5106
|
}
|
|
5107
|
+
return () => void 0;
|
|
5108
|
+
}
|
|
5109
|
+
function fitDocxPages(container) {
|
|
5110
|
+
const wrapper = container.querySelector(".ofv-docx-wrapper");
|
|
5111
|
+
if (!wrapper) {
|
|
5112
|
+
return () => void 0;
|
|
5113
|
+
}
|
|
5114
|
+
const update = () => {
|
|
5115
|
+
const frames = ensureDocxPageFrames(wrapper);
|
|
5116
|
+
if (frames.length === 0) {
|
|
5117
|
+
wrapper.style.removeProperty("--ofv-docx-scale");
|
|
5118
|
+
return;
|
|
5119
|
+
}
|
|
5120
|
+
const availableWidth = Math.max(1, container.clientWidth - 48);
|
|
5121
|
+
const pageWidth = Math.max(
|
|
5122
|
+
1,
|
|
5123
|
+
...frames.map(({ page }) => {
|
|
5124
|
+
const rectWidth = page.getBoundingClientRect().width;
|
|
5125
|
+
return page.offsetWidth || rectWidth || parseCssPixelValue(page.style.width) || 794;
|
|
5126
|
+
})
|
|
5127
|
+
);
|
|
5128
|
+
const scale = Math.min(1, Math.max(0.35, availableWidth / pageWidth));
|
|
5129
|
+
wrapper.style.setProperty("--ofv-docx-scale", formatCssNumber(scale));
|
|
5130
|
+
wrapper.style.setProperty("--ofv-docx-page-width", `${pageWidth}px`);
|
|
5131
|
+
for (const { frame, page } of frames) {
|
|
5132
|
+
const pageHeight = page.offsetHeight || page.getBoundingClientRect().height || parseCssPixelValue(page.style.height);
|
|
5133
|
+
if (pageHeight > 0) {
|
|
5134
|
+
frame.style.height = `${Math.ceil(pageHeight * scale)}px`;
|
|
5135
|
+
}
|
|
5136
|
+
}
|
|
5137
|
+
};
|
|
5138
|
+
update();
|
|
5139
|
+
const timers = [0, 80, 240].map((delay) => window.setTimeout(update, delay));
|
|
5140
|
+
if (typeof ResizeObserver === "undefined") {
|
|
5141
|
+
window.addEventListener("resize", update);
|
|
5142
|
+
return () => {
|
|
5143
|
+
timers.forEach((timer) => window.clearTimeout(timer));
|
|
5144
|
+
window.removeEventListener("resize", update);
|
|
5145
|
+
};
|
|
5146
|
+
}
|
|
5147
|
+
const observer = new ResizeObserver(update);
|
|
5148
|
+
observer.observe(container);
|
|
5149
|
+
observer.observe(wrapper);
|
|
5150
|
+
return () => {
|
|
5151
|
+
timers.forEach((timer) => window.clearTimeout(timer));
|
|
5152
|
+
observer.disconnect();
|
|
5153
|
+
};
|
|
5154
|
+
}
|
|
5155
|
+
function ensureDocxPageFrames(wrapper) {
|
|
5156
|
+
const pages = Array.from(wrapper.querySelectorAll("section.ofv-docx"));
|
|
5157
|
+
return pages.map((page) => {
|
|
5158
|
+
const parent = page.parentElement;
|
|
5159
|
+
if (parent?.classList.contains("ofv-docx-page-frame")) {
|
|
5160
|
+
return { frame: parent, page };
|
|
5161
|
+
}
|
|
5162
|
+
const frame = document.createElement("div");
|
|
5163
|
+
frame.className = "ofv-docx-page-frame";
|
|
5164
|
+
page.before(frame);
|
|
5165
|
+
frame.append(page);
|
|
5166
|
+
return { frame, page };
|
|
5167
|
+
});
|
|
5168
|
+
}
|
|
5169
|
+
function parseCssPixelValue(value) {
|
|
5170
|
+
const parsed = Number.parseFloat(value);
|
|
5171
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
5172
|
+
}
|
|
5173
|
+
function formatCssNumber(value) {
|
|
5174
|
+
return Number.isFinite(value) ? value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "") : "1";
|
|
4857
5175
|
}
|
|
4858
5176
|
async function renderDocxTextFallback(container, arrayBuffer) {
|
|
4859
5177
|
const article = document.createElement("article");
|
|
@@ -4952,7 +5270,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
|
|
|
4952
5270
|
workbook = xlsx.read(arrayBuffer, { type: "array" });
|
|
4953
5271
|
} catch (error) {
|
|
4954
5272
|
if (isLegacyOfficeBinary(extension)) {
|
|
4955
|
-
renderLegacyOfficeBinary(panel, extension, arrayBuffer, normalizeOfficeError(error));
|
|
5273
|
+
renderLegacyOfficeBinary(panel, extension, arrayBuffer, `\u8868\u683C\u89E3\u6790\u5931\u8D25\uFF1A${normalizeOfficeError(error)}`);
|
|
4956
5274
|
return;
|
|
4957
5275
|
}
|
|
4958
5276
|
renderSheetFallback(panel, extension, normalizeOfficeError(error));
|
|
@@ -6057,14 +6375,15 @@ function legacyOfficeFormatLabel(extension) {
|
|
|
6057
6375
|
function renderLegacyOfficeBinary(panel, extension, arrayBuffer, parseError) {
|
|
6058
6376
|
const fragments = extractLegacyOfficeText(arrayBuffer);
|
|
6059
6377
|
panel.replaceChildren();
|
|
6060
|
-
const section = createSection("Office \
|
|
6378
|
+
const section = createSection("Office \u8F6C\u6362\u63D0\u793A");
|
|
6379
|
+
section.classList.add("ofv-office-conversion");
|
|
6061
6380
|
const format = document.createElement("p");
|
|
6062
6381
|
const strong = document.createElement("strong");
|
|
6063
6382
|
strong.textContent = `.${extension}`;
|
|
6064
6383
|
format.append(
|
|
6065
6384
|
strong,
|
|
6066
6385
|
document.createTextNode(
|
|
6067
|
-
" \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\
|
|
6386
|
+
" \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"
|
|
6068
6387
|
)
|
|
6069
6388
|
);
|
|
6070
6389
|
const meta = document.createElement("dl");
|
|
@@ -6073,12 +6392,15 @@ function renderLegacyOfficeBinary(panel, extension, arrayBuffer, parseError) {
|
|
|
6073
6392
|
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");
|
|
6074
6393
|
appendOfficeBinaryMeta(meta, "\u6587\u672C\u7247\u6BB5", `${fragments.length} \u6BB5`);
|
|
6075
6394
|
if (parseError) {
|
|
6076
|
-
appendOfficeBinaryMeta(meta, "\
|
|
6395
|
+
appendOfficeBinaryMeta(meta, "\u89E3\u6790\u72B6\u6001", parseError);
|
|
6077
6396
|
}
|
|
6078
6397
|
section.append(format, meta);
|
|
6079
6398
|
if (fragments.length > 0) {
|
|
6080
6399
|
const article = document.createElement("article");
|
|
6081
|
-
article.className = "ofv-document";
|
|
6400
|
+
article.className = "ofv-document ofv-office-binary-fragments";
|
|
6401
|
+
const heading = document.createElement("h4");
|
|
6402
|
+
heading.textContent = "\u53EF\u8BFB\u6587\u672C\u7247\u6BB5";
|
|
6403
|
+
article.append(heading);
|
|
6082
6404
|
for (const fragment of fragments.slice(0, 80)) {
|
|
6083
6405
|
const paragraph = document.createElement("p");
|
|
6084
6406
|
paragraph.textContent = fragment;
|
|
@@ -6087,7 +6409,8 @@ function renderLegacyOfficeBinary(panel, extension, arrayBuffer, parseError) {
|
|
|
6087
6409
|
section.append(article);
|
|
6088
6410
|
} else {
|
|
6089
6411
|
const empty = document.createElement("p");
|
|
6090
|
-
empty.
|
|
6412
|
+
empty.className = "ofv-office-binary-empty";
|
|
6413
|
+
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";
|
|
6091
6414
|
section.append(empty);
|
|
6092
6415
|
}
|
|
6093
6416
|
panel.append(section);
|
|
@@ -6120,12 +6443,12 @@ function normalizeOfficeError(error) {
|
|
|
6120
6443
|
function extractLegacyOfficeText(arrayBuffer) {
|
|
6121
6444
|
const bytes = new Uint8Array(arrayBuffer);
|
|
6122
6445
|
const fragments = [
|
|
6123
|
-
...extractPrintableRuns(bytes),
|
|
6124
|
-
...extractUtf16Runs(bytes)
|
|
6125
|
-
].map((
|
|
6446
|
+
...extractPrintableRuns(bytes).map((text) => ({ text, source: "ascii" })),
|
|
6447
|
+
...extractUtf16Runs(bytes).map((text) => ({ text, source: "utf16" }))
|
|
6448
|
+
].map(({ text, source }) => ({ text: normalizeLegacyText(text), source })).filter(({ text, source }) => isReadableLegacyTextFragment(text, source));
|
|
6126
6449
|
const unique = [];
|
|
6127
6450
|
const seen = /* @__PURE__ */ new Set();
|
|
6128
|
-
for (const fragment of fragments) {
|
|
6451
|
+
for (const { text: fragment } of fragments) {
|
|
6129
6452
|
const key = fragment.toLowerCase();
|
|
6130
6453
|
if (!seen.has(key)) {
|
|
6131
6454
|
seen.add(key);
|
|
@@ -6134,6 +6457,104 @@ function extractLegacyOfficeText(arrayBuffer) {
|
|
|
6134
6457
|
}
|
|
6135
6458
|
return unique.slice(0, 160);
|
|
6136
6459
|
}
|
|
6460
|
+
function isReadableLegacyTextFragment(fragment, source) {
|
|
6461
|
+
if (fragment.length > 600) {
|
|
6462
|
+
return false;
|
|
6463
|
+
}
|
|
6464
|
+
if (isLegacyOfficeMetadataNoise(fragment)) {
|
|
6465
|
+
return false;
|
|
6466
|
+
}
|
|
6467
|
+
if (!/[\p{L}\p{N}]/u.test(fragment)) {
|
|
6468
|
+
return false;
|
|
6469
|
+
}
|
|
6470
|
+
const chars = Array.from(fragment);
|
|
6471
|
+
const letters = chars.filter((char) => /\p{L}/u.test(char)).length;
|
|
6472
|
+
const digits = chars.filter((char) => /\p{N}/u.test(char)).length;
|
|
6473
|
+
const spaces = chars.filter((char) => /\s/u.test(char)).length;
|
|
6474
|
+
const asciiLetters = chars.filter((char) => /[A-Za-z]/.test(char)).length;
|
|
6475
|
+
const cjkLetters = chars.filter((char) => /[\u3400-\u9fff]/u.test(char)).length;
|
|
6476
|
+
const punctuation = chars.filter((char) => /[^\p{L}\p{N}\s]/u.test(char)).length;
|
|
6477
|
+
const alphaNumeric = letters + digits;
|
|
6478
|
+
const readableRatio = alphaNumeric / chars.length;
|
|
6479
|
+
const punctuationRatio = punctuation / chars.length;
|
|
6480
|
+
if (fragment.length < 4 || readableRatio < 0.55 || punctuationRatio > 0.24) {
|
|
6481
|
+
return false;
|
|
6482
|
+
}
|
|
6483
|
+
if (/([\p{L}\p{N}])\1{4,}/u.test(fragment)) {
|
|
6484
|
+
return false;
|
|
6485
|
+
}
|
|
6486
|
+
if (cjkLetters >= 2) {
|
|
6487
|
+
const suspiciousCjk = chars.filter((char) => isAsciiBytePairCjk(char)).length;
|
|
6488
|
+
if (suspiciousCjk / cjkLetters > 0.65) {
|
|
6489
|
+
return false;
|
|
6490
|
+
}
|
|
6491
|
+
if (isLikelyCjkHeading(fragment)) {
|
|
6492
|
+
return true;
|
|
6493
|
+
}
|
|
6494
|
+
if (punctuation > 0 && fragment.length < 12) {
|
|
6495
|
+
return false;
|
|
6496
|
+
}
|
|
6497
|
+
return cjkLetters >= 8 || cjkLetters >= 4 && spaces > 0;
|
|
6498
|
+
}
|
|
6499
|
+
if (asciiLetters >= 4) {
|
|
6500
|
+
if (punctuation > 0 && spaces === 0) {
|
|
6501
|
+
return false;
|
|
6502
|
+
}
|
|
6503
|
+
if (source === "ascii" && /^[A-Z]{2,8}$/.test(fragment)) {
|
|
6504
|
+
return false;
|
|
6505
|
+
}
|
|
6506
|
+
if (spaces > 0) {
|
|
6507
|
+
return letters >= 3;
|
|
6508
|
+
}
|
|
6509
|
+
return fragment.length >= 6;
|
|
6510
|
+
}
|
|
6511
|
+
if (spaces > 0 && letters >= 3) {
|
|
6512
|
+
return true;
|
|
6513
|
+
}
|
|
6514
|
+
return false;
|
|
6515
|
+
}
|
|
6516
|
+
function isLegacyOfficeMetadataNoise(fragment) {
|
|
6517
|
+
if (/[$�\uFFFD]/u.test(fragment)) {
|
|
6518
|
+
return true;
|
|
6519
|
+
}
|
|
6520
|
+
if (/^(?:Root Entry|WordDocument|Workbook|Book|SummaryInformation|DocumentSummaryInformation|CompObj|ObjectPool|Data|PowerPoint Document|Pictures)$/i.test(fragment)) {
|
|
6521
|
+
return true;
|
|
6522
|
+
}
|
|
6523
|
+
if (/\.(dotm?|docm?|pptx?|ppsx?|xlsm?|xlsx?)\b/i.test(fragment)) {
|
|
6524
|
+
return true;
|
|
6525
|
+
}
|
|
6526
|
+
if (/^(?:默认段落字体|普通表格|正文|标题|副标题|目录|页眉|页脚|批注|超链接)(?:\s*\d+)?$/.test(fragment)) {
|
|
6527
|
+
return true;
|
|
6528
|
+
}
|
|
6529
|
+
if (/\b(?:Normal|Default|Calibri|Times New Roman|WPS Office|Microsoft Office|KSOP?ProductBuildVer)\b/i.test(fragment)) {
|
|
6530
|
+
return true;
|
|
6531
|
+
}
|
|
6532
|
+
if (/^\d+(?:Table|List|Heading|Title|Style)$/i.test(fragment)) {
|
|
6533
|
+
return true;
|
|
6534
|
+
}
|
|
6535
|
+
if (/[{(]?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[})]?/i.test(fragment)) {
|
|
6536
|
+
return true;
|
|
6537
|
+
}
|
|
6538
|
+
if (/^[A-Z_]{3,}$/.test(fragment) || /^[A-Za-z]+(?:Information|Document|Storage|Stream|Table|Data|Pool|Obj|Props)$/i.test(fragment)) {
|
|
6539
|
+
return true;
|
|
6540
|
+
}
|
|
6541
|
+
return false;
|
|
6542
|
+
}
|
|
6543
|
+
function isLikelyCjkHeading(fragment) {
|
|
6544
|
+
return /^(?:标题|第[一二三四五六七八九十\d]+[章节条]|[一二三四五六七八九十\d]+[、..])\s*[\p{L}\p{N}\s-]*$/u.test(fragment);
|
|
6545
|
+
}
|
|
6546
|
+
function isAsciiBytePairCjk(char) {
|
|
6547
|
+
const code = char.codePointAt(0) || 0;
|
|
6548
|
+
if (code < 13312 || code > 40959) {
|
|
6549
|
+
return false;
|
|
6550
|
+
}
|
|
6551
|
+
const low = code & 255;
|
|
6552
|
+
const high = code >> 8;
|
|
6553
|
+
return isPrintableAsciiByte(low) && isPrintableAsciiByte(high);
|
|
6554
|
+
}
|
|
6555
|
+
function isPrintableAsciiByte(value) {
|
|
6556
|
+
return value >= 32 && value <= 126;
|
|
6557
|
+
}
|
|
6137
6558
|
function extractPrintableRuns(bytes) {
|
|
6138
6559
|
const fragments = [];
|
|
6139
6560
|
let current = "";
|
|
@@ -6156,6 +6577,13 @@ function extractUtf16Runs(bytes) {
|
|
|
6156
6577
|
const fragments = [];
|
|
6157
6578
|
let current = "";
|
|
6158
6579
|
for (let index = 0; index < bytes.length - 1; index += 2) {
|
|
6580
|
+
if (looksLikeMisalignedAsciiUtf16(bytes[index], bytes[index + 1])) {
|
|
6581
|
+
if (current.length >= 3) {
|
|
6582
|
+
fragments.push(current);
|
|
6583
|
+
}
|
|
6584
|
+
current = "";
|
|
6585
|
+
continue;
|
|
6586
|
+
}
|
|
6159
6587
|
const code = bytes[index] | bytes[index + 1] << 8;
|
|
6160
6588
|
if (code >= 32 && code <= 55295 || code === 9) {
|
|
6161
6589
|
current += String.fromCharCode(code);
|
|
@@ -6171,6 +6599,9 @@ function extractUtf16Runs(bytes) {
|
|
|
6171
6599
|
}
|
|
6172
6600
|
return fragments;
|
|
6173
6601
|
}
|
|
6602
|
+
function looksLikeMisalignedAsciiUtf16(lowByte, highByte) {
|
|
6603
|
+
return lowByte === 0 && (highByte >= 48 && highByte <= 57 || highByte >= 65 && highByte <= 90 || highByte >= 97 && highByte <= 122);
|
|
6604
|
+
}
|
|
6174
6605
|
function normalizeLegacyText(value) {
|
|
6175
6606
|
return value.replace(/[\u0000-\u001f]+/g, " ").replace(/\s+/g, " ").trim();
|
|
6176
6607
|
}
|
|
@@ -6808,18 +7239,45 @@ function archivePlugin() {
|
|
|
6808
7239
|
layout.className = "ofv-archive-layout";
|
|
6809
7240
|
const sidebar = document.createElement("div");
|
|
6810
7241
|
sidebar.className = "ofv-archive-sidebar";
|
|
7242
|
+
const sidebarPanel = document.createElement("div");
|
|
7243
|
+
sidebarPanel.className = "ofv-archive-sidebar-panel";
|
|
6811
7244
|
const header = document.createElement("div");
|
|
6812
7245
|
header.className = "ofv-archive-header";
|
|
6813
|
-
|
|
6814
|
-
|
|
7246
|
+
const sidebarTitle = document.createElement("span");
|
|
7247
|
+
sidebarTitle.className = "ofv-archive-header-title";
|
|
7248
|
+
sidebarTitle.textContent = `\u6587\u4EF6\u5217\u8868 (${archiveEntries.filter((e) => !e.dir).length})`;
|
|
7249
|
+
const sidebarToggle = document.createElement("button");
|
|
7250
|
+
sidebarToggle.className = "ofv-archive-sidebar-toggle";
|
|
7251
|
+
sidebarToggle.type = "button";
|
|
7252
|
+
sidebarToggle.setAttribute("aria-label", "\u5C55\u5F00\u6587\u4EF6\u5217\u8868");
|
|
7253
|
+
sidebarToggle.setAttribute("aria-expanded", "false");
|
|
7254
|
+
sidebarToggle.title = "\u5C55\u5F00\u6587\u4EF6\u5217\u8868";
|
|
7255
|
+
sidebarToggle.textContent = "\u2039";
|
|
7256
|
+
header.append(sidebarToggle, sidebarTitle);
|
|
7257
|
+
sidebarPanel.append(header);
|
|
6815
7258
|
const tree = document.createElement("div");
|
|
6816
7259
|
tree.className = "ofv-archive-tree";
|
|
6817
|
-
|
|
7260
|
+
sidebarPanel.append(tree);
|
|
7261
|
+
sidebar.append(sidebarPanel);
|
|
6818
7262
|
const mainPanel = document.createElement("div");
|
|
6819
7263
|
mainPanel.className = "ofv-archive-main";
|
|
6820
7264
|
layout.append(sidebar, mainPanel);
|
|
6821
7265
|
panel.append(layout);
|
|
6822
7266
|
let currentSubInstance = null;
|
|
7267
|
+
const getSidebarViewportWidth = () => ctx.viewport.clientWidth || ctx.size.width;
|
|
7268
|
+
const shouldAutoCollapseSidebar = () => getSidebarViewportWidth() <= 520;
|
|
7269
|
+
const setSidebarCollapsed = (collapsed) => {
|
|
7270
|
+
layout.classList.toggle("is-sidebar-collapsed", collapsed);
|
|
7271
|
+
sidebarToggle.setAttribute("aria-expanded", String(!collapsed));
|
|
7272
|
+
const label = collapsed ? "\u5C55\u5F00\u6587\u4EF6\u5217\u8868" : "\u6536\u8D77\u6587\u4EF6\u5217\u8868";
|
|
7273
|
+
sidebarToggle.setAttribute("aria-label", label);
|
|
7274
|
+
sidebarToggle.title = label;
|
|
7275
|
+
sidebarToggle.textContent = collapsed ? "\u203A" : "\u2039";
|
|
7276
|
+
};
|
|
7277
|
+
setSidebarCollapsed(false);
|
|
7278
|
+
sidebarToggle.addEventListener("click", () => {
|
|
7279
|
+
setSidebarCollapsed(!layout.classList.contains("is-sidebar-collapsed"));
|
|
7280
|
+
});
|
|
6823
7281
|
const showDefaultSummary = () => {
|
|
6824
7282
|
mainPanel.replaceChildren();
|
|
6825
7283
|
const summary = document.createElement("div");
|
|
@@ -6859,6 +7317,9 @@ function archivePlugin() {
|
|
|
6859
7317
|
if (destroyed) {
|
|
6860
7318
|
return;
|
|
6861
7319
|
}
|
|
7320
|
+
if (shouldAutoCollapseSidebar()) {
|
|
7321
|
+
setSidebarCollapsed(true);
|
|
7322
|
+
}
|
|
6862
7323
|
const token = ++renderToken;
|
|
6863
7324
|
sidebar.querySelectorAll(".ofv-archive-item").forEach((el) => {
|
|
6864
7325
|
el.classList.remove("is-active");
|
|
@@ -9141,7 +9602,7 @@ function cadPlugin() {
|
|
|
9141
9602
|
return { destroy: () => panel.remove() };
|
|
9142
9603
|
}
|
|
9143
9604
|
const dxf = await readTextFile(ctx.file);
|
|
9144
|
-
const viewer = renderDxf(panel, dxf);
|
|
9605
|
+
const viewer = renderDxf(panel, dxf, ctx);
|
|
9145
9606
|
return {
|
|
9146
9607
|
canCommand(command) {
|
|
9147
9608
|
return viewer.canCommand(command);
|
|
@@ -9610,7 +10071,7 @@ function hexPreview(bytes) {
|
|
|
9610
10071
|
}
|
|
9611
10072
|
return rows.join("\n") || "\u65E0\u53EF\u5C55\u793A\u5B57\u8282\u3002";
|
|
9612
10073
|
}
|
|
9613
|
-
function renderDxf(panel, dxf) {
|
|
10074
|
+
function renderDxf(panel, dxf, ctx) {
|
|
9614
10075
|
const pairs = dxf.split(/\r?\n/).map((line) => line.trim());
|
|
9615
10076
|
const lines = [];
|
|
9616
10077
|
const circles = [];
|
|
@@ -9788,6 +10249,10 @@ function renderDxf(panel, dxf) {
|
|
|
9788
10249
|
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`;
|
|
9789
10250
|
section.append(note, svg);
|
|
9790
10251
|
panel.append(section);
|
|
10252
|
+
const updateToolbarZoom = () => {
|
|
10253
|
+
ctx.toolbar?.setZoom(initialViewBox.width / currentViewBox.width);
|
|
10254
|
+
};
|
|
10255
|
+
updateToolbarZoom();
|
|
9791
10256
|
return {
|
|
9792
10257
|
canCommand(command) {
|
|
9793
10258
|
return command === "zoom-in" || command === "zoom-out" || command === "zoom-reset";
|
|
@@ -9802,14 +10267,19 @@ function renderDxf(panel, dxf) {
|
|
|
9802
10267
|
currentViewBox.x = centerX - currentViewBox.width / 2;
|
|
9803
10268
|
currentViewBox.y = centerY - currentViewBox.height / 2;
|
|
9804
10269
|
applyViewBox();
|
|
10270
|
+
updateToolbarZoom();
|
|
9805
10271
|
return true;
|
|
9806
10272
|
}
|
|
9807
10273
|
if (command === "zoom-reset") {
|
|
9808
10274
|
currentViewBox = { ...initialViewBox };
|
|
9809
10275
|
applyViewBox();
|
|
10276
|
+
updateToolbarZoom();
|
|
9810
10277
|
return true;
|
|
9811
10278
|
}
|
|
9812
10279
|
return false;
|
|
10280
|
+
},
|
|
10281
|
+
destroy() {
|
|
10282
|
+
ctx.toolbar?.setZoom(void 0);
|
|
9813
10283
|
}
|
|
9814
10284
|
};
|
|
9815
10285
|
}
|
|
@@ -10085,6 +10555,12 @@ function model3dPlugin() {
|
|
|
10085
10555
|
renderer.setSize(width, height, false);
|
|
10086
10556
|
};
|
|
10087
10557
|
resize(ctx.size);
|
|
10558
|
+
const updateToolbarZoom = () => {
|
|
10559
|
+
const currentDistance = vectorDistance(camera.position, controls.target);
|
|
10560
|
+
const initialDistance = vectorDistance(initialFrame.cameraPosition, initialFrame.target);
|
|
10561
|
+
ctx.toolbar?.setZoom(initialDistance > 0 ? initialDistance / currentDistance : void 0);
|
|
10562
|
+
};
|
|
10563
|
+
updateToolbarZoom();
|
|
10088
10564
|
return {
|
|
10089
10565
|
canCommand(command) {
|
|
10090
10566
|
return command === "zoom-in" || command === "zoom-out" || command === "zoom-reset" || command === "rotate-right" || command === "rotate-left";
|
|
@@ -10095,6 +10571,7 @@ function model3dPlugin() {
|
|
|
10095
10571
|
camera.position.sub(controls.target).multiplyScalar(factor).add(controls.target);
|
|
10096
10572
|
camera.updateProjectionMatrix();
|
|
10097
10573
|
controls.update();
|
|
10574
|
+
updateToolbarZoom();
|
|
10098
10575
|
return true;
|
|
10099
10576
|
}
|
|
10100
10577
|
if (command === "zoom-reset") {
|
|
@@ -10104,6 +10581,7 @@ function model3dPlugin() {
|
|
|
10104
10581
|
camera.far = initialFrame.far;
|
|
10105
10582
|
camera.updateProjectionMatrix();
|
|
10106
10583
|
controls.update();
|
|
10584
|
+
updateToolbarZoom();
|
|
10107
10585
|
return true;
|
|
10108
10586
|
}
|
|
10109
10587
|
if (command === "rotate-right" || command === "rotate-left") {
|
|
@@ -10114,6 +10592,7 @@ function model3dPlugin() {
|
|
|
10114
10592
|
},
|
|
10115
10593
|
resize,
|
|
10116
10594
|
destroy() {
|
|
10595
|
+
ctx.toolbar?.setZoom(void 0);
|
|
10117
10596
|
window.cancelAnimationFrame(animationFrame);
|
|
10118
10597
|
controls.dispose();
|
|
10119
10598
|
renderer.dispose();
|
|
@@ -10125,6 +10604,12 @@ function model3dPlugin() {
|
|
|
10125
10604
|
}
|
|
10126
10605
|
};
|
|
10127
10606
|
}
|
|
10607
|
+
function vectorDistance(a, b) {
|
|
10608
|
+
if (typeof a.distanceTo === "function") {
|
|
10609
|
+
return a.distanceTo(b);
|
|
10610
|
+
}
|
|
10611
|
+
return Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z);
|
|
10612
|
+
}
|
|
10128
10613
|
function renderModelFallback(ctx, url, isExternal, message) {
|
|
10129
10614
|
const panel = document.createElement("div");
|
|
10130
10615
|
panel.className = "ofv-fallback";
|
|
@@ -10481,34 +10966,65 @@ function gisPlugin() {
|
|
|
10481
10966
|
}
|
|
10482
10967
|
const wrapper = document.createElement("div");
|
|
10483
10968
|
wrapper.className = "ofv-gis-viewer";
|
|
10484
|
-
|
|
10969
|
+
const summary = summarizeGeoJson(geojson);
|
|
10970
|
+
wrapper.append(createGisSummary(summary));
|
|
10485
10971
|
ctx.viewport.appendChild(wrapper);
|
|
10486
10972
|
const mapContainer = document.createElement("div");
|
|
10487
10973
|
mapContainer.className = "ofv-map-stage";
|
|
10488
10974
|
wrapper.appendChild(mapContainer);
|
|
10975
|
+
if (summary.features === 0) {
|
|
10976
|
+
mapContainer.append(createEmptyMapState());
|
|
10977
|
+
} else {
|
|
10978
|
+
mapContainer.append(createMapLegend(summary));
|
|
10979
|
+
}
|
|
10489
10980
|
const map = Leaflet.map(mapContainer).setView([0, 0], 2);
|
|
10490
10981
|
Leaflet.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
|
10491
10982
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
|
10492
10983
|
}).addTo(map);
|
|
10493
10984
|
const geojsonLayer = Leaflet.geoJSON(geojson, {
|
|
10494
10985
|
style: () => ({
|
|
10495
|
-
|
|
10986
|
+
className: "ofv-map-feature",
|
|
10987
|
+
color: "#e11d48",
|
|
10496
10988
|
weight: 2,
|
|
10497
|
-
opacity: 0.
|
|
10498
|
-
fillColor: "#
|
|
10499
|
-
fillOpacity: 0.
|
|
10989
|
+
opacity: 0.92,
|
|
10990
|
+
fillColor: "#fb923c",
|
|
10991
|
+
fillOpacity: 0.3,
|
|
10992
|
+
lineCap: "round",
|
|
10993
|
+
lineJoin: "round"
|
|
10500
10994
|
}),
|
|
10501
10995
|
pointToLayer: (feature, latlng) => {
|
|
10502
10996
|
return Leaflet.circleMarker(latlng, {
|
|
10503
|
-
|
|
10504
|
-
|
|
10997
|
+
className: "ofv-map-feature ofv-map-point",
|
|
10998
|
+
radius: 7,
|
|
10999
|
+
fillColor: "#e11d48",
|
|
10505
11000
|
color: "#ffffff",
|
|
10506
|
-
weight:
|
|
11001
|
+
weight: 2.5,
|
|
10507
11002
|
opacity: 1,
|
|
10508
|
-
fillOpacity: 0.
|
|
11003
|
+
fillOpacity: 0.9
|
|
10509
11004
|
});
|
|
10510
11005
|
},
|
|
10511
11006
|
onEachFeature: (feature, layer) => {
|
|
11007
|
+
const label = feature.properties?.name || feature.properties?.title || feature.properties?.label;
|
|
11008
|
+
if (label) {
|
|
11009
|
+
layer.bindTooltip?.(String(label), {
|
|
11010
|
+
className: "ofv-map-tooltip",
|
|
11011
|
+
direction: "top",
|
|
11012
|
+
sticky: true
|
|
11013
|
+
});
|
|
11014
|
+
}
|
|
11015
|
+
layer.on?.({
|
|
11016
|
+
mouseover(event) {
|
|
11017
|
+
event.target?.setStyle?.({
|
|
11018
|
+
weight: 4,
|
|
11019
|
+
opacity: 1,
|
|
11020
|
+
fillOpacity: 0.4
|
|
11021
|
+
});
|
|
11022
|
+
event.target?.bringToFront?.();
|
|
11023
|
+
},
|
|
11024
|
+
mouseout(event) {
|
|
11025
|
+
geojsonLayer.resetStyle?.(event.target);
|
|
11026
|
+
}
|
|
11027
|
+
});
|
|
10512
11028
|
if (feature.properties) {
|
|
10513
11029
|
const props = feature.properties;
|
|
10514
11030
|
const keys = Object.keys(props);
|
|
@@ -10543,15 +11059,20 @@ function gisPlugin() {
|
|
|
10543
11059
|
const bounds = geojsonLayer.getBounds();
|
|
10544
11060
|
if (bounds.isValid()) {
|
|
10545
11061
|
map.fitBounds(bounds, { padding: [20, 20] });
|
|
11062
|
+
map.invalidateSize();
|
|
10546
11063
|
}
|
|
10547
11064
|
} catch (e) {
|
|
10548
11065
|
console.warn("Could not fit bounds for GeoJSON data:", e);
|
|
10549
11066
|
}
|
|
11067
|
+
const resizeTimers = [0, 80, 240].map((delay) => window.setTimeout(() => {
|
|
11068
|
+
map.invalidateSize();
|
|
11069
|
+
}, delay));
|
|
10550
11070
|
return {
|
|
10551
11071
|
resize() {
|
|
10552
11072
|
map.invalidateSize();
|
|
10553
11073
|
},
|
|
10554
11074
|
destroy() {
|
|
11075
|
+
resizeTimers.forEach((timer) => window.clearTimeout(timer));
|
|
10555
11076
|
map.remove();
|
|
10556
11077
|
wrapper.remove();
|
|
10557
11078
|
}
|
|
@@ -10576,8 +11097,7 @@ function normalizeGisError(error, fileName) {
|
|
|
10576
11097
|
function isGisFallback(value) {
|
|
10577
11098
|
return typeof value === "object" && value !== null && "fallback" in value;
|
|
10578
11099
|
}
|
|
10579
|
-
function createGisSummary(
|
|
10580
|
-
const summary = summarizeGeoJson(geojson);
|
|
11100
|
+
function createGisSummary(summary) {
|
|
10581
11101
|
const bar = document.createElement("div");
|
|
10582
11102
|
bar.className = "ofv-gis-summary";
|
|
10583
11103
|
appendSummaryItem(bar, "\u8981\u7D20", String(summary.features));
|
|
@@ -10591,6 +11111,26 @@ function createGisSummary(geojson) {
|
|
|
10591
11111
|
}
|
|
10592
11112
|
return bar;
|
|
10593
11113
|
}
|
|
11114
|
+
function createEmptyMapState() {
|
|
11115
|
+
const empty = document.createElement("div");
|
|
11116
|
+
empty.className = "ofv-map-empty";
|
|
11117
|
+
const title = document.createElement("strong");
|
|
11118
|
+
title.textContent = "\u6682\u65E0\u53EF\u5C55\u793A\u7684\u5730\u56FE\u8981\u7D20";
|
|
11119
|
+
const detail = document.createElement("span");
|
|
11120
|
+
detail.textContent = "GeoJSON \u5DF2\u8BC6\u522B\uFF0C\u4F46 features \u4E3A\u7A7A\u3002";
|
|
11121
|
+
empty.append(title, detail);
|
|
11122
|
+
return empty;
|
|
11123
|
+
}
|
|
11124
|
+
function createMapLegend(summary) {
|
|
11125
|
+
const legend = document.createElement("div");
|
|
11126
|
+
legend.className = "ofv-map-legend";
|
|
11127
|
+
const title = document.createElement("strong");
|
|
11128
|
+
title.textContent = "GeoJSON";
|
|
11129
|
+
const detail = document.createElement("span");
|
|
11130
|
+
detail.textContent = `${summary.features} \u4E2A\u8981\u7D20 \xB7 ${formatGeometryCounts(summary.geometryCounts)}`;
|
|
11131
|
+
legend.append(title, detail);
|
|
11132
|
+
return legend;
|
|
11133
|
+
}
|
|
10594
11134
|
function appendSummaryItem(parent, label, value) {
|
|
10595
11135
|
const item = document.createElement("span");
|
|
10596
11136
|
const key = document.createElement("span");
|