@richscripts/richtexteditor 2.4.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/migration/README.md +100 -0
- package/migration/cli.js +294 -0
- package/migration/index.d.ts +18 -0
- package/migration/index.js +242 -0
- package/migration/report-schema-v2.json +127 -0
- package/package.json +11 -1
- package/richtexteditor/CHANGELOG.md +326 -1
- package/richtexteditor/all_plugins.js +14959 -4832
- package/richtexteditor/all_plugins.min.js +62 -42
- package/richtexteditor/plugins/accessibilitychecker.js +8 -7
- package/richtexteditor/plugins/aitoolkit.css +17 -8
- package/richtexteditor/plugins/aitoolkit.js +8 -8
- package/richtexteditor/plugins/all_plugins.js +14959 -4832
- package/richtexteditor/plugins/all_plugins.min.js +62 -42
- package/richtexteditor/plugins/autocorrect.js +353 -0
- package/richtexteditor/plugins/bookmarkcard.js +12 -1
- package/richtexteditor/plugins/changecase.js +238 -0
- package/richtexteditor/plugins/comments.js +15 -28
- package/richtexteditor/plugins/contentminimap.js +6 -5
- package/richtexteditor/plugins/crossreference.js +408 -0
- package/richtexteditor/plugins/documentimport.js +915 -21
- package/richtexteditor/plugins/documentoutline.js +5 -4
- package/richtexteditor/plugins/docxexport.js +960 -0
- package/richtexteditor/plugins/draghandle.js +309 -0
- package/richtexteditor/plugins/footnotes.js +398 -0
- package/richtexteditor/plugins/formatpainter.js +298 -0
- package/richtexteditor/plugins/formattingmarks.js +246 -0
- package/richtexteditor/plugins/html2pdf.js +108 -4
- package/richtexteditor/plugins/imageeditor.js +37 -7
- package/richtexteditor/plugins/insertgallery.js +553 -170
- package/richtexteditor/plugins/keyboarda11y.js +316 -0
- package/richtexteditor/plugins/linenumbers.js +352 -0
- package/richtexteditor/plugins/linkchecker.js +396 -0
- package/richtexteditor/plugins/mentionplugin.js +4 -3
- package/richtexteditor/plugins/mergefields.js +328 -0
- package/richtexteditor/plugins/multilevellist.js +137 -0
- package/richtexteditor/plugins/pagination.js +505 -0
- package/richtexteditor/plugins/pdfexport.js +1137 -0
- package/richtexteditor/plugins/permanentpen.js +211 -0
- package/richtexteditor/plugins/revisionhistory.js +47 -34
- package/richtexteditor/plugins/rtlui.js +150 -0
- package/richtexteditor/plugins/sanitizer.js +486 -0
- package/richtexteditor/plugins/slashcommand.js +142 -17
- package/richtexteditor/plugins/spellcheck.js +6 -3
- package/richtexteditor/plugins/tableofcontents.js +353 -0
- package/richtexteditor/plugins/tabletools.js +342 -0
- package/richtexteditor/plugins/textdirection.js +313 -0
- package/richtexteditor/plugins/trackedchanges.js +37 -6
- package/richtexteditor/plugins/tui.image-editor/tui-code-snippet.min.js +1 -1
- package/richtexteditor/plugins/tui.image-editor/tui-color-picker.min.js +1 -1
- package/richtexteditor/plugins/tui.image-editor/tui-image-editor.min.js +1 -1
- package/richtexteditor/plugins/vendor/html2pdf.bundle.min.js +3 -0
- package/richtexteditor/plugins/watermark.js +214 -0
- package/richtexteditor/plugins/wordexport.js +0 -0
- package/richtexteditor/plugins/yjscollab.js +49 -11
- package/richtexteditor/rte-config.js +44 -44
- package/richtexteditor/rte.js +91 -63
- package/richtexteditor/rte_theme_default.css +462 -384
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
// 2026-08-05 Image gallery browser.
|
|
2
|
+
//
|
|
3
|
+
// Two implementations of this dialog had drifted apart: the flagship shipped a
|
|
4
|
+
// flat grid built from config.galleryImages, while the RichTextBox package
|
|
5
|
+
// shipped a server-backed file manager (folders, breadcrumb, New folder, upload
|
|
6
|
+
// into the current folder). The tier audit requires one identical plugin
|
|
7
|
+
// everywhere, so keeping both meant one of them silently overwriting the other
|
|
8
|
+
// on the next sync — which is exactly what happened: the folder browser's CSS
|
|
9
|
+
// was dropped and its folder tiles rendered as blank boxes on the live site.
|
|
10
|
+
//
|
|
11
|
+
// This is the merged implementation. It is server-backed when an endpoint is
|
|
12
|
+
// configured and falls back to the packaged preset list otherwise, so a host
|
|
13
|
+
// with no gallery endpoint behaves exactly as the flat grid always did.
|
|
14
|
+
//
|
|
15
|
+
// config.galleryEndpoint URL returning { currentFolder, currentFolderDisplay,
|
|
16
|
+
// parentFolder, folders: [{folder,name}],
|
|
17
|
+
// images: [{url,name,folder,size,source}] }.
|
|
18
|
+
// Absent -> local mode: presets only, no request.
|
|
19
|
+
// config.galleryImages Preset images. String, [url, text], or an object
|
|
20
|
+
// with url/src/href plus optional thumbnail/name/meta.
|
|
21
|
+
//
|
|
22
|
+
// Uploads use window.richTextBoxUploadFile when present (it accepts a target
|
|
23
|
+
// folder), otherwise the generic window.rte_file_upload_handler.
|
|
24
|
+
|
|
1
25
|
RTE_DefaultConfig.plugin_insertgallery = RTE_Plugin_InsertGallery;
|
|
2
26
|
|
|
3
27
|
function RTE_Plugin_InsertGallery() {
|
|
@@ -35,12 +59,50 @@ function RTE_Plugin_InsertGallery() {
|
|
|
35
59
|
return tag;
|
|
36
60
|
}
|
|
37
61
|
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
62
|
+
function getGalleryEndpoint() {
|
|
63
|
+
return config.galleryEndpoint || window.RichTextBoxGalleryUrl || "";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function withFolder(url, folder) {
|
|
67
|
+
var separator = url.indexOf("?") >= 0 ? "&" : "?";
|
|
68
|
+
return url + separator + "folder=" + encodeURIComponent(folder || "");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function requestJson(method, url, body, callback) {
|
|
72
|
+
var request = new XMLHttpRequest();
|
|
73
|
+
request.open(method, url, true);
|
|
74
|
+
|
|
75
|
+
request.onreadystatechange = function () {
|
|
76
|
+
if (request.readyState !== 4) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (request.status < 200 || request.status >= 300) {
|
|
81
|
+
callback(null, "http-" + request.status);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
callback(JSON.parse(request.responseText), null);
|
|
87
|
+
} catch (ex) {
|
|
88
|
+
callback(null, "invalid-json");
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
request.onerror = function () {
|
|
93
|
+
callback(null, "network-error");
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
if (body) {
|
|
97
|
+
request.send(body);
|
|
98
|
+
return;
|
|
41
99
|
}
|
|
100
|
+
|
|
101
|
+
request.send();
|
|
42
102
|
}
|
|
43
103
|
|
|
104
|
+
// Query strings and percent-escapes both show up in real upload URLs, so
|
|
105
|
+
// strip and decode rather than splitting on "/" alone.
|
|
44
106
|
function getFileName(url) {
|
|
45
107
|
var value = String(url || "").split("#")[0].split("?")[0];
|
|
46
108
|
var lastSlash = value.lastIndexOf("/");
|
|
@@ -52,25 +114,24 @@ function RTE_Plugin_InsertGallery() {
|
|
|
52
114
|
return name || "Image";
|
|
53
115
|
}
|
|
54
116
|
|
|
55
|
-
function
|
|
56
|
-
if (
|
|
57
|
-
return
|
|
117
|
+
function humanSize(size) {
|
|
118
|
+
if (!size) {
|
|
119
|
+
return "";
|
|
120
|
+
}
|
|
121
|
+
if (size < 1024) {
|
|
122
|
+
return size + " B";
|
|
58
123
|
}
|
|
59
|
-
if (
|
|
60
|
-
return
|
|
124
|
+
if (size < 1024 * 1024) {
|
|
125
|
+
return Math.round(size / 1024) + " KB";
|
|
61
126
|
}
|
|
62
|
-
return
|
|
127
|
+
return (Math.round(size * 10 / (1024 * 1024)) / 10) + " MB";
|
|
63
128
|
}
|
|
64
129
|
|
|
65
|
-
function
|
|
130
|
+
function normalizePreset(item) {
|
|
66
131
|
var normalized = null;
|
|
67
132
|
|
|
68
133
|
if (typeof item === "string") {
|
|
69
|
-
normalized = {
|
|
70
|
-
url: item,
|
|
71
|
-
thumbnail: item,
|
|
72
|
-
name: getFileName(item)
|
|
73
|
-
};
|
|
134
|
+
normalized = { url: item, thumbnail: item, name: getFileName(item) };
|
|
74
135
|
} else if (item instanceof Array) {
|
|
75
136
|
normalized = {
|
|
76
137
|
url: item[0],
|
|
@@ -83,7 +144,6 @@ function RTE_Plugin_InsertGallery() {
|
|
|
83
144
|
if (!url) {
|
|
84
145
|
return null;
|
|
85
146
|
}
|
|
86
|
-
|
|
87
147
|
normalized = {
|
|
88
148
|
url: url,
|
|
89
149
|
thumbnail: item.thumbnail || item.thumb || item.preview || url,
|
|
@@ -96,64 +156,72 @@ function RTE_Plugin_InsertGallery() {
|
|
|
96
156
|
return null;
|
|
97
157
|
}
|
|
98
158
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
normalized.name = getFileName(normalized.url);
|
|
105
|
-
}
|
|
106
|
-
|
|
159
|
+
normalized.thumbnail = normalized.thumbnail || normalized.url;
|
|
160
|
+
normalized.name = normalized.name || getFileName(normalized.url);
|
|
161
|
+
normalized.source = "preset";
|
|
162
|
+
normalized.folder = "";
|
|
163
|
+
normalized.searchText = (normalized.name + " " + (normalized.meta || "") + " " + normalized.url).toLowerCase();
|
|
107
164
|
return normalized;
|
|
108
165
|
}
|
|
109
166
|
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
var i;
|
|
114
|
-
|
|
115
|
-
for (i = 0; i < fileList.length; i++) {
|
|
116
|
-
files.push(fileList[i]);
|
|
167
|
+
function normalizeServerFolder(item) {
|
|
168
|
+
if (!item || !item.folder) {
|
|
169
|
+
return null;
|
|
117
170
|
}
|
|
171
|
+
var name = item.name || getFileName(item.folder) || item.folder;
|
|
172
|
+
return {
|
|
173
|
+
name: name,
|
|
174
|
+
folder: item.folder,
|
|
175
|
+
searchText: (name + " " + item.folder).toLowerCase()
|
|
176
|
+
};
|
|
177
|
+
}
|
|
118
178
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return;
|
|
179
|
+
function normalizeServerImage(item) {
|
|
180
|
+
if (!item || !item.url) {
|
|
181
|
+
return null;
|
|
122
182
|
}
|
|
183
|
+
var name = item.name || getFileName(item.url);
|
|
184
|
+
return {
|
|
185
|
+
name: name,
|
|
186
|
+
url: item.url,
|
|
187
|
+
thumbnail: item.thumbnail || item.url,
|
|
188
|
+
folder: item.folder || "",
|
|
189
|
+
source: item.source || "upload",
|
|
190
|
+
size: item.size || 0,
|
|
191
|
+
searchText: (name + " " + item.url).toLowerCase()
|
|
192
|
+
};
|
|
193
|
+
}
|
|
123
194
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
195
|
+
function setDisabled(button, disabled) {
|
|
196
|
+
button.disabled = !!disabled;
|
|
197
|
+
if (disabled) {
|
|
198
|
+
button.setAttribute("aria-disabled", "true");
|
|
199
|
+
} else {
|
|
200
|
+
button.removeAttribute("aria-disabled");
|
|
130
201
|
}
|
|
202
|
+
}
|
|
131
203
|
|
|
132
|
-
|
|
204
|
+
function show(element, visible) {
|
|
205
|
+
element.style.display = visible ? "" : "none";
|
|
206
|
+
}
|
|
133
207
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
208
|
+
obj.DoInsertGallery = function () {
|
|
209
|
+
var endpoint = getGalleryEndpoint();
|
|
210
|
+
var serverMode = !!endpoint;
|
|
211
|
+
var uploadToFolder = typeof window.richTextBoxUploadFile === "function";
|
|
212
|
+
var genericUpload = typeof window.rte_file_upload_handler === "function";
|
|
213
|
+
var canUpload = uploadToFolder || genericUpload;
|
|
214
|
+
|
|
215
|
+
var presetImages = [];
|
|
216
|
+
var presetSource = config.galleryImages || [];
|
|
217
|
+
var i;
|
|
218
|
+
for (i = 0; i < presetSource.length; i++) {
|
|
219
|
+
var preset = normalizePreset(presetSource[i]);
|
|
220
|
+
if (preset) {
|
|
221
|
+
presetImages.push(preset);
|
|
138
222
|
}
|
|
139
|
-
|
|
140
|
-
var file = files[index];
|
|
141
|
-
handler(file, function (url, error) {
|
|
142
|
-
if (url) {
|
|
143
|
-
onUploaded(url, file, index, files);
|
|
144
|
-
} else if (onFailed) {
|
|
145
|
-
onFailed(error || ("Upload failed for " + file.name), file);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
index++;
|
|
149
|
-
next();
|
|
150
|
-
}, index, files);
|
|
151
223
|
}
|
|
152
224
|
|
|
153
|
-
next();
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
obj.DoInsertGallery = function () {
|
|
157
225
|
var dialoginner = editor.createDialog(editor.getLangText("insertgallerytitle") || "Image gallery", "rte-dialog-insertgallery");
|
|
158
226
|
var closeDialog = typeof dialoginner.close === "function" ? function () {
|
|
159
227
|
dialoginner.close();
|
|
@@ -162,50 +230,79 @@ function RTE_Plugin_InsertGallery() {
|
|
|
162
230
|
};
|
|
163
231
|
|
|
164
232
|
var browser = append(dialoginner, "div", "", "rte-gallery-browser");
|
|
165
|
-
|
|
166
|
-
//
|
|
167
|
-
//
|
|
233
|
+
|
|
234
|
+
// The dialog frame already draws a title bar and close button, so this is
|
|
235
|
+
// a one-line subtitle, not a second heading.
|
|
168
236
|
var header = append(browser, "div", "", "rte-dialog-browser-header");
|
|
169
237
|
var copy = append(header, "div", "", "rte-dialog-browser-copy");
|
|
170
|
-
copy.innerText =
|
|
238
|
+
copy.innerText = serverMode
|
|
239
|
+
? "Browse folders, upload new files, and insert the selected image into the editor."
|
|
240
|
+
: "Browse the available images, filter by name, and insert the selected image into the editor.";
|
|
171
241
|
|
|
172
242
|
var toolbar = append(browser, "div", "", "rte-gallery-browser-toolbar");
|
|
173
|
-
var
|
|
243
|
+
var toolbarLeft = append(toolbar, "div", "", "rte-gallery-browser-toolbar-group");
|
|
244
|
+
var toolbarRight = append(toolbar, "div", "", "rte-gallery-browser-toolbar-group rte-gallery-browser-toolbar-group-right");
|
|
245
|
+
|
|
246
|
+
var upButton = append(toolbarLeft, "button", "", "rte-gallery-browser-button");
|
|
247
|
+
upButton.type = "button";
|
|
248
|
+
upButton.innerText = "Up";
|
|
249
|
+
upButton.setAttribute("aria-label", "Go to the parent folder");
|
|
250
|
+
|
|
251
|
+
var path = append(toolbarLeft, "div", "", "rte-gallery-browser-path");
|
|
174
252
|
path.innerText = "/";
|
|
175
|
-
var type = append(toolbar, "div", "", "rte-gallery-browser-type");
|
|
176
|
-
type.innerText = "Image Files";
|
|
177
253
|
|
|
178
|
-
var
|
|
254
|
+
var type = append(toolbarLeft, "div", "", "rte-gallery-browser-type");
|
|
255
|
+
type.innerText = "Image files";
|
|
256
|
+
|
|
257
|
+
var createFolderButton = append(toolbarLeft, "button", "", "rte-gallery-browser-button");
|
|
258
|
+
createFolderButton.type = "button";
|
|
259
|
+
createFolderButton.innerText = "New folder";
|
|
260
|
+
|
|
261
|
+
var uploadButton = append(toolbarLeft, "button", "", "rte-gallery-browser-button rte-gallery-browser-button-primary");
|
|
179
262
|
uploadButton.type = "button";
|
|
180
263
|
uploadButton.innerText = "Upload";
|
|
181
264
|
|
|
182
|
-
var refreshButton = append(
|
|
265
|
+
var refreshButton = append(toolbarLeft, "button", "", "rte-gallery-browser-button");
|
|
183
266
|
refreshButton.type = "button";
|
|
184
267
|
refreshButton.innerText = "Refresh";
|
|
185
268
|
|
|
186
|
-
var search = append(
|
|
269
|
+
var search = append(toolbarRight, "input", "", "rte-gallery-browser-search");
|
|
187
270
|
search.type = "search";
|
|
188
271
|
search.placeholder = "Search images";
|
|
189
272
|
search.setAttribute("aria-label", "Search images");
|
|
190
273
|
|
|
191
|
-
var fileInput = append(
|
|
274
|
+
var fileInput = append(toolbarRight, "input", "display:none;");
|
|
192
275
|
fileInput.type = "file";
|
|
193
276
|
fileInput.accept = "image/*,.jpg,.jpeg,.png,.gif,.bmp,.webp,.svg";
|
|
194
277
|
fileInput.multiple = true;
|
|
195
278
|
|
|
279
|
+
// Without a server there is no folder tree to walk and nothing to create
|
|
280
|
+
// a folder in, so those controls are hidden rather than shown disabled.
|
|
281
|
+
show(upButton, serverMode);
|
|
282
|
+
show(path, serverMode);
|
|
283
|
+
show(createFolderButton, serverMode);
|
|
284
|
+
show(uploadButton, canUpload);
|
|
285
|
+
|
|
196
286
|
var status = append(browser, "div", "", "rte-gallery-browser-status");
|
|
197
287
|
status.setAttribute("role", "status");
|
|
198
288
|
status.setAttribute("aria-live", "polite");
|
|
289
|
+
|
|
199
290
|
var surface = append(browser, "div", "", "rte-gallery-browser-surface");
|
|
291
|
+
|
|
292
|
+
// Folders are navigation and images are choices, so they cannot share one
|
|
293
|
+
// listbox — a non-option child of a listbox is invalid ARIA.
|
|
294
|
+
var folderGrid = append(surface, "div", "", "rte-gallery-browser-grid rte-gallery-browser-grid-folders");
|
|
295
|
+
folderGrid.setAttribute("role", "group");
|
|
296
|
+
folderGrid.setAttribute("aria-label", "Folders");
|
|
297
|
+
|
|
200
298
|
var grid = append(surface, "div", "", "rte-gallery-browser-grid");
|
|
201
299
|
grid.setAttribute("role", "listbox");
|
|
202
300
|
grid.setAttribute("aria-label", "Available images");
|
|
301
|
+
|
|
203
302
|
var empty = append(surface, "div", "", "rte-gallery-browser-empty");
|
|
204
|
-
empty.innerText = "No images match this search. Upload a file or adjust the filter.";
|
|
205
303
|
|
|
206
304
|
var footer = append(browser, "div", "", "rte-gallery-browser-footer");
|
|
207
305
|
var footerText = append(footer, "div", "", "rte-gallery-browser-footer-text");
|
|
208
|
-
footerText.innerText = "Choose an image to enable insert.";
|
|
209
306
|
|
|
210
307
|
var cancelButton = append(footer, "button", "", "rte-gallery-browser-button");
|
|
211
308
|
cancelButton.type = "button";
|
|
@@ -214,149 +311,435 @@ function RTE_Plugin_InsertGallery() {
|
|
|
214
311
|
var insertButton = append(footer, "button", "", "rte-gallery-browser-button rte-gallery-browser-button-primary");
|
|
215
312
|
insertButton.type = "button";
|
|
216
313
|
insertButton.innerText = "Insert";
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
314
|
+
|
|
315
|
+
var state = {
|
|
316
|
+
currentFolder: "",
|
|
317
|
+
currentFolderDisplay: "/",
|
|
318
|
+
parentFolder: null,
|
|
319
|
+
folders: [],
|
|
320
|
+
images: presetImages.slice(0),
|
|
321
|
+
selectedUrl: "",
|
|
322
|
+
loading: false,
|
|
323
|
+
error: "",
|
|
324
|
+
fallbackMode: !serverMode
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
function filterList(list) {
|
|
328
|
+
var term = search.value.toLowerCase();
|
|
329
|
+
if (!term) {
|
|
330
|
+
return list.slice(0);
|
|
230
331
|
}
|
|
231
|
-
return list;
|
|
232
|
-
}
|
|
233
332
|
|
|
234
|
-
|
|
235
|
-
var
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
333
|
+
var items = [];
|
|
334
|
+
for (var index = 0; index < list.length; index++) {
|
|
335
|
+
if (list[index].searchText.indexOf(term) >= 0) {
|
|
336
|
+
items.push(list[index]);
|
|
337
|
+
}
|
|
239
338
|
}
|
|
339
|
+
return items;
|
|
340
|
+
}
|
|
240
341
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|| (item.url && item.url.toLowerCase().indexOf(keyword) >= 0);
|
|
245
|
-
});
|
|
342
|
+
function setSelected(url) {
|
|
343
|
+
state.selectedUrl = url || "";
|
|
344
|
+
render();
|
|
246
345
|
}
|
|
247
346
|
|
|
248
|
-
function
|
|
249
|
-
var
|
|
250
|
-
var
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
selectedName = items[i].name;
|
|
347
|
+
function insertSelected() {
|
|
348
|
+
var selected = null;
|
|
349
|
+
for (var index = 0; index < state.images.length; index++) {
|
|
350
|
+
if (state.images[index].url === state.selectedUrl) {
|
|
351
|
+
selected = state.images[index];
|
|
254
352
|
break;
|
|
255
353
|
}
|
|
256
354
|
}
|
|
257
355
|
|
|
258
|
-
|
|
259
|
-
+ (selectedName ? " " + selectedName + " selected." : " No image selected.");
|
|
260
|
-
footerText.innerText = selectedName ? ("Ready to insert " + selectedName + ".") : "Choose an image to enable insert.";
|
|
261
|
-
insertButton.disabled = !selectedName;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function insertSelected() {
|
|
265
|
-
if (!selectedUrl) {
|
|
356
|
+
if (!selected) {
|
|
266
357
|
return;
|
|
267
358
|
}
|
|
268
|
-
|
|
359
|
+
|
|
360
|
+
editor.insertImageByUrl(selected.url);
|
|
269
361
|
closeDialog();
|
|
270
362
|
editor.focus();
|
|
271
363
|
}
|
|
272
364
|
|
|
365
|
+
function updateStatus(folderCount, imageCount) {
|
|
366
|
+
var total = folderCount + imageCount;
|
|
367
|
+
var where = serverMode ? (" in " + state.currentFolderDisplay) : "";
|
|
368
|
+
var message = total + " item" + (total === 1 ? "" : "s") + where + ". ";
|
|
369
|
+
|
|
370
|
+
if (!state.selectedUrl) {
|
|
371
|
+
status.innerText = message + "No image selected.";
|
|
372
|
+
footerText.innerText = state.fallbackMode && serverMode
|
|
373
|
+
? "Showing packaged gallery items. Uploads still save into the current folder."
|
|
374
|
+
: "Choose an image to enable insert.";
|
|
375
|
+
setDisabled(insertButton, true);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
var selectedName = getFileName(state.selectedUrl);
|
|
380
|
+
status.innerText = message + "Selected: " + selectedName + ".";
|
|
381
|
+
setDisabled(insertButton, state.loading);
|
|
382
|
+
|
|
383
|
+
for (var index = 0; index < state.images.length; index++) {
|
|
384
|
+
if (state.images[index].url === state.selectedUrl) {
|
|
385
|
+
var size = humanSize(state.images[index].size);
|
|
386
|
+
footerText.innerText = selectedName + " ready to insert" + (size ? " (" + size + ")." : ".");
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
footerText.innerText = selectedName + " ready to insert.";
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function createFolderCard(item) {
|
|
395
|
+
var button = append(folderGrid, "button", "", "rte-gallery-browser-card rte-gallery-folder-card");
|
|
396
|
+
button.type = "button";
|
|
397
|
+
button.title = "Open " + item.name;
|
|
398
|
+
|
|
399
|
+
var selection = append(button, "div", "", "rte-gallery-browser-selection");
|
|
400
|
+
selection.innerHTML = " ";
|
|
401
|
+
|
|
402
|
+
var thumbnail = append(button, "div", "", "rte-gallery-browser-thumbnail rte-gallery-browser-thumbnail-folder");
|
|
403
|
+
append(thumbnail, "div", "", "rte-gallery-browser-folder-icon");
|
|
404
|
+
|
|
405
|
+
var label = append(button, "div", "", "rte-gallery-browser-name");
|
|
406
|
+
label.innerText = item.name;
|
|
407
|
+
|
|
408
|
+
var meta = append(button, "div", "", "rte-gallery-browser-meta");
|
|
409
|
+
meta.innerText = "Folder";
|
|
410
|
+
|
|
411
|
+
button.onclick = function () {
|
|
412
|
+
loadFolder(item.folder);
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function createImageCard(item) {
|
|
417
|
+
var isSelected = item.url === state.selectedUrl;
|
|
418
|
+
var button = append(grid, "button", "", "rte-gallery-browser-card rte-gallery-image-card" + (isSelected ? " is-selected" : ""));
|
|
419
|
+
button.type = "button";
|
|
420
|
+
button.title = item.name;
|
|
421
|
+
button.setAttribute("role", "option");
|
|
422
|
+
button.setAttribute("aria-selected", isSelected ? "true" : "false");
|
|
423
|
+
|
|
424
|
+
var selection = append(button, "div", "", "rte-gallery-browser-selection");
|
|
425
|
+
selection.innerHTML = isSelected ? "✓" : " ";
|
|
426
|
+
|
|
427
|
+
var thumbnail = append(button, "div", "", "rte-gallery-browser-thumbnail");
|
|
428
|
+
var image = append(thumbnail, "img", "", "rte-gallery-browser-thumbnail-image");
|
|
429
|
+
image.src = item.thumbnail || item.url;
|
|
430
|
+
image.alt = item.name;
|
|
431
|
+
|
|
432
|
+
var label = append(button, "div", "", "rte-gallery-browser-name");
|
|
433
|
+
label.innerText = item.name;
|
|
434
|
+
|
|
435
|
+
var meta = append(button, "div", "", "rte-gallery-browser-meta");
|
|
436
|
+
meta.innerText = item.meta || (item.source === "preset" ? "Preset image" : humanSize(item.size)) || item.url;
|
|
437
|
+
|
|
438
|
+
button.onclick = function () {
|
|
439
|
+
setSelected(item.url);
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
button.ondblclick = function () {
|
|
443
|
+
setSelected(item.url);
|
|
444
|
+
insertSelected();
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Presets belong to no folder, so they are only offered at the root —
|
|
449
|
+
// otherwise every folder would appear to contain them.
|
|
450
|
+
function mergePresets(serverImages) {
|
|
451
|
+
var merged = [];
|
|
452
|
+
var seen = {};
|
|
453
|
+
var index;
|
|
454
|
+
|
|
455
|
+
for (index = 0; index < serverImages.length; index++) {
|
|
456
|
+
merged.push(serverImages[index]);
|
|
457
|
+
seen[serverImages[index].url] = true;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (state.currentFolder) {
|
|
461
|
+
return merged;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
for (index = 0; index < presetImages.length; index++) {
|
|
465
|
+
if (!seen[presetImages[index].url]) {
|
|
466
|
+
merged.push(presetImages[index]);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return merged;
|
|
471
|
+
}
|
|
472
|
+
|
|
273
473
|
function render() {
|
|
274
|
-
|
|
275
|
-
var
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
474
|
+
var visibleFolders = filterList(state.folders);
|
|
475
|
+
var visibleImages = filterList(state.images);
|
|
476
|
+
|
|
477
|
+
path.innerText = state.currentFolderDisplay || "/";
|
|
478
|
+
setDisabled(upButton, state.loading || state.parentFolder === null);
|
|
479
|
+
setDisabled(createFolderButton, state.loading);
|
|
480
|
+
setDisabled(uploadButton, state.loading);
|
|
481
|
+
setDisabled(refreshButton, state.loading);
|
|
482
|
+
|
|
483
|
+
folderGrid.innerHTML = "";
|
|
484
|
+
grid.innerHTML = "";
|
|
485
|
+
show(empty, false);
|
|
486
|
+
show(folderGrid, false);
|
|
487
|
+
show(grid, true);
|
|
488
|
+
|
|
489
|
+
if (state.loading) {
|
|
490
|
+
empty.innerText = "Loading gallery...";
|
|
491
|
+
show(empty, true);
|
|
492
|
+
show(grid, false);
|
|
493
|
+
updateStatus(0, 0);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (state.error) {
|
|
498
|
+
empty.innerText = state.error;
|
|
499
|
+
show(empty, true);
|
|
500
|
+
show(grid, false);
|
|
501
|
+
updateStatus(0, 0);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
289
504
|
|
|
290
|
-
|
|
291
|
-
|
|
505
|
+
for (var folderIndex = 0; folderIndex < visibleFolders.length; folderIndex++) {
|
|
506
|
+
createFolderCard(visibleFolders[folderIndex]);
|
|
507
|
+
}
|
|
508
|
+
show(folderGrid, visibleFolders.length > 0);
|
|
292
509
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
510
|
+
for (var imageIndex = 0; imageIndex < visibleImages.length; imageIndex++) {
|
|
511
|
+
createImageCard(visibleImages[imageIndex]);
|
|
512
|
+
}
|
|
513
|
+
show(grid, visibleImages.length > 0);
|
|
297
514
|
|
|
298
|
-
|
|
299
|
-
name.innerText = item.name;
|
|
515
|
+
updateStatus(visibleFolders.length, visibleImages.length);
|
|
300
516
|
|
|
301
|
-
|
|
302
|
-
|
|
517
|
+
if (!visibleFolders.length && !visibleImages.length) {
|
|
518
|
+
empty.innerText = search.value
|
|
519
|
+
? "No folders or images match the current filter."
|
|
520
|
+
: (serverMode
|
|
521
|
+
? "This folder is empty. Create a folder or upload an image to get started."
|
|
522
|
+
: "No images are available. Upload a file to get started.");
|
|
523
|
+
show(empty, true);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
303
526
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
527
|
+
function applyResponse(payload, fallbackMode) {
|
|
528
|
+
var nextFolders = [];
|
|
529
|
+
var nextImages = [];
|
|
530
|
+
var index;
|
|
531
|
+
|
|
532
|
+
state.currentFolder = payload.currentFolder || "";
|
|
533
|
+
state.currentFolderDisplay = payload.currentFolderDisplay || "/";
|
|
534
|
+
state.parentFolder = typeof payload.parentFolder === "undefined" ? null : payload.parentFolder;
|
|
535
|
+
state.fallbackMode = !!fallbackMode;
|
|
536
|
+
state.error = "";
|
|
537
|
+
|
|
538
|
+
if (payload.folders) {
|
|
539
|
+
for (index = 0; index < payload.folders.length; index++) {
|
|
540
|
+
var normalizedFolder = normalizeServerFolder(payload.folders[index]);
|
|
541
|
+
if (normalizedFolder) {
|
|
542
|
+
nextFolders.push(normalizedFolder);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
308
546
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
547
|
+
if (payload.images) {
|
|
548
|
+
for (index = 0; index < payload.images.length; index++) {
|
|
549
|
+
var normalizedImage = normalizeServerImage(payload.images[index]);
|
|
550
|
+
if (normalizedImage) {
|
|
551
|
+
nextImages.push(normalizedImage);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
314
554
|
}
|
|
315
555
|
|
|
316
|
-
|
|
556
|
+
state.folders = nextFolders;
|
|
557
|
+
state.images = mergePresets(nextImages);
|
|
558
|
+
|
|
559
|
+
if (state.selectedUrl) {
|
|
317
560
|
var stillVisible = false;
|
|
318
|
-
for (
|
|
319
|
-
if (
|
|
561
|
+
for (index = 0; index < state.images.length; index++) {
|
|
562
|
+
if (state.images[index].url === state.selectedUrl) {
|
|
320
563
|
stillVisible = true;
|
|
321
564
|
break;
|
|
322
565
|
}
|
|
323
566
|
}
|
|
324
567
|
if (!stillVisible) {
|
|
325
|
-
selectedUrl = "";
|
|
568
|
+
state.selectedUrl = "";
|
|
326
569
|
}
|
|
327
570
|
}
|
|
571
|
+
}
|
|
328
572
|
|
|
329
|
-
|
|
573
|
+
function loadFallback(errorCode) {
|
|
574
|
+
applyResponse({
|
|
575
|
+
currentFolder: "",
|
|
576
|
+
currentFolderDisplay: "/",
|
|
577
|
+
parentFolder: null,
|
|
578
|
+
folders: [],
|
|
579
|
+
images: []
|
|
580
|
+
}, true);
|
|
581
|
+
state.images = presetImages.slice(0);
|
|
582
|
+
state.error = presetImages.length ? "" : ("The image gallery is unavailable right now (" + errorCode + ").");
|
|
583
|
+
state.loading = false;
|
|
584
|
+
render();
|
|
330
585
|
}
|
|
331
586
|
|
|
587
|
+
function loadFolder(folder, selectAfterLoad) {
|
|
588
|
+
if (!serverMode) {
|
|
589
|
+
state.images = presetImages.slice(0);
|
|
590
|
+
render();
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
state.loading = true;
|
|
595
|
+
state.error = "";
|
|
596
|
+
render();
|
|
597
|
+
|
|
598
|
+
requestJson("GET", withFolder(endpoint, folder), null, function (payload, errorCode) {
|
|
599
|
+
if (errorCode) {
|
|
600
|
+
loadFallback(errorCode);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
applyResponse(payload || {}, false);
|
|
605
|
+
if (selectAfterLoad) {
|
|
606
|
+
state.selectedUrl = selectAfterLoad;
|
|
607
|
+
}
|
|
608
|
+
state.loading = false;
|
|
609
|
+
render();
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function createFolder() {
|
|
614
|
+
var folderName = window.prompt("New folder name", "");
|
|
615
|
+
if (!folderName) {
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
state.loading = true;
|
|
620
|
+
state.error = "";
|
|
621
|
+
render();
|
|
622
|
+
|
|
623
|
+
var formData = new FormData();
|
|
624
|
+
formData.append("action", "create-folder");
|
|
625
|
+
formData.append("folder", state.currentFolder || "");
|
|
626
|
+
formData.append("name", folderName);
|
|
627
|
+
|
|
628
|
+
requestJson("POST", endpoint, formData, function (payload, errorCode) {
|
|
629
|
+
if (errorCode) {
|
|
630
|
+
state.loading = false;
|
|
631
|
+
state.error = "The folder could not be created right now.";
|
|
632
|
+
render();
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
applyResponse(payload || {}, false);
|
|
637
|
+
state.loading = false;
|
|
638
|
+
render();
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function uploadFiles(fileList) {
|
|
643
|
+
var files = [];
|
|
644
|
+
var index;
|
|
645
|
+
for (index = 0; index < fileList.length; index++) {
|
|
646
|
+
files.push(fileList[index]);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (!files.length || !canUpload) {
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
var lastUploadedUrl = "";
|
|
654
|
+
|
|
655
|
+
function uploadNext(nextIndex) {
|
|
656
|
+
if (nextIndex >= files.length) {
|
|
657
|
+
if (serverMode) {
|
|
658
|
+
loadFolder(state.currentFolder, lastUploadedUrl);
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// No server to re-list from, so fold the upload into the
|
|
663
|
+
// preset list directly and keep it selected.
|
|
664
|
+
if (lastUploadedUrl) {
|
|
665
|
+
var uploaded = normalizePreset(lastUploadedUrl);
|
|
666
|
+
if (uploaded) {
|
|
667
|
+
uploaded.source = "upload";
|
|
668
|
+
presetImages.unshift(uploaded);
|
|
669
|
+
config.galleryImages.unshift(lastUploadedUrl);
|
|
670
|
+
}
|
|
671
|
+
state.selectedUrl = lastUploadedUrl;
|
|
672
|
+
}
|
|
673
|
+
state.images = presetImages.slice(0);
|
|
674
|
+
state.loading = false;
|
|
675
|
+
render();
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
var file = files[nextIndex];
|
|
680
|
+
|
|
681
|
+
function done(url, errorCode) {
|
|
682
|
+
if (!url) {
|
|
683
|
+
state.error = errorCode || ("Upload failed for " + file.name + ".");
|
|
684
|
+
state.loading = false;
|
|
685
|
+
render();
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
lastUploadedUrl = url;
|
|
690
|
+
uploadNext(nextIndex + 1);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (uploadToFolder) {
|
|
694
|
+
window.richTextBoxUploadFile(file, function (url, errorCode) {
|
|
695
|
+
done(errorCode ? "" : url, errorCode ? ("Upload failed for " + file.name + ".") : "");
|
|
696
|
+
}, { folder: state.currentFolder }, nextIndex, files);
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
window.rte_file_upload_handler(file, function (url, error) {
|
|
701
|
+
done(url, error);
|
|
702
|
+
}, nextIndex, files);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
state.loading = true;
|
|
706
|
+
state.error = "";
|
|
707
|
+
render();
|
|
708
|
+
uploadNext(0);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
upButton.onclick = function () {
|
|
712
|
+
if (state.parentFolder === null) {
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
loadFolder(state.parentFolder);
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
createFolderButton.onclick = createFolder;
|
|
719
|
+
|
|
332
720
|
uploadButton.onclick = function () {
|
|
333
721
|
fileInput.click();
|
|
334
722
|
};
|
|
335
723
|
|
|
336
724
|
fileInput.onchange = function () {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}, function () {
|
|
344
|
-
if (lastUploaded) {
|
|
345
|
-
selectedUrl = lastUploaded;
|
|
346
|
-
}
|
|
347
|
-
fileInput.value = "";
|
|
348
|
-
render();
|
|
349
|
-
}, function (error) {
|
|
350
|
-
status.innerText = error || "Upload failed.";
|
|
351
|
-
});
|
|
725
|
+
uploadFiles(this.files);
|
|
726
|
+
this.value = "";
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
refreshButton.onclick = function () {
|
|
730
|
+
loadFolder(state.currentFolder, state.selectedUrl);
|
|
352
731
|
};
|
|
353
732
|
|
|
354
|
-
refreshButton.onclick = render;
|
|
355
733
|
search.oninput = render;
|
|
356
734
|
cancelButton.onclick = closeDialog;
|
|
357
735
|
insertButton.onclick = insertSelected;
|
|
358
736
|
|
|
359
|
-
|
|
737
|
+
if (serverMode) {
|
|
738
|
+
loadFolder("");
|
|
739
|
+
} else {
|
|
740
|
+
render();
|
|
741
|
+
}
|
|
742
|
+
|
|
360
743
|
search.focus();
|
|
361
744
|
};
|
|
362
745
|
}
|