@fiduswriter/image-manager 0.1.4 → 0.1.6

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.
Files changed (65) hide show
  1. package/README.md +53 -3
  2. package/dist/copyright_dialog/index.d.ts +10 -13
  3. package/dist/copyright_dialog/index.d.ts.map +1 -1
  4. package/dist/copyright_dialog/index.js +32 -14
  5. package/dist/copyright_dialog/index.js.map +1 -1
  6. package/dist/copyright_dialog/templates.d.ts +16 -12
  7. package/dist/copyright_dialog/templates.d.ts.map +1 -1
  8. package/dist/copyright_dialog/templates.js.map +1 -1
  9. package/dist/database.d.ts +7 -6
  10. package/dist/database.d.ts.map +1 -1
  11. package/dist/database.js +17 -11
  12. package/dist/database.js.map +1 -1
  13. package/dist/edit_dialog/index.d.ts +22 -19
  14. package/dist/edit_dialog/index.d.ts.map +1 -1
  15. package/dist/edit_dialog/index.js +65 -33
  16. package/dist/edit_dialog/index.js.map +1 -1
  17. package/dist/edit_dialog/model.d.ts +2 -19
  18. package/dist/edit_dialog/model.d.ts.map +1 -1
  19. package/dist/edit_dialog/model.js +51 -31
  20. package/dist/edit_dialog/model.js.map +1 -1
  21. package/dist/edit_dialog/templates.d.ts +7 -4
  22. package/dist/edit_dialog/templates.d.ts.map +1 -1
  23. package/dist/edit_dialog/templates.js.map +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/overview/categories.d.ts +7 -5
  27. package/dist/overview/categories.d.ts.map +1 -1
  28. package/dist/overview/categories.js +18 -13
  29. package/dist/overview/categories.js.map +1 -1
  30. package/dist/overview/index.d.ts +31 -27
  31. package/dist/overview/index.d.ts.map +1 -1
  32. package/dist/overview/index.js +64 -33
  33. package/dist/overview/index.js.map +1 -1
  34. package/dist/overview/menu.d.ts +4 -47
  35. package/dist/overview/menu.d.ts.map +1 -1
  36. package/dist/overview/menu.js +13 -8
  37. package/dist/overview/menu.js.map +1 -1
  38. package/dist/overview/templates.d.ts +7 -3
  39. package/dist/overview/templates.d.ts.map +1 -1
  40. package/dist/overview/templates.js.map +1 -1
  41. package/dist/selection_dialog/index.d.ts +17 -15
  42. package/dist/selection_dialog/index.d.ts.map +1 -1
  43. package/dist/selection_dialog/index.js +18 -8
  44. package/dist/selection_dialog/index.js.map +1 -1
  45. package/dist/types.d.ts +118 -0
  46. package/dist/types.d.ts.map +1 -0
  47. package/dist/types.js +2 -0
  48. package/dist/types.js.map +1 -0
  49. package/package.json +94 -78
  50. package/src/copyright_dialog/{index.js → index.ts} +70 -38
  51. package/src/copyright_dialog/{templates.js → templates.ts} +19 -3
  52. package/src/database.ts +83 -0
  53. package/src/edit_dialog/{index.js → index.ts} +110 -45
  54. package/src/edit_dialog/{model.js → model.ts} +95 -46
  55. package/src/edit_dialog/{templates.js → templates.ts} +13 -2
  56. package/src/global.d.ts +37 -0
  57. package/src/overview/{categories.js → categories.ts} +34 -21
  58. package/src/overview/{index.js → index.ts} +157 -75
  59. package/src/overview/menu.ts +85 -0
  60. package/src/overview/{templates.js → templates.ts} +16 -2
  61. package/src/selection_dialog/{index.js → index.ts} +85 -49
  62. package/src/types.ts +133 -0
  63. package/src/database.js +0 -57
  64. package/src/overview/menu.js +0 -75
  65. /package/src/{index.js → index.ts} +0 -0
@@ -1,13 +1,48 @@
1
1
  import {CheckableList, ContentMenu, Dialog, addAlert, gettext} from "fwtoolkit"
