diff --git a/playwright/e2e/version-preview.spec.ts b/playwright/e2e/version-preview.spec.ts
index 378dbe00..b4ca7512 100644
--- a/playwright/e2e/version-preview.spec.ts
+++ b/playwright/e2e/version-preview.spec.ts
@@ -4,7 +4,7 @@
*/
import { Buffer } from 'buffer'
-import { expect } from '@playwright/test'
+import { expect, type Page } from '@playwright/test'
import { test } from '../support/fixtures/random-user'
import {
addTextElement,
@@ -17,11 +17,251 @@ import {
waitForCanvas,
} from '../support/utils'
+const versionPropfindBody = `
+
+
+
+
+`
+
+const extractVersionIds = (xml: string, userId: string, fileId: number | string): string[] => {
+ const prefix = `/remote.php/dav/versions/${userId}/versions/${fileId}/`
+ const hrefRegex = /<[^:>]*:href>([^<]+)<\/[^:>]*:href>/g
+ const versionIds = new Set()
+ let match: RegExpExecArray | null = null
+
+ while ((match = hrefRegex.exec(xml)) !== null) {
+ const href = decodeURIComponent(match[1])
+ const index = href.indexOf(prefix)
+ if (index === -1) {
+ continue
+ }
+ const remainder = href.slice(index + prefix.length).replace(/\/$/, '')
+ if (remainder) {
+ versionIds.add(remainder)
+ }
+ }
+
+ return [...versionIds]
+}
+
+const findVersionByContent = async (
+ page: Page,
+ options: {
+ origin: string
+ userId: string
+ fileId: number
+ includeText: string
+ excludeText?: string
+ },
+): Promise<{ versionId: string, versionSource: string }> => {
+ const { origin, userId, fileId, includeText, excludeText } = options
+ const listUrl = `${origin}/remote.php/dav/versions/${userId}/versions/${fileId}`
+ const maxAttempts = 20
+ const requestToken = await page.evaluate(() => (window as any).OC?.requestToken
+ || (document.querySelector('head meta[name="requesttoken"]') as HTMLMetaElement | null)?.content
+ || null)
+ const versionHeaders = {
+ Depth: '1',
+ Accept: 'application/xml',
+ 'Content-Type': 'application/xml',
+ ...(requestToken ? { requesttoken: requestToken } : {}),
+ 'X-Requested-With': 'XMLHttpRequest',
+ }
+
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
+ const response = await page.request.fetch(listUrl, {
+ method: 'PROPFIND',
+ headers: versionHeaders,
+ data: versionPropfindBody,
+ })
+
+ if (!response.ok()) {
+ throw new Error(`Version list request failed with status ${response.status()}`)
+ }
+
+ const xml = await response.text()
+ const versionIds = extractVersionIds(xml, userId, fileId)
+
+ for (const versionId of versionIds) {
+ const versionSource = `/remote.php/dav/versions/${userId}/versions/${fileId}/${versionId}`
+ const versionResponse = await page.request.get(`${origin}${versionSource}`, {
+ headers: {
+ ...(requestToken ? { requesttoken: requestToken } : {}),
+ 'X-Requested-With': 'XMLHttpRequest',
+ },
+ })
+ if (!versionResponse.ok()) {
+ continue
+ }
+ const rawContent = await versionResponse.text()
+ if (!rawContent.includes(includeText)) {
+ continue
+ }
+ if (excludeText && rawContent.includes(excludeText)) {
+ continue
+ }
+ return { versionId, versionSource }
+ }
+
+ await page.waitForTimeout(1000)
+ }
+
+ throw new Error(`No matching version found for ${includeText}`)
+}
+
+const waitForBoardContent = async (page: Page, auth: { fileId: number, jwt: string }, text: string) => {
+ await expect.poll(async () => JSON.stringify(await fetchBoardContent(page, auth)), {
+ timeout: 20000,
+ intervals: [500],
+ }).toContain(text)
+}
+
+const prepareVersionScenario = async (
+ page: Page,
+ userId: string,
+ options: { boardName: string, initialText: string, updatedText: string },
+) => {
+ const { boardName, initialText, updatedText } = options
+ await createWhiteboard(page, { name: boardName })
+ const authPromise = captureBoardAuthFromSave(page, { containsText: initialText })
+ await addTextElement(page, initialText)
+ const { fileId, jwt } = await authPromise
+ const baseAuth = { fileId, jwt }
+ await waitForBoardContent(page, baseAuth, initialText)
+
+ await addTextElement(page, updatedText, { x: 720, y: 520 })
+ await waitForBoardContent(page, baseAuth, updatedText)
+
+ const origin = new URL(await page.url()).origin
+ const versionEntry = await findVersionByContent(page, {
+ origin,
+ userId,
+ fileId: baseAuth.fileId,
+ includeText: initialText,
+ excludeText: updatedText,
+ })
+
+ await openFilesApp(page)
+ const storedName = await resolveStoredFileName(page, boardName)
+
+ return { baseAuth, origin, versionEntry, storedName }
+}
+
+const openWhiteboardInViewer = async (
+ page: Page,
+ options: { fileId: number, fileName: string, source?: string | null, fileVersion?: string | null },
+) => {
+ const filePath = options.fileName.startsWith('/') ? options.fileName : `/${options.fileName}`
+ await page.waitForFunction(() => Boolean((window as any).OCA?.Viewer?.openWith), { timeout: 10000 })
+ await page.evaluate(({ fileId, filePathValue, fileName, source, fileVersion }) => {
+ const viewer = (window as any).OCA?.Viewer
+ if (!viewer?.openWith) {
+ throw new Error('Viewer openWith unavailable')
+ }
+ viewer.openWith('whiteboard', {
+ fileInfo: {
+ fileid: Number(fileId),
+ filename: filePathValue,
+ basename: fileName,
+ source: source ?? null,
+ fileVersion: fileVersion ?? null,
+ mime: 'application/vnd.excalidraw+json',
+ size: 0,
+ type: 'file',
+ },
+ enableSidebar: false,
+ })
+ }, {
+ fileId: options.fileId,
+ filePathValue: filePath,
+ fileName: options.fileName,
+ source: options.source ?? null,
+ fileVersion: options.fileVersion ?? null,
+ })
+}
+
test.beforeEach(async ({ page }) => {
await openFilesApp(page)
})
-test('version preview params still load board content', async ({ page, user }) => {
+test('version preview banner shows and exits to live board', async ({
+ page,
+ user,
+}) => {
+ test.setTimeout(120000)
+ const boardName = `Version preview ${Date.now()}`
+ const initialText = 'Version one'
+ const updatedText = 'Version two'
+
+ const { baseAuth, versionEntry, storedName } = await prepareVersionScenario(page, user.userId, {
+ boardName,
+ initialText,
+ updatedText,
+ })
+ await openWhiteboardInViewer(page, {
+ fileId: baseAuth.fileId,
+ fileName: storedName,
+ source: versionEntry.versionSource,
+ fileVersion: versionEntry.versionId,
+ })
+ await waitForCanvas(page)
+
+ const banner = page.locator('.version-preview-banner')
+ await expect(banner).toBeVisible({ timeout: 20000 })
+ await expect(page.getByRole('button', { name: 'Restore this version' })).toBeVisible()
+
+ const backButton = page.getByRole('button', { name: 'Back to latest version' })
+ await expect(backButton).toBeVisible()
+ await backButton.click()
+
+ await expect(banner).toBeHidden({ timeout: 20000 })
+ await waitForBoardContent(page, baseAuth, updatedText)
+})
+
+test('restore version replaces current content', async ({
+ page,
+ user,
+}) => {
+ test.setTimeout(120000)
+ const boardName = `Version restore ${Date.now()}`
+ const initialText = 'Restore one'
+ const updatedText = 'Restore two'
+
+ const { baseAuth, versionEntry, storedName } = await prepareVersionScenario(page, user.userId, {
+ boardName,
+ initialText,
+ updatedText,
+ })
+ await openWhiteboardInViewer(page, {
+ fileId: baseAuth.fileId,
+ fileName: storedName,
+ source: versionEntry.versionSource,
+ fileVersion: versionEntry.versionId,
+ })
+ await waitForCanvas(page)
+
+ const restoreButton = page.getByRole('button', { name: 'Restore this version' })
+ await expect(restoreButton).toBeVisible()
+ await restoreButton.click()
+
+ const banner = page.locator('.version-preview-banner')
+ await expect(banner).toBeHidden({ timeout: 20000 })
+
+ await expect.poll(async () => JSON.stringify(await fetchBoardContent(page, baseAuth)), {
+ timeout: 30000,
+ intervals: [500],
+ }).toContain(initialText)
+ await expect.poll(async () => JSON.stringify(await fetchBoardContent(page, baseAuth)), {
+ timeout: 30000,
+ intervals: [500],
+ }).not.toContain(updatedText)
+})
+
+test('version preview params still load board content', async ({
+ page,
+ user,
+}) => {
test.setTimeout(90000)
const boardName = `Version preview ${Date.now()}`
@@ -32,7 +272,9 @@ test('version preview params still load board content', async ({ page, user }) =
try {
return await getBoardAuth(page)
} catch {
- const { fileId, jwt } = await captureBoardAuthFromSave(page, { containsText: 'Live content' })
+ const { fileId, jwt } = await captureBoardAuthFromSave(page, {
+ containsText: 'Live content',
+ })
return { fileId, jwt }
}
}
@@ -52,19 +294,34 @@ test('version preview params still load board content', async ({ page, user }) =
const escapedName = storedName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const row = page.getByRole('row', { name: new RegExp(escapedName, 'i') })
await expect(row).toBeVisible({ timeout: 30000 })
- await row.click()
+ await openWhiteboardInViewer(page, {
+ fileId: baseAuth.fileId,
+ fileName: storedName,
+ source: versionSource,
+ fileVersion: '1.0',
+ })
await waitForCanvas(page)
- const tokenResponse = await page.request.get(`apps/whiteboard/${baseAuth.fileId}/token`)
+ const tokenResponse = await page.request.get(
+ `apps/whiteboard/${baseAuth.fileId}/token`,
+ )
expect(tokenResponse.ok()).toBeTruthy()
const token = (await tokenResponse.json()).token
const previewAuth = { fileId: baseAuth.fileId, jwt: token }
- const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString())
+ const payload = JSON.parse(
+ Buffer.from(token.split('.')[1], 'base64').toString(),
+ )
expect(payload?.isFileReadOnly).toBeFalsy()
- await expect.poll(async () => JSON.stringify(await fetchBoardContent(page, previewAuth)), {
- timeout: 20000,
- interval: 500,
- }).toContain('Live content')
+ await expect
+ .poll(
+ async () =>
+ JSON.stringify(await fetchBoardContent(page, previewAuth)),
+ {
+ timeout: 20000,
+ intervals: [500],
+ },
+ )
+ .toContain('Live content')
})
diff --git a/playwright/support/utils.ts b/playwright/support/utils.ts
index 71b38d0c..c2847ed5 100644
--- a/playwright/support/utils.ts
+++ b/playwright/support/utils.ts
@@ -6,6 +6,13 @@
import { expect } from '@playwright/test'
import type { Browser, Page } from '@playwright/test'
+const fileIdPropfindBody = `
+
+
+
+
+`
+
export async function openFilesApp(page: Page) {
await page.goto('apps/files')
await page.waitForURL(/apps\/files/)
@@ -111,8 +118,10 @@ export async function addTextElement(page: Page, text: string, point: Point = {
export async function openWhiteboardFromFiles(page: Page, name: string, options: OpenWhiteboardFromFilesOptions = {}) {
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const viewOrder = options.preferSharedView
- ? ['apps/files?view=sharingin', 'apps/files/shareoverview', 'apps/files']
+ ? ['apps/files/sharingin', 'apps/files?view=sharingin', 'apps/files/shareoverview', 'apps/files']
: ['apps/files', 'apps/files/shareoverview']
+ let activeViewId = 'files'
+ let activeDir = '/'
const attemptFindEntry = async () => {
const searchBox = page.getByRole('searchbox', { name: /search here/i }).first()
@@ -140,6 +149,11 @@ export async function openWhiteboardFromFiles(page: Page, name: string, options:
const visitView = async (path: string) => {
await page.goto(path)
await page.waitForURL(/apps\/files/, { timeout: 20000 }).catch(() => {})
+ const currentUrl = new URL(await page.url())
+ const viewParam = currentUrl.searchParams.get('view')
+ const viewSegment = currentUrl.pathname.split('/').filter(Boolean).pop() || 'files'
+ activeViewId = viewParam || (viewSegment === 'files' ? 'files' : viewSegment)
+ activeDir = currentUrl.searchParams.get('dir') || '/'
return attemptFindEntry()
}
@@ -151,26 +165,122 @@ export async function openWhiteboardFromFiles(page: Page, name: string, options:
}
}
-
if (!entry) {
throw new Error(`Whiteboard file not found: ${name}`)
}
await expect(entry).toBeVisible({ timeout: 15000 })
+ await entry.scrollIntoViewIfNeeded()
- const viewButton = entry.getByRole('button', { name: /view|open/i }).first()
- if (await viewButton.count()) {
- await viewButton.click()
+ const resolvedFileId = await entry.evaluate((row) => {
+ const element = row as HTMLElement
+ const direct = element.getAttribute('data-cy-files-list-row-fileid')
+ || element.getAttribute('data-fileid')
+ || element.getAttribute('data-id')
+ if (direct) {
+ return direct
+ }
+ const nested = element.querySelector('[data-cy-files-list-row-fileid], [data-fileid], [data-id]') as HTMLElement | null
+ return nested?.getAttribute('data-cy-files-list-row-fileid')
+ || nested?.getAttribute('data-fileid')
+ || nested?.getAttribute('data-id')
+ || null
+ })
+ const resolvedFileName = await entry.evaluate((row) => {
+ const element = row as HTMLElement
+ const direct = element.getAttribute('data-cy-files-list-row-name')
+ || element.getAttribute('data-entryname')
+ || element.getAttribute('data-file')
+ if (direct) {
+ return direct
+ }
+ const ariaLabel = element.getAttribute('aria-label') || ''
+ const ariaMatch = ariaLabel.match(/file \"([^\"]+)\"/)
+ if (ariaMatch?.[1]) {
+ return ariaMatch[1]
+ }
+ const text = element.textContent || ''
+ const textMatch = text.match(/([\w\s.-]+\.(whiteboard|excalidraw))/i)
+ if (textMatch?.[1]) {
+ return textMatch[1]
+ }
+ return null
+ })
+
+ const openViaViewer = async () => {
+ const fileNameToOpen = resolvedFileName || name
+ if (!fileNameToOpen) {
+ return false
+ }
+ const normalizedDir = activeDir && activeDir !== '/' ? activeDir.replace(/\/$/, '') : ''
+ const filePath = normalizedDir ? `${normalizedDir}/${fileNameToOpen}` : `/${fileNameToOpen}`
+ await page.waitForFunction(() => Boolean((window as any).OCA?.Viewer), { timeout: 10000 }).catch(() => {})
+ const result = await page.evaluate(({ path }) => {
+ const viewer = (window as any).OCA?.Viewer
+ if (!viewer) {
+ return { ok: false, reason: 'viewer-missing' }
+ }
+ const handlers = viewer.availableHandlers || []
+ const hasWhiteboard = Array.isArray(handlers) && handlers.some((handler) => handler?.id === 'whiteboard')
+ if (viewer.openWith && hasWhiteboard) {
+ viewer.openWith('whiteboard', { path })
+ return { ok: true }
+ }
+ if (viewer.open) {
+ viewer.open({ path })
+ return { ok: true }
+ }
+ return { ok: false, reason: 'open-missing' }
+ }, { path: filePath })
+ return Boolean(result?.ok)
+ }
+
+ const nameLink = entry.locator('[data-cy-files-list-row-name-link]').first()
+ if (await nameLink.count()) {
+ await nameLink.click()
} else {
- const target = entry.getByRole('link', { name: new RegExp(escaped, 'i') }).first()
- if (await target.count()) {
- await target.click()
+ const viewButton = entry.getByRole('button', { name: /view|open/i }).first()
+ if (await viewButton.count()) {
+ await viewButton.click()
} else {
- await entry.dblclick()
+ const target = entry.getByRole('link', { name: new RegExp(escaped, 'i') }).first()
+ if (await target.count()) {
+ await target.click()
+ } else {
+ const nameCell = entry.locator('[data-cy-files-list-row-name]').first()
+ if (await nameCell.count()) {
+ await nameCell.click()
+ } else {
+ await entry.click()
+ }
+ }
}
}
- await waitForCanvas(page)
+ try {
+ await waitForCanvas(page)
+ } catch (error) {
+ await entry.dblclick()
+ try {
+ await waitForCanvas(page)
+ } catch (retryError) {
+ const viewerOpened = await openViaViewer()
+ if (viewerOpened) {
+ try {
+ await waitForCanvas(page)
+ return
+ } catch {
+ // fallback below
+ }
+ }
+ const fallbackFileId = resolvedFileId || await resolveFileIdByDav(page, name)
+ if (!fallbackFileId) {
+ throw retryError
+ }
+ await openWhiteboardById(page, fallbackFileId, { viewId: activeViewId, dir: activeDir })
+ return
+ }
+ }
}
export async function newLoggedInPage(sourcePage: Page, browser: Browser) {
@@ -224,11 +334,70 @@ export async function getBoardAuth(page: Page): Promise<{ fileId: number, jwt: s
return { fileId, jwt }
}
-export async function openWhiteboardById(page: Page, fileId: number | string) {
- await page.goto(`apps/whiteboard/${fileId}`)
+export async function openWhiteboardById(
+ page: Page,
+ fileId: number | string,
+ { viewId = 'files', dir = '/' }: { viewId?: string, dir?: string } = {},
+) {
+ const normalizedView = viewId.replace(/^\/+/, '').replace(/\/+$/, '') || 'files'
+ const dirParam = encodeURIComponent(dir || '/')
+ await page.goto(`apps/files/${normalizedView}/${fileId}?dir=${dirParam}&openfile=true`)
await waitForCanvas(page)
}
+async function resolveFileIdByDav(page: Page, name: string): Promise {
+ const origin = new URL(await page.url()).origin
+ const userResponse = await page.request.get(`${origin}/ocs/v2.php/cloud/user?format=json`, {
+ headers: { 'OCS-APIREQUEST': 'true' },
+ })
+ if (!userResponse.ok()) {
+ return null
+ }
+ const userPayload = await userResponse.json().catch(() => null)
+ const userId = userPayload?.ocs?.data?.id
+ if (!userId) {
+ return null
+ }
+
+ const requestToken = await page.evaluate(() => (window as any).OC?.requestToken
+ || (document.querySelector('head meta[name="requesttoken"]') as HTMLMetaElement | null)?.content
+ || null)
+
+ const candidates = (() => {
+ const lower = name.toLowerCase()
+ if (lower.endsWith('.whiteboard') || lower.endsWith('.excalidraw')) {
+ return [name]
+ }
+ return [`${name}.whiteboard`, `${name}.excalidraw`, name]
+ })()
+
+ for (const candidate of candidates) {
+ const filePath = encodeURIComponent(candidate)
+ const response = await page.request.fetch(`${origin}/remote.php/dav/files/${userId}/${filePath}`, {
+ method: 'PROPFIND',
+ headers: {
+ Depth: '0',
+ Accept: 'application/xml',
+ 'Content-Type': 'application/xml',
+ ...(requestToken ? { requesttoken: requestToken } : {}),
+ 'X-Requested-With': 'XMLHttpRequest',
+ },
+ data: fileIdPropfindBody,
+ })
+
+ if (!response.ok()) {
+ continue
+ }
+ const xml = await response.text()
+ const match = xml.match(/<(?:oc:)?fileid>([^<]+)<\/(?:oc:)?fileid>/)
+ if (match?.[1]) {
+ return match[1]
+ }
+ }
+
+ return null
+}
+
export async function fetchBoardContent(page: Page, auth: { fileId: number | string, jwt: string }) {
const token = auth.jwt.startsWith('Bearer ') ? auth.jwt : `Bearer ${auth.jwt}`
const maxAttempts = 5
diff --git a/src/main.ts b/src/main.ts
index 50226e4c..6f77b6da 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -12,7 +12,10 @@ import { getSharingToken, isPublicShare } from '@nextcloud/sharing/public'
import './styles/index.scss'
import logger from './utils/logger'
-import { matchesComparisonRequest, renderWhiteboardView } from './utils/renderWhiteboardView'
+import {
+ matchesComparisonRequest,
+ renderWhiteboardView,
+} from './utils/renderWhiteboardView'
import type { WhiteboardRootHandle } from './utils/renderWhiteboardView'
declare global {
@@ -65,7 +68,9 @@ const bootstrapWhiteboardRuntime = (): void => {
}
const detectRuntime = (): RuntimeDescriptor => {
- const fileId = normalizeNumericState(loadState('whiteboard', 'file_id', '0'))
+ const fileId = normalizeNumericState(
+ loadState('whiteboard', 'file_id', '0'),
+ )
const collabBackendUrl = loadState('whiteboard', 'collabBackendUrl', '')
if (loadState('whiteboard', 'isRecording', false)) {
@@ -220,15 +225,16 @@ type ViewerComponentOptions = {
type WhiteboardComponentData = { root: WhiteboardRootHandle | null }
-type WhiteboardComponentInstance = Vue & WhiteboardComponentData & {
- fileid?: number | null
- fileId?: number | null
- fileVersion?: string | null
- source?: string | null
- isEmbedded?: boolean
- isComparisonView?: boolean
- basename?: string
-}
+type WhiteboardComponentInstance = Vue &
+ WhiteboardComponentData & {
+ fileid?: number | null
+ fileId?: number | null
+ fileVersion?: string | null
+ source?: string | null
+ isEmbedded?: boolean
+ isComparisonView?: boolean
+ basename?: string
+ }
type VueComponentDefinition = ComponentOptions & {
data: () => WhiteboardComponentData
@@ -277,9 +283,14 @@ type WindowWithViewer = Window & {
}
}
-const createWhiteboardComponent = (options: ViewerComponentOptions): VueComponentDefinition => ({
+const createWhiteboardComponent = (
+ options: ViewerComponentOptions,
+): VueComponentDefinition => ({
name: 'Whiteboard',
- render(this: WhiteboardComponentInstance, createElement: CreateElement): VNode {
+ render(
+ this: WhiteboardComponentInstance,
+ createElement: CreateElement,
+ ): VNode {
this.$emit('update:loaded', true)
const containerId = generateWhiteboardElementId()
@@ -289,26 +300,41 @@ const createWhiteboardComponent = (options: ViewerComponentOptions): VueComponen
return
}
- rootElement.addEventListener('keydown', event => {
+ rootElement.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
event.stopPropagation()
}
})
- const normalizedFileId = Number(this.fileid ?? this.fileId ?? 0) || 0
+ const normalizedFileId
+ = Number(this.fileid ?? this.fileId ?? 0) || 0
const isComparisonView = Boolean(this.isComparisonView)
const isEmbedded = Boolean(this.isEmbedded)
const rawVersionSource = this.source ?? null
const rawFileVersion = this.fileVersion ?? null
- const shouldUseVersionPreview = isComparisonView
- || matchesComparisonRequest(rawVersionSource, rawFileVersion ?? null)
+ const isVersionsDavSource
+ = rawVersionSource?.includes('/dav/versions/')
+ || rawVersionSource?.includes('/dav/trashbin/')
+ || false
+ const shouldUseVersionPreview
+ = isComparisonView
+ || (rawFileVersion !== null && isVersionsDavSource)
+ || matchesComparisonRequest(
+ rawVersionSource,
+ rawFileVersion ?? null,
+ )
const versionSource = isEmbedded
? rawVersionSource
- : (shouldUseVersionPreview ? rawVersionSource : null)
+ : shouldUseVersionPreview
+ ? rawVersionSource
+ : null
const fileVersion = isEmbedded
? rawFileVersion
- : (shouldUseVersionPreview ? rawFileVersion : null)
- const fileName = typeof this.basename === 'string' ? this.basename : ''
+ : shouldUseVersionPreview
+ ? rawFileVersion
+ : null
+ const fileName
+ = typeof this.basename === 'string' ? this.basename : ''
this.root = renderWhiteboardView(rootElement, {
fileId: normalizedFileId,
@@ -328,7 +354,11 @@ const createWhiteboardComponent = (options: ViewerComponentOptions): VueComponen
attrs: { id: containerId },
class: [
'whiteboard',
- { 'whiteboard-viewer__embedding': Boolean(this.isEmbedded) },
+ {
+ 'whiteboard-viewer__embedding': Boolean(
+ this.isEmbedded,
+ ),
+ },
],
},
'',
@@ -380,7 +410,8 @@ const registerViewerHandler = (
)
}
-const getViewerApi = (): ViewerApi | undefined => (window as WindowWithViewer).OCA?.Viewer
+const getViewerApi = (): ViewerApi | undefined =>
+ (window as WindowWithViewer).OCA?.Viewer
const runWhenDomReady = (callback: () => void | Promise): void => {
if (document.readyState === 'loading') {
@@ -395,7 +426,10 @@ const runWhenDomReady = (callback: () => void | Promise): void => {
callback()
}
-const primeRecordingJwt = async (fileId: number, jwt: string): Promise => {
+const primeRecordingJwt = async (
+ fileId: number,
+ jwt: string,
+): Promise => {
if (!jwt) {
return
}
@@ -426,12 +460,10 @@ const normalizeNumericState = (value: unknown): number => {
}
const generateWhiteboardElementId = () =>
- `whiteboard-${
- Math.random()
- .toString(36)
- .replace(/[^a-z]+/g, '')
- .substr(2, 10)
- }`
+ `whiteboard-${Math.random()
+ .toString(36)
+ .replace(/[^a-z]+/g, '')
+ .substr(2, 10)}`
const createWhiteboardElement = (id = generateWhiteboardElementId()) => {
const element = document.createElement('div')