Skip to content

Commit 7711716

Browse files
committed
feat: implement user mentions in Collabora docs
Users can be mentioned with `@` in Collabora documents, triggering a notification to the mentioned user. Also refactors all Collabora postMessage stuff to a dedicated composable.
1 parent adb8ff3 commit 7711716

7 files changed

Lines changed: 1243 additions & 371 deletions

File tree

packages/web-app-external/src/App.vue

Lines changed: 11 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
unref,
4343
nextTick,
4444
ref,
45+
toRef,
4546
watch,
4647
onMounted,
4748
useTemplateRef,
@@ -73,15 +74,10 @@ import {
7374
useSpacesStore,
7475
useClientService,
7576
useSharesStore,
76-
useModals,
77-
useRouter,
78-
useThemeStore,
79-
useFolderLink,
80-
FilePickerModal
77+
useThemeStore
8178
} from '@opencloud-eu/web-pkg'
82-
import FileNameModal from './components/FileNameModal.vue'
83-
import { DavProperty } from '@opencloud-eu/web-client/webdav'
8479
import { storeToRefs } from 'pinia'
80+
import { useCollaboraPostMessages } from './composables'
8581
8682
const { space, resource, isReadOnly } = defineProps<{
8783
space: SpaceResource
@@ -97,16 +93,13 @@ const { showErrorMessage } = useMessages()
9793
const capabilityStore = useCapabilityStore()
9894
const configStore = useConfigStore()
9995
const route = useRoute()
100-
const router = useRouter()
10196
const appProviderService = useAppProviderService()
10297
const { makeRequest } = useRequest()
10398
const spacesStore = useSpacesStore()
10499
const sharesStore = useSharesStore()
105-
const { graphAuthenticated: graphClient, webdav } = useClientService()
106-
const { dispatchModal } = useModals()
100+
const { graphAuthenticated: graphClient } = useClientService()
107101
const themeStore = useThemeStore()
108102
const { currentTheme } = storeToRefs(themeStore)
109-
const { getParentFolderLink } = useFolderLink()
110103
111104
const viewModeQuery = useRouteQuery('view_mode')
112105
const viewModeQueryValue = computed(() => {
@@ -260,166 +253,13 @@ const catchClickMicrosoftEdit = (event: MessageEvent) => {
260253
} catch {}
261254
}
262255
263-
const handlePostMessagesCollabora = async (event: MessageEvent) => {
264-
try {
265-
const message = JSON.parse(event.data || '{}')
266-
267-
if (message.MessageId === 'App_LoadingStatus') {
268-
postMessageToCollabora('Hide_Button', { id: 'toggledarktheme' })
269-
270-
if (message.Values?.Status === 'Frame_Ready') {
271-
postMessageToCollabora('Host_PostmessageReady')
272-
}
273-
return
274-
}
275-
276-
if (message.MessageId === 'UI_SaveAs') {
277-
if (Object.hasOwn(message.Values, 'format')) {
278-
dispatchModal({
279-
title: $gettext('Export »%{name}« as %{format}', {
280-
name: resource.name,
281-
format: message.Values.format
282-
}),
283-
customComponent: FileNameModal,
284-
customComponentAttrs: () => ({
285-
space,
286-
resource,
287-
fileExtension: message.Values.format,
288-
callbackFn: (newFileName: string) => {
289-
postMessageToCollabora('Action_SaveAs', {
290-
Filename: newFileName,
291-
Notify: true
292-
})
293-
}
294-
})
295-
})
296-
return
297-
}
298-
299-
dispatchModal({
300-
title: $gettext('Save »%{name}« with new name', { name: resource.name }),
301-
customComponent: FileNameModal,
302-
customComponentAttrs: () => ({
303-
space,
304-
resource,
305-
callbackFn: (newFileName: string) => {
306-
postMessageToCollabora('Action_SaveAs', {
307-
Filename: newFileName,
308-
Notify: true
309-
})
310-
}
311-
})
312-
})
313-
return
314-
}
315-
316-
if (message.MessageId === 'Action_Save_Resp') {
317-
if (!message.Values?.fileName) {
318-
return
319-
}
320-
321-
// FIXME: when we move to id based propfinds we magically need a fileId for the new file. Collabora doesn't provide that.
322-
const newFile = await webdav.getFileInfo(space, {
323-
path:
324-
resource.path.substring(0, resource.path.length - resource.name.length) +
325-
message.Values.fileName,
326-
fileId: undefined
327-
})
328-
await router.push({
329-
name: unref(route).name,
330-
params: {
331-
...unref(route).params,
332-
driveAliasAndItem: queryItemAsString(unref(route).params.driveAliasAndItem).replace(
333-
resource.name,
334-
newFile.name
335-
)
336-
},
337-
query: {
338-
...unref(route).query,
339-
fileId: newFile.fileId
340-
}
341-
})
342-
return
343-
}
344-
345-
if (message.MessageId === 'UI_InsertGraphic') {
346-
dispatchModal({
347-
elementClass: 'file-picker-modal',
348-
title: $gettext('Insert graphic'),
349-
customComponent: FilePickerModal,
350-
hideActions: true,
351-
customComponentAttrs: () => ({
352-
parentFolderLink: getParentFolderLink(resource),
353-
allowedFileTypes: ['image/png', 'image/gif', 'image/jpeg', 'image/svg'],
354-
callbackFn: async ({ resource }: { resource: Resource }) => {
355-
const { downloadURL: url } = await webdav.getFileInfo(space, resource, {
356-
davProperties: [DavProperty.DownloadURL]
357-
})
358-
359-
postMessageToCollabora('Action_InsertGraphic', { url })
360-
}
361-
}),
362-
focusTrapInitial: false
363-
})
364-
return
365-
}
366-
367-
if (message.MessageId === 'UI_InsertFile') {
368-
const callback = message.Values?.callback
369-
const mimeTypeFilter = message.Values?.mimeTypeFilter
370-
371-
dispatchModal({
372-
elementClass: 'file-picker-modal',
373-
title:
374-
callback === 'Action_CompareDocuments'
375-
? $gettext('Select document to compare')
376-
: $gettext('Insert file'),
377-
customComponent: FilePickerModal,
378-
hideActions: true,
379-
customComponentAttrs: () => ({
380-
parentFolderLink: getParentFolderLink(resource),
381-
allowedFileTypes: mimeTypeFilter || [],
382-
callbackFn: async ({ resource }: { resource: Resource }) => {
383-
const { downloadURL: url } = await webdav.getFileInfo(space, resource, {
384-
davProperties: [DavProperty.DownloadURL]
385-
})
386-
387-
const values: Record<string, unknown> = { url }
388-
if (callback === 'Action_CompareDocuments') {
389-
values.filename = resource.name
390-
}
391-
392-
postMessageToCollabora(callback, values)
393-
}
394-
}),
395-
focusTrapInitial: false
396-
})
397-
return
398-
}
256+
const appIframeRef = useTemplateRef<HTMLIFrameElement>('appIframe')
399257
400-
if (message.MessageId === 'UI_PickLink') {
401-
dispatchModal({
402-
elementClass: 'file-picker-modal',
403-
title: $gettext('Pick a file to link'),
404-
customComponent: FilePickerModal,
405-
hideActions: true,
406-
customComponentAttrs: () => ({
407-
parentFolderLink: getParentFolderLink(resource),
408-
allowedFileTypes: [],
409-
callbackFn: ({ resource }: { resource: Resource }) => {
410-
postMessageToCollabora('Action_InsertLink', {
411-
url: resource.privateLink,
412-
text: resource.name
413-
})
414-
}
415-
}),
416-
focusTrapInitial: false
417-
})
418-
}
419-
} catch (e) {
420-
console.debug('Error parsing Collabora PostMessage', e)
421-
}
422-
}
258+
const { handlePostMessagesCollabora, resetMentionState } = useCollaboraPostMessages({
259+
space: toRef(() => space),
260+
resource: toRef(() => resource),
261+
appIframeRef
262+
})
423263
424264
onMounted(() => {
425265
if (determineOpenAsPreview(unref(appName))) {
@@ -439,30 +279,14 @@ onBeforeUnmount(() => {
439279
}
440280
})
441281
442-
const appIframeRef = useTemplateRef<HTMLIFrameElement>('appIframe')
443-
const postMessageToCollabora = (messageId: string, values?: { [key: string]: unknown }): void => {
444-
if (!unref(appIframeRef)) {
445-
console.error('Collabora iframe not found')
446-
return
447-
}
448-
449-
return unref(appIframeRef).contentWindow.postMessage(
450-
JSON.stringify({
451-
MessageId: messageId,
452-
SendTime: Date.now(),
453-
...(values && { Values: values })
454-
}),
455-
'*'
456-
)
457-
}
458-
459282
watch(
460283
() => resource,
461284
async (newResource, oldResource) => {
462285
if (isSameResource(newResource, oldResource)) {
463286
return
464287
}
465288
289+
resetMentionState()
466290
let viewMode = 'read'
467291
468292
if (isShareSpaceResource(space)) {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './createFileHandler'
2+
export * from './useCollaboraPostMessages'

0 commit comments

Comments
 (0)