Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/Listener/BeforeTemplateRenderedListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ public function handle(Event $event): void {
return;
}

if ($event->getScope() === BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH) {
return;
}

try {
$node = $event->getShare()->getNode();
} catch (NotFoundException) {
Expand Down
120 changes: 86 additions & 34 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,31 +122,66 @@ function runRecordingRuntime(context: RecordingContext): void {
}

function runPublicShareRuntime(context: PublicShareContext): void {
let hasRegisteredViewer = false
document.body.classList.add('whiteboard-public-share')

// On NC29/30, there's a hidden input with id="mimetype" that we can check.
// On NC31+, this element doesn't exist. Since this script is only loaded
// for whiteboard files (via BeforeTemplateRenderedListener), we can safely
// skip this check if the element doesn't exist.
const mimetypeElmt = document.getElementById('mimetype') as HTMLInputElement | null
if (mimetypeElmt && mimetypeElmt.value !== 'application/vnd.excalidraw+json') {
return
}

const viewerContext: ViewerContext = {
collabBackendUrl: context.collabBackendUrl,
resolveSharingToken: () => context.sharingToken,
}

const ensureViewerRegistered = () => {
if (hasRegisteredViewer) {
let hasOpenedInViewer = false
const openInViewer = (): void => {
if (hasOpenedInViewer) {
return
}
hasRegisteredViewer = true

runDefaultViewerRuntime({
collabBackendUrl: context.collabBackendUrl,
resolveSharingToken: () => context.sharingToken,
})
}
const viewerApi = getViewerApi()
if (!viewerApi) {
return
}

const tryBootstrap = (): boolean => {
const previewHost = document.getElementById('preview') || document.getElementById('imgframe')
if (typeof viewerApi.openWith !== 'function' && typeof viewerApi.open !== 'function') {
return
}

if (!previewHost) {
return false
hasOpenedInViewer = true

try {
viewerApi.setRootElement?.(null)
} catch {
// ignore
}

try {
if (typeof viewerApi.openWith === 'function') {
viewerApi.openWith('whiteboard', { path: '/', enableSidebar: false, canLoop: false })
return
}

viewerApi.open?.({ path: '/', enableSidebar: false, canLoop: false })
} catch (error) {
hasOpenedInViewer = false
logger.error('Could not open public share in viewer', { error })
}
}

const openEmbeddedFallback = (): void => {
if (hasOpenedInViewer) {
return
}

const mimetypeElmt = document.getElementById('mimetype') as HTMLInputElement | null
const isWhiteboard = mimetypeElmt?.value === 'application/vnd.excalidraw+json'
if (isPublicShare() && !isWhiteboard) {
return true
const previewHost = document.getElementById('preview') || document.getElementById('imgframe')
if (!previewHost) {
return
}

previewHost.innerHTML = ''
Expand All @@ -163,25 +198,14 @@ function runPublicShareRuntime(context: PublicShareContext): void {
versionSource: null,
fileVersion: null,
})
return true
}

runWhenDomReady(() => {
ensureViewerRegistered()

if (tryBootstrap()) {
return
}

const observer = new MutationObserver(() => {
ensureViewerRegistered()
if (tryBootstrap()) {
observer.disconnect()
}
})
registerViewerHandler(createWhiteboardComponent(viewerContext), 0, openInViewer)

observer.observe(document.body, { childList: true, subtree: true })
window.setTimeout(() => observer.disconnect(), 20000)
window.setTimeout(() => {
openEmbeddedFallback()
}, 2500)
})
}

Expand Down Expand Up @@ -221,6 +245,29 @@ type ViewerHandlerRegistration = {

type ViewerApi = {
registerHandler?: (handler: ViewerHandlerRegistration) => void
open?: (options?: {
path?: string
fileInfo?: unknown
list?: unknown[]
enableSidebar?: boolean
loadMore?: () => unknown[]
canLoop?: boolean
onPrev?: () => void
onNext?: () => void
onClose?: () => void
}) => void
openWith?: (handlerId: string, options?: {
path?: string
fileInfo?: unknown
list?: unknown[]
enableSidebar?: boolean
loadMore?: () => unknown[]
canLoop?: boolean
onPrev?: () => void
onNext?: () => void
onClose?: () => void
}) => void
setRootElement?: (el?: string | null) => void
compareFileInfo?: unknown
}

Expand Down Expand Up @@ -302,7 +349,11 @@ const createWhiteboardComponent = (options: ViewerComponentOptions): VueComponen
data: (): WhiteboardComponentData => ({ root: null }),
})

const registerViewerHandler = (component: VueComponentDefinition, attempt = 0): void => {
const registerViewerHandler = (
component: VueComponentDefinition,
attempt = 0,
afterRegister?: () => void,
): void => {
const viewerApi = getViewerApi()

if (viewerApi?.registerHandler) {
Expand All @@ -314,6 +365,7 @@ const registerViewerHandler = (component: VueComponentDefinition, attempt = 0):
theme: 'default',
canCompare: true,
})
afterRegister?.()
return
}

Expand All @@ -323,7 +375,7 @@ const registerViewerHandler = (component: VueComponentDefinition, attempt = 0):
}

window.setTimeout(
() => registerViewerHandler(component, attempt + 1),
() => registerViewerHandler(component, attempt + 1, afterRegister),
VIEWER_REGISTRATION_DELAY_MS,
)
}
Expand Down
1 change: 1 addition & 0 deletions src/styles/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
@use './globals/layout';
@use './overrides/excalidraw';
@use './modes/viewer';
@use './modes/public-share';
64 changes: 64 additions & 0 deletions src/styles/modes/_public-share.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

#body-public.whiteboard-public-share {
--header-height: 0px;
--footer-height: 0px;
--whiteboard-public-share-main-text: var(--color-main-text);
}

#body-public.whiteboard-public-share #header {
display: none;
}

#body-public.whiteboard-public-share #content.app-files_sharing {
--body-container-margin: 0px;
--body-container-radius: 0px;
--footer-height: 0px;
--body-height: calc(100% - env(safe-area-inset-bottom));
margin: 0 !important;
}

#body-public.whiteboard-public-share #content.app-files_sharing + footer {
display: none;
}

#body-public.whiteboard-public-share #content.app-files_sharing #app-content {
overflow: hidden;
}

#body-public.whiteboard-public-share #content.app-files_sharing #files-public-content,
#body-public.whiteboard-public-share #content.app-files_sharing #preview {
height: 100%;
}

#body-public.whiteboard-public-share #content.app-files_sharing #preview {
position: relative;
}

#body-public.whiteboard-public-share #content.app-files_sharing .whiteboard {
height: 100%;
width: 100%;
inset: 0;
}

#body-public.whiteboard-public-share .network-status,
#body-public.whiteboard-public-share .grid-toggle-button {
display: none;
}

#body-public.whiteboard-public-share .excalidraw .sidebar-trigger__label {
line-height: 1.2;
height: auto;
}

#body-public.whiteboard-public-share #viewer .modal-header .button-vue {
color: var(--whiteboard-public-share-main-text) !important;
}

#body-public.whiteboard-public-share #viewer .whiteboard {
top: 50px;
height: calc(100% - 50px);
}
Loading