fwtoolkit 0.1.0-alpha.6 → 0.1.0-beta.1
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/css/alerts.css +7 -0
- package/css/dialog.css +62 -0
- package/css/overview_menu.css +7 -0
- package/dist/basic.d.ts +49 -36
- package/dist/basic.d.ts.map +1 -1
- package/dist/basic.js +58 -39
- package/dist/basic.js.map +1 -1
- package/dist/blob.d.ts +1 -1
- package/dist/blob.d.ts.map +1 -1
- package/dist/blob.js +0 -1
- package/dist/blob.js.map +1 -1
- package/dist/content_menu.d.ts +63 -20
- package/dist/content_menu.d.ts.map +1 -1
- package/dist/content_menu.js +23 -20
- package/dist/content_menu.js.map +1 -1
- package/dist/datatable_bulk.d.ts +34 -6
- package/dist/datatable_bulk.d.ts.map +1 -1
- package/dist/datatable_bulk.js +4 -5
- package/dist/datatable_bulk.js.map +1 -1
- package/dist/dialog.d.ts +82 -7
- package/dist/dialog.d.ts.map +1 -1
- package/dist/dialog.js +21 -16
- package/dist/dialog.js.map +1 -1
- package/dist/events.d.ts +1 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +3 -3
- package/dist/events.js.map +1 -1
- package/dist/faq_dialog.d.ts +13 -4
- package/dist/faq_dialog.d.ts.map +1 -1
- package/dist/faq_dialog.js +4 -2
- package/dist/faq_dialog.js.map +1 -1
- package/dist/file/dialog.d.ts +33 -13
- package/dist/file/dialog.d.ts.map +1 -1
- package/dist/file/dialog.js +6 -8
- package/dist/file/dialog.js.map +1 -1
- package/dist/file/index.d.ts.map +1 -1
- package/dist/file/index.js +0 -1
- package/dist/file/index.js.map +1 -1
- package/dist/file/new_folder_dialog.d.ts +5 -2
- package/dist/file/new_folder_dialog.d.ts.map +1 -1
- package/dist/file/new_folder_dialog.js +1 -2
- package/dist/file/new_folder_dialog.js.map +1 -1
- package/dist/file/selector.d.ts +47 -14
- package/dist/file/selector.d.ts.map +1 -1
- package/dist/file/selector.js +11 -9
- package/dist/file/selector.js.map +1 -1
- package/dist/file/templates.d.ts +1 -1
- package/dist/file/templates.d.ts.map +1 -1
- package/dist/file/templates.js +0 -1
- package/dist/file/templates.js.map +1 -1
- package/dist/file/tools.d.ts +4 -4
- package/dist/file/tools.d.ts.map +1 -1
- package/dist/file/tools.js +4 -4
- package/dist/file/tools.js.map +1 -1
- package/dist/focus.d.ts +1 -1
- package/dist/focus.d.ts.map +1 -1
- package/dist/focus.js +5 -5
- package/dist/focus.js.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -3
- package/dist/index.js.map +1 -1
- package/dist/network.d.ts +19 -7
- package/dist/network.d.ts.map +1 -1
- package/dist/network.js +15 -12
- package/dist/network.js.map +1 -1
- package/dist/overview_menu.d.ts +77 -18
- package/dist/overview_menu.d.ts.map +1 -1
- package/dist/overview_menu.js +54 -35
- package/dist/overview_menu.js.map +1 -1
- package/dist/settings.d.ts +7 -2
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +0 -1
- package/dist/settings.js.map +1 -1
- package/dist/user.d.ts +8 -2
- package/dist/user.d.ts.map +1 -1
- package/dist/user.js +1 -2
- package/dist/user.js.map +1 -1
- package/dist/worker.d.ts +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +1 -2
- package/dist/worker.js.map +1 -1
- package/dist/ws.d.ts +59 -25
- package/dist/ws.d.ts.map +1 -1
- package/dist/ws.js +19 -15
- package/dist/ws.js.map +1 -1
- package/package.json +2 -1
- package/src/basic.ts +136 -69
- package/src/blob.ts +1 -2
- package/src/content_menu.ts +127 -44
- package/src/datatable_bulk.ts +72 -35
- package/src/dialog.ts +156 -61
- package/src/diff-dom.d.ts +16 -0
- package/src/events.ts +3 -3
- package/src/faq_dialog.ts +25 -11
- package/src/file/dialog.ts +48 -14
- package/src/file/index.ts +0 -1
- package/src/file/new_folder_dialog.ts +7 -5
- package/src/file/selector.ts +86 -36
- package/src/file/templates.ts +2 -3
- package/src/file/tools.ts +17 -8
- package/src/focus.ts +11 -13
- package/src/global.d.ts +11 -4
- package/src/index.ts +0 -3
- package/src/network.ts +58 -20
- package/src/overview_menu.ts +183 -109
- package/src/settings.ts +9 -4
- package/src/user.ts +10 -5
- package/src/w3c-keyname.d.ts +3 -0
- package/src/worker.ts +1 -2
- package/src/ws.ts +115 -50
- package/css/ui_dialogs.css +0 -144
- package/dist/templates.d.ts +0 -7
- package/dist/templates.d.ts.map +0 -1
- package/dist/templates.js +0 -43
- package/dist/templates.js.map +0 -1
- package/dist/user_util.d.ts +0 -7
- package/dist/user_util.d.ts.map +0 -1
- package/dist/user_util.js +0 -19
- package/dist/user_util.js.map +0 -1
- package/src/templates.ts +0 -43
- package/src/user_util.ts +0 -17
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import {Dialog} from "../dialog.js"
|
|
3
2
|
import {newFolderTemplate} from "./templates.js"
|
|
4
3
|
|
|
5
4
|
export class NewFolderDialog {
|
|
6
|
-
|
|
5
|
+
callback: (folderName: string) => void
|
|
6
|
+
dialog: Dialog
|
|
7
|
+
|
|
8
|
+
constructor(callback: (folderName: string) => void = () => {}) {
|
|
7
9
|
this.callback = callback
|
|
8
10
|
this.dialog = new Dialog({
|
|
9
11
|
title: gettext("New folder"),
|
|
@@ -18,9 +20,9 @@ export class NewFolderDialog {
|
|
|
18
20
|
classes: "fw-dark",
|
|
19
21
|
click: () => {
|
|
20
22
|
const folderName =
|
|
21
|
-
this.dialog.dialogEl
|
|
23
|
+
(this.dialog.dialogEl!.querySelector(
|
|
22
24
|
"#new-folder-name"
|
|
23
|
-
).value
|
|
25
|
+
) as HTMLInputElement).value
|
|
24
26
|
this.dialog.close()
|
|
25
27
|
if (!folderName.length) {
|
|
26
28
|
return
|
|
@@ -32,7 +34,7 @@ export class NewFolderDialog {
|
|
|
32
34
|
})
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
open() {
|
|
37
|
+
open(): void {
|
|
36
38
|
return this.dialog.open()
|
|
37
39
|
}
|
|
38
40
|
}
|
package/src/file/selector.ts
CHANGED
|
@@ -1,18 +1,65 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import {escapeText, findTarget} from "../basic.js"
|
|
3
2
|
import {ensureCSS} from "../network.js"
|
|
4
3
|
|
|
4
|
+
export interface FileDescriptor {
|
|
5
|
+
id?: number
|
|
6
|
+
title?: string
|
|
7
|
+
path: string
|
|
8
|
+
[key: string]: unknown
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface FileSelectorFolder {
|
|
12
|
+
name: string
|
|
13
|
+
type: "folder"
|
|
14
|
+
open: boolean
|
|
15
|
+
selected: boolean
|
|
16
|
+
path: string
|
|
17
|
+
children: FileSelectorEntry[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface FileSelectorFile {
|
|
21
|
+
name: string
|
|
22
|
+
type: "file"
|
|
23
|
+
path: string
|
|
24
|
+
selected?: boolean
|
|
25
|
+
file: FileDescriptor
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type FileSelectorEntry = FileSelectorFolder | FileSelectorFile
|
|
29
|
+
|
|
30
|
+
export interface FileSelectorOptions {
|
|
31
|
+
dom: HTMLElement
|
|
32
|
+
files: FileDescriptor[]
|
|
33
|
+
showFiles?: boolean
|
|
34
|
+
selectFolders?: boolean
|
|
35
|
+
multiSelect?: boolean
|
|
36
|
+
selectDir?: (path: string) => void
|
|
37
|
+
selectFile?: (path: string) => void
|
|
38
|
+
fileIcon?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
5
41
|
export class FileSelector {
|
|
42
|
+
dom: HTMLElement
|
|
43
|
+
files: FileDescriptor[]
|
|
44
|
+
showFiles: boolean
|
|
45
|
+
selectFolders: boolean
|
|
46
|
+
multiSelect: boolean
|
|
47
|
+
selectDir: (path: string) => void
|
|
48
|
+
selectFile: (path: string) => void
|
|
49
|
+
fileIcon: string
|
|
50
|
+
root: FileSelectorFolder
|
|
51
|
+
selected: FileSelectorEntry[]
|
|
52
|
+
|
|
6
53
|
constructor({
|
|
7
54
|
dom,
|
|
8
55
|
files,
|
|
9
56
|
showFiles = true,
|
|
10
57
|
selectFolders = true,
|
|
11
58
|
multiSelect = false,
|
|
12
|
-
selectDir =
|
|
13
|
-
selectFile =
|
|
59
|
+
selectDir = () => {},
|
|
60
|
+
selectFile = () => {},
|
|
14
61
|
fileIcon = "far fa-file-alt"
|
|
15
|
-
}) {
|
|
62
|
+
}: FileSelectorOptions) {
|
|
16
63
|
this.dom = dom
|
|
17
64
|
this.files = files
|
|
18
65
|
this.showFiles = showFiles // Whether to show existing files or only folders
|
|
@@ -36,7 +83,7 @@ export class FileSelector {
|
|
|
36
83
|
}
|
|
37
84
|
}
|
|
38
85
|
|
|
39
|
-
init() {
|
|
86
|
+
init(): void {
|
|
40
87
|
this.readDirStructure()
|
|
41
88
|
this.sortDirStructure()
|
|
42
89
|
ensureCSS(staticUrl("css/file_selector.css"))
|
|
@@ -45,7 +92,7 @@ export class FileSelector {
|
|
|
45
92
|
this.bind()
|
|
46
93
|
}
|
|
47
94
|
|
|
48
|
-
readDirStructure() {
|
|
95
|
+
readDirStructure(): void {
|
|
49
96
|
// Read directory structure from existing file paths.
|
|
50
97
|
// A file's title is used as the final path segment when the file has
|
|
51
98
|
// no explicit folder path. We strip any "/" from that segment because
|
|
@@ -80,7 +127,7 @@ export class FileSelector {
|
|
|
80
127
|
}
|
|
81
128
|
let folder = treeWalker.find(
|
|
82
129
|
item => item.name === pathPart && item.type === "folder"
|
|
83
|
-
)
|
|
130
|
+
) as FileSelectorFolder | undefined
|
|
84
131
|
if (!folder) {
|
|
85
132
|
folder = {
|
|
86
133
|
name: pathPart,
|
|
@@ -97,7 +144,7 @@ export class FileSelector {
|
|
|
97
144
|
})
|
|
98
145
|
}
|
|
99
146
|
|
|
100
|
-
sortDirStructure(entries = this.root.children) {
|
|
147
|
+
sortDirStructure(entries = this.root.children): void {
|
|
101
148
|
entries.sort((a, b) => {
|
|
102
149
|
if (a.type !== b.type) {
|
|
103
150
|
return a.type === "folder" ? -1 : 1
|
|
@@ -111,32 +158,33 @@ export class FileSelector {
|
|
|
111
158
|
})
|
|
112
159
|
}
|
|
113
160
|
|
|
114
|
-
addFolder(rawName) {
|
|
161
|
+
addFolder(rawName: string): void {
|
|
115
162
|
const name = rawName.replace(/\//g, "")
|
|
116
163
|
// Add a new folder as a subfolder to the currently selected folder
|
|
117
164
|
if (
|
|
118
165
|
!this.selected.length ||
|
|
119
166
|
this.selected[0].type !== "folder" ||
|
|
120
|
-
this.selected[0].children.find(
|
|
167
|
+
(this.selected[0] as FileSelectorFolder).children.find(
|
|
121
168
|
child => child.type === "folder" && child.name === name
|
|
122
169
|
)
|
|
123
170
|
) {
|
|
124
171
|
// A file is selected. Give up.
|
|
125
172
|
return
|
|
126
173
|
}
|
|
127
|
-
const
|
|
174
|
+
const selectedFolder = this.selected[0] as FileSelectorFolder
|
|
175
|
+
const newFolder: FileSelectorFolder = {
|
|
128
176
|
name,
|
|
129
177
|
type: "folder",
|
|
130
178
|
open: true,
|
|
131
179
|
selected: true,
|
|
132
|
-
path:
|
|
180
|
+
path: selectedFolder.path + name + "/",
|
|
133
181
|
children: []
|
|
134
182
|
}
|
|
135
|
-
|
|
136
|
-
this.sortDirStructure(
|
|
137
|
-
|
|
183
|
+
selectedFolder.children.push(newFolder)
|
|
184
|
+
this.sortDirStructure(selectedFolder.children)
|
|
185
|
+
selectedFolder.open = true
|
|
138
186
|
if (!this.multiSelect) {
|
|
139
|
-
|
|
187
|
+
selectedFolder.selected = false
|
|
140
188
|
this.selected = []
|
|
141
189
|
}
|
|
142
190
|
this.selected.push(newFolder)
|
|
@@ -144,17 +192,17 @@ export class FileSelector {
|
|
|
144
192
|
this.render()
|
|
145
193
|
}
|
|
146
194
|
|
|
147
|
-
deselectAll() {
|
|
195
|
+
deselectAll(): void {
|
|
148
196
|
this.selected.forEach(entry => (entry.selected = false))
|
|
149
197
|
this.selected = []
|
|
150
198
|
this.render()
|
|
151
199
|
}
|
|
152
200
|
|
|
153
|
-
render() {
|
|
201
|
+
render(): void {
|
|
154
202
|
this.dom.innerHTML = this.renderFolder(this.root)
|
|
155
203
|
}
|
|
156
204
|
|
|
157
|
-
renderFolder(folder, indentLevel = 0) {
|
|
205
|
+
renderFolder(folder: FileSelectorFolder, indentLevel = 0): string {
|
|
158
206
|
let returnString = ""
|
|
159
207
|
returnString += `<div class="folder${folder.open ? "" : " closed"}">`
|
|
160
208
|
returnString += `<p style="margin-left:${indentLevel * 10}px;">${
|
|
@@ -179,42 +227,44 @@ export class FileSelector {
|
|
|
179
227
|
return returnString
|
|
180
228
|
}
|
|
181
229
|
|
|
182
|
-
findEntry(dom) {
|
|
183
|
-
const searchPath = []
|
|
184
|
-
let seekItem = dom
|
|
185
|
-
|
|
230
|
+
findEntry(dom: Element): FileSelectorEntry {
|
|
231
|
+
const searchPath: number[] = []
|
|
232
|
+
let seekItem: Element | null = dom
|
|
233
|
+
let closest = seekItem.closest("div.folder, p.file")
|
|
234
|
+
while (closest) {
|
|
235
|
+
seekItem = closest
|
|
186
236
|
let itemNumber = 0
|
|
187
|
-
seekItem = seekItem.closest("div.folder, p.file")
|
|
188
237
|
while (seekItem.previousElementSibling) {
|
|
189
238
|
itemNumber++
|
|
190
239
|
seekItem = seekItem.previousElementSibling
|
|
191
240
|
}
|
|
192
241
|
searchPath.push(itemNumber)
|
|
193
242
|
seekItem = seekItem.parentElement
|
|
243
|
+
closest = seekItem?.closest("div.folder, p.file") ?? null
|
|
194
244
|
}
|
|
195
|
-
let entry = this.root
|
|
245
|
+
let entry: FileSelectorEntry = this.root
|
|
196
246
|
searchPath.pop()
|
|
197
247
|
while (searchPath.length) {
|
|
198
|
-
entry = entry.children[searchPath.pop()]
|
|
248
|
+
entry = (entry as FileSelectorFolder).children[searchPath.pop()!]
|
|
199
249
|
}
|
|
200
250
|
return entry
|
|
201
251
|
}
|
|
202
252
|
|
|
203
|
-
bind() {
|
|
253
|
+
bind(): void {
|
|
204
254
|
this.dom.addEventListener("click", event => {
|
|
205
|
-
const el = {}
|
|
255
|
+
const el: {target?: Element | null} = {}
|
|
206
256
|
switch (true) {
|
|
207
257
|
case findTarget(event, ".fa-plus-square", el): {
|
|
208
258
|
event.preventDefault()
|
|
209
|
-
const entry = this.findEntry(el.target)
|
|
210
|
-
entry.open = true
|
|
259
|
+
const entry = this.findEntry(el.target!)
|
|
260
|
+
;(entry as FileSelectorFolder).open = true
|
|
211
261
|
this.render()
|
|
212
262
|
break
|
|
213
263
|
}
|
|
214
264
|
case findTarget(event, ".fa-minus-square", el): {
|
|
215
265
|
event.preventDefault()
|
|
216
|
-
const entry = this.findEntry(el.target)
|
|
217
|
-
entry.open = false
|
|
266
|
+
const entry = this.findEntry(el.target!)
|
|
267
|
+
;(entry as FileSelectorFolder).open = false
|
|
218
268
|
this.render()
|
|
219
269
|
break
|
|
220
270
|
}
|
|
@@ -224,7 +274,7 @@ export class FileSelector {
|
|
|
224
274
|
// Folders cannot be selected
|
|
225
275
|
return
|
|
226
276
|
}
|
|
227
|
-
const entry = this.findEntry(el.target)
|
|
277
|
+
const entry = this.findEntry(el.target!)
|
|
228
278
|
if (this.selected.includes(entry)) {
|
|
229
279
|
entry.selected = false
|
|
230
280
|
this.selected = this.selected.filter(e => e !== entry)
|
|
@@ -236,13 +286,13 @@ export class FileSelector {
|
|
|
236
286
|
}
|
|
237
287
|
this.selected.push(entry)
|
|
238
288
|
this.render()
|
|
239
|
-
this.selectDir(entry.path)
|
|
289
|
+
this.selectDir((entry as FileSelectorFolder).path)
|
|
240
290
|
}
|
|
241
291
|
break
|
|
242
292
|
}
|
|
243
293
|
case findTarget(event, ".file-name", el): {
|
|
244
294
|
event.preventDefault()
|
|
245
|
-
const entry = this.findEntry(el.target)
|
|
295
|
+
const entry = this.findEntry(el.target!)
|
|
246
296
|
if (this.selected.includes(entry)) {
|
|
247
297
|
entry.selected = false
|
|
248
298
|
this.selected = this.selected.filter(e => e !== entry)
|
|
@@ -254,7 +304,7 @@ export class FileSelector {
|
|
|
254
304
|
}
|
|
255
305
|
this.selected.push(entry)
|
|
256
306
|
this.render()
|
|
257
|
-
this.selectFile(entry.path)
|
|
307
|
+
this.selectFile((entry as FileSelectorFile).path)
|
|
258
308
|
}
|
|
259
309
|
break
|
|
260
310
|
}
|
package/src/file/templates.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import {escapeText} from "../index.js"
|
|
3
2
|
|
|
4
|
-
export const moveTemplate = ({path}) =>
|
|
3
|
+
export const moveTemplate = ({path}: {path: string}): string =>
|
|
5
4
|
`<div>
|
|
6
5
|
<span>${gettext("Path")}:</span>
|
|
7
6
|
<input type="text" value="${escapeText(path)}" id="path" placeholder="${gettext("Insert path")}">
|
|
8
7
|
<div class="file-selector"></div>
|
|
9
8
|
</div>`
|
|
10
9
|
|
|
11
|
-
export const newFolderTemplate = () =>
|
|
10
|
+
export const newFolderTemplate = (): string =>
|
|
12
11
|
`<div><input type="text" id="new-folder-name" placeholder="${gettext("Insert folder name")}"></div>`
|
package/src/file/tools.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import {escapeText} from "../basic.js"
|
|
3
2
|
import {postJson} from "../network.js"
|
|
4
3
|
|
|
5
|
-
export const shortFileTitle = (title, path) => {
|
|
4
|
+
export const shortFileTitle = (title: string, path: string): string => {
|
|
6
5
|
if (!path.length || path.endsWith("/")) {
|
|
7
6
|
return escapeText(title || gettext("Untitled"))
|
|
8
7
|
}
|
|
9
|
-
return escapeText(path.split("/").pop())
|
|
8
|
+
return escapeText(path.split("/").pop() || "")
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
export const longFilePath = (
|
|
11
|
+
export const longFilePath = (
|
|
12
|
+
title: string,
|
|
13
|
+
path: string,
|
|
14
|
+
prefix = ""
|
|
15
|
+
): string => {
|
|
13
16
|
if (!path.length) {
|
|
14
17
|
path = "/"
|
|
15
18
|
}
|
|
@@ -26,7 +29,7 @@ export const longFilePath = (title, path, prefix = "") => {
|
|
|
26
29
|
return path
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
export const cleanPath = (title, path) => {
|
|
32
|
+
export const cleanPath = (title: string, path: string): string => {
|
|
30
33
|
if (!path.startsWith("/")) {
|
|
31
34
|
path = "/" + path
|
|
32
35
|
}
|
|
@@ -45,14 +48,20 @@ export const cleanPath = (title, path) => {
|
|
|
45
48
|
return path
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
export const moveFile = (
|
|
51
|
+
export const moveFile = (
|
|
52
|
+
fileId: number,
|
|
53
|
+
title: string,
|
|
54
|
+
path: string,
|
|
55
|
+
moveUrl: string
|
|
56
|
+
): Promise<string> => {
|
|
49
57
|
path = cleanPath(title, path)
|
|
50
58
|
return new Promise((resolve, reject) => {
|
|
51
59
|
postJson(moveUrl, {id: fileId, path}).then(({json}) => {
|
|
52
|
-
|
|
60
|
+
const response = json as {done?: boolean}
|
|
61
|
+
if (response.done) {
|
|
53
62
|
resolve(path)
|
|
54
63
|
} else {
|
|
55
|
-
reject()
|
|
64
|
+
reject(new Error("Could not move file"))
|
|
56
65
|
}
|
|
57
66
|
})
|
|
58
67
|
})
|
package/src/focus.ts
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const FOCUSABLE_SELECTOR =
|
|
2
|
+
"a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex], [contenteditable]"
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
)
|
|
4
|
+
// Get the index number of currently focused element. This is to set the focus close by after doing some dom changes.
|
|
5
|
+
|
|
6
|
+
export const getFocusIndex = (): number => {
|
|
7
|
+
return Array.from(document.querySelectorAll(FOCUSABLE_SELECTOR)).findIndex(
|
|
8
|
+
el => el === document.activeElement
|
|
9
|
+
)
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export const setFocusIndex = index => {
|
|
12
|
+
export const setFocusIndex = (index: number): void => {
|
|
13
13
|
const focusableElements = Array.from(
|
|
14
|
-
document.querySelectorAll(
|
|
15
|
-
"a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex], [contenteditable]"
|
|
16
|
-
)
|
|
14
|
+
document.querySelectorAll(FOCUSABLE_SELECTOR)
|
|
17
15
|
)
|
|
18
16
|
if (index >= 0 && index < focusableElements.length) {
|
|
19
|
-
focusableElements[index].focus()
|
|
17
|
+
(focusableElements[index] as HTMLElement).focus()
|
|
20
18
|
}
|
|
21
19
|
}
|
package/src/global.d.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
// Globals provided by the Fidus Writer host page.
|
|
2
2
|
|
|
3
|
-
declare
|
|
3
|
+
declare function gettext(msgid: string): string
|
|
4
4
|
|
|
5
|
-
declare
|
|
5
|
+
declare function interpolate(
|
|
6
|
+
fmt: string,
|
|
7
|
+
args: unknown[],
|
|
8
|
+
named?: boolean
|
|
9
|
+
): string
|
|
6
10
|
|
|
7
|
-
declare
|
|
11
|
+
declare function staticUrl(path: string): string
|
|
12
|
+
|
|
13
|
+
declare const settings: Record<string, unknown>
|
|
8
14
|
|
|
9
15
|
interface Window {
|
|
10
|
-
settings?: Record<string,
|
|
16
|
+
settings?: Record<string, unknown>
|
|
17
|
+
csrfToken?: string
|
|
11
18
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
export {OverviewMenuView} from "./overview_menu.js"
|
|
3
2
|
export {
|
|
4
3
|
dropdownSelect,
|
|
@@ -42,9 +41,7 @@ export {
|
|
|
42
41
|
export {Dialog} from "./dialog.js"
|
|
43
42
|
export {ContentMenu} from "./content_menu.js"
|
|
44
43
|
export {makeWorker} from "./worker.js"
|
|
45
|
-
export {baseBodyTemplate} from "./templates.js"
|
|
46
44
|
export {WebSocketConnector} from "./ws.js"
|
|
47
|
-
export {filterPrimaryEmail} from "./user_util.js"
|
|
48
45
|
export {DatatableBulk} from "./datatable_bulk.js"
|
|
49
46
|
export {faqDialog} from "./faq_dialog.js"
|
|
50
47
|
export {
|
package/src/network.ts
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import {getSettings} from "./settings.js"
|
|
3
2
|
|
|
3
|
+
export interface FileUploadValue {
|
|
4
|
+
file: Blob
|
|
5
|
+
filename: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type PostFiles = Record<string, Blob | File | FileUploadValue | Blob[] | File[] | string[] | string>
|
|
9
|
+
|
|
10
|
+
export interface PostOptions {
|
|
11
|
+
csrfToken?: string
|
|
12
|
+
keepalive?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
4
15
|
/** Get cookie to set as part of the request header of all AJAX requests to the server.
|
|
5
16
|
* @param name The name of the token to look for in the cookie.
|
|
6
17
|
*/
|
|
7
|
-
export const getCookie = name => {
|
|
18
|
+
export const getCookie = (name: string): string | null => {
|
|
8
19
|
if (!document.cookie || document.cookie === "") {
|
|
9
20
|
return null
|
|
10
21
|
}
|
|
@@ -25,18 +36,20 @@ export const getCookie = name => {
|
|
|
25
36
|
}
|
|
26
37
|
|
|
27
38
|
/* from https://www.tjvantoll.com/2015/09/13/fetch-and-errors/ */
|
|
28
|
-
const handleFetchErrors = response => {
|
|
39
|
+
const handleFetchErrors = (response: Response): Response => {
|
|
29
40
|
if (!response.ok) {
|
|
30
41
|
throw response
|
|
31
42
|
}
|
|
32
43
|
return response
|
|
33
44
|
}
|
|
34
45
|
|
|
35
|
-
export const get = (
|
|
46
|
+
export const get = (
|
|
47
|
+
url: string,
|
|
48
|
+
params: Record<string, string> = {},
|
|
49
|
+
csrfToken: string | false = false
|
|
50
|
+
): Promise<Response> => {
|
|
36
51
|
const settings = getSettings()
|
|
37
|
-
|
|
38
|
-
csrfToken = settings.getCsrfToken() // Won't work in web worker.
|
|
39
|
-
}
|
|
52
|
+
const token = csrfToken || settings.getCsrfToken() // Won't work in web worker.
|
|
40
53
|
const queryString = Object.keys(params)
|
|
41
54
|
.map(
|
|
42
55
|
key =>
|
|
@@ -49,7 +62,7 @@ export const get = (url, params = {}, csrfToken = false) => {
|
|
|
49
62
|
return fetch(settings.apiUrl(url), {
|
|
50
63
|
method: "GET",
|
|
51
64
|
headers: {
|
|
52
|
-
"X-CSRFToken":
|
|
65
|
+
"X-CSRFToken": token,
|
|
53
66
|
Accept: "application/json",
|
|
54
67
|
"X-Requested-With": "XMLHttpRequest"
|
|
55
68
|
},
|
|
@@ -57,16 +70,24 @@ export const get = (url, params = {}, csrfToken = false) => {
|
|
|
57
70
|
}).then(handleFetchErrors)
|
|
58
71
|
}
|
|
59
72
|
|
|
60
|
-
export const getJson = (
|
|
61
|
-
|
|
73
|
+
export const getJson = (
|
|
74
|
+
url: string,
|
|
75
|
+
params: Record<string, string> = {},
|
|
76
|
+
csrfToken: string | false = false
|
|
77
|
+
): Promise<unknown> => get(url, params, csrfToken).then(response => response.json())
|
|
62
78
|
|
|
63
|
-
export const postBare = (
|
|
79
|
+
export const postBare = (
|
|
80
|
+
url: string,
|
|
81
|
+
object: Record<string, unknown> = {},
|
|
82
|
+
files: PostFiles = {},
|
|
83
|
+
options: PostOptions = {}
|
|
84
|
+
): Promise<Response> => {
|
|
64
85
|
const settings = getSettings()
|
|
65
86
|
|
|
66
87
|
const {csrfToken: csrfTokenOpt, keepalive = false} = options
|
|
67
88
|
const csrfToken = csrfTokenOpt || settings.getCsrfToken() // Won't work in web worker.
|
|
68
89
|
|
|
69
|
-
const fetchOptions = {
|
|
90
|
+
const fetchOptions: RequestInit = {
|
|
70
91
|
method: "POST",
|
|
71
92
|
headers: {
|
|
72
93
|
"X-CSRFToken": csrfToken,
|
|
@@ -83,34 +104,51 @@ export const postBare = (url, object = {}, files = {}, options = {}) => {
|
|
|
83
104
|
body.append("json", JSON.stringify(object))
|
|
84
105
|
Object.keys(files).forEach(key => {
|
|
85
106
|
const value = files[key]
|
|
86
|
-
if (
|
|
87
|
-
|
|
107
|
+
if (
|
|
108
|
+
typeof value === "object" &&
|
|
109
|
+
value !== null &&
|
|
110
|
+
"file" in value &&
|
|
111
|
+
"filename" in value
|
|
112
|
+
) {
|
|
113
|
+
const uploadValue = value as FileUploadValue
|
|
114
|
+
body.append(key, uploadValue.file, uploadValue.filename)
|
|
88
115
|
} else if (Array.isArray(value)) {
|
|
89
116
|
value.forEach(item => body.append(`${key}[]`, item))
|
|
90
|
-
} else {
|
|
91
|
-
body.append(key, value)
|
|
117
|
+
} else if (value !== undefined && value !== null) {
|
|
118
|
+
body.append(key, value as Blob | string)
|
|
92
119
|
}
|
|
93
120
|
})
|
|
94
121
|
fetchOptions.body = body
|
|
95
122
|
} else {
|
|
96
|
-
fetchOptions.headers["Content-Type"] =
|
|
123
|
+
;(fetchOptions.headers as Record<string, string>)["Content-Type"] =
|
|
124
|
+
"application/json"
|
|
97
125
|
fetchOptions.body = JSON.stringify(object)
|
|
98
126
|
}
|
|
99
127
|
|
|
100
128
|
return fetch(settings.apiUrl(url), fetchOptions)
|
|
101
129
|
}
|
|
102
130
|
|
|
103
|
-
export const post = (
|
|
131
|
+
export const post = (
|
|
132
|
+
url: string,
|
|
133
|
+
object: Record<string, unknown> = {},
|
|
134
|
+
files: PostFiles = {},
|
|
135
|
+
options: PostOptions = {}
|
|
136
|
+
): Promise<Response> => {
|
|
104
137
|
return postBare(url, object, files, options).then(handleFetchErrors)
|
|
105
138
|
}
|
|
106
139
|
|
|
107
140
|
// post json object and return json and status
|
|
108
|
-
export const postJson = (
|
|
141
|
+
export const postJson = (
|
|
142
|
+
url: string,
|
|
143
|
+
object: Record<string, unknown> = {},
|
|
144
|
+
files: PostFiles = {},
|
|
145
|
+
options: PostOptions = {}
|
|
146
|
+
): Promise<{json: unknown; status: number}> =>
|
|
109
147
|
post(url, object, files, options).then(response =>
|
|
110
148
|
response.json().then(json => ({json, status: response.status}))
|
|
111
149
|
)
|
|
112
150
|
|
|
113
|
-
export const ensureCSS = cssUrl => {
|
|
151
|
+
export const ensureCSS = (cssUrl: string | string[]): boolean | void => {
|
|
114
152
|
if (typeof cssUrl === "object") {
|
|
115
153
|
cssUrl.forEach(url => ensureCSS(url))
|
|
116
154
|
return
|