@fiduswriter/image-manager 0.1.0
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/LICENSE +661 -0
- package/README.md +5 -0
- package/dist/copyright_dialog/index.d.ts +17 -0
- package/dist/copyright_dialog/index.d.ts.map +1 -0
- package/dist/copyright_dialog/index.js +148 -0
- package/dist/copyright_dialog/index.js.map +1 -0
- package/dist/copyright_dialog/templates.d.ts +13 -0
- package/dist/copyright_dialog/templates.d.ts.map +1 -0
- package/dist/copyright_dialog/templates.js +48 -0
- package/dist/copyright_dialog/templates.js.map +1 -0
- package/dist/database.d.ts +9 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +51 -0
- package/dist/database.js.map +1 -0
- package/dist/edit_dialog/index.d.ts +24 -0
- package/dist/edit_dialog/index.d.ts.map +1 -0
- package/dist/edit_dialog/index.js +186 -0
- package/dist/edit_dialog/index.js.map +1 -0
- package/dist/edit_dialog/model.d.ts +20 -0
- package/dist/edit_dialog/model.d.ts.map +1 -0
- package/dist/edit_dialog/model.js +153 -0
- package/dist/edit_dialog/model.js.map +1 -0
- package/dist/edit_dialog/templates.d.ts +5 -0
- package/dist/edit_dialog/templates.d.ts.map +1 -0
- package/dist/edit_dialog/templates.js +33 -0
- package/dist/edit_dialog/templates.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/overview/categories.d.ts +8 -0
- package/dist/overview/categories.d.ts.map +1 -0
- package/dist/overview/categories.js +94 -0
- package/dist/overview/categories.js.map +1 -0
- package/dist/overview/index.d.ts +39 -0
- package/dist/overview/index.d.ts.map +1 -0
- package/dist/overview/index.js +358 -0
- package/dist/overview/index.js.map +1 -0
- package/dist/overview/menu.d.ts +48 -0
- package/dist/overview/menu.d.ts.map +1 -0
- package/dist/overview/menu.js +67 -0
- package/dist/overview/menu.js.map +1 -0
- package/dist/overview/templates.d.ts +4 -0
- package/dist/overview/templates.d.ts.map +1 -0
- package/dist/overview/templates.js +24 -0
- package/dist/overview/templates.js.map +1 -0
- package/dist/selection_dialog/index.d.ts +19 -0
- package/dist/selection_dialog/index.d.ts.map +1 -0
- package/dist/selection_dialog/index.js +149 -0
- package/dist/selection_dialog/index.js.map +1 -0
- package/package.json +53 -0
- package/src/copyright_dialog/index.js +179 -0
- package/src/copyright_dialog/templates.js +56 -0
- package/src/database.js +57 -0
- package/src/edit_dialog/index.js +222 -0
- package/src/edit_dialog/model.js +184 -0
- package/src/edit_dialog/templates.js +41 -0
- package/src/index.js +4 -0
- package/src/overview/categories.js +115 -0
- package/src/overview/index.js +435 -0
- package/src/overview/menu.js +75 -0
- package/src/overview/templates.js +30 -0
- package/src/selection_dialog/index.js +176 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import {CheckableList, ContentMenu, Dialog, addAlert, gettext} from "fwtoolkit"
|
|
2
|
+
import {E2EEEncryptor} from "fwtoolkit/e2ee/encryptor"
|
|
3
|
+
import {imageEditModel} from "./model"
|
|
4
|
+
import {imageEditTemplate} from "./templates"
|
|
5
|
+
export class ImageEditDialog {
|
|
6
|
+
constructor(imageDB, imageId = false, page) {
|
|
7
|
+
this.imageDB = imageDB
|
|
8
|
+
this.page = page
|
|
9
|
+
this.imageId = imageId
|
|
10
|
+
this.dialog = false
|
|
11
|
+
this.copyright = this.imageId
|
|
12
|
+
? this.imageDB.db[this.imageId].copyright
|
|
13
|
+
: {
|
|
14
|
+
holder: false,
|
|
15
|
+
year: false,
|
|
16
|
+
freeToRead: true,
|
|
17
|
+
licenses: []
|
|
18
|
+
}
|
|
19
|
+
this.menu = this.page.menu?.imageEditModel || imageEditModel()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
//open a dialog for uploading an image
|
|
23
|
+
init() {
|
|
24
|
+
if (this.page.app.isOffline()) {
|
|
25
|
+
this.showOffline()
|
|
26
|
+
return Promise.resolve()
|
|
27
|
+
}
|
|
28
|
+
const returnPromise = new Promise(resolve => {
|
|
29
|
+
this.dialog = new Dialog({
|
|
30
|
+
title: this.imageId
|
|
31
|
+
? gettext("Update Image Information")
|
|
32
|
+
: gettext("Upload Image"),
|
|
33
|
+
id: "editimage",
|
|
34
|
+
classes: "fw-media-uploader",
|
|
35
|
+
body: imageEditTemplate({
|
|
36
|
+
image: this.imageId ? this.imageDB.db[this.imageId] : false,
|
|
37
|
+
cats: this.imageDB.cats
|
|
38
|
+
}),
|
|
39
|
+
buttons: [
|
|
40
|
+
{
|
|
41
|
+
text: this.imageId
|
|
42
|
+
? gettext("Update")
|
|
43
|
+
: gettext("Upload"),
|
|
44
|
+
click: () => resolve(this.saveImage()),
|
|
45
|
+
classes: "fw-dark"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
type: "cancel",
|
|
49
|
+
classes: "fw-orange",
|
|
50
|
+
click: () => this.dialog.close()
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
})
|
|
54
|
+
this.dialog.open()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const image = this.imageId ? this.imageDB.db[this.imageId] : false
|
|
58
|
+
const catsEl = document.getElementById("image-edit-categories")
|
|
59
|
+
if (catsEl) {
|
|
60
|
+
this.catsList = new CheckableList({
|
|
61
|
+
dom: catsEl,
|
|
62
|
+
options: this.imageDB.cats.map(cat => ({
|
|
63
|
+
id: cat.id,
|
|
64
|
+
label: cat.category_title
|
|
65
|
+
})),
|
|
66
|
+
initialValue: image ? image.cats : [],
|
|
67
|
+
multiple: true
|
|
68
|
+
})
|
|
69
|
+
} else {
|
|
70
|
+
this.catsList = {value: []}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!this.imageId) {
|
|
74
|
+
this.bindMediaUploadEvents()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
document
|
|
78
|
+
.querySelector(".figure-edit-menu")
|
|
79
|
+
.addEventListener("click", event => {
|
|
80
|
+
event.preventDefault()
|
|
81
|
+
event.stopImmediatePropagation()
|
|
82
|
+
|
|
83
|
+
const contentMenu = new ContentMenu({
|
|
84
|
+
menu: this.menu,
|
|
85
|
+
width: 220,
|
|
86
|
+
page: this,
|
|
87
|
+
menuPos: {X: event.pageX - 50, Y: event.pageY + 50}
|
|
88
|
+
})
|
|
89
|
+
contentMenu.open()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
return returnPromise
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
//add image upload events
|
|
96
|
+
bindMediaUploadEvents() {
|
|
97
|
+
const selectButton = document.querySelector(
|
|
98
|
+
"#editimage .fw-media-select-button"
|
|
99
|
+
),
|
|
100
|
+
mediaInputSelector = document.querySelector(
|
|
101
|
+
"#editimage .fw-media-file-input"
|
|
102
|
+
)
|
|
103
|
+
this.mediaPreviewerDiv = document.querySelector(
|
|
104
|
+
"#editimage .figure-preview > div"
|
|
105
|
+
)
|
|
106
|
+
this.rotation = 0
|
|
107
|
+
this.cropped = false
|
|
108
|
+
|
|
109
|
+
selectButton.addEventListener("click", () => {
|
|
110
|
+
mediaInputSelector.click()
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
mediaInputSelector.addEventListener("change", () => {
|
|
114
|
+
this.mediaInput = mediaInputSelector.files[0]
|
|
115
|
+
const fr = new window.FileReader()
|
|
116
|
+
fr.onload = () => {
|
|
117
|
+
this.mediaPreviewerDiv.innerHTML = `<div class="img" style="background-image: url(${fr.result});" />`
|
|
118
|
+
this.mediaPreviewer =
|
|
119
|
+
this.mediaPreviewerDiv.querySelector(".img")
|
|
120
|
+
this.mediaPreviewerDiv.classList.remove("crop-mode")
|
|
121
|
+
this.dialog.centerDialog()
|
|
122
|
+
}
|
|
123
|
+
fr.readAsDataURL(this.mediaInput)
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
displayCreateImageError(errors) {
|
|
128
|
+
Object.keys(errors).forEach(eKey => {
|
|
129
|
+
const eMsg = `<div class="fw-warning">${errors[eKey]}</div>`
|
|
130
|
+
if ("error" == eKey) {
|
|
131
|
+
document
|
|
132
|
+
.getElementById("editimage")
|
|
133
|
+
.insertAdjacentHTML("afterbegin", eMsg)
|
|
134
|
+
} else {
|
|
135
|
+
document
|
|
136
|
+
.getElementById(`id_${eKey}`)
|
|
137
|
+
.insertAdjacentHTML("afterend", eMsg)
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async saveImage() {
|
|
143
|
+
const imageData = {
|
|
144
|
+
title: document.querySelector("#editimage .fw-media-title").value,
|
|
145
|
+
copyright: this.copyright,
|
|
146
|
+
cats: this.catsList.value
|
|
147
|
+
}
|
|
148
|
+
if (this.imageId) {
|
|
149
|
+
imageData.id = this.imageId
|
|
150
|
+
} else if (!this.rotation && !this.cropped) {
|
|
151
|
+
imageData.image = this.mediaInput
|
|
152
|
+
} else {
|
|
153
|
+
const mediaPreviewerStyle =
|
|
154
|
+
this.mediaPreviewer.currentStyle ||
|
|
155
|
+
window.getComputedStyle(this.mediaPreviewer, false)
|
|
156
|
+
const base64data = mediaPreviewerStyle.backgroundImage
|
|
157
|
+
.slice(4, -1)
|
|
158
|
+
.replace(/"/g, "")
|
|
159
|
+
const bstr = atob(base64data.split(",")[1])
|
|
160
|
+
let n = bstr.length
|
|
161
|
+
const u8arr = new Uint8Array(n)
|
|
162
|
+
while (n--) {
|
|
163
|
+
u8arr[n] = bstr.charCodeAt(n)
|
|
164
|
+
}
|
|
165
|
+
imageData.image = new File([u8arr], this.mediaInput.name, {
|
|
166
|
+
type: this.mediaInput.type
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// For E2EE documents, encrypt the image and copyright before uploading
|
|
171
|
+
const isE2EE = this.page.e2ee?.encrypted === true
|
|
172
|
+
if (isE2EE && imageData.image) {
|
|
173
|
+
imageData.image = await E2EEEncryptor.encryptImage(
|
|
174
|
+
imageData.image,
|
|
175
|
+
this.page.e2ee.key
|
|
176
|
+
)
|
|
177
|
+
imageData.original_file_type = this.mediaInput?.type || "image/png"
|
|
178
|
+
// Encrypt copyright metadata so the server cannot read it
|
|
179
|
+
imageData.copyright = await E2EEEncryptor.encryptObject(
|
|
180
|
+
imageData.copyright,
|
|
181
|
+
this.page.e2ee.key
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Remove old warning messages
|
|
186
|
+
document
|
|
187
|
+
.querySelectorAll("#editimage .fw-warning")
|
|
188
|
+
.forEach(el => el.parentElement.removeChild(el))
|
|
189
|
+
return new Promise(resolve => {
|
|
190
|
+
this.imageDB.saveImage(imageData).then(
|
|
191
|
+
imageId => {
|
|
192
|
+
this.dialog.close()
|
|
193
|
+
addAlert("success", gettext("The image has been updated."))
|
|
194
|
+
this.imageId = imageId
|
|
195
|
+
resolve(imageId)
|
|
196
|
+
},
|
|
197
|
+
errors => {
|
|
198
|
+
if (this.page.app.isOffline()) {
|
|
199
|
+
this.showOffline()
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
this.displayCreateImageError(errors)
|
|
203
|
+
addAlert(
|
|
204
|
+
"error",
|
|
205
|
+
gettext(
|
|
206
|
+
"Some errors were found. Please examine the form."
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
)
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
showOffline() {
|
|
215
|
+
addAlert(
|
|
216
|
+
"info",
|
|
217
|
+
gettext(
|
|
218
|
+
"You are currently offline. Please try again after going online."
|
|
219
|
+
)
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import Cropper from "cropperjs"
|
|
2
|
+
|
|
3
|
+
import {gettext} from "fwtoolkit"
|
|
4
|
+
import {CopyrightDialog} from "../../copyright_dialog"
|
|
5
|
+
|
|
6
|
+
let mediaPreviewerImg = false
|
|
7
|
+
|
|
8
|
+
export const imageEditModel = () => ({
|
|
9
|
+
content: [
|
|
10
|
+
{
|
|
11
|
+
title: gettext("Rotate Left"),
|
|
12
|
+
type: "action",
|
|
13
|
+
tooltip: gettext("Rotate-left"),
|
|
14
|
+
order: 0,
|
|
15
|
+
action: dialog => {
|
|
16
|
+
const mediaPreviewerStyle =
|
|
17
|
+
dialog.mediaPreviewer.currentStyle ||
|
|
18
|
+
window.getComputedStyle(dialog.mediaPreviewer, false)
|
|
19
|
+
rotateBase64Image(
|
|
20
|
+
mediaPreviewerStyle.backgroundImage
|
|
21
|
+
.slice(4, -1)
|
|
22
|
+
.replace(/"/g, ""),
|
|
23
|
+
dialog.mediaInput.type,
|
|
24
|
+
"left"
|
|
25
|
+
).then(response =>
|
|
26
|
+
dialog.mediaPreviewer.setAttribute(
|
|
27
|
+
"style",
|
|
28
|
+
`background-image: url(${response});`
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
if (dialog.rotation === 0) {
|
|
32
|
+
dialog.rotation = 270
|
|
33
|
+
} else {
|
|
34
|
+
dialog.rotation -= 90
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
disabled: dialog => dialog.imageId,
|
|
38
|
+
icon: "redo fa-rotate-180"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
title: gettext("Rotate Right"),
|
|
42
|
+
type: "action",
|
|
43
|
+
tooltip: gettext("Rotate-right"),
|
|
44
|
+
order: 1,
|
|
45
|
+
action: dialog => {
|
|
46
|
+
const mediaPreviewerStyle =
|
|
47
|
+
dialog.mediaPreviewer.currentStyle ||
|
|
48
|
+
window.getComputedStyle(dialog.mediaPreviewer, false)
|
|
49
|
+
rotateBase64Image(
|
|
50
|
+
mediaPreviewerStyle.backgroundImage
|
|
51
|
+
.slice(4, -1)
|
|
52
|
+
.replace(/"/g, ""),
|
|
53
|
+
dialog.mediaInput.type,
|
|
54
|
+
"right"
|
|
55
|
+
).then(response =>
|
|
56
|
+
dialog.mediaPreviewer.setAttribute(
|
|
57
|
+
"style",
|
|
58
|
+
`background-image: url(${response});`
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
if (dialog.rotation === 270) {
|
|
62
|
+
dialog.rotation = 0
|
|
63
|
+
} else {
|
|
64
|
+
dialog.rotation += 90
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
disabled: dialog => dialog.imageId,
|
|
68
|
+
icon: "undo"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
title: gettext("Crop"),
|
|
72
|
+
type: "action",
|
|
73
|
+
tooltip: gettext("Crop image"),
|
|
74
|
+
order: 2,
|
|
75
|
+
action: dialog => {
|
|
76
|
+
const mediaPreviewerStyle =
|
|
77
|
+
dialog.mediaPreviewer.currentStyle ||
|
|
78
|
+
window.getComputedStyle(dialog.mediaPreviewer, false)
|
|
79
|
+
//const base64data = mediaPreviewerStyle.backgroundImage.slice(4, -1).replace(/"/g, "")
|
|
80
|
+
mediaPreviewerImg = document.createElement("img")
|
|
81
|
+
//img.src = `url(${base64data})`
|
|
82
|
+
mediaPreviewerImg.src = mediaPreviewerStyle.backgroundImage
|
|
83
|
+
.slice(4, -1)
|
|
84
|
+
.replace(/"/g, "")
|
|
85
|
+
dialog.mediaPreviewer.parentElement.replaceChild(
|
|
86
|
+
mediaPreviewerImg,
|
|
87
|
+
dialog.mediaPreviewer
|
|
88
|
+
)
|
|
89
|
+
const cropper = new Cropper(mediaPreviewerImg, {
|
|
90
|
+
viewMode: 1,
|
|
91
|
+
responsive: true
|
|
92
|
+
})
|
|
93
|
+
toggleCropMode(true, dialog, cropper)
|
|
94
|
+
},
|
|
95
|
+
disabled: dialog => dialog.imageId,
|
|
96
|
+
icon: "crop"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
title: gettext("Set Copyright"),
|
|
100
|
+
type: "action",
|
|
101
|
+
tooltip: gettext("Specify copyright information"),
|
|
102
|
+
order: 3,
|
|
103
|
+
action: dialog => {
|
|
104
|
+
const crDialog = new CopyrightDialog(dialog.copyright)
|
|
105
|
+
crDialog.init().then(copyright => {
|
|
106
|
+
if (copyright) {
|
|
107
|
+
dialog.copyright = copyright
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
let oldButtons = false
|
|
116
|
+
|
|
117
|
+
const toggleCropMode = (val, dialog, cropper) => {
|
|
118
|
+
if (val && !oldButtons) {
|
|
119
|
+
dialog.mediaPreviewerDiv.classList.add("crop-mode")
|
|
120
|
+
oldButtons = dialog.dialog.buttons
|
|
121
|
+
dialog.dialog.setButtons([
|
|
122
|
+
{
|
|
123
|
+
text: gettext("Crop"),
|
|
124
|
+
click: () => {
|
|
125
|
+
dialog.mediaPreviewer.setAttribute(
|
|
126
|
+
"style",
|
|
127
|
+
`background-image: url(${cropper
|
|
128
|
+
.getCroppedCanvas()
|
|
129
|
+
.toDataURL(dialog.mediaInput.type)});`
|
|
130
|
+
)
|
|
131
|
+
dialog.cropped = true
|
|
132
|
+
cropper.destroy()
|
|
133
|
+
toggleCropMode(false, dialog, cropper)
|
|
134
|
+
},
|
|
135
|
+
classes: "fw-dark"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
type: "cancel",
|
|
139
|
+
classes: "fw-orange",
|
|
140
|
+
click: () => {
|
|
141
|
+
cropper.destroy()
|
|
142
|
+
toggleCropMode(false, dialog, cropper)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
])
|
|
146
|
+
} else {
|
|
147
|
+
dialog.mediaPreviewerDiv.classList.remove("crop-mode")
|
|
148
|
+
if (mediaPreviewerImg) {
|
|
149
|
+
mediaPreviewerImg.parentElement.replaceChild(
|
|
150
|
+
dialog.mediaPreviewer,
|
|
151
|
+
mediaPreviewerImg
|
|
152
|
+
)
|
|
153
|
+
mediaPreviewerImg = false
|
|
154
|
+
}
|
|
155
|
+
if (oldButtons) {
|
|
156
|
+
dialog.dialog.buttons = oldButtons
|
|
157
|
+
oldButtons = false
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
dialog.dialog.refreshButtons()
|
|
161
|
+
dialog.dialog.centerDialog()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const rotateBase64Image = (base64data, type, direction) => {
|
|
165
|
+
return new Promise(resolve => {
|
|
166
|
+
const canvas = document.createElement("canvas")
|
|
167
|
+
const ctx = canvas.getContext("2d")
|
|
168
|
+
const image = new Image()
|
|
169
|
+
image.src = base64data
|
|
170
|
+
image.onload = () => {
|
|
171
|
+
canvas.height = image.width
|
|
172
|
+
canvas.width = image.height
|
|
173
|
+
if (direction == "left") {
|
|
174
|
+
ctx.rotate((90 * Math.PI) / 180)
|
|
175
|
+
ctx.translate(0, -canvas.width)
|
|
176
|
+
} else {
|
|
177
|
+
ctx.rotate((-90 * Math.PI) / 180)
|
|
178
|
+
ctx.translate(-canvas.height, 0)
|
|
179
|
+
}
|
|
180
|
+
ctx.drawImage(image, 0, 0)
|
|
181
|
+
resolve(canvas.toDataURL(type))
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {escapeText, gettext} from "fwtoolkit"
|
|
2
|
+
|
|
3
|
+
/* A template for the image category selection of the image selection dialog. */
|
|
4
|
+
const imageEditCategoryTemplate = ({cats}) => {
|
|
5
|
+
if (!cats.length) {
|
|
6
|
+
return ""
|
|
7
|
+
}
|
|
8
|
+
return `<div class="fw-media-category">
|
|
9
|
+
<div>${gettext("Select categories")}</div>
|
|
10
|
+
<div id="image-edit-categories"></div>
|
|
11
|
+
</div>`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* A template for the form for the image upload dialog. */
|
|
15
|
+
export const imageEditTemplate = ({image, cats}) =>
|
|
16
|
+
`<div>
|
|
17
|
+
<input name="title" class="fw-media-title" type="text"
|
|
18
|
+
placeholder="${gettext("Insert a title")}" value="${
|
|
19
|
+
image ? escapeText(image.title) : ""
|
|
20
|
+
}" />
|
|
21
|
+
${
|
|
22
|
+
image
|
|
23
|
+
? ""
|
|
24
|
+
: `<button type="button" class="fw-media-select-button fw-button fw-light">
|
|
25
|
+
${gettext("Select a file")}
|
|
26
|
+
</button>
|
|
27
|
+
<input name="image" type="file" class="fw-media-file-input">`
|
|
28
|
+
}
|
|
29
|
+
</div>
|
|
30
|
+
<div class="figure-preview">
|
|
31
|
+
<button class="figure-edit-menu" title="${gettext("Edit Image")}">
|
|
32
|
+
<span class="dot-menu-icon"><i class="fa fa-ellipsis-v"></i></span>
|
|
33
|
+
</button>
|
|
34
|
+
<div>
|
|
35
|
+
${
|
|
36
|
+
image && image.image
|
|
37
|
+
? `<div class="img" style="background-image: url(${image.image});"></div>`
|
|
38
|
+
: ""
|
|
39
|
+
}
|
|
40
|
+
</div></div>
|
|
41
|
+
${imageEditCategoryTemplate({cats})}`
|
package/src/index.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Dialog,
|
|
3
|
+
activateWait,
|
|
4
|
+
addAlert,
|
|
5
|
+
deactivateWait,
|
|
6
|
+
gettext,
|
|
7
|
+
postJson
|
|
8
|
+
} from "fwtoolkit"
|
|
9
|
+
import {usermediaEditcategoriesTemplate} from "./templates"
|
|
10
|
+
|
|
11
|
+
export class ImageOverviewCategories {
|
|
12
|
+
constructor(imageOverview) {
|
|
13
|
+
this.imageOverview = imageOverview
|
|
14
|
+
imageOverview.mod.categories = this
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//save changes or create a new category
|
|
18
|
+
saveCategories(cats) {
|
|
19
|
+
activateWait()
|
|
20
|
+
|
|
21
|
+
postJson("/api/usermedia/save_category/", {
|
|
22
|
+
ids: cats.ids,
|
|
23
|
+
titles: cats.titles
|
|
24
|
+
})
|
|
25
|
+
.catch(error => {
|
|
26
|
+
addAlert("error", gettext("Could not update categories"))
|
|
27
|
+
deactivateWait()
|
|
28
|
+
throw error
|
|
29
|
+
})
|
|
30
|
+
.then(({json}) => {
|
|
31
|
+
this.imageOverview.app.imageDB.cats = json.entries
|
|
32
|
+
this.setImageCategoryList(json.entries)
|
|
33
|
+
addAlert("success", gettext("The categories have been updated"))
|
|
34
|
+
deactivateWait()
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
setImageCategoryList(imageCategories) {
|
|
39
|
+
const catSelector = this.imageOverview.menu.model.content.find(
|
|
40
|
+
menuItem => menuItem.id === "cat_selector"
|
|
41
|
+
)
|
|
42
|
+
catSelector.content = catSelector.content.filter(
|
|
43
|
+
cat => cat.type !== "category"
|
|
44
|
+
)
|
|
45
|
+
catSelector.content = catSelector.content.concat(
|
|
46
|
+
imageCategories.map(cat => ({
|
|
47
|
+
type: "category",
|
|
48
|
+
title: cat.category_title,
|
|
49
|
+
action: _overview => {
|
|
50
|
+
const trs = document.querySelectorAll(
|
|
51
|
+
"#imagelist > tbody > tr"
|
|
52
|
+
)
|
|
53
|
+
trs.forEach(tr => {
|
|
54
|
+
if (
|
|
55
|
+
tr
|
|
56
|
+
.querySelector(".fw-usermedia-image")
|
|
57
|
+
.classList.contains(`cat_${cat.id}`)
|
|
58
|
+
) {
|
|
59
|
+
tr.style.display = ""
|
|
60
|
+
} else {
|
|
61
|
+
tr.style.display = "none"
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
}))
|
|
66
|
+
)
|
|
67
|
+
this.imageOverview.menu.update()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
//open a dialog for editing categories
|
|
71
|
+
editCategoryDialog() {
|
|
72
|
+
const buttons = [
|
|
73
|
+
{
|
|
74
|
+
text: gettext("Submit"),
|
|
75
|
+
classes: "fw-dark",
|
|
76
|
+
click: () => {
|
|
77
|
+
const cats = {
|
|
78
|
+
ids: [],
|
|
79
|
+
titles: []
|
|
80
|
+
}
|
|
81
|
+
document
|
|
82
|
+
.querySelectorAll("#edit-categories .category-form")
|
|
83
|
+
.forEach(el => {
|
|
84
|
+
const thisVal = el.value.trim()
|
|
85
|
+
let thisId = el.dataset.id
|
|
86
|
+
if ("undefined" == typeof thisId) {
|
|
87
|
+
thisId = 0
|
|
88
|
+
}
|
|
89
|
+
if ("" !== thisVal) {
|
|
90
|
+
cats.ids.push(thisId)
|
|
91
|
+
cats.titles.push(thisVal)
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
this.saveCategories(cats)
|
|
95
|
+
dialog.close()
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
type: "cancel"
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
const dialog = new Dialog({
|
|
104
|
+
id: "edit-categories",
|
|
105
|
+
title: gettext("Edit Categories"),
|
|
106
|
+
body: usermediaEditcategoriesTemplate({
|
|
107
|
+
categories: this.imageOverview.app.imageDB.cats
|
|
108
|
+
}),
|
|
109
|
+
width: 350,
|
|
110
|
+
height: 350,
|
|
111
|
+
buttons
|
|
112
|
+
})
|
|
113
|
+
dialog.open()
|
|
114
|
+
}
|
|
115
|
+
}
|