2
+ import type {CheckableListOptions} from "fwtoolkit"
3
+ import type {ContentMenuInit} from "fwtoolkit/content_menu"
2
4
  import {E2EEEncryptor} from "fwtoolkit/e2ee/encryptor"
5
+
3
6
  import {imageEditModel} from "./model.js"
4
7
  import {imageEditTemplate} from "./templates.js"
8
+ import type {ImageDB} from "../database.js"
9
+ import type {
10
+ Copyright,
11
+ Image,
12
+ ImageCategory,
13
+ ImageManagerPage,
14
+ SaveImageRequest
15
+ } from "../types.js"
16
+
5
17
  export class ImageEditDialog {
6
- constructor(imageDB, imageId = false, page) {
18
+ imageDB: ImageDB
19
+
20
+ page: ImageManagerPage
21
+
22
+ imageId: number | false
23
+
24
+ dialog: Dialog | false = false
25
+
26
+ copyright: Copyright
27
+
28
+ menu: ContentMenuInit
29
+
30
+ catsList: CheckableList | {value: (string | number)[]} = {value: []}
31
+
32
+ mediaPreviewerDiv?: HTMLElement
33
+
34
+ mediaPreviewer?: HTMLElement
35
+
36
+ rotation = 0
37
+
38
+ cropped = false
39
+
40
+ mediaInput?: File
41
+
42
+ constructor(imageDB: ImageDB, imageId: number | false = false, page: ImageManagerPage) {
7
43
  this.imageDB = imageDB
8
44
  this.page = page
9
45
  this.imageId = imageId
10
- this.dialog = false
11
46
  this.copyright = this.imageId
12
47
  ? this.imageDB.db[this.imageId].copyright
13
48
  : {
@@ -16,17 +51,20 @@ export class ImageEditDialog {
16
51
  freeToRead: true,
17
52
  licenses: []
18
53
  }
19
- this.menu = this.page.menu?.imageEditModel || imageEditModel()
54
+ this.menu =
55
+ (this.page.menu as {imageEditModel?: ContentMenuInit} | undefined)
56
+ ?.imageEditModel || imageEditModel()
20
57
  }
21
58
 
22
59
  //open a dialog for uploading an image
23
- init() {
60
+ init(): Promise<number | void> {
24
61
  if (this.page.app.isOffline()) {
25
62
  this.showOffline()
26
63
  return Promise.resolve()
27
64
  }
28
- const returnPromise = new Promise(resolve => {
29
- this.dialog = new Dialog({
65
+ const returnPromise = new Promise<number | void>(resolve => {
66
+ let dialog: Dialog
67
+ dialog = new Dialog({
30
68
  title: this.imageId
31
69
  ? gettext("Update Image Information")
32
70
  : gettext("Upload Image"),
@@ -47,25 +85,29 @@ export class ImageEditDialog {
47
85
  {
48
86
  type: "cancel",
49
87
  classes: "fw-orange",
50
- click: () => this.dialog.close()
88
+ click: () => dialog.close()
51
89
  }
52
90
  ]
53
91
  })
54
- this.dialog.open()
92
+ this.dialog = dialog
93
+ dialog.open()
55
94
  })
56
95
 
57
- const image = this.imageId ? this.imageDB.db[this.imageId] : false
96
+ const image: Image | false = this.imageId
97
+ ? this.imageDB.db[this.imageId]
98
+ : false
58
99
  const catsEl = document.getElementById("image-edit-categories")
59
100
  if (catsEl) {
60
- this.catsList = new CheckableList({
101
+ const checkableOptions: CheckableListOptions = {
61
102
  dom: catsEl,
62
- options: this.imageDB.cats.map(cat => ({
103
+ options: this.imageDB.cats.map((cat: ImageCategory) => ({
63
104
  id: cat.id,
64
105
  label: cat.category_title
65
106
  })),
66
107
  initialValue: image ? image.cats : [],
67
108
  multiple: true
68
- })
109
+ }
110
+ this.catsList = new CheckableList(checkableOptions)
69
111
  } else {
70
112
  this.catsList = {value: []}
71
113
  }
@@ -74,9 +116,9 @@ export class ImageEditDialog {
74
116
  this.bindMediaUploadEvents()
75
117
  }
76
118
 
77
- document
78
- .querySelector(".figure-edit-menu")
79
- .addEventListener("click", event => {
119
+ const figureEditMenu = document.querySelector(".figure-edit-menu")
120
+ if (figureEditMenu) {
121
+ figureEditMenu.addEventListener("click", event => {
80
122
  event.preventDefault()
81
123
  event.stopImmediatePropagation()
82
124
 
@@ -84,75 +126,95 @@ export class ImageEditDialog {
84
126
  menu: this.menu,
85
127
  width: 220,
86
128
  page: this,
87
- menuPos: {X: event.pageX - 50, Y: event.pageY + 50}
129
+ menuPos: {X: (event as MouseEvent).pageX - 50, Y: (event as MouseEvent).pageY + 50}
88
130
  })
89
131
  contentMenu.open()
90
132
  })
133
+ }
91
134
 
92
135
  return returnPromise
93
136
  }
94
137
 
95
138
  //add image upload events
96
- bindMediaUploadEvents() {
139
+ bindMediaUploadEvents(): void {
97
140
  const selectButton = document.querySelector(
98
141
  "#editimage .fw-media-select-button"
99
- ),
142
+ ) as HTMLButtonElement | null,
100
143
  mediaInputSelector = document.querySelector(
101
144
  "#editimage .fw-media-file-input"
102
- )
145
+ ) as HTMLInputElement | null
103
146
  this.mediaPreviewerDiv = document.querySelector(
104
147
  "#editimage .figure-preview > div"
105
- )
148
+ ) as HTMLElement | null || undefined
106
149
  this.rotation = 0
107
150
  this.cropped = false
108
151
 
152
+ if (!selectButton || !mediaInputSelector || !this.mediaPreviewerDiv) {
153
+ return
154
+ }
155
+
109
156
  selectButton.addEventListener("click", () => {
110
157
  mediaInputSelector.click()
111
158
  })
112
159
 
160
+ const dialog = this.dialog
161
+ if (!dialog) {
162
+ return
163
+ }
164
+
113
165
  mediaInputSelector.addEventListener("change", () => {
114
- this.mediaInput = mediaInputSelector.files[0]
166
+ this.mediaInput = mediaInputSelector.files![0]
115
167
  const fr = new window.FileReader()
116
168
  fr.onload = () => {
117
- this.mediaPreviewerDiv.innerHTML = `<div class="img" style="background-image: url(${fr.result});" />`
169
+ this.mediaPreviewerDiv!.innerHTML = `<div class="img" style="background-image: url(${fr.result});" />`
118
170
  this.mediaPreviewer =
119
- this.mediaPreviewerDiv.querySelector(".img")
120
- this.mediaPreviewerDiv.classList.remove("crop-mode")
121
- this.dialog.centerDialog()
171
+ this.mediaPreviewerDiv!.querySelector(".img") || undefined
172
+ this.mediaPreviewerDiv!.classList.remove("crop-mode")
173
+ dialog.centerDialog()
122
174
  }
123
175
  fr.readAsDataURL(this.mediaInput)
124
176
  })
125
177
  }
126
178
 
127
- displayCreateImageError(errors) {
179
+ displayCreateImageError(errors: Record<string, string>): void {
128
180
  Object.keys(errors).forEach(eKey => {
129
181
  const eMsg = `<div class="fw-warning">${errors[eKey]}</div>`
130
182
  if ("error" == eKey) {
131
183
  document
132
- .getElementById("editimage")
184
+ .getElementById("editimage")!
133
185
  .insertAdjacentHTML("afterbegin", eMsg)
134
186
  } else {
135
- document
136
- .getElementById(`id_${eKey}`)
137
- .insertAdjacentHTML("afterend", eMsg)
187
+ const fieldEl = document.getElementById(`id_${eKey}`)
188
+ if (fieldEl) {
189
+ fieldEl.insertAdjacentHTML("afterend", eMsg)
190
+ }
138
191
  }
139
192
  })
140
193
  }
141
194
 
142
- async saveImage() {
143
- const imageData = {
144
- title: document.querySelector("#editimage .fw-media-title").value,
195
+ async saveImage(): Promise<number | void> {
196
+ const titleInput = document.querySelector(
197
+ "#editimage .fw-media-title"
198
+ ) as HTMLInputElement | null
199
+ const imageData: SaveImageRequest = {
200
+ title: titleInput ? titleInput.value : "",
145
201
  copyright: this.copyright,
146
202
  cats: this.catsList.value
147
203
  }
148
204
  if (this.imageId) {
149
205
  imageData.id = this.imageId
150
- } else if (!this.rotation && !this.cropped) {
206
+ } else if (!this.rotation && !this.cropped && this.mediaInput) {
151
207
  imageData.image = this.mediaInput
152
- } else {
208
+ } else if (this.mediaPreviewer && this.mediaInput) {
209
+ const mediaPreviewer = this.mediaPreviewer as HTMLElement & {
210
+ currentStyle?: CSSStyleDeclaration
211
+ }
153
212
  const mediaPreviewerStyle =
154
- this.mediaPreviewer.currentStyle ||
155
- window.getComputedStyle(this.mediaPreviewer, false)
213
+ mediaPreviewer.currentStyle ||
214
+ window.getComputedStyle(
215
+ mediaPreviewer,
216
+ false as unknown as string | null
217
+ )
156
218
  const base64data = mediaPreviewerStyle.backgroundImage
157
219
  .slice(4, -1)
158
220
  .replace(/"/g, "")
@@ -171,25 +233,28 @@ export class ImageEditDialog {
171
233
  const isE2EE = this.page.e2ee?.encrypted === true
172
234
  if (isE2EE && imageData.image) {
173
235
  imageData.image = await E2EEEncryptor.encryptImage(
174
- imageData.image,
175
- this.page.e2ee.key
236
+ imageData.image as File | Blob,
237
+ this.page.e2ee!.key
176
238
  )
177
239
  imageData.original_file_type = this.mediaInput?.type || "image/png"
178
240
  // Encrypt copyright metadata so the server cannot read it
179
241
  imageData.copyright = await E2EEEncryptor.encryptObject(
180
242
  imageData.copyright,
181
- this.page.e2ee.key
243
+ this.page.e2ee!.key
182
244
  )
183
245
  }
184
246
 
185
247
  // Remove old warning messages
186
248
  document
187
249
  .querySelectorAll("#editimage .fw-warning")
188
- .forEach(el => el.parentElement.removeChild(el))
189
- return new Promise(resolve => {
250
+ .forEach(el => el.parentElement!.removeChild(el))
251
+ return new Promise<number | void>(resolve => {
252
+ const dialog = this.dialog
190
253
  this.imageDB.saveImage(imageData).then(
191
254
  imageId => {
192
- this.dialog.close()
255
+ if (dialog) {
256
+ dialog.close()
257
+ }
193
258
  addAlert("success", gettext("The image has been updated."))
194
259
  this.imageId = imageId
195
260
  resolve(imageId)
@@ -199,7 +264,7 @@ export class ImageEditDialog {
199
264
  this.showOffline()
200
265
  return
201
266
  }
202
- this.displayCreateImageError(errors)
267
+ this.displayCreateImageError(errors as Record<string, string>)
203
268
  addAlert(
204
269
  "error",
205
270
  gettext(
@@ -211,7 +276,7 @@ export class ImageEditDialog {
211
276
  })
212
277
  }
213
278
 
214
- showOffline() {
279
+ showOffline(): void {
215
280
  addAlert(
216
281
  "info",
217
282
  gettext(
@@ -1,40 +1,56 @@
1
1
  import Cropper from "cropperjs"
2
2
 
3
3
  import {gettext} from "fwtoolkit"
4
- import {CopyrightDialog} from "@fiduswriter/image-manager/copyright_dialog/index"
4
+ import type {ContentMenuInit} from "fwtoolkit/content_menu"
5
+ import type {Dialog as FwDialog} from "fwtoolkit/dialog"
5
6
 
6
- let mediaPreviewerImg = false
7
+ import {CopyrightDialog} from "../copyright_dialog/index.js"
8
+ import type {ImageEditDialog} from "./index.js"
7
9
 
8
- export const imageEditModel = () => ({
10
+ let mediaPreviewerImg: HTMLImageElement | false = false
11
+
12
+ export const imageEditModel = (): ContentMenuInit => ({
9
13
  content: [
10
14
  {
11
15
  title: gettext("Rotate Left"),
12
16
  type: "action",
13
17
  tooltip: gettext("Rotate-left"),
14
18
  order: 0,
15
- action: dialog => {
19
+ action: (dialog: unknown) => {
20
+ const d = dialog as ImageEditDialog
21
+ const mediaPreviewer = d.mediaPreviewer as
22
+ | (HTMLElement & {
23
+ currentStyle?: CSSStyleDeclaration
24
+ })
25
+ | undefined
26
+ if (!mediaPreviewer) {
27
+ return
28
+ }
16
29
  const mediaPreviewerStyle =
17
- dialog.mediaPreviewer.currentStyle ||
18
- window.getComputedStyle(dialog.mediaPreviewer, false)
30
+ mediaPreviewer.currentStyle ||
31
+ window.getComputedStyle(
32
+ mediaPreviewer,
33
+ false as unknown as string | null
34
+ )
19
35
  rotateBase64Image(
20
36
  mediaPreviewerStyle.backgroundImage
21
37
  .slice(4, -1)
22
38
  .replace(/"/g, ""),
23
- dialog.mediaInput.type,
39
+ d.mediaInput!.type,
24
40
  "left"
25
41
  ).then(response =>
26
- dialog.mediaPreviewer.setAttribute(
42
+ d.mediaPreviewer!.setAttribute(
27
43
  "style",
28
44
  `background-image: url(${response});`
29
45
  )
30
46
  )
31
- if (dialog.rotation === 0) {
32
- dialog.rotation = 270
47
+ if (d.rotation === 0) {
48
+ d.rotation = 270
33
49
  } else {
34
- dialog.rotation -= 90
50
+ d.rotation -= 90
35
51
  }
36
52
  },
37
- disabled: dialog => dialog.imageId,
53
+ disabled: (dialog: unknown) => !!(dialog as ImageEditDialog).imageId,
38
54
  icon: "redo fa-rotate-180"
39
55
  },
40
56
  {
@@ -42,29 +58,41 @@ export const imageEditModel = () => ({
42
58
  type: "action",
43
59
  tooltip: gettext("Rotate-right"),
44
60
  order: 1,
45
- action: dialog => {
61
+ action: (dialog: unknown) => {
62
+ const d = dialog as ImageEditDialog
63
+ const mediaPreviewer = d.mediaPreviewer as
64
+ | (HTMLElement & {
65
+ currentStyle?: CSSStyleDeclaration
66
+ })
67
+ | undefined
68
+ if (!mediaPreviewer) {
69
+ return
70
+ }
46
71
  const mediaPreviewerStyle =
47
- dialog.mediaPreviewer.currentStyle ||
48
- window.getComputedStyle(dialog.mediaPreviewer, false)
72
+ mediaPreviewer.currentStyle ||
73
+ window.getComputedStyle(
74
+ mediaPreviewer,
75
+ false as unknown as string | null
76
+ )
49
77
  rotateBase64Image(
50
78
  mediaPreviewerStyle.backgroundImage
51
79
  .slice(4, -1)
52
80
  .replace(/"/g, ""),
53
- dialog.mediaInput.type,
81
+ d.mediaInput!.type,
54
82
  "right"
55
83
  ).then(response =>
56
- dialog.mediaPreviewer.setAttribute(
84
+ d.mediaPreviewer!.setAttribute(
57
85
  "style",
58
86
  `background-image: url(${response});`
59
87
  )
60
88
  )
61
- if (dialog.rotation === 270) {
62
- dialog.rotation = 0
89
+ if (d.rotation === 270) {
90
+ d.rotation = 0
63
91
  } else {
64
- dialog.rotation += 90
92
+ d.rotation += 90
65
93
  }
66
94
  },
67
- disabled: dialog => dialog.imageId,
95
+ disabled: (dialog: unknown) => !!(dialog as ImageEditDialog).imageId,
68
96
  icon: "undo"
69
97
  },
70
98
  {
@@ -72,27 +100,39 @@ export const imageEditModel = () => ({
72
100
  type: "action",
73
101
  tooltip: gettext("Crop image"),
74
102
  order: 2,
75
- action: dialog => {
103
+ action: (dialog: unknown) => {
104
+ const d = dialog as ImageEditDialog
105
+ const mediaPreviewer = d.mediaPreviewer as
106
+ | (HTMLElement & {
107
+ currentStyle?: CSSStyleDeclaration
108
+ })
109
+ | undefined
110
+ if (!mediaPreviewer) {
111
+ return
112
+ }
76
113
  const mediaPreviewerStyle =
77
- dialog.mediaPreviewer.currentStyle ||
78
- window.getComputedStyle(dialog.mediaPreviewer, false)
114
+ mediaPreviewer.currentStyle ||
115
+ window.getComputedStyle(
116
+ mediaPreviewer,
117
+ false as unknown as string | null
118
+ )
79
119
  //const base64data = mediaPreviewerStyle.backgroundImage.slice(4, -1).replace(/"/g, "")
80
120
  mediaPreviewerImg = document.createElement("img")
81
121
  //img.src = `url(${base64data})`
82
122
  mediaPreviewerImg.src = mediaPreviewerStyle.backgroundImage
83
123
  .slice(4, -1)
84
124
  .replace(/"/g, "")
85
- dialog.mediaPreviewer.parentElement.replaceChild(
125
+ d.mediaPreviewer!.parentElement!.replaceChild(
86
126
  mediaPreviewerImg,
87
- dialog.mediaPreviewer
127
+ d.mediaPreviewer!
88
128
  )
89
129
  const cropper = new Cropper(mediaPreviewerImg, {
90
130
  viewMode: 1,
91
131
  responsive: true
92
132
  })
93
- toggleCropMode(true, dialog, cropper)
133
+ toggleCropMode(true, d, cropper)
94
134
  },
95
- disabled: dialog => dialog.imageId,
135
+ disabled: (dialog: unknown) => !!(dialog as ImageEditDialog).imageId,
96
136
  icon: "crop"
97
137
  },
98
138
  {
@@ -100,11 +140,12 @@ export const imageEditModel = () => ({
100
140
  type: "action",
101
141
  tooltip: gettext("Specify copyright information"),
102
142
  order: 3,
103
- action: dialog => {
104
- const crDialog = new CopyrightDialog(dialog.copyright)
143
+ action: (dialog: unknown) => {
144
+ const d = dialog as ImageEditDialog
145
+ const crDialog = new CopyrightDialog(d.copyright)
105
146
  crDialog.init().then(copyright => {
106
147
  if (copyright) {
107
- dialog.copyright = copyright
148
+ d.copyright = copyright
108
149
  }
109
150
  })
110
151
  }
@@ -112,21 +153,25 @@ export const imageEditModel = () => ({
112
153
  ]
113
154
  })
114
155
 
115
- let oldButtons = false
156
+ let oldButtons: FwDialog["buttons"] | false = false
116
157
 
117
- const toggleCropMode = (val, dialog, cropper) => {
158
+ const toggleCropMode = (val: boolean, dialog: ImageEditDialog, cropper: Cropper) => {
159
+ if (!dialog.dialog) {
160
+ return
161
+ }
162
+ const dialogEl = dialog.dialog
118
163
  if (val && !oldButtons) {
119
- dialog.mediaPreviewerDiv.classList.add("crop-mode")
120
- oldButtons = dialog.dialog.buttons
121
- dialog.dialog.setButtons([
164
+ dialog.mediaPreviewerDiv!.classList.add("crop-mode")
165
+ oldButtons = dialogEl.buttons
166
+ dialogEl.setButtons([
122
167
  {
123
168
  text: gettext("Crop"),
124
169
  click: () => {
125
- dialog.mediaPreviewer.setAttribute(
170
+ dialog.mediaPreviewer!.setAttribute(
126
171
  "style",
127
172
  `background-image: url(${cropper
128
173
  .getCroppedCanvas()
129
- .toDataURL(dialog.mediaInput.type)});`
174
+ .toDataURL(dialog.mediaInput!.type)});`
130
175
  )
131
176
  dialog.cropped = true
132
177
  cropper.destroy()
@@ -144,27 +189,31 @@ const toggleCropMode = (val, dialog, cropper) => {
144
189
  }
145
190
  ])
146
191
  } else {
147
- dialog.mediaPreviewerDiv.classList.remove("crop-mode")
192
+ dialog.mediaPreviewerDiv!.classList.remove("crop-mode")
148
193
  if (mediaPreviewerImg) {
149
- mediaPreviewerImg.parentElement.replaceChild(
150
- dialog.mediaPreviewer,
194
+ mediaPreviewerImg.parentElement!.replaceChild(
195
+ dialog.mediaPreviewer!,
151
196
  mediaPreviewerImg
152
197
  )
153
198
  mediaPreviewerImg = false
154
199
  }
155
200
  if (oldButtons) {
156
- dialog.dialog.buttons = oldButtons
201
+ dialogEl.buttons = oldButtons
157
202
  oldButtons = false
158
203
  }
159
204
  }
160
- dialog.dialog.refreshButtons()
161
- dialog.dialog.centerDialog()
205
+ dialogEl.refreshButtons()
206
+ dialogEl.centerDialog()
162
207
  }
163
208
 
164
- const rotateBase64Image = (base64data, type, direction) => {
209
+ const rotateBase64Image = (
210
+ base64data: string,
211
+ type: string,
212
+ direction: "left" | "right"
213
+ ): Promise<string> => {
165
214
  return new Promise(resolve => {
166
215
  const canvas = document.createElement("canvas")
167
- const ctx = canvas.getContext("2d")
216
+ const ctx = canvas.getContext("2d")!
168
217
  const image = new Image()
169
218
  image.src = base64data
170
219
  image.onload = () => {
@@ -1,7 +1,18 @@
1
1
  import {escapeText, gettext} from "fwtoolkit"
2
2
 
3
+ import type {Image, ImageCategory} from "../types.js"
4
+
5
+ interface ImageEditCategoryTemplateData {
6
+ cats: ImageCategory[]
7
+ }
8
+
9
+ interface ImageEditTemplateData {
10
+ image: Image | false
11
+ cats: ImageCategory[]
12
+ }
13
+
3
14
  /* A template for the image category selection of the image selection dialog. */
4
- const imageEditCategoryTemplate = ({cats}) => {
15
+ const imageEditCategoryTemplate = ({cats}: ImageEditCategoryTemplateData) => {
5
16
  if (!cats.length) {
6
17
  return ""
7
18
  }
@@ -12,7 +23,7 @@ const imageEditCategoryTemplate = ({cats}) => {
12
23
  }
13
24
 
14
25
  /* A template for the form for the image upload dialog. */
15
- export const imageEditTemplate = ({image, cats}) =>
26
+ export const imageEditTemplate = ({image, cats}: ImageEditTemplateData) =>
16
27
  `<div>
17
28
  <input name="title" class="fw-media-title" type="text"
18
29
  placeholder="${gettext("Insert a title")}" value="${
@@ -0,0 +1,37 @@
1
+ // Ambient declarations for dependencies without bundled types.
2
+
3
+ declare module "@fiduswriter/common/common" {
4
+ export function baseBodyTemplate(...args: unknown[]): string
5
+ }
6
+
7
+ declare module "@fiduswriter/common/feedback" {
8
+ export class FeedbackTab {
9
+ init(): void
10
+ }
11
+ }
12
+
13
+ declare module "@fiduswriter/common/menu" {
14
+ export class SiteMenu {
15
+ constructor(app: unknown, section: string)
16
+ init(): void
17
+ }
18
+ }
19
+
20
+ // Globals provided by the Fidus Writer host page.
21
+
22
+ declare function gettext(msgid: string): string
23
+
24
+ declare function interpolate(
25
+ fmt: string,
26
+ args: unknown[],
27
+ named?: boolean
28
+ ): string
29
+
30
+ declare function staticUrl(path: string): string
31
+
32
+ declare const settings: Record<string, unknown>
33
+
34
+ interface Window {
35
+ settings?: Record<string, unknown>
36
+ csrfToken?: string
37
+ }