@fiduswriter/editor 0.1.75 → 0.1.76
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/dist/document_template/index.js +3 -3
- package/dist/document_template/index.js.map +1 -1
- package/dist/documents/access_rights/index.d.ts +5 -2
- package/dist/documents/access_rights/index.d.ts.map +1 -1
- package/dist/documents/access_rights/index.js +20 -10
- package/dist/documents/access_rights/index.js.map +1 -1
- package/dist/documents/access_rights/request_access_dialog.d.ts +23 -0
- package/dist/documents/access_rights/request_access_dialog.d.ts.map +1 -0
- package/dist/documents/access_rights/request_access_dialog.js +102 -0
- package/dist/documents/access_rights/request_access_dialog.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -30
- package/dist/index.js.map +1 -1
- package/dist/menus/headerbar/model.d.ts +2 -2
- package/dist/menus/headerbar/model.d.ts.map +1 -1
- package/dist/menus/headerbar/model.js +37 -32
- package/dist/menus/headerbar/model.js.map +1 -1
- package/package.json +1 -1
- package/src/document_template/index.ts +3 -3
- package/src/documents/access_rights/index.ts +39 -26
- package/src/documents/access_rights/request_access_dialog.ts +151 -0
- package/src/index.ts +57 -35
- package/src/menus/headerbar/model.ts +44 -51
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {Dialog, addAlert} from "fwtoolkit"
|
|
2
|
+
|
|
3
|
+
import type {EditorDocumentApi} from "../../types.js"
|
|
4
|
+
|
|
5
|
+
interface RequestableRight {
|
|
6
|
+
value: string
|
|
7
|
+
label: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const REQUESTABLE_RIGHTS: RequestableRight[] = [
|
|
11
|
+
{value: "read", label: gettext("Read")},
|
|
12
|
+
{value: "comment", label: gettext("Comment")},
|
|
13
|
+
{value: "review", label: gettext("Review")},
|
|
14
|
+
{value: "write-tracked", label: gettext("Write tracked")},
|
|
15
|
+
{value: "write", label: gettext("Write")}
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
const RIGHT_ORDER = REQUESTABLE_RIGHTS.map(right => right.value)
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Dialog for requesting access to a document.
|
|
22
|
+
*
|
|
23
|
+
* For users who already have some access, only rights that are higher than
|
|
24
|
+
* the current level are offered. Token users and users without any known
|
|
25
|
+
* access can choose from all requestable rights.
|
|
26
|
+
*/
|
|
27
|
+
export class RequestAccessDialog {
|
|
28
|
+
documentId: number
|
|
29
|
+
currentRights: string
|
|
30
|
+
documentApi: EditorDocumentApi
|
|
31
|
+
isTokenUser: boolean
|
|
32
|
+
|
|
33
|
+
constructor(
|
|
34
|
+
documentId: number,
|
|
35
|
+
currentRights = "",
|
|
36
|
+
documentApi: EditorDocumentApi,
|
|
37
|
+
isTokenUser = false
|
|
38
|
+
) {
|
|
39
|
+
this.documentId = documentId
|
|
40
|
+
this.currentRights = currentRights
|
|
41
|
+
this.documentApi = documentApi
|
|
42
|
+
this.isTokenUser = isTokenUser
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
open(): void {
|
|
46
|
+
const rightsOptions = this.getRightsOptions()
|
|
47
|
+
if (!rightsOptions.length) {
|
|
48
|
+
addAlert(
|
|
49
|
+
"error",
|
|
50
|
+
gettext("You already have the highest possible access level.")
|
|
51
|
+
)
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
const defaultRights =
|
|
55
|
+
rightsOptions.find(right => right.value === "write")?.value ||
|
|
56
|
+
rightsOptions[rightsOptions.length - 1].value
|
|
57
|
+
const bodyText = this.currentRights
|
|
58
|
+
? gettext(
|
|
59
|
+
"Select a higher access level to request from the document owner."
|
|
60
|
+
)
|
|
61
|
+
: gettext(
|
|
62
|
+
"You do not have access to this document. Select the access level you would like to request from the document owner."
|
|
63
|
+
)
|
|
64
|
+
const dialog = new Dialog({
|
|
65
|
+
title: gettext("Request Access"),
|
|
66
|
+
id: "request-access-dialog",
|
|
67
|
+
width: 500,
|
|
68
|
+
body: `<p>${bodyText}</p>
|
|
69
|
+
<table class="fw-dialog-table">
|
|
70
|
+
<tbody>
|
|
71
|
+
<tr>
|
|
72
|
+
<th><label for="request-access-rights">${gettext(
|
|
73
|
+
"Access level"
|
|
74
|
+
)}</label></th>
|
|
75
|
+
<td class="entry-field">
|
|
76
|
+
<select id="request-access-rights" class="fw-button fw-light fw-large">
|
|
77
|
+
${rightsOptions
|
|
78
|
+
.map(
|
|
79
|
+
right =>
|
|
80
|
+
`<option value="${right.value}"${right.value === defaultRights ? " selected" : ""}>${right.label}</option>`
|
|
81
|
+
)
|
|
82
|
+
.join("")}
|
|
83
|
+
</select>
|
|
84
|
+
<div class="fw-select-arrow fa-solid fa-caret-down"></div>
|
|
85
|
+
</td>
|
|
86
|
+
</tr>
|
|
87
|
+
</tbody>
|
|
88
|
+
</table>`,
|
|
89
|
+
buttons: [
|
|
90
|
+
{
|
|
91
|
+
text: gettext("Request"),
|
|
92
|
+
classes: "fw-dark",
|
|
93
|
+
click: () => {
|
|
94
|
+
const rights = (
|
|
95
|
+
dialog.dialogEl.querySelector(
|
|
96
|
+
"#request-access-rights"
|
|
97
|
+
) as HTMLSelectElement
|
|
98
|
+
).value
|
|
99
|
+
this.documentApi
|
|
100
|
+
.requestAccess({
|
|
101
|
+
document_id: this.documentId,
|
|
102
|
+
rights
|
|
103
|
+
})
|
|
104
|
+
.then(({json}: {json: unknown}) => {
|
|
105
|
+
const data = json as {
|
|
106
|
+
success?: boolean
|
|
107
|
+
error?: string
|
|
108
|
+
}
|
|
109
|
+
if (data.success) {
|
|
110
|
+
addAlert(
|
|
111
|
+
"success",
|
|
112
|
+
gettext(
|
|
113
|
+
"Your access request has been sent to the document owner."
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
dialog.close()
|
|
117
|
+
} else {
|
|
118
|
+
addAlert(
|
|
119
|
+
"error",
|
|
120
|
+
data.error ||
|
|
121
|
+
gettext(
|
|
122
|
+
"Could not send access request."
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
.catch(() => {
|
|
128
|
+
addAlert(
|
|
129
|
+
"error",
|
|
130
|
+
gettext("Could not send access request.")
|
|
131
|
+
)
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
{type: "cancel"}
|
|
136
|
+
]
|
|
137
|
+
})
|
|
138
|
+
dialog.open()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
getRightsOptions(): RequestableRight[] {
|
|
142
|
+
if (this.isTokenUser || !this.currentRights) {
|
|
143
|
+
return REQUESTABLE_RIGHTS
|
|
144
|
+
}
|
|
145
|
+
const currentIndex = RIGHT_ORDER.indexOf(this.currentRights)
|
|
146
|
+
if (currentIndex < 0) {
|
|
147
|
+
return REQUESTABLE_RIGHTS
|
|
148
|
+
}
|
|
149
|
+
return REQUESTABLE_RIGHTS.filter((_, index) => index > currentIndex)
|
|
150
|
+
}
|
|
151
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -41,6 +41,7 @@ import {ModCollab} from "./collab/index.js"
|
|
|
41
41
|
import {ModComments} from "./comments/index.js"
|
|
42
42
|
import {ModDB} from "./databases/index.js"
|
|
43
43
|
import {ModDocumentTemplate} from "./document_template/index.js"
|
|
44
|
+
import {RequestAccessDialog} from "./documents/access_rights/request_access_dialog.js"
|
|
44
45
|
import {E2EESnapshotManager} from "./e2ee/snapshot-manager.js"
|
|
45
46
|
import {ExportFidusFile} from "./exporter/native/file.js"
|
|
46
47
|
import {ModFootnotes} from "./footnotes/index.js"
|
|
@@ -374,47 +375,68 @@ export class Editor {
|
|
|
374
375
|
token: this.docInfo.token
|
|
375
376
|
})
|
|
376
377
|
: Promise.resolve({json: {ws_base: ""}})
|
|
377
|
-
const stylesPromise = documentApi
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
378
|
+
const stylesPromise = documentApi
|
|
379
|
+
.getDocumentStyles(
|
|
380
|
+
stylesPayload as {id: number; token?: string}
|
|
381
|
+
)
|
|
382
|
+
.catch(() => {
|
|
383
|
+
return {json: {}}
|
|
384
|
+
})
|
|
385
|
+
const docDataPromise = documentApi
|
|
386
|
+
.getDocumentData(
|
|
387
|
+
stylesPayload as {id: number; token?: string}
|
|
388
|
+
)
|
|
389
|
+
.catch(error => {
|
|
390
|
+
// Only show "Invalid Share Link" for token validation errors.
|
|
391
|
+
// For authenticated users without access, offer to request access.
|
|
392
|
+
// This is also shown when the document does not exist so that we
|
|
393
|
+
// do not reveal whether a document with the given ID exists.
|
|
394
|
+
if (error.message === "Invalid or expired share link") {
|
|
395
|
+
deactivateWait()
|
|
396
|
+
const errorDialog = new Dialog({
|
|
397
|
+
title: gettext("Invalid Share Link"),
|
|
398
|
+
id: "invalid_share_link_dialog",
|
|
399
|
+
body: gettext(
|
|
400
|
+
"This share link has expired or is invalid. Please ask the document owner for a new link."
|
|
401
|
+
),
|
|
402
|
+
buttons: [
|
|
403
|
+
{
|
|
404
|
+
text: gettext("OK"),
|
|
405
|
+
classes: "fw-dark",
|
|
406
|
+
click: () => {
|
|
407
|
+
window.location.href = "/"
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
],
|
|
411
|
+
canClose: false
|
|
412
|
+
})
|
|
413
|
+
errorDialog.open()
|
|
414
|
+
return Promise.reject(false)
|
|
415
|
+
} else if (
|
|
416
|
+
error.status === 401 &&
|
|
417
|
+
this.user.is_authenticated
|
|
418
|
+
) {
|
|
419
|
+
deactivateWait()
|
|
420
|
+
const requestAccessDialog = new RequestAccessDialog(
|
|
421
|
+
this.docInfo.id as number,
|
|
422
|
+
"",
|
|
423
|
+
documentApi,
|
|
424
|
+
Boolean(this.docInfo.token)
|
|
425
|
+
)
|
|
426
|
+
requestAccessDialog.open()
|
|
427
|
+
return Promise.reject(false)
|
|
428
|
+
} else {
|
|
429
|
+
deactivateWait()
|
|
430
|
+
console.error("Editor initialization failed:", error)
|
|
431
|
+
}
|
|
432
|
+
return Promise.reject(error)
|
|
433
|
+
})
|
|
383
434
|
return Promise.all([
|
|
384
435
|
wsBasePromise,
|
|
385
436
|
stylesPromise,
|
|
386
437
|
docDataPromise
|
|
387
438
|
])
|
|
388
439
|
})
|
|
389
|
-
.catch(error => {
|
|
390
|
-
// Only show "Invalid Share Link" for token validation errors.
|
|
391
|
-
// Other errors (REST failures, etc.) should not show this dialog.
|
|
392
|
-
if (error.message === "Invalid or expired share link") {
|
|
393
|
-
deactivateWait()
|
|
394
|
-
const errorDialog = new Dialog({
|
|
395
|
-
title: gettext("Invalid Share Link"),
|
|
396
|
-
id: "invalid_share_link_dialog",
|
|
397
|
-
body: gettext(
|
|
398
|
-
"This share link has expired or is invalid. Please ask the document owner for a new link."
|
|
399
|
-
),
|
|
400
|
-
buttons: [
|
|
401
|
-
{
|
|
402
|
-
text: gettext("OK"),
|
|
403
|
-
classes: "fw-dark",
|
|
404
|
-
click: () => {
|
|
405
|
-
window.location.href = "/"
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
],
|
|
409
|
-
canClose: false
|
|
410
|
-
})
|
|
411
|
-
errorDialog.open()
|
|
412
|
-
} else {
|
|
413
|
-
deactivateWait()
|
|
414
|
-
console.error("Editor initialization failed:", error)
|
|
415
|
-
}
|
|
416
|
-
return Promise.reject(error)
|
|
417
|
-
})
|
|
418
440
|
.then(([wsResult, stylesResult, docResult]) => {
|
|
419
441
|
let resubScribed = false
|
|
420
442
|
this.render()
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import type {BibDB, ExportDoc, ImageDB} from "@fiduswriter/document"
|
|
8
8
|
import {CopyrightDialog} from "../../copyright_dialog/index.js"
|
|
9
9
|
import {DocumentAccessRightsDialog} from "../../documents/access_rights/index.js"
|
|
10
|
+
import {RequestAccessDialog} from "../../documents/access_rights/request_access_dialog.js"
|
|
10
11
|
import {SaveCopy, SaveRevision} from "../../exporter/native/index.js"
|
|
11
12
|
import {ExportFidusFile} from "../../exporter/native/file.js"
|
|
12
13
|
import {LanguageDialog, RevisionDialog} from "../../dialogs/index.js"
|
|
@@ -72,6 +73,13 @@ const exportProgress = (doc: {title: string; path?: string}) => {
|
|
|
72
73
|
task.update(percentage ?? null, message)
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
const showRequestAccess = (editor: Editor): boolean =>
|
|
77
|
+
editor.user.is_authenticated === true &&
|
|
78
|
+
!editor.docInfo.is_owner &&
|
|
79
|
+
(Boolean(editor.docInfo.token) ||
|
|
80
|
+
(Boolean(editor.docInfo.access_rights) &&
|
|
81
|
+
editor.docInfo.access_rights !== "write"))
|
|
82
|
+
|
|
75
83
|
const languageItem = (
|
|
76
84
|
language: string,
|
|
77
85
|
name: string,
|
|
@@ -115,62 +123,26 @@ export const headerbarModel = () => ({
|
|
|
115
123
|
content: [
|
|
116
124
|
{
|
|
117
125
|
title: (editor: Editor) =>
|
|
118
|
-
editor
|
|
119
|
-
editor.docInfo.token &&
|
|
120
|
-
!editor.docInfo.is_owner
|
|
126
|
+
showRequestAccess(editor)
|
|
121
127
|
? gettext("Request Access")
|
|
122
128
|
: gettext("Share"),
|
|
123
129
|
type: "action",
|
|
124
130
|
//icon: 'share',
|
|
125
131
|
tooltip: (editor: Editor) =>
|
|
126
|
-
editor
|
|
127
|
-
editor.docInfo.token &&
|
|
128
|
-
!editor.docInfo.is_owner
|
|
132
|
+
showRequestAccess(editor)
|
|
129
133
|
? gettext("Request to be added as a collaborator.")
|
|
130
134
|
: gettext("Share the document with other users."),
|
|
131
135
|
order: 0,
|
|
132
136
|
action: (editor: Editor) => {
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
rights: "write"
|
|
143
|
-
})
|
|
144
|
-
.then(({json}: {json: unknown}) => {
|
|
145
|
-
const data = json as {
|
|
146
|
-
success?: boolean
|
|
147
|
-
error?: string
|
|
148
|
-
}
|
|
149
|
-
if (data.success) {
|
|
150
|
-
addAlert(
|
|
151
|
-
"success",
|
|
152
|
-
gettext(
|
|
153
|
-
"Your access request has been sent to the document owner."
|
|
154
|
-
)
|
|
155
|
-
)
|
|
156
|
-
} else {
|
|
157
|
-
addAlert(
|
|
158
|
-
"error",
|
|
159
|
-
data.error ||
|
|
160
|
-
gettext(
|
|
161
|
-
"Could not send access request."
|
|
162
|
-
)
|
|
163
|
-
)
|
|
164
|
-
}
|
|
165
|
-
})
|
|
166
|
-
.catch(() => {
|
|
167
|
-
addAlert(
|
|
168
|
-
"error",
|
|
169
|
-
gettext(
|
|
170
|
-
"Could not send access request."
|
|
171
|
-
)
|
|
172
|
-
)
|
|
173
|
-
})
|
|
137
|
+
if (showRequestAccess(editor)) {
|
|
138
|
+
// Request higher access rights from the document owner
|
|
139
|
+
const requestAccessDialog = new RequestAccessDialog(
|
|
140
|
+
editor.docInfo.id as number,
|
|
141
|
+
editor.docInfo.access_rights || "",
|
|
142
|
+
editor.app.apiConnectors.document,
|
|
143
|
+
Boolean(editor.docInfo.token)
|
|
144
|
+
)
|
|
145
|
+
requestAccessDialog.open()
|
|
174
146
|
return
|
|
175
147
|
}
|
|
176
148
|
const onShareSuccess = async (
|
|
@@ -254,19 +226,40 @@ export const headerbarModel = () => ({
|
|
|
254
226
|
editor.e2ee?.password || "",
|
|
255
227
|
onShareSuccess,
|
|
256
228
|
editor.app.settings,
|
|
229
|
+
editor.docInfo.is_owner,
|
|
257
230
|
editor.app.apiConnectors.contacts,
|
|
258
231
|
editor.app.apiConnectors.document
|
|
259
232
|
)
|
|
260
233
|
shareDialog.init()
|
|
261
234
|
},
|
|
235
|
+
available: (editor: Editor) => {
|
|
236
|
+
if (editor.app.settings.EDITOR_SAVE_MODE === "external") {
|
|
237
|
+
return false
|
|
238
|
+
}
|
|
239
|
+
if (!editor.user.is_authenticated) {
|
|
240
|
+
return true
|
|
241
|
+
}
|
|
242
|
+
if (editor.docInfo.is_owner) {
|
|
243
|
+
return true
|
|
244
|
+
}
|
|
245
|
+
if (editor.docInfo.token) {
|
|
246
|
+
return true
|
|
247
|
+
}
|
|
248
|
+
if (!editor.docInfo.access_rights) {
|
|
249
|
+
return false
|
|
250
|
+
}
|
|
251
|
+
return editor.docInfo.access_rights !== "write"
|
|
252
|
+
},
|
|
262
253
|
disabled: (editor: Editor) => {
|
|
263
254
|
return (
|
|
264
255
|
editor.app.isOffline() ||
|
|
265
|
-
!editor.user.is_authenticated
|
|
256
|
+
!editor.user.is_authenticated ||
|
|
257
|
+
!editor.docInfo.owner ||
|
|
258
|
+
(!editor.docInfo.is_owner &&
|
|
259
|
+
!editor.docInfo.token &&
|
|
260
|
+
editor.docInfo.access_rights === "write")
|
|
266
261
|
)
|
|
267
|
-
}
|
|
268
|
-
available: (editor: Editor) =>
|
|
269
|
-
editor.app.settings.EDITOR_SAVE_MODE !== "external"
|
|
262
|
+
}
|
|
270
263
|
},
|
|
271
264
|
{
|
|
272
265
|
title: (editor: Editor) =>
|