@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.
- package/README.md +53 -3
- package/dist/copyright_dialog/index.d.ts +10 -13
- package/dist/copyright_dialog/index.d.ts.map +1 -1
- package/dist/copyright_dialog/index.js +32 -14
- package/dist/copyright_dialog/index.js.map +1 -1
- package/dist/copyright_dialog/templates.d.ts +16 -12
- package/dist/copyright_dialog/templates.d.ts.map +1 -1
- package/dist/copyright_dialog/templates.js.map +1 -1
- package/dist/database.d.ts +7 -6
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +17 -11
- package/dist/database.js.map +1 -1
- package/dist/edit_dialog/index.d.ts +22 -19
- package/dist/edit_dialog/index.d.ts.map +1 -1
- package/dist/edit_dialog/index.js +65 -33
- package/dist/edit_dialog/index.js.map +1 -1
- package/dist/edit_dialog/model.d.ts +2 -19
- package/dist/edit_dialog/model.d.ts.map +1 -1
- package/dist/edit_dialog/model.js +51 -31
- package/dist/edit_dialog/model.js.map +1 -1
- package/dist/edit_dialog/templates.d.ts +7 -4
- package/dist/edit_dialog/templates.d.ts.map +1 -1
- package/dist/edit_dialog/templates.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/overview/categories.d.ts +7 -5
- package/dist/overview/categories.d.ts.map +1 -1
- package/dist/overview/categories.js +18 -13
- package/dist/overview/categories.js.map +1 -1
- package/dist/overview/index.d.ts +31 -27
- package/dist/overview/index.d.ts.map +1 -1
- package/dist/overview/index.js +64 -33
- package/dist/overview/index.js.map +1 -1
- package/dist/overview/menu.d.ts +4 -47
- package/dist/overview/menu.d.ts.map +1 -1
- package/dist/overview/menu.js +13 -8
- package/dist/overview/menu.js.map +1 -1
- package/dist/overview/templates.d.ts +7 -3
- package/dist/overview/templates.d.ts.map +1 -1
- package/dist/overview/templates.js.map +1 -1
- package/dist/selection_dialog/index.d.ts +17 -15
- package/dist/selection_dialog/index.d.ts.map +1 -1
- package/dist/selection_dialog/index.js +18 -8
- package/dist/selection_dialog/index.js.map +1 -1
- package/dist/types.d.ts +118 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +94 -78
- package/src/copyright_dialog/{index.js → index.ts} +70 -38
- package/src/copyright_dialog/{templates.js → templates.ts} +19 -3
- package/src/database.ts +83 -0
- package/src/edit_dialog/{index.js → index.ts} +110 -45
- package/src/edit_dialog/{model.js → model.ts} +95 -46
- package/src/edit_dialog/{templates.js → templates.ts} +13 -2
- package/src/global.d.ts +37 -0
- package/src/overview/{categories.js → categories.ts} +34 -21
- package/src/overview/{index.js → index.ts} +157 -75
- package/src/overview/menu.ts +85 -0
- package/src/overview/{templates.js → templates.ts} +16 -2
- package/src/selection_dialog/{index.js → index.ts} +85 -49
- package/src/types.ts +133 -0
- package/src/database.js +0 -57
- package/src/overview/menu.js +0 -75
- /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
|
-
|
|
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 =
|
|
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
|
-
|
|
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: () =>
|
|
88
|
+
click: () => dialog.close()
|
|
51
89
|
}
|
|
52
90
|
]
|
|
53
91
|
})
|
|
54
|
-
this.dialog
|
|
92
|
+
this.dialog = dialog
|
|
93
|
+
dialog.open()
|
|
55
94
|
})
|
|
56
95
|
|
|
57
|
-
const image
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
169
|
+
this.mediaPreviewerDiv!.innerHTML = `<div class="img" style="background-image: url(${fr.result});" />`
|
|
118
170
|
this.mediaPreviewer =
|
|
119
|
-
this.mediaPreviewerDiv
|
|
120
|
-
this.mediaPreviewerDiv
|
|
121
|
-
|
|
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
|
-
|
|
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
|
|
144
|
-
|
|
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
|
-
|
|
155
|
-
window.getComputedStyle(
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 {
|
|
4
|
+
import type {ContentMenuInit} from "fwtoolkit/content_menu"
|
|
5
|
+
import type {Dialog as FwDialog} from "fwtoolkit/dialog"
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
import {CopyrightDialog} from "../copyright_dialog/index.js"
|
|
8
|
+
import type {ImageEditDialog} from "./index.js"
|
|
7
9
|
|
|
8
|
-
|
|
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
|
-
|
|
18
|
-
window.getComputedStyle(
|
|
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
|
-
|
|
39
|
+
d.mediaInput!.type,
|
|
24
40
|
"left"
|
|
25
41
|
).then(response =>
|
|
26
|
-
|
|
42
|
+
d.mediaPreviewer!.setAttribute(
|
|
27
43
|
"style",
|
|
28
44
|
`background-image: url(${response});`
|
|
29
45
|
)
|
|
30
46
|
)
|
|
31
|
-
if (
|
|
32
|
-
|
|
47
|
+
if (d.rotation === 0) {
|
|
48
|
+
d.rotation = 270
|
|
33
49
|
} else {
|
|
34
|
-
|
|
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
|
-
|
|
48
|
-
window.getComputedStyle(
|
|
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
|
-
|
|
81
|
+
d.mediaInput!.type,
|
|
54
82
|
"right"
|
|
55
83
|
).then(response =>
|
|
56
|
-
|
|
84
|
+
d.mediaPreviewer!.setAttribute(
|
|
57
85
|
"style",
|
|
58
86
|
`background-image: url(${response});`
|
|
59
87
|
)
|
|
60
88
|
)
|
|
61
|
-
if (
|
|
62
|
-
|
|
89
|
+
if (d.rotation === 270) {
|
|
90
|
+
d.rotation = 0
|
|
63
91
|
} else {
|
|
64
|
-
|
|
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
|
-
|
|
78
|
-
window.getComputedStyle(
|
|
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
|
-
|
|
125
|
+
d.mediaPreviewer!.parentElement!.replaceChild(
|
|
86
126
|
mediaPreviewerImg,
|
|
87
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
120
|
-
oldButtons =
|
|
121
|
-
|
|
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
|
|
170
|
+
dialog.mediaPreviewer!.setAttribute(
|
|
126
171
|
"style",
|
|
127
172
|
`background-image: url(${cropper
|
|
128
173
|
.getCroppedCanvas()
|
|
129
|
-
.toDataURL(dialog.mediaInput
|
|
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
|
|
192
|
+
dialog.mediaPreviewerDiv!.classList.remove("crop-mode")
|
|
148
193
|
if (mediaPreviewerImg) {
|
|
149
|
-
mediaPreviewerImg.parentElement
|
|
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
|
-
|
|
201
|
+
dialogEl.buttons = oldButtons
|
|
157
202
|
oldButtons = false
|
|
158
203
|
}
|
|
159
204
|
}
|
|
160
|
-
|
|
161
|
-
|
|
205
|
+
dialogEl.refreshButtons()
|
|
206
|
+
dialogEl.centerDialog()
|
|
162
207
|
}
|
|
163
208
|
|
|
164
|
-
const rotateBase64Image = (
|
|
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="${
|
package/src/global.d.ts
ADDED
|
@@ -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
|
+
}
|