@fiduswriter/image-manager 0.1.6 → 0.1.8

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.
@@ -1,251 +1,236 @@
1
- import {baseBodyTemplate} from "@fiduswriter/common/common"
2
- import {FeedbackTab} from "@fiduswriter/common/feedback"
3
- import {SiteMenu} from "@fiduswriter/common/menu"
4
1
  import {
5
- Dialog,
6
- OverviewDataTable,
7
- OverviewMenuView,
8
- activateWait,
9
- addAlert,
10
- deactivateWait,
11
- ensureCSS,
12
- escapeText,
13
- findTarget,
14
- gettext,
15
- isActivationEvent,
16
- localizeDate,
17
- post,
18
- setDocTitle,
19
- staticUrl,
20
- whenReady
21
- } from "fwtoolkit"
22
- import type {DataTable} from "simple-datatables"
23
-
24
- import {ImageOverviewCategories} from "./categories.js"
25
- import {bulkMenuModel, menuModel} from "./menu.js"
2
+ Dialog,
3
+ OverviewDataTable,
4
+ OverviewMenuView,
5
+ activateWait,
6
+ addAlert,
7
+ deactivateWait,
8
+ ensureCSS,
9
+ escapeText,
10
+ findTarget,
11
+ gettext,
12
+ isActivationEvent,
13
+ localizeDate,
14
+ post,
15
+ setDocTitle,
16
+ staticUrl,
17
+ whenReady,
18
+ } from "fwtoolkit";
19
+ import type { DataTable } from "simple-datatables";
20
+
21
+ import { imageOverviewTemplate } from "./templates.js";
22
+ import { ImageOverviewCategories } from "./categories.js";
23
+ import { bulkMenuModel, menuModel } from "./menu.js";
26
24
  import type {
27
- ImageManagerApp,
28
- ImageManagerPage,
29
- ImageOverviewPlugin,
30
- ImageOverviewPluginConstructor,
31
- ImageTableRow
32
- } from "../types.js"
25
+ ImageManagerApp,
26
+ ImageManagerPage,
27
+ ImageOverviewPlugin,
28
+ ImageOverviewPluginConstructor,
29
+ ImageTableRow,
30
+ } from "../types.js";
33
31
 
34
32
  interface DataTableRow {
35
- cells: {data: unknown; text?: string}[]
33
+ cells: { data: unknown; text?: string }[];
36
34
  }
37
35
 
38
36
  interface VirtualNode {
39
- nodeName: string
40
- attributes?: Record<string, string | boolean>
41
- childNodes?: VirtualNode[]
37
+ nodeName: string;
38
+ attributes?: Record<string, string | boolean>;
39
+ childNodes?: VirtualNode[];
42
40
  }
43
41
 
44
42
  /** Helper functions for user added images/SVGs.*/
45
43
 
