@fiduswriter/image-manager 0.1.5 → 0.1.7

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,269 @@ 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))
295
- }
265
+ })
266
+ .filter((rowIndex): rowIndex is number => rowIndex !== false);
296
267
 
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
268
+ if (existingRows.length) {
269
+ this.table.rows.remove(existingRows);
270
+ }
271
+ }
305
272
 
306
- const contentsEl = this.dom.querySelector(".fw-contents") as HTMLElement | null
307
- if (!contentsEl) {
308
- return
309
- }
310
- contentsEl.innerHTML = ""
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;
288
+
289
+ const contentsEl = this.dom.querySelector(
290
+ ".fw-contents",
291
+ ) as HTMLElement | null;
292
+ if (!contentsEl) {
293
+ return;
294
+ }
295
+ contentsEl.innerHTML = "";
311
296
 
312
- const hiddenCols: number[] = [0]
297
+ const hiddenCols: number[] = [0];
313
298
 
314
- if (window.innerWidth < 500) {
315
- hiddenCols.push(1)
316
- }
299
+ if (window.innerWidth < 500) {
300
+ hiddenCols.push(1);
301
+ }
317
302
 
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>
303
+ this.overviewTable = new OverviewDataTable({
304
+ dom: contentsEl,
305
+ classes: ["fw-data-table", "fw-large"],
306
+ columns: [
307
+ {
308
+ select: 0,
309
+ type: "number",
310
+ },
311
+ {
312
+ select: 1,
313
+ type: "boolean",
314
+ },
315
+ {
316
+ select: hiddenCols,
317
+ hidden: true,
318
+ },
319
+ {
320
+ select: [1, 3, 5],
321
+ sortable: false,
322
+ },
323
+ {
324
+ select: [this.lastSort.column],
325
+ sort: this.lastSort.dir,
326
+ },
327
+ ],
328
+ data: ids.map((id) => this.createTableRow(Number.parseInt(id))),
329
+ idColumn: 0,
330
+ checkboxColumn: 1,
331
+ bulkMenu: bulkMenuModel(),
332
+ bulkMenuPage: this as Record<string, unknown>,
333
+ searchable: true,
334
+ scrollY: `${Math.max(window.innerHeight - 360, 100)}px`,
335
+ tabIndex: 1,
336
+ labels: {
337
+ noRows: gettext("No images available"), // Message shown when there are no images
338
+ noResults: gettext("No images found"), // Message shown when no images are found after search
339
+ },
340
+ headings: [
341
+ "",
342
+ "",
343
+ gettext("File"),
344
+ gettext("Size (px)"),
345
+ gettext("Added"),
346
+ "",
347
+ ],
348
+ template: (
349
+ options: {
350
+ classes: Record<string, string>;
351
+ scrollY: string;
352
+ paging?: boolean;
353
+ },
354
+ _dom,
355
+ ) =>
356
+ `<div class='${options.classes.container}'${options.scrollY.length ? ` style='height: ${options.scrollY}; overflow-Y: auto;'` : ""}></div>
365
357
  <div class='${options.classes.bottom}'>
366
358
  ${
367
- options.paging
368
- ? `<div class='${options.classes.info}'></div>`
369
- : ""
359
+ options.paging
360
+ ? `<div class='${options.classes.info}'></div>`
361
+ : ""
370
362
  }
371
363
  <nav class='${options.classes.pagination}'></nav>
372
364
  </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
- }
365
+ rowRender: (row, tr, _index) => {
366
+ const id = row.cells[0].data as number;
367
+ const inputNode: VirtualNode = {
368
+ nodeName: "input",
369
+ attributes: {
370
+ type: "checkbox",
371
+ class: "entry-select fw-check",
372
+ "data-id": String(id),
373
+ id: `doc-img-${id}`,
374
+ },
375
+ };
376
+ if (row.cells[1].data) {
377
+ inputNode.attributes!.checked = "checked";
378
+ }
379
+ const trNode = tr as {
380
+ childNodes: { childNodes: VirtualNode[] }[];
381
+ };
382
+ trNode.childNodes[0].childNodes = [
383
+ inputNode,
384
+ {
385
+ nodeName: "label",
386
+ attributes: {
387
+ for: `doc-img-${id}`,
418
388
  },
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
389
+ },
390
+ ];
391
+ },
392
+ onEnter: (row, _event) => {
393
+ if (this.getSelected().length > 0) {
394
+ return;
455
395
  }
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
396
+ if (!this.table) {
397
+ return;
398
+ }
399
+ const rowIndex = this.table.data.data.findIndex(
400
+ (dataRow) =>
401
+ (dataRow as unknown as DataTableRow).cells[0].data ===
402
+ row.cells[0].data,
403
+ );
404
+ const button = this.table.dom.querySelector(
405
+ `tr[data-index="${rowIndex}"] span.edit-image`,
406
+ );
407
+ if (button) {
408
+ (button as HTMLElement).click();
409
+ }
410
+ },
411
+ onDelete: (row) => {
412
+ const imageId = row.cells[0].data as number;
413
+ this.deleteImageDialog([imageId]);
414
+ },
415
+ });
416
+ this.overviewTable.init();
417
+ this.table = this.overviewTable.table!;
418
+ (this.table as unknown as { id: string }).id = "imagelist";
419
+ this.dtBulk = this.overviewTable.dtBulk || null;
420
+
421
+ this.table.on("datatable.sort", (column, dir) => {
422
+ this.lastSort = { column: column as number, dir: dir as "asc" | "desc" };
423
+ });
424
+
425
+ this.table.dom.focus();
426
+ }
427
+
428
+ // get IDs of selected bib entries
429
+ getSelected(): number[] {
430
+ return Array.from(
431
+ this.dom.querySelectorAll(".entry-select:checked:not(:disabled)"),
432
+ ).map((el) => Number.parseInt(el.getAttribute("data-id") || "0"));
433
+ }
434
+
435
+ bindEvents(): void {
436
+ this.dom.addEventListener("click", (event) => this.handleActivation(event));
437
+ this.dom.addEventListener("keydown", (event) =>
438
+ this.handleActivation(event),
439
+ );
440
+ }
441
+
442
+ handleActivation(event: Event): void {
443
+ if (!isActivationEvent(event)) {
444
+ return;
445
+ }
446
+ const el: { target?: Element | null } = {};
447
+ switch (true) {
448
+ case findTarget(event, ".delete-image", el): {
449
+ const imageId = (el.target as HTMLElement | null)?.dataset.id;
450
+ this.deleteImageDialog([imageId || ""]);
451
+ break;
452
+ }
453
+ case findTarget(event, ".edit-image", el): {
454
+ const imageId = (el.target as HTMLElement | null)?.dataset.id;
455
+ import("../edit_dialog/index.js").then(({ ImageEditDialog }) => {
456
+ const dialog = new ImageEditDialog(
457
+ this.app.imageDB,
458
+ imageId ? Number.parseInt(imageId) : false,
459
+ this as unknown as ImageManagerPage,
460
+ );
461
+ dialog.init().then(() => {
462
+ if (imageId) {
463
+ this.updateTable([Number.parseInt(imageId)]);
480
464
  }
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">
465
+ });
466
+ });
467
+ break;
468
+ }
469
+ case findTarget(event, ".fw-add-input", el): {
470
+ const itemEl = (el.target as HTMLElement | null)?.closest(
471
+ ".fw-list-input",
472
+ ) as HTMLElement;
473
+ if (!itemEl.nextElementSibling) {
474
+ itemEl.insertAdjacentHTML(
475
+ "afterend",
476
+ `<tr class="fw-list-input">
487
477
  <td>
488
478
  <input type="text" class="category-form">
489
479
  <span class="fw-add-input icon-addremove" tabindex="0"></span>
490
480
  </td>
491
- </tr>`
492
- )
493
- } else {
494
- itemEl.parentElement!.removeChild(itemEl)
495
- }
496
- break
497
- }
498
- default:
499
- break
481
+ </tr>`,
482
+ );
483
+ } else {
484
+ itemEl.parentElement!.removeChild(itemEl);
500
485
  }
486
+ break;
487
+ }
488
+ default:
489
+ break;
501
490
  }
491
+ }
502
492
 
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
- }
493
+ close(): void {
494
+ if (this.table) {
495
+ this.table.destroy();
496
+ this.table = null;
497
+ }
498
+ if (this.dtBulk) {
499
+ this.dtBulk.destroy();
500
+ this.dtBulk = null;
501
+ }
502
+ if (this.menu) {
503
+ this.menu.destroy();
504
+ this.menu = null as unknown as OverviewMenuView;
516
505
  }
506
+ }
517
507
  }