feat(drive): refactor document and drawing editors with new metadata handling
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Replaced suite page metadata with drive-specific metadata for document and drawing editors. - Introduced new `driveEditorPageMetadata` function to manage titles and favicons based on editor type. - Updated layout components for document and drawing editors to utilize the new metadata structure. - Enhanced document title handling in various editor components to reflect the current editing context. - Added new SVG icons for UltiDocs, UltiSheets, UltiSlides, and UltiDraw to improve visual consistency across editors. - Improved print styles and layout handling for better document rendering in print and PDF formats.
This commit is contained in:
parent
8f81d7aba1
commit
82ca9a27db
@ -1,11 +1,8 @@
|
||||
import type { Metadata } from "next"
|
||||
import { suitePageMetadata } from "@/lib/suite/page-metadata"
|
||||
import { driveEditorPageMetadata } from "@/lib/drive/drive-editor-metadata"
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
return suitePageMetadata({
|
||||
app: "drive",
|
||||
titleSegment: "Document",
|
||||
})
|
||||
return driveEditorPageMetadata("docs", "Document")
|
||||
}
|
||||
|
||||
export default function DriveDocsEditLayout({
|
||||
|
||||
14
app/drive/draw/[fileId]/edit/layout.tsx
Normal file
14
app/drive/draw/[fileId]/edit/layout.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import type { Metadata } from "next"
|
||||
import { driveEditorPageMetadata } from "@/lib/drive/drive-editor-metadata"
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
return driveEditorPageMetadata("draw", "Dessin")
|
||||
}
|
||||
|
||||
export default function DriveDrawEditLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return children
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
import type { Metadata } from "next"
|
||||
import { displayFileBaseName } from "@/lib/drive/display-file-name"
|
||||
import { suitePageMetadata } from "@/lib/suite/page-metadata"
|
||||
import { driveEditorPageMetadata } from "@/lib/drive/drive-editor-metadata"
|
||||
import { resolveOnlyOfficeEditorKind } from "@/lib/drive/onlyoffice-formats"
|
||||
|
||||
type LayoutProps = {
|
||||
children: React.ReactNode
|
||||
@ -12,10 +13,8 @@ export async function generateMetadata({ params }: LayoutProps): Promise<Metadat
|
||||
const raw = decodeURIComponent(fileId)
|
||||
const baseName = raw.split("/").filter(Boolean).pop() ?? raw
|
||||
const name = displayFileBaseName(baseName)
|
||||
return suitePageMetadata({
|
||||
app: "drive",
|
||||
titleSegment: name,
|
||||
})
|
||||
const kind = resolveOnlyOfficeEditorKind({ name: baseName })
|
||||
return driveEditorPageMetadata(kind, name)
|
||||
}
|
||||
|
||||
export default function EditLayout({ children }: LayoutProps) {
|
||||
|
||||
@ -13,6 +13,7 @@ import { displayFileBaseName } from "@/lib/drive/display-file-name"
|
||||
import { resolveRenameName } from "@/lib/drive/drive-default-name"
|
||||
import { driveFolderHref } from "@/lib/drive/drive-sidebar-tree"
|
||||
import { buildDriveEditHref, resolveDriveEditReturnTo } from "@/lib/drive/drive-url"
|
||||
import { resolveOnlyOfficeEditorKind } from "@/lib/drive/onlyoffice-formats"
|
||||
import { useDriveDocumentTitle } from "@/lib/drive/use-drive-document-title"
|
||||
import { useDriveUIStore } from "@/lib/stores/drive-ui-store"
|
||||
|
||||
@ -48,7 +49,8 @@ export function OfficeEditor({
|
||||
|
||||
const fileName = fileNameFromPath(displayPath)
|
||||
const title = displayFileBaseName(fileName)
|
||||
useDriveDocumentTitle(title)
|
||||
const editorKind = resolveOnlyOfficeEditorKind({ name: fileName })
|
||||
useDriveDocumentTitle(title, editorKind)
|
||||
|
||||
const backHref = useMemo(
|
||||
() =>
|
||||
|
||||
@ -10,6 +10,7 @@ import { displayFileBaseName } from "@/lib/drive/display-file-name"
|
||||
import { getGuestEditorIdentity } from "@/lib/drive/guest-editor-identity"
|
||||
import { resolvePublicShareEditReturnTo, shouldShowPublicShareEditorBack } from "@/lib/drive/public-share-url"
|
||||
import type { PublicShareRootType } from "@/lib/drive/public-share-url"
|
||||
import { resolveOnlyOfficeEditorKind } from "@/lib/drive/onlyoffice-formats"
|
||||
import { useDriveDocumentTitle } from "@/lib/drive/use-drive-document-title"
|
||||
|
||||
function fileNameFromPath(filePath: string, fallback?: string): string {
|
||||
@ -44,7 +45,8 @@ export function PublicOfficeEditor({
|
||||
|
||||
const fileName = fileDisplayName || fileNameFromPath(filePath)
|
||||
const title = displayFileBaseName(fileName)
|
||||
useDriveDocumentTitle(title)
|
||||
const editorKind = resolveOnlyOfficeEditorKind({ name: fileName })
|
||||
useDriveDocumentTitle(title, editorKind)
|
||||
|
||||
const backHref = useMemo(
|
||||
() => resolvePublicShareEditReturnTo(token, returnTo, filePath),
|
||||
|
||||
@ -47,7 +47,7 @@ export function PublicRichTextEditor({
|
||||
|
||||
const fileName = fileDisplayName || fileNameFromPath(filePath)
|
||||
const title = displayFileBaseName(fileName)
|
||||
useDriveDocumentTitle(title)
|
||||
useDriveDocumentTitle(title, "docs")
|
||||
|
||||
const backHref = useMemo(
|
||||
() => resolvePublicShareEditReturnTo(token, returnTo, filePath),
|
||||
|
||||
@ -49,7 +49,7 @@ export function PublicUltidrawEditor({
|
||||
|
||||
const fileName = fileDisplayName || fileNameFromPath(filePath)
|
||||
const title = displayFileBaseName(fileName)
|
||||
useDriveDocumentTitle(title)
|
||||
useDriveDocumentTitle(title, "draw")
|
||||
|
||||
const backHref = useMemo(
|
||||
() => resolvePublicShareEditReturnTo(token, returnTo, filePath),
|
||||
|
||||
@ -918,7 +918,7 @@ export function RichTextDocumentEditor({
|
||||
/>
|
||||
) : null}
|
||||
{chrome ? (
|
||||
<div className="flex min-h-0 flex-1 flex-row">
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-row">
|
||||
<DocsEditorWorkspace
|
||||
editor={editor}
|
||||
pageLayout={pageLayout}
|
||||
|
||||
@ -53,7 +53,7 @@ export function RichTextEditor({ fileId }: { fileId: string }) {
|
||||
|
||||
const fileName = file?.name ?? fileNameFromPath(displayPath)
|
||||
const title = displayFileBaseName(fileName)
|
||||
useDriveDocumentTitle(title)
|
||||
useDriveDocumentTitle(title, "docs")
|
||||
|
||||
const [backHref, setBackHref] = useState("/drive")
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ export function DocsBodyMarginMasks({
|
||||
return (
|
||||
<div key={`body-mask-${index}`} aria-hidden>
|
||||
<div
|
||||
className="pointer-events-none absolute z-[15]"
|
||||
className="docs-body-margin-mask pointer-events-none absolute z-[15] box-border border-l border-r border-[#dadce0]"
|
||||
style={{
|
||||
top: pageTop,
|
||||
left: 0,
|
||||
@ -48,7 +48,7 @@ export function DocsBodyMarginMasks({
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className="pointer-events-none absolute z-[15]"
|
||||
className="docs-body-margin-mask pointer-events-none absolute z-[15] box-border border-l border-r border-[#dadce0]"
|
||||
style={{
|
||||
top: footerTop,
|
||||
left: 0,
|
||||
|
||||
@ -178,7 +178,12 @@ export function DocsChrome({
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="-mt-1 flex min-w-0 items-center overflow-x-auto overflow-y-visible">
|
||||
<div
|
||||
className={cn(
|
||||
"-mt-1 flex min-w-0 items-center overflow-x-auto overflow-y-visible",
|
||||
"[scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden"
|
||||
)}
|
||||
>
|
||||
<DocsMenubar
|
||||
className="docs-menubar shrink-0"
|
||||
viewMenuActions={viewMenuActions}
|
||||
|
||||
@ -139,7 +139,7 @@ export function DocsEditorWorkspace({
|
||||
}, [onPageStackReady, pageCount, showLayout, zoom])
|
||||
|
||||
return (
|
||||
<div className="docs-editor-workspace flex min-h-0 flex-1 flex-col">
|
||||
<div className="docs-editor-workspace flex min-h-0 min-w-0 flex-1 flex-col">
|
||||
<DocsRulerMarginDragTooltip tooltip={dragTooltip} />
|
||||
<DocsGraphicFloatingToolbar
|
||||
editor={editor}
|
||||
@ -159,7 +159,7 @@ export function DocsEditorWorkspace({
|
||||
{showToolbarShell ? (
|
||||
<div
|
||||
className={cn(
|
||||
"docs-toolbar-shell shrink-0",
|
||||
"docs-toolbar-shell min-w-0 shrink-0",
|
||||
toolbarShellClassName
|
||||
)}
|
||||
>
|
||||
|
||||
@ -218,6 +218,9 @@ export function DocsHeaderFooterBand({
|
||||
footerGeom.zoneBottom - contentHeight - DOCS_HF_CHROME_BAR_PX
|
||||
const chromeBarTop = isHeader ? headerChromeTop : footerChromeTop
|
||||
|
||||
const editLateralTop = isHeader ? zoneTop : footerChromeTop
|
||||
const editLateralHeight = contentHeight + DOCS_HF_CHROME_BAR_PX
|
||||
|
||||
const [formatOpen, setFormatOpen] = useState(false)
|
||||
const [pageNumOpen, setPageNumOpen] = useState(false)
|
||||
const contentPersistTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
@ -348,6 +351,26 @@ export function DocsHeaderFooterBand({
|
||||
aria-hidden
|
||||
/>
|
||||
|
||||
<div
|
||||
className="docs-hf-lateral-border pointer-events-none absolute border-l border-[#dadce0]"
|
||||
style={{
|
||||
top: editLateralTop,
|
||||
left: 0,
|
||||
height: editLateralHeight,
|
||||
}}
|
||||
aria-hidden
|
||||
/>
|
||||
<div
|
||||
className="docs-hf-lateral-border pointer-events-none absolute border-r border-[#dadce0]"
|
||||
style={{
|
||||
top: editLateralTop,
|
||||
right: 0,
|
||||
width: 0,
|
||||
height: editLateralHeight,
|
||||
}}
|
||||
aria-hidden
|
||||
/>
|
||||
|
||||
{isHeader ? (
|
||||
<div
|
||||
className="docs-hf-separator pointer-events-none absolute border-t border-[#dadce0]"
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
import {
|
||||
DOCS_LOGO_BODY,
|
||||
DOCS_LOGO_COLOR,
|
||||
DOCS_LOGO_FOLD,
|
||||
DOCS_LOGO_FOLD_Y_OFFSET,
|
||||
DOCS_LOGO_LINE_1,
|
||||
DOCS_LOGO_LINE_2,
|
||||
} from "@/lib/drive/docs-logo-paths"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
/** material-symbols:description (Iconify MCP) — corps sans le pli. */
|
||||
const BODY =
|
||||
"M8 18h8v-2H8zm0-4h8v-2H8zm-2 8q-.825 0-1.412-.587T4 20V4q0-.825.588-1.412T6 2h8l6 6v12q0 .825-.587 1.413T18 22z"
|
||||
const FOLD = "M13 7h5l-5-5z"
|
||||
const LINE_1 = "M8 18h8v-2H8z"
|
||||
const LINE_2 = "M8 14h8v-2H8z"
|
||||
|
||||
/** Décalage vertical du pli blanc (viewBox 24). */
|
||||
const FOLD_Y_OFFSET = 1
|
||||
|
||||
export function DocsLogoIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
@ -18,13 +16,13 @@ export function DocsLogoIcon({ className }: { className?: string }) {
|
||||
className={cn("shrink-0", className)}
|
||||
aria-hidden
|
||||
>
|
||||
<path fill="#4285F4" d={BODY} />
|
||||
<path fill="#ffffff" d={LINE_1} />
|
||||
<path fill="#ffffff" d={LINE_2} />
|
||||
<path fill={DOCS_LOGO_COLOR} d={DOCS_LOGO_BODY} />
|
||||
<path fill="#ffffff" d={DOCS_LOGO_LINE_1} />
|
||||
<path fill="#ffffff" d={DOCS_LOGO_LINE_2} />
|
||||
<path
|
||||
fill="#ffffff"
|
||||
d={FOLD}
|
||||
transform={`translate(0 ${FOLD_Y_OFFSET})`}
|
||||
d={DOCS_LOGO_FOLD}
|
||||
transform={`translate(0 ${DOCS_LOGO_FOLD_Y_OFFSET})`}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
@ -557,8 +557,10 @@ function DocsToolbarInner({
|
||||
fonts,
|
||||
])
|
||||
|
||||
const reservedTrailingPx = showChromeToggle ? 44 : 0
|
||||
const { containerRef, measureRef, visibleCount, hasOverflow } = useToolbarOverflow(
|
||||
segments.length
|
||||
segments.length,
|
||||
reservedTrailingPx
|
||||
)
|
||||
|
||||
if (!editor) return null
|
||||
@ -569,7 +571,7 @@ function DocsToolbarInner({
|
||||
const toolbarRow = (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="docs-toolbar relative flex items-center gap-0 overflow-hidden px-1.5 py-0.5"
|
||||
className="docs-toolbar relative flex min-w-0 w-full max-w-full items-center gap-0 overflow-hidden px-1.5 py-0.5"
|
||||
>
|
||||
<div
|
||||
ref={measureRef}
|
||||
|
||||
@ -46,7 +46,7 @@ export function UltidrawEditor({ fileId }: { fileId: string }) {
|
||||
|
||||
const fileName = file?.name ?? fileNameFromPath(displayPath)
|
||||
const title = displayFileBaseName(fileName)
|
||||
useDriveDocumentTitle(title)
|
||||
useDriveDocumentTitle(title, "draw")
|
||||
|
||||
const [backHref, setBackHref] = useState("/drive")
|
||||
|
||||
|
||||
@ -5,6 +5,9 @@
|
||||
},
|
||||
{
|
||||
"path": "../ulti-backend"
|
||||
},
|
||||
{
|
||||
"path": "../ultisuite-deploy"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
|
||||
8
lib/drive/docs-logo-paths.ts
Normal file
8
lib/drive/docs-logo-paths.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/** Shared UltiDocs logo paths (material-symbols:description). Used by chrome + favicon. */
|
||||
export const DOCS_LOGO_BODY =
|
||||
"M8 18h8v-2H8zm0-4h8v-2H8zm-2 8q-.825 0-1.412-.587T4 20V4q0-.825.588-1.412T6 2h8l6 6v12q0 .825-.587 1.413T18 22z"
|
||||
export const DOCS_LOGO_FOLD = "M13 7h5l-5-5z"
|
||||
export const DOCS_LOGO_LINE_1 = "M8 18h8v-2H8z"
|
||||
export const DOCS_LOGO_LINE_2 = "M8 14h8v-2H8z"
|
||||
export const DOCS_LOGO_FOLD_Y_OFFSET = 1
|
||||
export const DOCS_LOGO_COLOR = "#4285F4"
|
||||
@ -2,19 +2,179 @@ import type { DocPageLayout } from "@/lib/drive/doc-page-setup"
|
||||
import { buildParagraphStylesCss } from "@/lib/drive/docs-paragraph-styles-css"
|
||||
import type { DocParagraphStylesCatalog } from "@/lib/drive/docs-paragraph-styles"
|
||||
import type { DocsExportSnapshot } from "@/lib/drive/docs-export-snapshot"
|
||||
import { DOCS_PAGE_GAP_PX } from "@/lib/drive/docs-page-layout-constants"
|
||||
|
||||
const PRINT_STYLE_ID = "docs-print-dynamic-styles"
|
||||
const PRINT_IFRAME_ID = "docs-print-iframe"
|
||||
|
||||
function buildPageRule(pageLayout: DocPageLayout): string {
|
||||
const wMm = pageLayout.format.widthMm
|
||||
const hMm = pageLayout.format.heightMm
|
||||
const landscape =
|
||||
pageLayout.format.widthMm > pageLayout.format.heightMm ||
|
||||
(pageLayout.format.widthMm === pageLayout.format.heightMm && false)
|
||||
const landscape = pageLayout.format.widthMm > pageLayout.format.heightMm
|
||||
const size = landscape ? `${hMm}mm ${wMm}mm` : `${wMm}mm ${hMm}mm`
|
||||
return `@page { size: ${size}; margin: 0; }`
|
||||
}
|
||||
|
||||
function buildPrintLayoutRules(pageLayout: DocPageLayout): string {
|
||||
const pageWidth = pageLayout.widthPx
|
||||
const pageHeight = pageLayout.heightPx
|
||||
return `
|
||||
html, body {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
background: white !important;
|
||||
width: ${pageWidth}px !important;
|
||||
min-height: 0 !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.docs-print-root {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: ${pageWidth}px !important;
|
||||
background: white !important;
|
||||
}
|
||||
|
||||
[data-docs-page-stack] {
|
||||
position: relative !important;
|
||||
left: 0 !important;
|
||||
top: 0 !important;
|
||||
transform: none !important;
|
||||
transform-origin: top left !important;
|
||||
width: ${pageWidth}px !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.ultidrive-docs-page {
|
||||
width: ${pageWidth}px !important;
|
||||
height: ${pageHeight}px !important;
|
||||
box-shadow: none !important;
|
||||
border: none !important;
|
||||
break-after: page;
|
||||
page-break-after: always;
|
||||
print-color-adjust: exact;
|
||||
-webkit-print-color-adjust: exact;
|
||||
}
|
||||
|
||||
.ultidrive-docs-page:last-child {
|
||||
break-after: auto;
|
||||
page-break-after: auto;
|
||||
}
|
||||
|
||||
.docs-body-margin-mask {
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.docs-hf-chrome,
|
||||
.docs-graphic-handle,
|
||||
.docs-graphic-rotate-handle,
|
||||
.docs-graphic-outline,
|
||||
.docs-graphic-snap-guides {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.docs-hf-band .docs-region-editor-root {
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
type StyleRestore = () => void
|
||||
|
||||
function patchInlineStyle(el: HTMLElement, property: string, value: string): StyleRestore {
|
||||
const previous = el.style.getPropertyValue(property)
|
||||
const priority = el.style.getPropertyPriority(property)
|
||||
el.style.setProperty(property, value)
|
||||
return () => {
|
||||
if (previous) {
|
||||
el.style.setProperty(property, previous, priority)
|
||||
} else {
|
||||
el.style.removeProperty(property)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Collapse on-screen page gaps and zoom offsets so print matches page size. */
|
||||
function adjustPageStackForPrint(
|
||||
stack: HTMLElement,
|
||||
pageLayout: DocPageLayout,
|
||||
pageCount: number
|
||||
): StyleRestore {
|
||||
const restores: StyleRestore[] = []
|
||||
const pageWidth = pageLayout.widthPx
|
||||
const pageHeight = pageLayout.heightPx
|
||||
const pageStep = pageHeight + DOCS_PAGE_GAP_PX
|
||||
const gapTotal = Math.max(0, pageCount - 1) * DOCS_PAGE_GAP_PX
|
||||
|
||||
restores.push(patchInlineStyle(stack, "transform", "none"))
|
||||
restores.push(patchInlineStyle(stack, "left", "0"))
|
||||
restores.push(patchInlineStyle(stack, "width", `${pageWidth}px`))
|
||||
|
||||
let wrapper = stack.parentElement
|
||||
while (wrapper && !wrapper.classList.contains("ultidrive-docs-canvas")) {
|
||||
restores.push(patchInlineStyle(wrapper, "width", `${pageWidth}px`))
|
||||
restores.push(patchInlineStyle(wrapper, "height", "auto"))
|
||||
restores.push(patchInlineStyle(wrapper, "min-height", "0"))
|
||||
restores.push(patchInlineStyle(wrapper, "padding-top", "0"))
|
||||
restores.push(patchInlineStyle(wrapper, "padding-bottom", "0"))
|
||||
restores.push(patchInlineStyle(wrapper, "margin-left", "0"))
|
||||
restores.push(patchInlineStyle(wrapper, "margin-right", "0"))
|
||||
wrapper = wrapper.parentElement
|
||||
}
|
||||
|
||||
const stackHeight = parseFloat(stack.style.height)
|
||||
if (Number.isFinite(stackHeight) && gapTotal > 0) {
|
||||
restores.push(patchInlineStyle(stack, "height", `${stackHeight - gapTotal}px`))
|
||||
}
|
||||
|
||||
stack.querySelectorAll<HTMLElement>("[style]").forEach((el) => {
|
||||
const top = parseFloat(el.style.top)
|
||||
if (!Number.isFinite(top) || top <= 0) return
|
||||
const pageIndex = Math.floor(top / pageStep)
|
||||
const nextTop = top - pageIndex * DOCS_PAGE_GAP_PX
|
||||
if (nextTop !== top) {
|
||||
restores.push(patchInlineStyle(el, "top", `${nextTop}px`))
|
||||
}
|
||||
})
|
||||
|
||||
stack.querySelectorAll<HTMLElement>(".docs-page-flow-spacer").forEach((spacer) => {
|
||||
const height = parseFloat(spacer.style.height)
|
||||
if (Number.isFinite(height) && height >= DOCS_PAGE_GAP_PX) {
|
||||
restores.push(
|
||||
patchInlineStyle(spacer, "height", `${height - DOCS_PAGE_GAP_PX}px`)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const surface = stack.querySelector<HTMLElement>(".ultidrive-docs-editor-surface")
|
||||
if (surface) {
|
||||
const surfaceHeight = parseFloat(surface.style.height)
|
||||
if (Number.isFinite(surfaceHeight) && gapTotal > 0) {
|
||||
restores.push(
|
||||
patchInlineStyle(surface, "height", `${surfaceHeight - gapTotal}px`)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
for (let i = restores.length - 1; i >= 0; i -= 1) {
|
||||
restores[i]()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildDynamicPrintStyles(
|
||||
pageLayout: DocPageLayout,
|
||||
paragraphStyles: DocParagraphStylesCatalog
|
||||
): string {
|
||||
return [
|
||||
buildPageRule(pageLayout),
|
||||
buildPrintLayoutRules(pageLayout),
|
||||
buildParagraphStylesCss(paragraphStyles, ".ultidrive-richtext-editor, .ultidrive-richtext-region-editor"),
|
||||
].join("\n")
|
||||
}
|
||||
|
||||
function injectPrintStyles(
|
||||
pageLayout: DocPageLayout,
|
||||
paragraphStyles: DocParagraphStylesCatalog
|
||||
@ -24,10 +184,7 @@ function injectPrintStyles(
|
||||
|
||||
const style = document.createElement("style")
|
||||
style.id = PRINT_STYLE_ID
|
||||
style.textContent = [
|
||||
buildPageRule(pageLayout),
|
||||
buildParagraphStylesCss(paragraphStyles, ".ultidrive-richtext-editor, .ultidrive-richtext-region-editor"),
|
||||
].join("\n")
|
||||
style.textContent = buildDynamicPrintStyles(pageLayout, paragraphStyles)
|
||||
document.head.appendChild(style)
|
||||
|
||||
return () => {
|
||||
@ -35,6 +192,108 @@ function injectPrintStyles(
|
||||
}
|
||||
}
|
||||
|
||||
function copyDocumentStyles(targetDoc: Document): void {
|
||||
document.querySelectorAll('style, link[rel="stylesheet"]').forEach((node) => {
|
||||
targetDoc.head.appendChild(node.cloneNode(true))
|
||||
})
|
||||
}
|
||||
|
||||
async function waitForDocumentReady(doc: Document): Promise<void> {
|
||||
const links = Array.from(doc.querySelectorAll('link[rel="stylesheet"]'))
|
||||
await Promise.all(
|
||||
links.map(
|
||||
(link) =>
|
||||
new Promise<void>((resolve) => {
|
||||
const sheet = link as HTMLLinkElement
|
||||
if (sheet.sheet) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
link.addEventListener("load", () => resolve(), { once: true })
|
||||
link.addEventListener("error", () => resolve(), { once: true })
|
||||
})
|
||||
)
|
||||
)
|
||||
if (doc.fonts?.ready) {
|
||||
await doc.fonts.ready
|
||||
}
|
||||
await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
|
||||
await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
|
||||
}
|
||||
|
||||
function removePrintIframe(): void {
|
||||
document.getElementById(PRINT_IFRAME_ID)?.remove()
|
||||
}
|
||||
|
||||
async function printStackInIframe(
|
||||
stack: HTMLElement,
|
||||
snapshot: DocsExportSnapshot
|
||||
): Promise<void> {
|
||||
removePrintIframe()
|
||||
|
||||
const iframe = document.createElement("iframe")
|
||||
iframe.id = PRINT_IFRAME_ID
|
||||
iframe.setAttribute("aria-hidden", "true")
|
||||
iframe.style.cssText =
|
||||
"position:fixed;right:0;bottom:0;width:0;height:0;border:0;opacity:0;pointer-events:none"
|
||||
document.body.appendChild(iframe)
|
||||
|
||||
const doc = iframe.contentDocument
|
||||
const win = iframe.contentWindow
|
||||
if (!doc || !win) {
|
||||
removePrintIframe()
|
||||
throw new Error("Impossible de préparer la fenêtre d'impression")
|
||||
}
|
||||
|
||||
copyDocumentStyles(doc)
|
||||
|
||||
const printStyle = doc.createElement("style")
|
||||
printStyle.textContent = buildDynamicPrintStyles(
|
||||
snapshot.pageLayout,
|
||||
snapshot.paragraphStyles
|
||||
)
|
||||
doc.head.appendChild(printStyle)
|
||||
|
||||
doc.documentElement.classList.add("docs-printing")
|
||||
|
||||
const root = doc.createElement("div")
|
||||
root.className = "docs-print-root"
|
||||
root.appendChild(stack.cloneNode(true))
|
||||
doc.body.replaceChildren(root)
|
||||
|
||||
await waitForDocumentReady(doc)
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const cleanup = () => {
|
||||
win.removeEventListener("afterprint", onAfterPrint)
|
||||
removePrintIframe()
|
||||
}
|
||||
|
||||
const onAfterPrint = () => {
|
||||
cleanup()
|
||||
resolve()
|
||||
}
|
||||
|
||||
win.addEventListener("afterprint", onAfterPrint)
|
||||
|
||||
try {
|
||||
win.focus()
|
||||
win.print()
|
||||
} catch (error) {
|
||||
cleanup()
|
||||
reject(error)
|
||||
return
|
||||
}
|
||||
|
||||
window.setTimeout(() => {
|
||||
if (document.getElementById(PRINT_IFRAME_ID)) {
|
||||
cleanup()
|
||||
resolve()
|
||||
}
|
||||
}, 2000)
|
||||
})
|
||||
}
|
||||
|
||||
async function waitForPrintLayout(): Promise<void> {
|
||||
if (typeof document !== "undefined" && document.fonts?.ready) {
|
||||
await document.fonts.ready
|
||||
@ -56,9 +315,17 @@ export async function prepareDocsPrintEnvironment(
|
||||
}
|
||||
|
||||
const removeStyles = injectPrintStyles(snapshot.pageLayout, snapshot.paragraphStyles)
|
||||
|
||||
const stack = snapshot.getPageStackElement()
|
||||
const restoreStackLayout =
|
||||
stack != null
|
||||
? adjustPageStackForPrint(stack, snapshot.pageLayout, snapshot.pageCount)
|
||||
: () => {}
|
||||
|
||||
await waitForPrintLayout()
|
||||
|
||||
return () => {
|
||||
restoreStackLayout()
|
||||
root.classList.remove("docs-printing", "docs-pdf-capture")
|
||||
removeStyles()
|
||||
}
|
||||
@ -67,18 +334,15 @@ export async function prepareDocsPrintEnvironment(
|
||||
export async function printDocsDocument(snapshot: DocsExportSnapshot): Promise<void> {
|
||||
const cleanup = await prepareDocsPrintEnvironment(snapshot, "print")
|
||||
|
||||
const onAfterPrint = () => {
|
||||
cleanup()
|
||||
window.removeEventListener("afterprint", onAfterPrint)
|
||||
}
|
||||
window.addEventListener("afterprint", onAfterPrint)
|
||||
|
||||
window.print()
|
||||
|
||||
// Fallback cleanup if afterprint never fires (some browsers)
|
||||
window.setTimeout(() => {
|
||||
if (document.documentElement.classList.contains("docs-printing")) {
|
||||
cleanup()
|
||||
try {
|
||||
const stack = snapshot.getPageStackElement()
|
||||
if (!stack) {
|
||||
window.print()
|
||||
return
|
||||
}
|
||||
}, 2000)
|
||||
|
||||
await printStackInIframe(stack, snapshot)
|
||||
} finally {
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
97
lib/drive/drive-editor-metadata.ts
Normal file
97
lib/drive/drive-editor-metadata.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import type { Metadata } from "next"
|
||||
import { SUITE_TITLE_SEP } from "@/lib/suite/page-metadata"
|
||||
|
||||
export type DriveEditorKind = "docs" | "sheets" | "presentation" | "draw"
|
||||
|
||||
export const DRIVE_EDITOR_LABELS: Record<DriveEditorKind, string> = {
|
||||
docs: "UltiDocs",
|
||||
sheets: "Sheets",
|
||||
presentation: "Presentation",
|
||||
draw: "Draw",
|
||||
}
|
||||
|
||||
export const DRIVE_EDITOR_FAVICONS: Record<DriveEditorKind, string> = {
|
||||
docs: "/drive/ultidocs-mark.svg",
|
||||
sheets: "/drive/ultisheets-mark.svg",
|
||||
presentation: "/drive/ultislides-mark.svg",
|
||||
draw: "/drive/ultidraw-mark.svg",
|
||||
}
|
||||
|
||||
export function driveEditorDocumentTitle(
|
||||
titleSegment: string,
|
||||
kind: DriveEditorKind
|
||||
): string {
|
||||
const trimmed = titleSegment.trim()
|
||||
const label = DRIVE_EDITOR_LABELS[kind]
|
||||
if (!trimmed) return label
|
||||
return `${trimmed}${SUITE_TITLE_SEP}${label}`
|
||||
}
|
||||
|
||||
export function driveEditorPageMetadata(
|
||||
kind: DriveEditorKind,
|
||||
titleSegment?: string
|
||||
): Metadata {
|
||||
const label = DRIVE_EDITOR_LABELS[kind]
|
||||
const favicon = DRIVE_EDITOR_FAVICONS[kind]
|
||||
|
||||
return {
|
||||
title:
|
||||
titleSegment !== undefined
|
||||
? {
|
||||
default: titleSegment,
|
||||
template: `%s${SUITE_TITLE_SEP}${label}`,
|
||||
}
|
||||
: label,
|
||||
icons: {
|
||||
icon: [{ url: favicon, type: "image/svg+xml" }],
|
||||
apple: [{ url: favicon, type: "image/svg+xml" }],
|
||||
shortcut: favicon,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function iconLinks(): HTMLLinkElement[] {
|
||||
return Array.from(
|
||||
document.querySelectorAll<HTMLLinkElement>(
|
||||
'link[rel="icon"], link[rel="shortcut icon"], link[rel="apple-touch-icon"]'
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export function applyDriveEditorFavicon(kind: DriveEditorKind): () => void {
|
||||
const href = DRIVE_EDITOR_FAVICONS[kind]
|
||||
const links = iconLinks()
|
||||
const previous = links.map((link) => ({
|
||||
link,
|
||||
href: link.getAttribute("href"),
|
||||
type: link.getAttribute("type"),
|
||||
}))
|
||||
|
||||
if (links.length === 0) {
|
||||
const link = document.createElement("link")
|
||||
link.rel = "icon"
|
||||
link.type = "image/svg+xml"
|
||||
link.href = href
|
||||
document.head.appendChild(link)
|
||||
return () => {
|
||||
link.remove()
|
||||
}
|
||||
}
|
||||
|
||||
for (const link of links) {
|
||||
link.href = href
|
||||
link.type = "image/svg+xml"
|
||||
}
|
||||
|
||||
return () => {
|
||||
for (const { link, href: prevHref, type } of previous) {
|
||||
if (prevHref == null) {
|
||||
link.remove()
|
||||
continue
|
||||
}
|
||||
link.href = prevHref
|
||||
if (type) link.type = type
|
||||
else link.removeAttribute("type")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -120,3 +120,33 @@ export function isOnlyOfficeFile(file: {
|
||||
if (mime && isOnlyOfficeMime(mime)) return true
|
||||
return isOnlyOfficeExtension(fileExtension(file.name))
|
||||
}
|
||||
|
||||
const ONLYOFFICE_CELL_SET = new Set<string>(ONLYOFFICE_CELL)
|
||||
const ONLYOFFICE_SLIDE_SET = new Set<string>([...ONLYOFFICE_SLIDE, ...ONLYOFFICE_DIAGRAM])
|
||||
|
||||
export function resolveOnlyOfficeEditorKind(file: {
|
||||
name: string
|
||||
mime_type?: string
|
||||
}): "sheets" | "presentation" {
|
||||
const mime = (file.mime_type ?? "").toLowerCase()
|
||||
const ext = fileExtension(file.name)
|
||||
|
||||
if (
|
||||
mime.includes("spreadsheet") ||
|
||||
mime.includes("excel") ||
|
||||
ONLYOFFICE_CELL_SET.has(ext)
|
||||
) {
|
||||
return "sheets"
|
||||
}
|
||||
|
||||
if (
|
||||
mime.includes("presentation") ||
|
||||
mime.includes("powerpoint") ||
|
||||
mime.includes("visio") ||
|
||||
ONLYOFFICE_SLIDE_SET.has(ext)
|
||||
) {
|
||||
return "presentation"
|
||||
}
|
||||
|
||||
return "sheets"
|
||||
}
|
||||
|
||||
@ -1,16 +1,20 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect } from "react"
|
||||
import { SUITE_TITLE_SEP } from "@/lib/suite/page-metadata"
|
||||
import {
|
||||
applyDriveEditorFavicon,
|
||||
driveEditorDocumentTitle,
|
||||
type DriveEditorKind,
|
||||
} from "@/lib/drive/drive-editor-metadata"
|
||||
|
||||
export function useDriveDocumentTitle(titleSegment: string) {
|
||||
export function useDriveDocumentTitle(titleSegment: string, kind: DriveEditorKind) {
|
||||
useEffect(() => {
|
||||
const trimmed = titleSegment.trim()
|
||||
if (!trimmed) return
|
||||
const previous = document.title
|
||||
document.title = `${trimmed}${SUITE_TITLE_SEP}UltiDrive`
|
||||
const previousTitle = document.title
|
||||
document.title = driveEditorDocumentTitle(titleSegment, kind)
|
||||
const restoreFavicon = applyDriveEditorFavicon(kind)
|
||||
return () => {
|
||||
document.title = previous
|
||||
document.title = previousTitle
|
||||
restoreFavicon()
|
||||
}
|
||||
}, [titleSegment])
|
||||
}, [titleSegment, kind])
|
||||
}
|
||||
|
||||
@ -4,11 +4,15 @@ import { useLayoutEffect, useRef, useState } from "react"
|
||||
|
||||
const OVERFLOW_BUTTON_WIDTH = 36
|
||||
|
||||
export function useToolbarOverflow(itemCount: number) {
|
||||
export function useToolbarOverflow(itemCount: number, reservedTrailingPx = 0) {
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const measureRef = useRef<HTMLDivElement>(null)
|
||||
const [visibleCount, setVisibleCount] = useState(itemCount)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setVisibleCount(itemCount)
|
||||
}, [itemCount])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const container = containerRef.current
|
||||
const measure = measureRef.current
|
||||
@ -16,15 +20,24 @@ export function useToolbarOverflow(itemCount: number) {
|
||||
|
||||
const recalculate = () => {
|
||||
const children = Array.from(measure.children) as HTMLElement[]
|
||||
if (children.length === 0) return
|
||||
if (children.length === 0) {
|
||||
setVisibleCount(0)
|
||||
return
|
||||
}
|
||||
|
||||
const containerWidth = container.clientWidth
|
||||
if (containerWidth <= 0) return
|
||||
|
||||
const totalWidth = children.reduce((sum, child) => sum + child.offsetWidth, 0)
|
||||
if (totalWidth <= container.clientWidth) {
|
||||
const trailingReserve = reservedTrailingPx
|
||||
const maxWithoutOverflow = containerWidth - trailingReserve
|
||||
|
||||
if (totalWidth <= maxWithoutOverflow) {
|
||||
setVisibleCount(children.length)
|
||||
return
|
||||
}
|
||||
|
||||
const available = container.clientWidth - OVERFLOW_BUTTON_WIDTH
|
||||
const available = containerWidth - OVERFLOW_BUTTON_WIDTH - trailingReserve
|
||||
let used = 0
|
||||
let fit = 0
|
||||
|
||||
@ -39,10 +52,22 @@ export function useToolbarOverflow(itemCount: number) {
|
||||
}
|
||||
|
||||
recalculate()
|
||||
|
||||
const ro = new ResizeObserver(recalculate)
|
||||
ro.observe(container)
|
||||
return () => ro.disconnect()
|
||||
}, [itemCount])
|
||||
ro.observe(measure)
|
||||
for (const child of measure.children) {
|
||||
ro.observe(child)
|
||||
}
|
||||
|
||||
const mo = new MutationObserver(recalculate)
|
||||
mo.observe(measure, { childList: true })
|
||||
|
||||
return () => {
|
||||
ro.disconnect()
|
||||
mo.disconnect()
|
||||
}
|
||||
}, [itemCount, reservedTrailingPx])
|
||||
|
||||
const hasOverflow = visibleCount < itemCount
|
||||
|
||||
|
||||
7
public/drive/ultidocs-mark.svg
Normal file
7
public/drive/ultidocs-mark.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<!-- Keep in sync with lib/drive/docs-logo-paths.ts + DocsLogoIcon -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" role="img" aria-label="UltiDocs">
|
||||
<path fill="#4285F4" d="M8 18h8v-2H8zm0-4h8v-2H8zm-2 8q-.825 0-1.412-.587T4 20V4q0-.825.588-1.412T6 2h8l6 6v12q0 .825-.587 1.413T18 22z"/>
|
||||
<path fill="#ffffff" d="M8 18h8v-2H8z"/>
|
||||
<path fill="#ffffff" d="M8 14h8v-2H8z"/>
|
||||
<path fill="#ffffff" d="M13 7h5l-5-5z" transform="translate(0 1)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 469 B |
5
public/drive/ultidraw-mark.svg
Normal file
5
public/drive/ultidraw-mark.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" role="img" aria-label="UltiDraw">
|
||||
<rect width="40" height="40" rx="8" fill="#6965DB"/>
|
||||
<path fill="none" stroke="#ffffff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" d="M11 27c4-8 8-12 12-14 2 4 2 8 0 12-4 2-8 2-12 2z"/>
|
||||
<path fill="none" stroke="#FFC107" stroke-width="2.5" stroke-linecap="round" d="M24 13l5-4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 410 B |
5
public/drive/ultisheets-mark.svg
Normal file
5
public/drive/ultisheets-mark.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" role="img" aria-label="UltiSheets">
|
||||
<path fill="#0F9D58" d="M8 6h18l8 8v22q0 1.65-1.18 2.83T30 40H10q-1.65 0-2.83-1.17T6 36V8q0-1.65 1.17-2.83T10 6z"/>
|
||||
<path fill="#ffffff" d="M26 6v8h8z" opacity="0.95"/>
|
||||
<path fill="#ffffff" d="M12 18h16v2H12zm0 6h16v2H12zm0 6h11v2H12z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 347 B |
6
public/drive/ultislides-mark.svg
Normal file
6
public/drive/ultislides-mark.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" role="img" aria-label="UltiPresentation">
|
||||
<path fill="#F4B400" d="M8 6h18l8 8v22q0 1.65-1.18 2.83T30 40H10q-1.65 0-2.83-1.17T6 36V8q0-1.65 1.17-2.83T10 6z"/>
|
||||
<path fill="#ffffff" d="M26 6v8h8z" opacity="0.95"/>
|
||||
<rect x="12" y="17" width="16" height="11" rx="1.5" fill="#ffffff"/>
|
||||
<path fill="#F4B400" d="M20 31 12 36h16z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 399 B |
@ -1,66 +1,5 @@
|
||||
/* UltiDocs print / PDF capture styles */
|
||||
/* UltiDocs PDF capture styles (print uses a dedicated iframe) */
|
||||
|
||||
@media print {
|
||||
.docs-printing header,
|
||||
.docs-printing .docs-toolbar-shell,
|
||||
.docs-printing .docs-rulers-left-rail,
|
||||
.docs-printing .docs-status-bar,
|
||||
.docs-printing .docs-hf-chrome,
|
||||
.docs-printing .docs-graphic-floating-toolbar,
|
||||
.docs-printing .docs-table-floating-toolbar,
|
||||
.docs-printing .docs-graphic-options-sidebar,
|
||||
.docs-printing .docs-loading-splash,
|
||||
.docs-printing .docs-graphic-handle,
|
||||
.docs-printing .docs-graphic-rotate-handle,
|
||||
.docs-printing .docs-graphic-outline,
|
||||
.docs-printing .docs-graphic-snap-guides,
|
||||
.docs-printing .docs-ruler-margin-drag-tooltip {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.docs-printing .ultidrive-docs-canvas {
|
||||
overflow: visible !important;
|
||||
background: white !important;
|
||||
height: auto !important;
|
||||
min-height: 0 !important;
|
||||
}
|
||||
|
||||
.docs-printing [data-docs-page-stack] {
|
||||
transform: none !important;
|
||||
position: relative !important;
|
||||
left: auto !important;
|
||||
top: auto !important;
|
||||
}
|
||||
|
||||
.docs-printing .ultidrive-docs-page {
|
||||
box-shadow: none !important;
|
||||
break-after: page;
|
||||
page-break-after: always;
|
||||
}
|
||||
|
||||
.docs-printing .ultidrive-docs-page:last-child {
|
||||
break-after: auto;
|
||||
page-break-after: auto;
|
||||
}
|
||||
|
||||
.docs-printing .docs-hf-band--editing {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.docs-printing .docs-hf-band .docs-region-editor-root {
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.docs-printing #docs-page-graphic-layer-behind .docs-graphic-handle,
|
||||
.docs-printing #docs-page-graphic-layer-front .docs-graphic-handle,
|
||||
.docs-printing #docs-page-graphic-layer-behind .docs-graphic-outline,
|
||||
.docs-printing #docs-page-graphic-layer-front .docs-graphic-outline {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* PDF capture uses the same layout rules without @media print */
|
||||
.docs-printing.docs-pdf-capture header,
|
||||
.docs-printing.docs-pdf-capture .docs-toolbar-shell,
|
||||
.docs-printing.docs-pdf-capture .docs-rulers-left-rail,
|
||||
@ -72,19 +11,53 @@
|
||||
.docs-printing.docs-pdf-capture .docs-graphic-handle,
|
||||
.docs-printing.docs-pdf-capture .docs-graphic-rotate-handle,
|
||||
.docs-printing.docs-pdf-capture .docs-graphic-outline,
|
||||
.docs-printing.docs-pdf-capture .docs-graphic-snap-guides {
|
||||
.docs-printing.docs-pdf-capture .docs-graphic-snap-guides,
|
||||
.docs-printing.docs-pdf-capture .docs-ruler-margin-drag-tooltip,
|
||||
.docs-printing.docs-pdf-capture .docs-editor-workspace > div > .absolute {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.docs-printing.docs-pdf-capture .docs-editor-workspace,
|
||||
.docs-printing.docs-pdf-capture .docs-editor-workspace > div,
|
||||
.docs-printing.docs-pdf-capture .docs-editor-workspace .flex-row {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.docs-printing.docs-pdf-capture .ultidrive-docs-canvas {
|
||||
overflow: visible !important;
|
||||
background: white !important;
|
||||
height: auto !important;
|
||||
min-height: 0 !important;
|
||||
}
|
||||
|
||||
.docs-printing.docs-pdf-capture .ultidrive-docs-canvas > div,
|
||||
.docs-printing.docs-pdf-capture .ultidrive-docs-canvas > div > div {
|
||||
height: auto !important;
|
||||
min-height: 0 !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.docs-printing.docs-pdf-capture [data-docs-page-stack] {
|
||||
position: relative !important;
|
||||
left: 0 !important;
|
||||
top: 0 !important;
|
||||
transform: none !important;
|
||||
transform-origin: top left !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.docs-printing.docs-pdf-capture .ultidrive-docs-page {
|
||||
box-shadow: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.docs-printing.docs-pdf-capture .docs-body-margin-mask {
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.docs-printing.docs-pdf-capture .ultidrive-docs-editor-surface--dimmed .ProseMirror {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
@ -476,6 +476,10 @@ html.dark .ultidrive-richtext-region-editor table th {
|
||||
z-index: 24;
|
||||
}
|
||||
|
||||
.docs-hf-lateral-border {
|
||||
z-index: 25;
|
||||
}
|
||||
|
||||
.docs-hf-chrome {
|
||||
z-index: 27;
|
||||
}
|
||||
@ -965,6 +969,10 @@ html.dark .docs-menu-badge {
|
||||
|
||||
.docs-toolbar-shell {
|
||||
padding: 0;
|
||||
padding-inline: 8px 16px;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.docs-toolbar-shell--collapsed {
|
||||
@ -973,13 +981,13 @@ html.dark .docs-menu-badge {
|
||||
|
||||
.docs-toolbar-shell > .docs-toolbar {
|
||||
padding-inline: 12px;
|
||||
margin-inline-start: 8px;
|
||||
margin-inline-end: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.docs-toolbar {
|
||||
flex-wrap: nowrap;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
color: #202124;
|
||||
background: #edf2fa;
|
||||
border-radius: 9999px;
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user