46
44
  export class ImageOverview {
47
- app: ImageManagerApp
48
-
49
- user: unknown
50
-
51
- plugins: [string, Record<string, ImageOverviewPluginConstructor>][]
52
-
53
- mod: {
54
- categories?: ImageOverviewCategories
55
- }
56
-
57
- lastSort: {
58
- column: number
59
- dir: "asc" | "desc"
60
- }
61
-
62
- pluginsActivated = false
63
-
64
- dom!: HTMLBodyElement
65
-
66
- menu!: OverviewMenuView
67
-
68
- overviewTable: OverviewDataTable | null = null
69
-
70
- table: DataTable | null = null
71
-
72
- dtBulk: import("fwtoolkit").DatatableBulk | null = null
73
-
74
- constructor({
75
- app,
76
- user,
77
- plugins = []
78
- }: {
79
- app: ImageManagerApp
80
- user: unknown
81
- plugins?: [string, Record<string, ImageOverviewPluginConstructor>][]
82
- }) {
83
- this.app = app
84
- this.user = user
85
- this.plugins = plugins
86
- this.mod = {}
87
-
88
- this.lastSort = {column: 0, dir: "asc"}
45
+ app: ImageManagerApp;
46
+
47
+ container: HTMLElement;
48
+
49
+ plugins: [string, Record<string, ImageOverviewPluginConstructor>][];
50
+
51
+ mod: {
52
+ categories?: ImageOverviewCategories;
53
+ };
54
+
55
+ lastSort: {
56
+ column: number;
57
+ dir: "asc" | "desc";
58
+ };
59
+
60
+ pluginsActivated = false;
61
+
62
+ dom!: HTMLElement;
63
+
64
+ menu!: OverviewMenuView;
65
+
66
+ overviewTable: OverviewDataTable | null = null;
67
+
68
+ table: DataTable | null = null;
69
+
70
+ dtBulk: import("fwtoolkit").DatatableBulk | null = null;
71
+
72
+ constructor({
73
+ app,
74
+ container,
75
+ plugins = [],
76
+ }: {
77
+ app: ImageManagerApp;
78
+ container: HTMLElement;
79
+ plugins?: [string, Record<string, ImageOverviewPluginConstructor>][];
80
+ }) {
81
+ this.app = app;
82
+ this.container = container;
83
+ this.plugins = plugins;
84
+ this.mod = {};
85
+
86
+ this.lastSort = { column: 0, dir: "asc" };
87
+ }
88
+
89
+ init(): Promise<void> {
90
+ ensureCSS([
91
+ staticUrl("css/dialog_usermedia.css"),
92
+ staticUrl("css/dot_menu.css"),
93
+ ]);
94
+
95
+ return whenReady().then(() => {
96
+ this.render();
97
+ new ImageOverviewCategories(this);
98
+ this.menu = new OverviewMenuView(this, menuModel);
99
+ this.menu.init();
100
+ this.activatePlugins();
101
+ this.bindEvents();
102
+ this.mod.categories!.setImageCategoryList(this.app.imageDB.cats);
103
+ this.initTable(Object.keys(this.app.imageDB.db));
104
+ // Reset scroll position to top to prevent Safari from auto-scrolling
105
+ // to the focused table element, which would hide the header/menu
106
+ window.scrollTo(0, 0);
107
+ });
108
+ }
109
+
110
+ render(): void {
111
+ this.dom = this.container;
112
+ this.dom.innerHTML = imageOverviewTemplate();
113
+ ensureCSS([staticUrl("css/cropper.min.css")]);
114
+ setDocTitle(gettext("Media Manager"), this.app);
115
+ }
116
+
117
+ activatePlugins(): Promise<void> {
118
+ if (this.pluginsActivated) {
119
+ // Plugins have been activated already
120
+ return Promise.resolve();
89
121
  }
90
-
91
- init(): Promise<void> {
92
- ensureCSS([
93
- staticUrl("css/dialog_usermedia.css"),
94
- staticUrl("css/dot_menu.css")
95
- ])
96
-
97
- return whenReady().then(() => {
98
- this.render()
99
- new ImageOverviewCategories(this)
100
- const smenu = new SiteMenu(this.app, "images")
101
- smenu.init()
102
- this.menu = new OverviewMenuView(this, menuModel)
103
- this.menu.init()
104
- this.activatePlugins()
105
- this.bindEvents()
106
- this.mod.categories!.setImageCategoryList(this.app.imageDB.cats)
107
- this.initTable(Object.keys(this.app.imageDB.db))
108
- // Reset scroll position to top to prevent Safari from auto-scrolling
109
- // to the focused table element, which would hide the header/menu
110
- window.scrollTo(0, 0)
111
- })
112
- }
113
-
114
- render(): void {
115
- this.dom = document.createElement("body")
116
- this.dom.innerHTML = baseBodyTemplate({
117
- contents: "",
118
- user: this.user,
119
- hasOverview: true,
120
- app: this.app
121
- })
122
- document.body = this.dom
123
- ensureCSS([staticUrl("css/cropper.min.css")])
124
- setDocTitle(gettext("Media Manager"), this.app)
125
- const feedbackTab = new FeedbackTab()
126
- feedbackTab.init()
127
- }
128
-
129
- activatePlugins(): Promise<void> {
130
- if (this.pluginsActivated) {
131
- // Plugins have been activated already
132
- return Promise.resolve()
122
+ this.pluginsActivated = true;
123
+ // Add plugins.
124
+ const pluginInstances: Record<string, ImageOverviewPlugin> = {};
125
+
126
+ return Promise.all(
127
+ this.plugins.map(([app, plugin]) => {
128
+ if (!this.app.settings.APPS.includes(app)) {
129
+ return Promise.resolve();
133
130
  }
134
- this.pluginsActivated = true
135
- // Add plugins.
136
- const pluginInstances: Record<string, ImageOverviewPlugin> = {}
137
-
138
131
  return Promise.all(
139
- this.plugins.map(([app, plugin]) => {
140
- if (!this.app.settings.APPS.includes(app)) {
141
- return Promise.resolve()
142
- }
143
- return Promise.all(
144
- Object.values(plugin).map(pluginExport => {
145
- if (typeof pluginExport === "function") {
146
- const Plugin = pluginExport as ImageOverviewPluginConstructor
147
- pluginInstances[Plugin.name] = new Plugin(this)
148
- return (
149
- pluginInstances[Plugin.name].init?.() ||
150
- Promise.resolve()
151
- )
152
- }
153
- return Promise.resolve()
154
- })
155
- )
156
- })
157
- ).then(() => undefined)
158
- }
159
-
160
- //delete image
161
- deleteImage(ids: (string | number)[]): void {
162
- const numericIds = ids.map(id => Number.parseInt(String(id)))
163
- if (this.app.isOffline()) {
164
- addAlert(
165
- "error",
166
- gettext(
167
- "You are currently offline. Please try again when you are back online."
168
- )
169
- )
170
- return
171
- }
172
- activateWait()
173
- post("/api/usermedia/delete/", {ids: numericIds})
174
- .catch(error => {
175
- addAlert("error", gettext("The image(s) could not be deleted"))
176
- deactivateWait()
177
- if (this.app.isOffline()) {
178
- addAlert(
179
- "error",
180
- gettext(
181
- "You are currently offline. Please try again when you are back online."
182
- )
183
- )
184
- } else {
185
- throw error
186
- }
187
- })
188
- .then(() => {
189
- numericIds.forEach(id => delete this.app.imageDB.db[id])
190
- this.removeTableRows(numericIds)
191
- addAlert("success", gettext("The image(s) have been deleted"))
192
- })
193
- .then(() => deactivateWait())
194
- }
195
-
196
- deleteImageDialog(ids: (string | number)[]): void {
197
- const buttons = [
198
- {
199
- text: gettext("Delete"),
200
- classes: "fw-dark",
201
- click: () => {
202
- this.deleteImage(ids)
203
- dialog.close()
204
- }
205
- },
206
- {
207
- type: "cancel" as const
132
+ Object.values(plugin).map((pluginExport) => {
133
+ if (typeof pluginExport === "function") {
134
+ const Plugin = pluginExport as ImageOverviewPluginConstructor;
135
+ pluginInstances[Plugin.name] = new Plugin(this);
136
+ return pluginInstances[Plugin.name].init?.() || Promise.resolve();
208
137
  }
209
- ]
210
- const dialog = new Dialog({
211
- id: "confirmdeletion",
212
- icon: "exclamation-triangle",
213
- title: gettext("Confirm deletion"),
214
- body: `<p>${gettext("Delete the image(s)")}?</p>`,
215
- buttons
216
- })
217
- dialog.open()
138
+ return Promise.resolve();
139
+ }),
140
+ );
141
+ }),
142
+ ).then(() => undefined);
143
+ }
144
+
145
+ //delete image
146
+ deleteImage(ids: (string | number)[]): void {
147
+ const numericIds = ids.map((id) => Number.parseInt(String(id)));
148
+ if (this.app.isOffline()) {
149
+ addAlert(
150
+ "error",
151
+ gettext(
152
+ "You are currently offline. Please try again when you are back online.",
153
+ ),
154
+ );
155
+ return;
218
156
  }
219
-
220
- updateTable(ids: (string | number)[]): void {
221
- // Remove items that already exist
222
- this.removeTableRows(ids)
223
- if (this.table) {
224
- this.table.insert({
225
- data: ids.map(id => this.createTableRow(Number(id)))
226
- })
227
- // Redo last sort
228
- this.table.columns.sort(this.lastSort.column, this.lastSort.dir)
157
+ activateWait();
158
+ post("/api/usermedia/delete/", { ids: numericIds })
159
+ .catch((error) => {
160
+ addAlert("error", gettext("The image(s) could not be deleted"));
161
+ deactivateWait();
162
+ if (this.app.isOffline()) {
163
+ addAlert(
164
+ "error",
165
+ gettext(
166
+ "You are currently offline. Please try again when you are back online.",
167
+ ),
168
+ );
169
+ } else {
170
+ throw error;
229
171
  }
172
+ })
173
+ .then(() => {
174
+ numericIds.forEach((id) => delete this.app.imageDB.db[id]);
175
+ this.removeTableRows(numericIds);
176
+ addAlert("success", gettext("The image(s) have been deleted"));
177
+ })
178
+ .then(() => deactivateWait());
179
+ }
180
+
181
+ deleteImageDialog(ids: (string | number)[]): void {
182
+ const buttons = [
183
+ {
184
+ text: gettext("Delete"),
185
+ classes: "fw-dark",
186
+ click: () => {
187
+ this.deleteImage(ids);
188
+ dialog.close();
189
+ },
190
+ },
191
+ {
192
+ type: "cancel" as const,
193
+ },
194
+ ];
195
+ const dialog = new Dialog({
196
+ id: "confirmdeletion",
197
+ icon: "exclamation-triangle",
198
+ title: gettext("Confirm deletion"),
199
+ body: `<p>${gettext("Delete the image(s)")}?</p>`,
200
+ buttons,
201
+ });
202
+ dialog.open();
203
+ }
204
+
205
+ updateTable(ids: (string | number)[]): void {
206
+ // Remove items that already exist
207
+ this.removeTableRows(ids);
208
+ if (this.table) {
209
+ this.table.insert({
210
+ data: ids.map((id) => this.createTableRow(Number(id))),
211
+ });
212
+ // Redo last sort
213
+ this.table.columns.sort(this.lastSort.column, this.lastSort.dir);
230
214
  }
215
+ }
231
216
 
232
- createTableRow(id: number): ImageTableRow {
233
- const image = this.app.imageDB.db[id]
234
- const cats = image.cats.map(cat => `cat_${cat}`)
217
+ createTableRow(id: number): ImageTableRow {
218
+ const image = this.app.imageDB.db[id];
219
+ const cats = image.cats.map((cat) => `cat_${cat}`);
235
220
 
236
- const fileTypeParts = image.file_type.split("/")
221
+ const fileTypeParts = image.file_type.split("/");
237
222
 
238
- let fileType: string
239
- if (1 < fileTypeParts.length) {
240
- fileType = fileTypeParts[1].toUpperCase()
241
- } else {
242
- fileType = fileTypeParts[0].toUpperCase()
243
- }
223
+ let fileType: string;
224
+ if (1 < fileTypeParts.length) {
225
+ fileType = fileTypeParts[1].toUpperCase();
226
+ } else {
227
+ fileType = fileTypeParts[0].toUpperCase();
228
+ }
244
229
 
245
- return [
246
- id,
247
- false, // checkbox
248
- `<span class="fw-usermedia-image ${cats.join(" ")}">
230
+ return [
231
+ id,
232
+ false, // checkbox
233
+ `<span class="fw-usermedia-image ${cats.join(" ")}">
249
234
  <img src="${image.thumbnail ? image.thumbnail : image.image}">
250
235
  </span>
251
236
  <span class="fw-usermedia-title">
@@ -254,264 +239,264 @@ export class ImageOverview {
254
239
  </span>
255
240
  <span class="fw-usermedia-type">${fileType}</span>
256
241
  </span>`,
257
- `<span>${image.width} x ${image.height}</span>`,
258
- `<span class="fw-date">${localizeDate(image.added, "sortable-date")}</span>`,
259
- `<span class="delete-image fw-link-text" data-id="${id}">
242
+ `<span>${image.width} x ${image.height}</span>`,
243
+ `<span class="fw-date">${localizeDate(image.added, "sortable-date")}</span>`,
244
+ `<span class="delete-image fw-link-text" data-id="${id}">
260
245
  <i class="fa fa-trash-alt"></i>
261
- </span>`
262
- ]
263
- }
264
-
265
- removeTableRows(ids: (string | number)[]): void {
266
- const numericIds = ids.map(id => Number.parseInt(String(id)))
267
-
268
- if (!this.table) {
269
- return
270
- }
246
+ </span>`,
247
+ ];
248
+ }
271
249
 
272
- const existingRows = this.table.data.data
273
- .map((row: DataTableRow, index: number) => {
274
- const id = Number(row.cells[0].data)
275
- if (numericIds.includes(id)) {
276
- return index
277
- } else {
278
- return false
279
- }
280
- })
281
- .filter(
282
- (rowIndex): rowIndex is number => rowIndex !== false
283
- )
250
+ removeTableRows(ids: (string | number)[]): void {
251
+ const numericIds = ids.map((id) => Number.parseInt(String(id)));
284
252
 
285
- if (existingRows.length) {
286
- this.table.rows.remove(existingRows)
287
- }
253
+ if (!this.table) {
254
+ return;
288
255
  }
289
256
 
290
- onResize(): void {
291
- if (!this.table) {
292
- return
257
+ const existingRows = this.table.data.data
258
+ .map((row: DataTableRow, index: number) => {
259
+ const id = Number(row.cells[0].data);
260
+ if (numericIds.includes(id)) {
261
+ return index;
262
+ } else {
263
+ return false;
293
264
  }
294
- this.initTable(Object.keys(this.app.imageDB.db))
265
+ })
266
+ .filter((rowIndex): rowIndex is number => rowIndex !== false);
267
+
268
+ if (existingRows.length) {
269
+ this.table.rows.remove(existingRows);
295
270
  }
271
+ }
296
272
 
297
- /* Initialize the overview table */
298
- initTable(ids: string[]): void {
299
- if (this.overviewTable) {
300
- this.overviewTable.destroy()
301
- this.overviewTable = null
302
- }
303
- this.table = null
304
- this.dtBulk = null
273
+ onResize(): void {
274
+ if (!this.table) {
275
+ return;
276
+ }
277
+ this.initTable(Object.keys(this.app.imageDB.db));
278
+ }
279
+
280
+ /* Initialize the overview table */
281
+ initTable(ids: string[]): void {
282
+ if (this.overviewTable) {
283
+ this.overviewTable.destroy();
284
+ this.overviewTable = null;
285
+ }
286
+ this.table = null;
287
+ this.dtBulk = null;
305
288
 
306
- const contentsEl = this.dom.querySelector(".fw-contents") as HTMLElement | null
307
- if (!contentsEl) {
308
- return
309
- }
310
- contentsEl.innerHTML = ""
289
+ const contentsEl = this.dom as HTMLElement;
290
+ contentsEl.innerHTML = "";
311
291
 
312
- const hiddenCols: number[] = [0]
292
+ const hiddenCols: number[] = [0];
313
293
 
314
- if (window.innerWidth < 500) {
315
- hiddenCols.push(1)
316
- }
294
+ if (window.innerWidth < 500) {
295
+ hiddenCols.push(1);
296
+ }
317
297
 
318
- this.overviewTable = new OverviewDataTable({
319
- dom: contentsEl,
320
- classes: ["fw-data-table", "fw-large"],
321
- columns: [
322
- {
323
- select: 0,
324
- type: "number"
325
- },
326
- {
327
- select: 1,
328
- type: "boolean"
329
- },
330
- {
331
- select: hiddenCols,
332
- hidden: true
333
- },
334
- {
335
- select: [1, 3, 5],
336
- sortable: false
337
- },
338
- {
339
- select: [this.lastSort.column],
340
- sort: this.lastSort.dir
341
- }
342
- ],
343
- data: ids.map(id => this.createTableRow(Number.parseInt(id))),
344
- idColumn: 0,
345
- checkboxColumn: 1,
346
- bulkMenu: bulkMenuModel(),
347
- bulkMenuPage: this as Record<string, unknown>,
348
- searchable: true,
349
- scrollY: `${Math.max(window.innerHeight - 360, 100)}px`,
350
- tabIndex: 1,
351
- labels: {
352
- noRows: gettext("No images available"), // Message shown when there are no images
353
- noResults: gettext("No images found") // Message shown when no images are found after search
354
- },
355
- headings: [
356
- "",
357
- "",
358
- gettext("File"),
359
- gettext("Size (px)"),
360
- gettext("Added"),
361
- ""
362
- ],
363
- template: (options: {classes: Record<string, string>; scrollY: string; paging?: boolean}, _dom) =>
364
- `<div class='${options.classes.container}'${options.scrollY.length ? ` style='height: ${options.scrollY}; overflow-Y: auto;'` : ""}></div>
298
+ this.overviewTable = new OverviewDataTable({
299
+ dom: contentsEl,
300
+ classes: ["fw-data-table", "fw-large"],
301
+ columns: [
302
+ {
303
+ select: 0,
304
+ type: "number",
305
+ },
306
+ {
307
+ select: 1,
308
+ type: "boolean",
309
+ },
310
+ {
311
+ select: hiddenCols,
312
+ hidden: true,
313
+ },
314
+ {
315
+ select: [1, 3, 5],
316
+ sortable: false,
317
+ },
318
+ {
319
+ select: [this.lastSort.column],
320
+ sort: this.lastSort.dir,
321
+ },
322
+ ],
323
+ data: ids.map((id) => this.createTableRow(Number.parseInt(id))),
324
+ idColumn: 0,
325
+ checkboxColumn: 1,
326
+ bulkMenu: bulkMenuModel(),
327
+ bulkMenuPage: this as Record<string, unknown>,
328
+ searchable: true,
329
+ scrollY: `${Math.max(window.innerHeight - 360, 100)}px`,
330
+ tabIndex: 1,
331
+ labels: {
332
+ noRows: gettext("No images available"), // Message shown when there are no images
333
+ noResults: gettext("No images found"), // Message shown when no images are found after search
334
+ },
335
+ headings: [
336
+ "",
337
+ "",
338
+ gettext("File"),
339
+ gettext("Size (px)"),
340
+ gettext("Added"),
341
+ "",
342
+ ],
343
+ template: (
344
+ options: {
345
+ classes: Record<string, string>;
346
+ scrollY: string;
347
+ paging?: boolean;
348
+ },
349
+ _dom,
350
+ ) =>
351
+ `<div class='${options.classes.container}'${options.scrollY.length ? ` style='height: ${options.scrollY}; overflow-Y: auto;'` : ""}></div>
365
352
  <div class='${options.classes.bottom}'>
366
353
  ${
367
- options.paging
368
- ? `<div class='${options.classes.info}'></div>`
369
- : ""
354
+ options.paging
355
+ ? `<div class='${options.classes.info}'></div>`
356
+ : ""
370
357
  }
371
358
  <nav class='${options.classes.pagination}'></nav>
372
359
  </div>`,
373
- rowRender: (row, tr, _index) => {
374
- const id = row.cells[0].data as number
375
- const inputNode: VirtualNode = {
376
- nodeName: "input",
377
- attributes: {
378
- type: "checkbox",
379
- class: "entry-select fw-check",
380
- "data-id": String(id),
381
- id: `doc-img-${id}`
382
- }
383
- }
384
- if (row.cells[1].data) {
385
- inputNode.attributes!.checked = "checked"
386
- }
387
- const trNode = tr as {
388
- childNodes: {childNodes: VirtualNode[]}[]
389
- }
390
- trNode.childNodes[0].childNodes = [
391
- inputNode,
392
- {
393
- nodeName: "label",
394
- attributes: {
395
- for: `doc-img-${id}`
396
- }
397
- }
398
- ]
399
- },
400
- onEnter: (row, _event) => {
401
- if (this.getSelected().length > 0) {
402
- return
403
- }
404
- if (!this.table) {
405
- return
406
- }
407
- const rowIndex = this.table.data.data.findIndex(
408
- dataRow =>
409
- (dataRow as unknown as DataTableRow).cells[0].data ===
410
- row.cells[0].data
411
- )
412
- const button = this.table.dom.querySelector(
413
- `tr[data-index="${rowIndex}"] span.edit-image`
414
- )
415
- if (button) {
416
- ;(button as HTMLElement).click()
417
- }
360
+ rowRender: (row, tr, _index) => {
361
+ const id = row.cells[0].data as number;
362
+ const inputNode: VirtualNode = {
363
+ nodeName: "input",
364
+ attributes: {
365
+ type: "checkbox",
366
+ class: "entry-select fw-check",
367
+ "data-id": String(id),
368
+ id: `doc-img-${id}`,
369
+ },
370
+ };
371
+ if (row.cells[1].data) {
372
+ inputNode.attributes!.checked = "checked";
373
+ }
374
+ const trNode = tr as {
375
+ childNodes: { childNodes: VirtualNode[] }[];
376
+ };
377
+ trNode.childNodes[0].childNodes = [
378
+ inputNode,
379
+ {
380
+ nodeName: "label",
381
+ attributes: {
382
+ for: `doc-img-${id}`,
418
383
  },
419
- onDelete: row => {
420
- const imageId = row.cells[0].data as number
421
- this.deleteImageDialog([imageId])
422
- }
423
- })
424
- this.overviewTable.init()
425
- this.table = this.overviewTable.table!
426
- ;(this.table as unknown as {id: string}).id = "imagelist"
427
- this.dtBulk = this.overviewTable.dtBulk || null
428
-
429
- this.table.on("datatable.sort", (column, dir) => {
430
- this.lastSort = {column: column as number, dir: dir as "asc" | "desc"}
431
- })
432
-
433
- this.table.dom.focus()
434
- }
435
-
436
- // get IDs of selected bib entries
437
- getSelected(): number[] {
438
- return Array.from(
439
- this.dom.querySelectorAll(".entry-select:checked:not(:disabled)")
440
- ).map(el => Number.parseInt(el.getAttribute("data-id") || "0"))
441
- }
442
-
443
- bindEvents(): void {
444
- this.dom.addEventListener("click", event =>
445
- this.handleActivation(event)
446
- )
447
- this.dom.addEventListener("keydown", event =>
448
- this.handleActivation(event)
449
- )
450
- }
451
-
452
- handleActivation(event: Event): void {
453
- if (!isActivationEvent(event)) {
454
- return
384
+ },
385
+ ];
386
+ },
387
+ onEnter: (row, _event) => {
388
+ if (this.getSelected().length > 0) {
389
+ return;
455
390
  }
456
- const el: {target?: Element | null} = {}
457
- switch (true) {
458
- case findTarget(event, ".delete-image", el): {
459
- const imageId = (el.target as HTMLElement | null)?.dataset.id
460
- this.deleteImageDialog([imageId || ""])
461
- break
462
- }
463
- case findTarget(event, ".edit-image", el): {
464
- const imageId = (el.target as HTMLElement | null)?.dataset.id
465
- import("../edit_dialog/index.js").then(
466
- ({ImageEditDialog}) => {
467
- const dialog = new ImageEditDialog(
468
- this.app.imageDB,
469
- imageId ? Number.parseInt(imageId) : false,
470
- this as unknown as ImageManagerPage
471
- )
472
- dialog.init().then(() => {
473
- if (imageId) {
474
- this.updateTable([Number.parseInt(imageId)])
475
- }
476
- })
477
- }
478
- )
479
- break
391
+ if (!this.table) {
392
+ return;
393
+ }
394
+ const rowIndex = this.table.data.data.findIndex(
395
+ (dataRow) =>
396
+ (dataRow as unknown as DataTableRow).cells[0].data ===
397
+ row.cells[0].data,
398
+ );
399
+ const button = this.table.dom.querySelector(
400
+ `tr[data-index="${rowIndex}"] span.edit-image`,
401
+ );
402
+ if (button) {
403
+ (button as HTMLElement).click();
404
+ }
405
+ },
406
+ onDelete: (row) => {
407
+ const imageId = row.cells[0].data as number;
408
+ this.deleteImageDialog([imageId]);
409
+ },
410
+ });
411
+ this.overviewTable.init();
412
+ this.table = this.overviewTable.table!;
413
+ (this.table as unknown as { id: string }).id = "imagelist";
414
+ this.dtBulk = this.overviewTable.dtBulk || null;
415
+
416
+ this.table.on("datatable.sort", (column, dir) => {
417
+ this.lastSort = { column: column as number, dir: dir as "asc" | "desc" };
418
+ });
419
+
420
+ this.table.dom.focus();
421
+ }
422
+
423
+ // get IDs of selected bib entries
424
+ getSelected(): number[] {
425
+ return Array.from(
426
+ this.dom.querySelectorAll(".entry-select:checked:not(:disabled)"),
427
+ ).map((el) => Number.parseInt(el.getAttribute("data-id") || "0"));
428
+ }
429
+
430
+ bindEvents(): void {
431
+ this.dom.addEventListener("click", (event) => this.handleActivation(event));
432
+ this.dom.addEventListener("keydown", (event) =>
433
+ this.handleActivation(event),
434
+ );
435
+ }
436
+
437
+ handleActivation(event: Event): void {
438
+ if (!isActivationEvent(event)) {
439
+ return;
440
+ }
441
+ const el: { target?: Element | null } = {};
442
+ switch (true) {
443
+ case findTarget(event, ".delete-image", el): {
444
+ const imageId = (el.target as HTMLElement | null)?.dataset.id;
445
+ this.deleteImageDialog([imageId || ""]);
446
+ break;
447
+ }
448
+ case findTarget(event, ".edit-image", el): {
449
+ const imageId = (el.target as HTMLElement | null)?.dataset.id;
450
+ import("../edit_dialog/index.js").then(({ ImageEditDialog }) => {
451
+ const dialog = new ImageEditDialog(
452
+ this.app.imageDB,
453
+ imageId ? Number.parseInt(imageId) : false,
454
+ this as unknown as ImageManagerPage,
455
+ );
456
+ dialog.init().then(() => {
457
+ if (imageId) {
458
+ this.updateTable([Number.parseInt(imageId)]);
480
459
  }
481
- case findTarget(event, ".fw-add-input", el): {
482
- const itemEl = (el.target as HTMLElement | null)?.closest(".fw-list-input") as HTMLElement
483
- if (!itemEl.nextElementSibling) {
484
- itemEl.insertAdjacentHTML(
485
- "afterend",
486
- `<tr class="fw-list-input">
460
+ });
461
+ });
462
+ break;
463
+ }
464
+ case findTarget(event, ".fw-add-input", el): {
465
+ const itemEl = (el.target as HTMLElement | null)?.closest(
466
+ ".fw-list-input",
467
+ ) as HTMLElement;
468
+ if (!itemEl.nextElementSibling) {
469
+ itemEl.insertAdjacentHTML(
470
+ "afterend",
471
+ `<tr class="fw-list-input">
487
472
  <td>
488
473
  <input type="text" class="category-form">
489
474
  <span class="fw-add-input icon-addremove" tabindex="0"></span>
490
475
  </td>
491
- </tr>`
492
- )
493
- } else {
494
- itemEl.parentElement!.removeChild(itemEl)
495
- }
496
- break
497
- }
498
- default:
499
- break
476
+ </tr>`,
477
+ );
478
+ } else {
479
+ itemEl.parentElement!.removeChild(itemEl);
500
480
  }
481
+ break;
482
+ }
483
+ default:
484
+ break;
501
485
  }
486
+ }
502
487
 
503
- close(): void {
504
- if (this.table) {
505
- this.table.destroy()
506
- this.table = null
507
- }
508
- if (this.dtBulk) {
509
- this.dtBulk.destroy()
510
- this.dtBulk = null
511
- }
512
- if (this.menu) {
513
- this.menu.destroy()
514
- this.menu = null as unknown as OverviewMenuView
515
- }
488
+ close(): void {
489
+ if (this.table) {
490
+ this.table.destroy();
491
+ this.table = null;
492
+ }
493
+ if (this.dtBulk) {
494
+ this.dtBulk.destroy();
495
+ this.dtBulk = null;
496
+ }
497
+ if (this.menu) {
498
+ this.menu.destroy();
499
+ this.menu = null as unknown as OverviewMenuView;
516
500
  }
501
+ }
517
502
  }