From b6ce88cf3eb534b6180774e2e781c333e60eec56 Mon Sep 17 00:00:00 2001 From: vanyauhalin Date: Thu, 11 Jul 2024 16:20:31 +0400 Subject: [PATCH 01/25] make the eleventy context compatible with the eleventy data --- packages/eleventy-types/lib/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eleventy-types/lib/main.ts b/packages/eleventy-types/lib/main.ts index 7b090d276..90bb44712 100644 --- a/packages/eleventy-types/lib/main.ts +++ b/packages/eleventy-types/lib/main.ts @@ -207,7 +207,7 @@ export interface Page { /** * {@link https://www.11ty.dev/docs/data-eleventy-supplied/ Eleventy Reference} */ -export interface Context { +export interface Context extends Data { collections: Collections content: Content eleventy: Eleventy From ffe4b7638c24e01da8c181f5eeb45157adadaeba Mon Sep 17 00:00:00 2001 From: vanyauhalin Date: Thu, 11 Jul 2024 16:29:59 +0400 Subject: [PATCH 02/25] split crosslink rehype plugin into image and link one, add link component --- site/internal/crosslink.tsx | 86 ------------------------------------ site/internal/image.tsx | 61 ++++++++++++++++---------- site/internal/link.tsx | 87 +++++++++++++++++++++++++++++++++++++ site/internal/markdown.tsx | 4 +- 4 files changed, 126 insertions(+), 112 deletions(-) delete mode 100644 site/internal/crosslink.tsx create mode 100644 site/internal/link.tsx diff --git a/site/internal/crosslink.tsx b/site/internal/crosslink.tsx deleted file mode 100644 index e625ce1f3..000000000 --- a/site/internal/crosslink.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import path from "node:path" -import {Sitemap} from "@onlyoffice/eleventy-sitemap" -import {rootDir} from "@onlyoffice/eleventy-env" -import {type Element, type Root} from "hast" -import {visit} from "unist-util-visit" -import {type VFile} from "vfile" - -export interface RehypeCrosslinkTransform { - (tree: Root, file: VFile): void -} - -export function rehypeCrosslink(): RehypeCrosslinkTransform { - return function (t, f) { - visit(t, "element", (n) => { - switch (n.tagName) { - case "a": - a(f, n) - break - case "img": - img(f, n) - break - } - }) - } - - function a(f: VFile, n: Element): void { - const s = Sitemap.shared - - const u = n.properties.href - if (!u || typeof u !== "string") { - return - } - - if (URL.canParse(u) || u.startsWith("#")) { - return - } - - const p = resolve(f, u) - if (!p) { - return - } - - const e = s.find(p, "path") - if (!e || e.type !== "page") { - return - } - - n.properties.href = e.url - } - - function img(f: VFile, n: Element): void { - const u = n.properties.src - if (!u || typeof u !== "string") { - return - } - - if (URL.canParse(u)) { - return - } - - const p = resolve(f, u) - if (!p) { - return - } - - n.properties.src = p - } - - function resolve(f: VFile, u: string): string { - let p = "" - - if (path.isAbsolute(u)) { - p = `.${u}` - } else { - p = path.dirname(f.path) - p = path.resolve(p, u) - p = p.replace(rootDir(), ".") - } - - if (p) { - p = decodeURIComponent(p) - } - - return p - } -} diff --git a/site/internal/image.tsx b/site/internal/image.tsx index a03deeaa9..b3b822feb 100644 --- a/site/internal/image.tsx +++ b/site/internal/image.tsx @@ -1,4 +1,5 @@ -import {basename, extname, isAbsolute} from "node:path" +import path from "node:path" +import {rootDir} from "@onlyoffice/eleventy-env" import {type ImageOptions} from "@11ty/eleventy-img" import {modify} from "@onlyoffice/hast-util-eleventy-img" import {generate} from "@onlyoffice/preact-eleventy-img" @@ -7,6 +8,7 @@ import {type Root} from "hast" import {type HTMLAttributes} from "preact/compat" import {type JSX, h} from "preact" import {visit} from "unist-util-visit" +import {type VFile} from "vfile" export function Image(p: HTMLAttributes): JSX.Element { let r: JSX.Element | null = null @@ -19,21 +21,18 @@ export function Image(p: HTMLAttributes): JSX.Element { p.alt = "" } - if (!p.src || typeof p.src !== "string") { + if (p.src === undefined) { throw new Error("The 'src' attribute is required, but missing.") } - - if (!URL.canParse(p.src) && !isAbsolute(p.src)) { + if (typeof p.src !== "string") { + throw new Error("The 'src' attribute must be a string.") + } + if (!URL.canParse(p.src) && !path.isAbsolute(p.src)) { throw new Error("The 'src' attribute must be an absolute URL.") } - if (isAbsolute(p.src)) { - p.src = `.${p.src}` - } - - // todo: this is a temporary solution during the migration. - if (p.src.startsWith("./content")) { - return null + if (path.isAbsolute(p.src)) { + p.src = decodeURIComponent(`.${p.src}`) } if (p.width !== undefined) { @@ -72,8 +71,12 @@ export function Image(p: HTMLAttributes): JSX.Element { return {() => r} } -export function rehypeImage() { - return async function (t: Root) { +export interface RehypeImageTransform { + (tree: Root, file: VFile): Promise +} + +export function rehypeImage(): RehypeImageTransform { + return async function (t, f) { let r = Promise.resolve() visit(t, "element", (n, i, pa) => { @@ -82,24 +85,34 @@ export function rehypeImage() { } const p = n.properties - if (p.style === undefined || p.style === null) { + if (!p.style) { p.style = "" } - // todo: this is a temporary solution during the migration. + // todo: this is temporary solutions during the migration. if (!p.alt) { p.alt = "" } - - if (!p.src || typeof p.src !== "string") { - throw new Error("The 'src' attribute is required, but missing.") + if (p.src && typeof p.src === "string" && p.src.startsWith("/content")) { + return } - // todo: this is a temporary solution during the migration. - if (p.src.startsWith("./content")) { + if (typeof p.src !== "string") { return } + if (!URL.canParse(p.src)) { + let u = "" + if (path.isAbsolute(p.src)) { + u = `.${p.src}` + } else { + u = path.dirname(f.path) + u = path.resolve(u, p.src) + u = u.replace(rootDir(), ".") + } + p.src = decodeURIComponent(u) + } + if (p.width !== undefined && p.width !== null) { if (typeof p.style !== "string") { throw new Error("The 'style' attribute must be a string.") @@ -150,13 +163,13 @@ function options(s: string): ImageOptions { urlPath: "/assets/", outputDir: "dist/assets/", filenameFormat(id: string, s: string, w: number, f: string) { - const e = extname(s) - const n = basename(s, e) + const e = path.extname(s) + const n = path.basename(s, e) return `${n}-${w}w-${id}.${f}` - } + }, } - const e = extname(s) + const e = path.extname(s) if (e === ".svg") { o.formats = ["svg"] } diff --git a/site/internal/link.tsx b/site/internal/link.tsx new file mode 100644 index 000000000..042fdd223 --- /dev/null +++ b/site/internal/link.tsx @@ -0,0 +1,87 @@ +import path from "node:path" +import {Sitemap} from "@onlyoffice/eleventy-sitemap" +import {rootDir} from "@onlyoffice/eleventy-env" +import {type Root} from "hast" +import {type HTMLAttributes} from "preact/compat" +import {type JSX, h} from "preact" +import {visit} from "unist-util-visit" +import {type VFile} from "vfile" + +export function Link(p: HTMLAttributes): JSX.Element { + p = {...p} + + if (p.href === undefined) { + throw new Error("The 'href' attribute is required, but missing.") + } + if (typeof p.href !== "string") { + throw new Error("The 'href' attribute must be a string.") + } + if (!URL.canParse(p.href) && !path.isAbsolute(p.href)) { + console.log(p.href) + throw new Error("The 'href' attribute must be an absolute URL.") + } + + if (path.isAbsolute(p.href)) { + const s = Sitemap.shared + const u = decodeURIComponent(`.${p.href}`) + + const e = s.find(u, "path") + if (!e) { + throw new Error(`Expected an entity for the path: ${u}`) + } + if (e.type !== "page") { + throw new Error(`Expected a page entity for the path: ${u}`) + } + + p.href = e.url + } + + return +} + +export interface RehypeLinkTransform { + (tree: Root, file: VFile): void +} + +export function rehypeLink(): RehypeLinkTransform { + return function (t, f) { + visit(t, "element", (n) => { + if (n.tagName !== "a") { + return + } + + const p = n.properties + if ( + typeof p.href !== "string" || + URL.canParse(p.href) || + p.href.startsWith("#") + ) { + return + } + + const s = Sitemap.shared + + let u = "" + if (path.isAbsolute(p.href)) { + u = `.${p.href}` + } else { + u = path.dirname(f.path) + u = path.resolve(u, p.href) + u = u.replace(rootDir(), ".") + } + u = decodeURIComponent(u) + + const e = s.find(u, "path") + if (!e) { + // todo: this is a temporary solution during the migration. + return + // throw new Error(`Expected an entity for the path: ${u}`) + } + if (e.type !== "page") { + throw new Error(`Expected a page entity for the path: ${u}`) + } + + n.properties.href = e.url + }) + } +} diff --git a/site/internal/markdown.tsx b/site/internal/markdown.tsx index 545cf93dd..a2f6b9a35 100644 --- a/site/internal/markdown.tsx +++ b/site/internal/markdown.tsx @@ -16,8 +16,8 @@ import remarkRehype from "remark-rehype" import {type PluggableList, unified} from "unified" import {VFile} from "vfile" import {rehypeDocumentBuilderContainer} from "../components/document-builder-container/rehype.ts" -import {rehypeCrosslink} from "./crosslink.tsx" import {rehypeImage} from "./image.tsx" +import {rehypeLink} from "./link.tsx" export function Markdown(p: ChildrenIncludable): JSX.Element { let r: JSX.Element | null = null @@ -69,9 +69,9 @@ export function remarkPlugins(): PluggableList { export function rehypePlugins(): PluggableList { return [ - rehypeCrosslink, rehypeSlug, [rehypeAutolink, {behavior: "wrap"}], + rehypeLink, rehypeImage, [rehypeStarryNight, starryNight], rehypeDocumentBuilderContainer From b29eb6d6f3b992cc37dda46cb342dfd6ab3cf3fc Mon Sep 17 00:00:00 2001 From: vanyauhalin Date: Thu, 11 Jul 2024 16:31:31 +0400 Subject: [PATCH 03/25] add the ability to specify a path in the global navigation --- site/internal/global-navigation.tsx | 62 ++++++++++++++++++++++++++--- site/layouts/page.tsx | 2 +- site/pages/pages.data.ts | 24 ++++++++++- 3 files changed, 80 insertions(+), 8 deletions(-) diff --git a/site/internal/global-navigation.tsx b/site/internal/global-navigation.tsx index c0193ae33..91f5ea728 100644 --- a/site/internal/global-navigation.tsx +++ b/site/internal/global-navigation.tsx @@ -6,10 +6,57 @@ import {Sitemap} from "@onlyoffice/eleventy-sitemap" import {type JSX, h} from "preact" -import {Icon} from "@/internal/icon.tsx" +import {Icon} from "./icon.tsx" +import {Link} from "./link.tsx" + +declare module "@onlyoffice/eleventy-types" { + interface Data { + globalNavigation?: GlobalNavigationData + } + + interface EleventyComputed { + globalNavigation?(data: Data): GlobalNavigationData + } +} + +export interface GlobalNavigationData { + icon: string + title: string + path: string +} + +export class GlobalNavigationDatum implements GlobalNavigationData { + icon = "" + title = "" + path = "" + + static merge(a: GlobalNavigationData, b: GlobalNavigationData): GlobalNavigationData { + const c = new GlobalNavigationDatum() + + if (b.icon) { + c.icon = b.icon + } else if (a.icon) { + c.icon = a.icon + } + + if (b.title) { + c.title = b.title + } else if (a.title) { + c.title = a.title + } + + if (b.path) { + c.path = b.path + } else if (a.path) { + c.path = a.path + } + + return c + } +} export interface GlobalNavigationProperties { - url: string + current: string } export function GlobalNavigation(p: GlobalNavigationProperties): JSX.Element { @@ -32,7 +79,7 @@ export function GlobalNavigation(p: GlobalNavigationProperties): JSX.Element { } let c = "global-navigation__menu-link" - if (p.url.startsWith(e.url)) { + if (p.current.startsWith(e.url)) { c += " global-navigation__menu-link_active" } @@ -48,9 +95,14 @@ export function GlobalNavigation(p: GlobalNavigationProperties): JSX.Element { throw new Error(`Entity is not a page: ${id}`) } + const n = e.data.globalNavigation + if (!n) { + throw new Error(`Global navigation data not found: ${id}`) + } + return })} diff --git a/site/layouts/page.tsx b/site/layouts/page.tsx index 7390ae905..9007ee7db 100644 --- a/site/layouts/page.tsx +++ b/site/layouts/page.tsx @@ -32,7 +32,7 @@ export function render({content, ...ctx}: Context): JSX.Element { - +
{content}
diff --git a/site/pages/pages.data.ts b/site/pages/pages.data.ts index 016b89907..55878667b 100644 --- a/site/pages/pages.data.ts +++ b/site/pages/pages.data.ts @@ -2,6 +2,7 @@ import {type SitemapData} from "@onlyoffice/eleventy-sitemap" import {type Data} from "@onlyoffice/eleventy-types" import {cutPrefix, cutSuffix} from "@onlyoffice/strings" import {slug} from "github-slugger" +import {type GlobalNavigationData, GlobalNavigationDatum} from "@/internal/global-navigation.tsx" declare module "@onlyoffice/eleventy-types" { interface Data { @@ -10,6 +11,7 @@ declare module "@onlyoffice/eleventy-types" { crosslink?(data: Data, s: string): string slug?(data: Data): string defaultSitemap?(d: Data): SitemapData + defaultGlobalNavigation?(data: Data): GlobalNavigationData } interface EleventyComputed { @@ -65,6 +67,14 @@ export function data(): Data { return d.defaultSitemap(d) }, + defaultGlobalNavigation(d) { + const m = new GlobalNavigationDatum() + m.icon = d.icon + m.title = d.title + m.path = d.page.inputPath.slice(1) + return m + }, + eleventyComputed: { title(data) { if (!data) { @@ -75,12 +85,22 @@ export function data(): Data { } return data.title }, + layout(data) { if (!data || !data.layout) { return undefined } return `${data.layout}.tsx` - } - } + }, + + globalNavigation(d) { + const a = d.defaultGlobalNavigation(d) + const b = d.globalNavigation + if (!b) { + return a + } + return GlobalNavigationDatum.merge(a, b) + }, + }, } } From 49a41917ecccb9013c83536ddc83076f428fd3e5 Mon Sep 17 00:00:00 2001 From: vanyauhalin Date: Thu, 11 Jul 2024 16:32:01 +0400 Subject: [PATCH 04/25] specify global navigation paths --- site/pages/DocSpace/API Backend/index.md | 2 ++ site/pages/DocSpace/For Hosting Providers/index.md | 2 ++ site/pages/DocSpace/JavaScript SDK/index.md | 2 ++ site/pages/DocSpace/Plugins SDK/index.md | 2 ++ site/pages/Docs/Desktop Editors/index.md | 2 ++ site/pages/Docs/Docs API/index.md | 2 ++ site/pages/Docs/Document Builder/index.md | 2 ++ site/pages/Docs/Office API/index.md | 2 ++ site/pages/Docs/Plugin and Macros/index.md | 2 ++ site/pages/Workspace/API Backend/index.md | 2 ++ site/pages/Workspace/For Hosting Providers/index.md | 2 ++ 11 files changed, 22 insertions(+) diff --git a/site/pages/DocSpace/API Backend/index.md b/site/pages/DocSpace/API Backend/index.md index dac8566ab..9dd115f4f 100644 --- a/site/pages/DocSpace/API Backend/index.md +++ b/site/pages/DocSpace/API Backend/index.md @@ -2,4 +2,6 @@ order: -1 icon: docspace-api summary: In this section, you will learn how to integrate ONLYOFFICE DocSpace into your own application and interact with its backend using the DocSpace API Backend which is implemented as REST over HTTP using GET/POST/PUT/DELETE. +globalNavigation: + path: /pages/DocSpace/API Backend/Get Started/Basic concepts/index.md --- diff --git a/site/pages/DocSpace/For Hosting Providers/index.md b/site/pages/DocSpace/For Hosting Providers/index.md index dc6a094f5..82f7c7cec 100644 --- a/site/pages/DocSpace/For Hosting Providers/index.md +++ b/site/pages/DocSpace/For Hosting Providers/index.md @@ -1,4 +1,6 @@ --- icon: self-hosted summary: In this section, you will learn how to provide the DocSpace portal as a SaaS solution on your own servers using our API methods. +globalNavigation: + path: /pages/DocSpace/For Hosting Providers/Get Started/Basic concepts/index.md --- diff --git a/site/pages/DocSpace/JavaScript SDK/index.md b/site/pages/DocSpace/JavaScript SDK/index.md index bba13012e..c4e3b6a5e 100644 --- a/site/pages/DocSpace/JavaScript SDK/index.md +++ b/site/pages/DocSpace/JavaScript SDK/index.md @@ -2,4 +2,6 @@ order: -3 icon: javascript-sdk summary: In this section, you will learn how to connect DocSpace as a frame to your website using api.js. You can embed an entire DocSpace portal, a single room, or a document. +globalNavigation: + path: /pages/DocSpace/JavaScript SDK/Get Started/Basic concepts/index.md --- diff --git a/site/pages/DocSpace/Plugins SDK/index.md b/site/pages/DocSpace/Plugins SDK/index.md index 7d4231e12..9d268c3ed 100644 --- a/site/pages/DocSpace/Plugins SDK/index.md +++ b/site/pages/DocSpace/Plugins SDK/index.md @@ -2,4 +2,6 @@ order: -2 icon: plugins summary: In this section, you will learn how to create your own plugins and add them to the DocSpace portal. +globalNavigation: + path: /pages/DocSpace/Plugins SDK/Get Started/Basic concepts/index.md --- diff --git a/site/pages/Docs/Desktop Editors/index.md b/site/pages/Docs/Desktop Editors/index.md index 0a8351095..031701dc7 100644 --- a/site/pages/Docs/Desktop Editors/index.md +++ b/site/pages/Docs/Desktop Editors/index.md @@ -1,4 +1,6 @@ --- icon: desktop-editors summary: In this section, you will learn how to extend the ONLYOFFICE Desktop Editors functionality by setting up, customizing and integrating them with the document management systems. +globalNavigation: + path: /pages/Docs/Desktop Editors/Get Started/Overview/index.md --- diff --git a/site/pages/Docs/Docs API/index.md b/site/pages/Docs/Docs API/index.md index 56acc2501..404928af9 100644 --- a/site/pages/Docs/Docs API/index.md +++ b/site/pages/Docs/Docs API/index.md @@ -2,4 +2,6 @@ order: -4 icon: docs-api summary: In this section, you will learn how to bring document editing and co-authoring to your web app users, set up, configure, and integrate ONLYOFFICE Docs. +globalNavigation: + path: /pages/Docs/Docs API/Get Started/Basic concepts/index.md --- diff --git a/site/pages/Docs/Document Builder/index.md b/site/pages/Docs/Document Builder/index.md index 933cf38cb..3306552cb 100644 --- a/site/pages/Docs/Document Builder/index.md +++ b/site/pages/Docs/Document Builder/index.md @@ -2,4 +2,6 @@ order: -1 icon: document-builder summary: In this section, you will learn how to generate documents easily without running a document editor and integrate Document builder into your DMS, CRM system, etc. +globalNavigation: + path: /pages/Docs/Document Builder/Get Started/Overview/index.md --- diff --git a/site/pages/Docs/Office API/index.md b/site/pages/Docs/Office API/index.md index 8424848fd..69cb78606 100644 --- a/site/pages/Docs/Office API/index.md +++ b/site/pages/Docs/Office API/index.md @@ -2,4 +2,6 @@ order: -3 icon: office-api summary: "In this section, you will learn how to use our JavaScript library to write code for your plugins, macros, builder scripts, etc. This library contains classes and methods for every document type: Text document API, Spreadsheet API, Presentation API, and Form API." +globalNavigation: + path: /pages/Docs/Office API/Get Started/Overview/index.md --- diff --git a/site/pages/Docs/Plugin and Macros/index.md b/site/pages/Docs/Plugin and Macros/index.md index e93057b84..fda4a2b7d 100644 --- a/site/pages/Docs/Plugin and Macros/index.md +++ b/site/pages/Docs/Plugin and Macros/index.md @@ -2,4 +2,6 @@ order: -2 icon: plugins summary: In this section, you will learn how to extend the ONLYOFFICE Docs functionality by creating your own plugins/macros. Here you will find the information about their structure, development lifecycle, and examples. +globalNavigation: + path: /pages/Docs/Plugin and Macros/Get Started/Overview/index.md --- diff --git a/site/pages/Workspace/API Backend/index.md b/site/pages/Workspace/API Backend/index.md index 6c9d30a6a..3764ce8d2 100644 --- a/site/pages/Workspace/API Backend/index.md +++ b/site/pages/Workspace/API Backend/index.md @@ -3,4 +3,6 @@ order: -1 layout: table-of-contents icon: docspace-api summary: In this section, you will learn how to integrate ONLYOFFICE Workspace into your own application and interact with its backend using the Workspace API Backend which is implemented as REST over HTTP using GET/POST/PUT/DELETE. This solution is provided without editors, you need to install ONLYOFFICE Docs separately. +globalNavigation: + path: /pages/Workspace/API Backend/Get Started/Basic concepts/index.md --- diff --git a/site/pages/Workspace/For Hosting Providers/index.md b/site/pages/Workspace/For Hosting Providers/index.md index f2cd7a269..642fbd79c 100644 --- a/site/pages/Workspace/For Hosting Providers/index.md +++ b/site/pages/Workspace/For Hosting Providers/index.md @@ -1,4 +1,6 @@ --- icon: self-hosted summary: In this section, you will learn how to provide the Workspace portal as a SaaS solution on your own servers using our API methods. +globalNavigation: + path: /pages/Workspace/For Hosting Providers/Get Started/Basic concepts/index.md --- From 9d86cf2685c98fe292938640019ae2faac6278ff Mon Sep 17 00:00:00 2001 From: vanyauhalin Date: Thu, 11 Jul 2024 17:37:10 +0400 Subject: [PATCH 05/25] do not apply images sizes if they are equal to -1 --- site/internal/image.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/site/internal/image.tsx b/site/internal/image.tsx index b3b822feb..036ba09da 100644 --- a/site/internal/image.tsx +++ b/site/internal/image.tsx @@ -45,7 +45,9 @@ export function Image(p: HTMLAttributes): JSX.Element { if (typeof p.width === "object" && "peek" in p.width) { throw new Error("The 'width' attribute must not be a signal.") } - p.style.maxWidth = p.width + if (p.width !== -1) { + p.style.maxWidth = p.width + } } if (p.height !== undefined) { @@ -58,7 +60,9 @@ export function Image(p: HTMLAttributes): JSX.Element { if (typeof p.height === "object" && "peek" in p.height) { throw new Error("The 'height' attribute must not be a signal.") } - p.style.maxHeight = p.height + if (p.height !== -1) { + p.style.maxHeight = p.height + } } const o = options(p.src) From f77a24b0d19288dd246314b8cfffaff0e3d1c394 Mon Sep 17 00:00:00 2001 From: vanyauhalin Date: Thu, 11 Jul 2024 17:38:38 +0400 Subject: [PATCH 06/25] update the home page implementation - move frontmatter data to the home property - allow to specify image sizes in the frontmatter - reuse global navigation paths for children --- site/assets/main.css | 19 +--- site/internal/home.tsx | 176 ++++++++++++++++++++++++++++++++++ site/pages/DocSpace/index.md | 37 +++---- site/pages/Docs/index.md | 39 ++++---- site/pages/Workspace/index.md | 43 +++++---- site/pages/index.tsx | 67 +------------ site/pages/pages.data.ts | 17 ++++ 7 files changed, 269 insertions(+), 129 deletions(-) create mode 100644 site/internal/home.tsx diff --git a/site/assets/main.css b/site/assets/main.css index 1b18e7910..bcc40775b 100644 --- a/site/assets/main.css +++ b/site/assets/main.css @@ -91,23 +91,12 @@ table { height: auto !important; } +.home__preview img { + width: auto !important; +} + /* todo: temp solution */ document-builder { display: block; height: 550px; } - -.home img[src^="/assets/docs-preview"] { - height: 540px !important; - width: auto !important; -} - -.home img[src^="/assets/docspace-preview"] { - height: 430px !important; - width: auto !important; -} - -.home img[src^="/assets/workspace-preview"] { - height: 500px !important; - width: auto !important; -} diff --git a/site/internal/home.tsx b/site/internal/home.tsx new file mode 100644 index 000000000..d8ba63ab7 --- /dev/null +++ b/site/internal/home.tsx @@ -0,0 +1,176 @@ +import { + Home as SHome, + HomeHero, + HomeIn, + HomeLink, + HomeLinks, + type HomePartParameters, + HomePart, + HomePreview, + SearchClear, + SearchContainer, + SearchField, + SearchPlaceholder, +} from "@onlyoffice/site-kit" +import {Sitemap} from "@onlyoffice/eleventy-sitemap" +import {CodePreview} from "@onlyoffice/ui-kit" +import {type JSX, h} from "preact" +import {SyntaxHighlight} from "../components/syntax-highlight/syntax-highlight.ts" +import {Icon} from "./icon.tsx" +import {Image} from "./image.tsx" +import {Link} from "./link.tsx" + +declare module "@onlyoffice/eleventy-types" { + interface Data { + home?: HomeData + } + + interface EleventyComputed { + home?(data: Data): HomeData + } +} + +export interface HomeData { + image: { + alt: string + src: string + width: number + height: number + } + title: string + description: string + sample: { + syntax: string + code: string + } +} + +export class HomeDatum implements HomeData { + image = { + alt: "", + src: "", + width: -1, + height: -1, + } + title = "" + description = "" + sample = { + syntax: "", + code: "", + } + + static merge(a: HomeData, b: HomeData): HomeData { + const c = new HomeDatum() + + if (b.image) { + c.image = b.image + } else if (a.image) { + c.image = a.image + } + + if (b.title) { + c.title = b.title + } else if (a.title) { + c.title = a.title + } + + if (b.description) { + c.description = b.description + } else if (a.description) { + c.description = a.description + } + + if (b.sample) { + c.sample = b.sample + } else if (a.sample) { + c.sample = a.sample + } + + return c + } +} + +export function Home(): JSX.Element { + const s = Sitemap.shared + + const e = s.find("/", "url") + if (!e) { + throw new Error("Root entity not found") + } + if (e.type !== "page") { + throw new Error("Root entity is not a page") + } + + return + +

Welcome to ONLYOFFICE API

+ + Search in all sections + + + +
+ {e.children.map((id, i) => { + const e = s.find(id, "id") + if (!e) { + throw new Error(`Entity not found: ${id}`) + } + if (e.type !== "page") { + throw new Error(`Entity is not a page: ${id}`) + } + + const d = e.data.home + if (!d) { + throw new Error(`Home data not found: ${id}`) + } + + let v: HomePartParameters["variant"] = "default" + if (i % 2 !== 0) { + v = "reverse" + } + + return + +

{d.title}

+

{d.description}

+ More + + {e.children.map((id) => { + const e = s.find(id, "id") + if (!e) { + throw new Error(`Entity not found: ${id}`) + } + if (e.type !== "page") { + throw new Error(`Entity is not a page: ${id}`) + } + + const n = e.data.globalNavigation + if (!n) { + throw new Error(`Global navigation data not found: ${id}`) + } + + return + + {n.title} + + })} + +
+ + + {d.image.alt} + +

+              {d.sample.code}
+            
+
+
+
+ })} +
+} diff --git a/site/pages/DocSpace/index.md b/site/pages/DocSpace/index.md index 09165cd7b..43399bcb6 100644 --- a/site/pages/DocSpace/index.md +++ b/site/pages/DocSpace/index.md @@ -1,23 +1,28 @@ --- order: -2 layout: part -preview: /assets/images/docspace-preview.svg description: ONLYOFFICE DocSpace is a collaborative cloud platform that allows users to store, manage, edit, and collaborate on documents, spreadsheets, presentations, and forms in customizable rooms. -summary: In this section, you will learn how to integrate ONLYOFFICE DocSpace into your own application and interact with its backend using the DocSpace API Backend. You will also find the information on how to embed DocSpace using JavaScript SDK, create your own plugins with our Plugins SDK, and host a portal on your own servers using our methods for hosting providers. -sample: - syntax: html - code: | - - - - - DocSpace JavaScript SDK - - - -
- - + +home: + image: + src: /assets/images/docspace-preview.svg + height: 430 + description: In this section, you will learn how to integrate ONLYOFFICE DocSpace into your own application and interact with its backend using the DocSpace API Backend. You will also find the information on how to embed DocSpace using JavaScript SDK, create your own plugins with our Plugins SDK, and host a portal on your own servers using our methods for hosting providers. + sample: + syntax: html + code: | + + + + + DocSpace JavaScript SDK + + + +
+ + + help: faq: http://0.0.0.0/ issues: http://0.0.0.0/ diff --git a/site/pages/Docs/index.md b/site/pages/Docs/index.md index 423fc6fce..78553d682 100644 --- a/site/pages/Docs/index.md +++ b/site/pages/Docs/index.md @@ -1,27 +1,32 @@ --- order: -1 layout: part -preview: /assets/images/docs-preview.png description: ONLYOFFICE Docs is a collaborative office suite that includes editors for text documents, spreadsheets, presentations, fillable forms, and PDFs. -summary: In this section, you will learn how to bring document editing and co-authoring to your web app users, set up, configure and integrate ONLYOFFICE Docs, extend its functionality using your own plugins/macros, and integrate document editors into the desktop applications. You will also find the information on how to use Document Builder to generate documents easily without running document editors. -sample: - syntax: js - code: | - oTextForm = Api.CreateTextForm({ - key: "Date of Birth", - placeholder: "DDMMYYYY", - comb: true - }) - oLogoForm.SetImage("https://example.com/blank.png") +home: + image: + src: /assets/images/docs-preview.png + height: 540 + description: In this section, you will learn how to bring document editing and co-authoring to your web app users, set up, configure and integrate ONLYOFFICE Docs, extend its functionality using your own plugins/macros, and integrate document editors into the desktop applications. You will also find the information on how to use Document Builder to generate documents easily without running document editors. + sample: + syntax: js + code: | + oTextForm = Api.CreateTextForm({ + key: "Date of Birth", + placeholder: "DDMMYYYY", + comb: true + }) - oParagraph.AddElement(oTextForm) - oParagraph.AddElement(oLogoForm) - oDocument.Push(oParagraph) + oLogoForm.SetImage("https://example.com/blank.png") + + oParagraph.AddElement(oTextForm) + oParagraph.AddElement(oLogoForm) + oDocument.Push(oParagraph) + + Api.Save() + builder.SaveFile("oform", "University.docxf") + builder.CloseFile() - Api.Save() - builder.SaveFile("oform", "University.docxf") - builder.CloseFile() help: faq: http://0.0.0.0/ issues: http://0.0.0.0/ diff --git a/site/pages/Workspace/index.md b/site/pages/Workspace/index.md index 4a3664774..fb0c8b38d 100644 --- a/site/pages/Workspace/index.md +++ b/site/pages/Workspace/index.md @@ -1,26 +1,31 @@ --- layout: part -preview: /assets/images/workspace-preview.png description: ONLYOFFICE Workspace is a collaborative cloud platform that allows users to manage documents, projects, emails, client relations, and timetables in one place. -summary: In this section, you will learn how to integrate ONLYOFFICE Workspace into your own application and interact with its backend using the Workspace API Backend. This solution is provided without editors, you need to install ONLYOFFICE Docs separately. You can also host a portal and interact with it using our methods for hosting providers. -sample: - syntax: js - code: | - POST api/2.0/project/1234/task - Host: yourportal.onlyoffice.co - Content-Type: application/json - Accept: application/json - { - "description": "New task", - "priority": "Normal", - "title": "Task title", - "milestoneid": 1234, - "responsibles": [ - "9924256A-739C-462b-AF15-E652A3B1B6EB" - ], - "startDate": "2024-06-24T11-30-00.000Z" - } +home: + image: + src: /assets/images/workspace-preview.png + height: 500 + description: In this section, you will learn how to integrate ONLYOFFICE Workspace into your own application and interact with its backend using the Workspace API Backend. This solution is provided without editors, you need to install ONLYOFFICE Docs separately. You can also host a portal and interact with it using our methods for hosting providers. + sample: + syntax: js + code: | + POST api/2.0/project/1234/task + Host: yourportal.onlyoffice.co + Content-Type: application/json + Accept: application/json + + { + "description": "New task", + "priority": "Normal", + "title": "Task title", + "milestoneid": 1234, + "responsibles": [ + "9924256A-739C-462b-AF15-E652A3B1B6EB" + ], + "startDate": "2024-06-24T11-30-00.000Z" + } + help: faq: http://0.0.0.0/ issues: http://0.0.0.0/ diff --git a/site/pages/index.tsx b/site/pages/index.tsx index 4610e93f2..7f1923325 100644 --- a/site/pages/index.tsx +++ b/site/pages/index.tsx @@ -1,71 +1,14 @@ -import {type Context, type Data} from "@onlyoffice/eleventy-types" -import { - Home, - HomeHero, - HomeIn, - HomeLink, - HomeLinks, - type HomePartParameters, - HomePart, - HomePreview, - SearchClear, - SearchContainer, - SearchField, - SearchPlaceholder -} from "@onlyoffice/site-kit" -import {CodePreview} from "@onlyoffice/ui-kit" +import {type Data} from "@onlyoffice/eleventy-types" import {type JSX, h} from "preact" -import {SyntaxHighlight} from "@/components/syntax-highlight/syntax-highlight.ts" -import {Icon} from "@/internal/icon.tsx" -import {Image} from "@/internal/image.tsx" +import {Home} from "@/internal/home.tsx" export function data(): Data { return { title: "Welcome to ONLYOFFICE API", - layout: "page" + layout: "page", } } -export function render({collections}: Context): JSX.Element { - return - -

Welcome to ONLYOFFICE API

- - Search in all sections - - - -
- {collections.navigation.map((item, i) => { - return - -

{item.title}

-

{item.summary}

- More - - {item.children.map((item, i) => - - {item.title} - )} - -
- - - - -

-              {item.sample.code}
-            
-
-
-
- - function variant(): HomePartParameters["variant"] { - if (i % 2 === 0) { - return "default" - } - return "reverse" - } - })} -
+export function render(): JSX.Element { + return } diff --git a/site/pages/pages.data.ts b/site/pages/pages.data.ts index 55878667b..08db29f37 100644 --- a/site/pages/pages.data.ts +++ b/site/pages/pages.data.ts @@ -3,6 +3,7 @@ import {type Data} from "@onlyoffice/eleventy-types" import {cutPrefix, cutSuffix} from "@onlyoffice/strings" import {slug} from "github-slugger" import {type GlobalNavigationData, GlobalNavigationDatum} from "@/internal/global-navigation.tsx" +import {type HomeData, HomeDatum} from "@/internal/home.tsx" declare module "@onlyoffice/eleventy-types" { interface Data { @@ -11,6 +12,7 @@ declare module "@onlyoffice/eleventy-types" { crosslink?(data: Data, s: string): string slug?(data: Data): string defaultSitemap?(d: Data): SitemapData + defaultHome?(data: Data): HomeData defaultGlobalNavigation?(data: Data): GlobalNavigationData } @@ -67,6 +69,12 @@ export function data(): Data { return d.defaultSitemap(d) }, + defaultHome(d) { + const m = new HomeDatum() + m.title = d.title + return m + }, + defaultGlobalNavigation(d) { const m = new GlobalNavigationDatum() m.icon = d.icon @@ -93,6 +101,15 @@ export function data(): Data { return `${data.layout}.tsx` }, + home(d) { + const a = d.defaultHome(d) + const b = d.home + if (!b) { + return a + } + return HomeDatum.merge(a, b) + }, + globalNavigation(d) { const a = d.defaultGlobalNavigation(d) const b = d.globalNavigation From f7dde847462ea4ecf5bc856f8f2a80ee3e1fca1f Mon Sep 17 00:00:00 2001 From: Sergey Linnik Date: Thu, 11 Jul 2024 17:00:10 +0300 Subject: [PATCH 07/25] docs: fix jira link --- .../Ready-to-use connectors/Jira integration/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/pages/Docs/Docs API/Get Started/Ready-to-use connectors/Jira integration/index.md b/site/pages/Docs/Docs API/Get Started/Ready-to-use connectors/Jira integration/index.md index 1822956f8..471acecc5 100644 --- a/site/pages/Docs/Docs API/Get Started/Ready-to-use connectors/Jira integration/index.md +++ b/site/pages/Docs/Docs API/Get Started/Ready-to-use connectors/Jira integration/index.md @@ -18,7 +18,7 @@ The easiest way to start an instance of ONLYOFFICE Docs is to use [Docker](https Upload the compiled *target/onlyoffice-jira-app.jar* to Jira on the **Manage apps** page. -The latest compiled package files are available [here](https://github.com/ONLYOFFICE/onlyoffice-jira/releases) and on [Atlassian Marketplace](https://marketplace.atlassian.com/???). +The latest compiled package files are available [here](https://github.com/ONLYOFFICE/onlyoffice-jira/releases) and on [Atlassian Marketplace](https://marketplace.atlassian.com/apps/1226616/onlyoffice-connector-for-jira). You can also install the app from the Jira administration panel: From c9bbb216bffd7faa7c1f5f3f23cf857d98e6e086 Mon Sep 17 00:00:00 2001 From: vanyauhalin Date: Fri, 12 Jul 2024 09:33:44 +0400 Subject: [PATCH 08/25] update the part layout implementation - reuse global navigation paths for children - move the help section to a separate component --- site/internal/help.tsx | 100 +++++++++++++++++++++++++++++++++++++ site/internal/part.tsx | 105 +++++++++++++++++++++++++++++++++++++++ site/layouts/part.tsx | 67 ++----------------------- site/pages/pages.data.ts | 35 +++++++++++++ 4 files changed, 245 insertions(+), 62 deletions(-) create mode 100644 site/internal/help.tsx create mode 100644 site/internal/part.tsx diff --git a/site/internal/help.tsx b/site/internal/help.tsx new file mode 100644 index 000000000..079dc3ba6 --- /dev/null +++ b/site/internal/help.tsx @@ -0,0 +1,100 @@ +import {Sitemap} from "@onlyoffice/eleventy-sitemap" +import {Help as SHelp} from "@onlyoffice/site-kit" +import {GithubIcon} from "@onlyoffice/ui-icons/rich/24.tsx" +import {type JSX, h} from "preact" + +declare module "@onlyoffice/eleventy-types" { + interface Data { + help?: HelpData + } + + interface EleventyComputed { + help?(data: Data): HelpData | undefined + } +} + +export interface HelpData { + title: string + faq: string + forum: string + issues: string +} + +export class HelpDatum implements HelpData { + title = "" + faq = "" + forum = "" + issues = "" + + static merge(a: HelpData, b: HelpData): HelpData { + const c = new HelpDatum() + + if (b.title) { + c.title = b.title + } else if (a.title) { + c.title = a.title + } + + if (b.faq) { + c.faq = b.faq + } else if (a.faq) { + c.faq = a.faq + } + + if (b.forum) { + c.forum = b.forum + } else if (a.forum) { + c.forum = a.forum + } + + if (b.issues) { + c.issues = b.issues + } else if (a.issues) { + c.issues = a.issues + } + + return c + } +} + +export interface HelpProperties { + current: string +} + +export function Help(p: HelpProperties): JSX.Element { + const s = Sitemap.shared + + const e = s.find(p.current, "url") + if (!e) { + throw new Error(`Entity not found: ${p.current}`) + } + if (e.type !== "page") { + throw new Error(`Entity is not a page: ${p.current}`) + } + + let d = e.data.help + if (!d) { + const t = s.trace(e) + for (const id of t) { + const e = s.find(id, "id") + if (!e || e.type !== "page" || !e.data.help) { + continue + } + d = e.data.help + break + } + } + if (!d) { + throw new Error(`Help data not found: ${e.id} (${e.url})`) + } + + return + +

Get Help

+
    +
  • If you have any questions about ONLYOFFICE {d.title}, try the FAQ section first.
  • +
  • You can request a feature or report a bug by posting an issue on GitHub.
  • +
  • You can also ask our developers on ONLYOFFICE forum (registration required).
  • +
+
+} diff --git a/site/internal/part.tsx b/site/internal/part.tsx new file mode 100644 index 000000000..bd47a088d --- /dev/null +++ b/site/internal/part.tsx @@ -0,0 +1,105 @@ +import {Sitemap} from "@onlyoffice/eleventy-sitemap" +import { + Part as SPart, + PartChapter, + PartChapters, + PartHelp, + PartHero, +} from "@onlyoffice/site-kit" +import {SrOnly} from "@onlyoffice/ui-kit" +import {type JSX, h} from "preact" +import {Help} from "./help.tsx" +import {Icon} from "./icon.tsx" +import {Link} from "./link.tsx" + +declare module "@onlyoffice/eleventy-types" { + interface Data { + part?: PartData + } + + interface EleventyComputed { + part?(data: Data): PartData + } +} + +export interface PartData { + title: string + description: string +} + +export class PartDatum implements PartData { + title = "" + description = "" + + static merge(a: PartData, b: PartData): PartData { + const c = new PartDatum() + + if (b.title) { + c.title = b.title + } else if (a.title) { + c.title = a.title + } + + if (b.description) { + c.description = b.description + } else if (a.description) { + c.description = a.description + } + + return c + } +} + +export interface PartParameters { + current: string +} + +export function Part(p: PartParameters): JSX.Element { + const s = Sitemap.shared + + const e = s.find(p.current, "url") + if (!e) { + throw new Error(`Entity not found: ${p.current}`) + } + if (e.type !== "page") { + throw new Error(`Entity is not a page: ${p.current}`) + } + + const d = e.data.part + if (!d) { + throw new Error(`Part data not found: ${e.id} (${e.url})`) + } + + return + +

{d.title}

+

{d.description}

+
+ +

Chapters

+ {e.children.map((id) => { + const e = s.find(id, "id") + if (!e) { + throw new Error(`Entity not found: ${id}`) + } + if (e.type !== "page") { + throw new Error(`Entity is not a page: ${id}`) + } + + const n = e.data.globalNavigation + if (!n) { + throw new Error(`Global navigation data not found: ${id}`) + } + + return + +

{n.title}

+

{e.data.summary}

+
+ })} +
+ + + +
+} diff --git a/site/layouts/part.tsx b/site/layouts/part.tsx index 897f7db2e..8554889e5 100644 --- a/site/layouts/part.tsx +++ b/site/layouts/part.tsx @@ -1,70 +1,13 @@ import {type Context, type Data} from "@onlyoffice/eleventy-types" -import { - Help, - Part, - PartChapter, - PartChapters, - PartHelp, - PartHero -} from "@onlyoffice/site-kit" -import {SrOnly} from "@onlyoffice/ui-kit" -import {GithubIcon} from "@onlyoffice/ui-icons/rich/24.tsx" -import {Fragment, type JSX, h} from "preact" -import {Icon} from "@/internal/icon.tsx" - -declare module "@onlyoffice/eleventy-types" { - interface Context { - help?: Help - } -} - -export interface Help { - faq?: string - forum?: string - issues?: string -} +import {type JSX, h} from "preact" +import {Part} from "../internal/part.tsx" export function data(): Data { return { - layout: "page" + layout: "page", } } -export function render({ - collections, - description, - help, - page, - title -}: Context): JSX.Element { - return - -

{title}

-

{description}

-
- -

Chapters

- {collections.navigation.map((item, i) => { - if (!item.link.startsWith(page.url)) { - return - } - return {item.children.map((item, i) => - -

{item.title}

-

{item.summary}

-
)}
- })} -
- - - -

Get Help

-
    -
  • If you have any questions about ONLYOFFICE DocSpace, try the FAQ section first.
  • -
  • You can request a feature or report a bug by posting an issue on GitHub.
  • -
  • You can also ask our developers on ONLYOFFICE forum (registration required).
  • -
-
-
-
+export function render(c: Context): JSX.Element { + return } diff --git a/site/pages/pages.data.ts b/site/pages/pages.data.ts index 08db29f37..83a5bf643 100644 --- a/site/pages/pages.data.ts +++ b/site/pages/pages.data.ts @@ -3,7 +3,9 @@ import {type Data} from "@onlyoffice/eleventy-types" import {cutPrefix, cutSuffix} from "@onlyoffice/strings" import {slug} from "github-slugger" import {type GlobalNavigationData, GlobalNavigationDatum} from "@/internal/global-navigation.tsx" +import {type HelpData, HelpDatum} from "@/internal/help.tsx" import {type HomeData, HomeDatum} from "@/internal/home.tsx" +import {type PartData, PartDatum} from "@/internal/part.tsx" declare module "@onlyoffice/eleventy-types" { interface Data { @@ -13,6 +15,8 @@ declare module "@onlyoffice/eleventy-types" { slug?(data: Data): string defaultSitemap?(d: Data): SitemapData defaultHome?(data: Data): HomeData + defaultPart?(data: Data): PartData + defaultHelp?(data: Data): HelpData defaultGlobalNavigation?(data: Data): GlobalNavigationData } @@ -75,6 +79,19 @@ export function data(): Data { return m }, + defaultPart(d) { + const m = new PartDatum() + m.title = d.title + m.description = d.description + return m + }, + + defaultHelp(d) { + const m = new HelpDatum() + m.title = d.title + return m + }, + defaultGlobalNavigation(d) { const m = new GlobalNavigationDatum() m.icon = d.icon @@ -110,6 +127,24 @@ export function data(): Data { return HomeDatum.merge(a, b) }, + part(d) { + const a = d.defaultPart(d) + const b = d.part + if (!b) { + return a + } + return PartDatum.merge(a, b) + }, + + help(d) { + const a = d.defaultHelp(d) + const b = d.help + if (!b) { + return + } + return HelpDatum.merge(a, b) + }, + globalNavigation(d) { const a = d.defaultGlobalNavigation(d) const b = d.globalNavigation From bf1721df501b81d60854392e9ef74c3d7e0fddf1 Mon Sep 17 00:00:00 2001 From: vanyauhalin Date: Fri, 12 Jul 2024 09:35:30 +0400 Subject: [PATCH 09/25] remove the old navigation module --- site/config/navigation.ts | 191 -------------------------------------- site/eleventy.config.ts | 2 - site/pages/pages.data.ts | 1 - 3 files changed, 194 deletions(-) delete mode 100644 site/config/navigation.ts diff --git a/site/config/navigation.ts b/site/config/navigation.ts deleted file mode 100644 index 8d9b08b98..000000000 --- a/site/config/navigation.ts +++ /dev/null @@ -1,191 +0,0 @@ -// todo: replace it with sitemap. - -import { extname } from "node:path" -import type { UserConfig } from "@11ty/eleventy" - -export interface NavigationItem { - title: string - link: string - children?: NavigationItem[] -} - -interface TemporalNavigationItem { - title: string - link: string - order: number - children: Record -} - -interface Context { - hierarchy: string[] -} - -// todo: do not use weak map -const cache = new WeakMap() -const doCache = true // isBuild() - -export function navigationPlugin(uc: UserConfig): void { - uc.addCollection("navigation", (tc) => { - if (doCache) { - if (cache.has(navigation)) { - return cache.get(navigation) - } - const n = navigation(tc) - cache.set(navigation, n) - return n - } - return navigation(tc) - }) -} - -function navigation(tc: any): NavigationItem[] { - const ctx: Context = { - hierarchy: [] - } - const l = tc.getFilteredByTag("navigation") - const t = collectNavigation(l) - const r = processNavigation(ctx, t) - if (r.children === undefined) { - return [] - } - return r.children -} - -function collectNavigation(l: any[]): TemporalNavigationItem { - const temp: TemporalNavigationItem = { - title: "", - link: "", - order: 0, - children: {} - } - - l.forEach((item) => { - if (item.url === "/") { - return - } - if (extname(item.outputPath) !== ".html") { - return - } - - const m = item.url.match(/^\/([\S\s]*)\/$/) - if (!m) { - return - } - - const [, u] = m - if (!u) { - return - } - - let r = temp.children - - // if (item.url === "/docspace/rest-api/wildlife/") { - // console.log(item) - // } - - const us = u.split("/") - us.forEach((s, i) => { - if (r[s] === undefined) { - r[s] = { - title: "", - link: "", - order: 0, - children: {} - } - } - - switch (i) { - case us.length - 1: - r[s].title = item.data.title - r[s].link = item.url - r[s].icon = item.data.icon - r[s].logo = item.data.logo - r[s].preview = item.data.preview - r[s].description = item.data.description - r[s].summary = item.data.summary - r[s].sample = item.data.sample - r[s].faq = item.data.faq - r[s].issues = item.data.issues - r[s].forume = item.data.forume - if (item.data.order !== undefined) { - r[s].order = item.data.order - } - r[s].file = item.data.page.inputPath - break - default: - r = r[s].children - break - } - }) - }) - - return temp -} - -function processNavigation(ctx: Context, t: TemporalNavigationItem): NavigationItem { - const item: NavigationItem = { - title: t.title, - link: t.link, - icon: t.icon, - logo: t.logo, - preview: t.preview, - description: t.description, - summary: t.summary, - sample: t.sample, - faq: t.faq, - issues: t.issues, - forume: t.forume, - file: t.file - } - - const en = Object.entries(t.children) - if (en.length > 0) { - item.children = en - .map(([s, t]) => { - ctx.hierarchy.push(s) - t = preprocessNavigation(ctx, t) - const r = processNavigation(ctx, t) - ctx.hierarchy.pop() - return { - o: t.order, - r - } - }) - .sort((a, b) => { - const d = a.o - b.o - if (d !== 0) { - return d - } - return a.r.title.localeCompare(b.r.title) - }) - .map((t) => { - return t.r - }) - } - - return item -} - -function preprocessNavigation(ctx: Context, t: TemporalNavigationItem): TemporalNavigationItem { - // todo: is it okay? explain why it might happen. - // todo: thrown an error on production. - // todo: print a warning on development. - - if (t.link === "") { - t.link = ctx.hierarchy.join("/") - // console.log("nav", t.link, t) - if (!t.link.startsWith("/")) { - t.link = `/${t.link}` - } - if (!t.link.endsWith("/")) { - t.link += "/" - } - } - - if (t.title === "") { - const i = t.link.lastIndexOf("/", t.link.lastIndexOf("/") - 1) - t.title = t.link.substring(i + 1, t.link.length - 1) - } - - return t -} diff --git a/site/eleventy.config.ts b/site/eleventy.config.ts index 013411c94..9d142ddb2 100644 --- a/site/eleventy.config.ts +++ b/site/eleventy.config.ts @@ -12,7 +12,6 @@ import {type UserConfig} from "@onlyoffice/eleventy-types" import {eleventyYAML} from "@onlyoffice/eleventy-yaml" import {Config} from "@onlyoffice/site-config" import {markupPlugin} from "./config/markup.ts" -import {navigationPlugin} from "./config/navigation.ts" import {staticPlugin} from "./config/static.ts" import {eleventyMarkdown} from "./internal/markdown.tsx" @@ -40,7 +39,6 @@ function config(uc: UserConfig): unknown { sortAttributes: true }) - uc.addPlugin(navigationPlugin) uc.addPlugin(eleventyStarryNight) uc.addPlugin(eleventyYAML) diff --git a/site/pages/pages.data.ts b/site/pages/pages.data.ts index 83a5bf643..47cc08e04 100644 --- a/site/pages/pages.data.ts +++ b/site/pages/pages.data.ts @@ -33,7 +33,6 @@ declare module "@onlyoffice/eleventy-types" { export function data(): Data { return { layout: "chapter", - tags: ["navigation"], permalink(data) { let p = data.page.filePathStem From 8d5b246e115949996e25214d1d73feb94b9a4d43 Mon Sep 17 00:00:00 2001 From: vanyauhalin Date: Fri, 12 Jul 2024 10:38:57 +0400 Subject: [PATCH 10/25] add objects package --- packages/objects/lib/main.test.ts | 52 +++++++++++++++++++++++++++++++ packages/objects/lib/main.ts | 26 ++++++++++++++++ packages/objects/package.json | 19 +++++++++++ packages/objects/tsconfig.json | 4 +++ pnpm-lock.yaml | 16 ++++++++++ 5 files changed, 117 insertions(+) create mode 100644 packages/objects/lib/main.test.ts create mode 100644 packages/objects/lib/main.ts create mode 100644 packages/objects/package.json create mode 100644 packages/objects/tsconfig.json diff --git a/packages/objects/lib/main.test.ts b/packages/objects/lib/main.test.ts new file mode 100644 index 000000000..a401a6a3c --- /dev/null +++ b/packages/objects/lib/main.test.ts @@ -0,0 +1,52 @@ +import {is} from "uvu/assert" +import {test} from "uvu" +import {isEmpty} from "./main.ts" + +test("isEmpty(): returns false for a string", () => { + const a = isEmpty("") + is(a, false) +}) + +test("isEmpty(): returns false for a number", () => { + const a = isEmpty(0) + is(a, false) +}) + +test("isEmpty(): returns false for an arrow function", () => { + const a = isEmpty(() => {}) + is(a, false) +}) + +test("isEmpty(): returns false for a function", () => { + // eslint-disable-next-line prefer-arrow-callback + const a = isEmpty(function () {}) + is(a, false) +}) + +test("isEmpty(): returns false for a boolean", () => { + const a = isEmpty(false) + is(a, false) +}) + +test("isEmpty(): returns false for a null", () => { + const a = isEmpty(null) + is(a, false) +}) + +test("isEmpty(): returns false for an undefined", () => { + // eslint-disable-next-line unicorn/no-useless-undefined + const a = isEmpty(undefined) + is(a, false) +}) + +test("isEmpty(): returns true for an empty object", () => { + const a = isEmpty({}) + is(a, true) +}) + +test("isEmpty(): returns false for an object with properties", () => { + const a = isEmpty({a: 1}) + is(a, false) +}) + +test.run() diff --git a/packages/objects/lib/main.ts b/packages/objects/lib/main.ts new file mode 100644 index 000000000..2db5b87d3 --- /dev/null +++ b/packages/objects/lib/main.ts @@ -0,0 +1,26 @@ +export function isEmpty(o: unknown): boolean { + if (!isPlain(o)) { + return false + } + + for (const k in o) { + if (Object.hasOwn(o, k)) { + return false + } + } + + return true +} + +export function isPlain(o: unknown): o is object { + if (!o || typeof o !== "object") { + return false + } + + const p = Object.getPrototypeOf(o) + if (!p && p !== Object.prototype) { + return false + } + + return true +} diff --git a/packages/objects/package.json b/packages/objects/package.json new file mode 100644 index 000000000..b68677488 --- /dev/null +++ b/packages/objects/package.json @@ -0,0 +1,19 @@ +{ + "name": "@onlyoffice/objects", + "private": true, + "type": "module", + "main": "lib/main.ts", + "scripts": { + "test:types": "tsc", + "test:unit": "c8 --config ../../c8.config.json tsx node_modules/uvu/bin.js lib ^.*\\.test\\.ts$", + "test": "pnpm test:types && pnpm test:unit" + }, + "dependencies": { + "typescript": "^5.4.5" + }, + "devDependencies": { + "c8": "^9.1.0", + "tsx": "^4.10.5", + "uvu": "^0.5.6" + } +} diff --git a/packages/objects/tsconfig.json b/packages/objects/tsconfig.json new file mode 100644 index 000000000..f0d8cf437 --- /dev/null +++ b/packages/objects/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["lib"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5a806c8ec..8cba3d458 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1223,6 +1223,22 @@ importers: specifier: ^0.5.6 version: 0.5.6 + packages/objects: + dependencies: + typescript: + specifier: ^5.4.5 + version: 5.4.5 + devDependencies: + c8: + specifier: ^9.1.0 + version: 9.1.0 + tsx: + specifier: ^4.10.5 + version: 4.11.0 + uvu: + specifier: ^0.5.6 + version: 0.5.6 + packages/openapi-declaration: dependencies: '@onlyoffice/console': From 3f987ad2274bda776be23518dbf46eb8e45d57e5 Mon Sep 17 00:00:00 2001 From: vanyauhalin Date: Fri, 12 Jul 2024 10:39:27 +0400 Subject: [PATCH 11/25] add sitemap datum --- packages/eleventy-sitemap/lib/main.ts | 103 ++++++++++++++++++------- packages/eleventy-sitemap/package.json | 1 + pnpm-lock.yaml | 3 + site/generations/library.ts | 64 ++++++++------- site/pages/pages.data.ts | 29 ++++--- 5 files changed, 127 insertions(+), 73 deletions(-) diff --git a/packages/eleventy-sitemap/lib/main.ts b/packages/eleventy-sitemap/lib/main.ts index 1bf00da5c..f5253065b 100644 --- a/packages/eleventy-sitemap/lib/main.ts +++ b/packages/eleventy-sitemap/lib/main.ts @@ -1,24 +1,87 @@ +import {randomUUID} from "node:crypto" import {extname} from "node:path" import {type Data, type UserConfig} from "@onlyoffice/eleventy-types" -import {randomUUID} from "node:crypto" +import {isEmpty} from "@onlyoffice/objects" declare module "@onlyoffice/eleventy-types" { interface Data { - sitemap?(d: Data): SitemapData + sitemap?: SitemapData + } + + interface EleventyComputed { + sitemap?(data: Data): SitemapData | undefined } } export interface SitemapData { - title?: string - url?: string - path?: string - order?: number - groups?(): { + title: string + url: string + path: string + order: number + groups: { title?: string order?: number }[] - group?(): string - data?: Data + group: string + data: Data +} + +export class SitemapDatum implements SitemapData { + title = "" + url = "" + path = "" + order = 0 + groups: SitemapData["groups"] = [] + group = "" + data: SitemapData["data"] = {} + + static merge(a: SitemapData, b: SitemapData): SitemapData { + const c = new SitemapDatum() + + if (b.title) { + c.title = b.title + } else if (a.title) { + c.title = a.title + } + + if (b.url) { + c.url = b.url + } else if (a.url) { + c.url = a.url + } + + if (b.path) { + c.path = b.path + } else if (a.path) { + c.path = a.path + } + + if (b.order) { + c.order = b.order + } else if (a.order) { + c.order = a.order + } + + if (b.groups.length !== 0) { + c.groups = b.groups + } else if (a.groups.length !== 0) { + c.groups = a.groups + } + + if (b.group) { + c.group = b.group + } else if (a.group) { + c.group = a.group + } + + if (!isEmpty(b.data)) { + c.data = b.data + } else if (!isEmpty(a.data)) { + c.data = a.data + } + + return c + } } export interface SitemapAccessible { @@ -105,24 +168,11 @@ export function eleventySitemap(uc: UserConfig): void { continue } - const d = te.data.sitemap(te.data) + const d = te.data.sitemap if (!d) { continue } - if (!d.title) { - throw new Error("No title") - } - if (!d.url) { - throw new Error("No URL") - } - if (!d.path) { - throw new Error("No path") - } - if (!d.data) { - throw new Error("No data") - } - const p = new SitemapPage() p.id = randomUUID() p.title = d.title @@ -155,7 +205,7 @@ export function eleventySitemap(uc: UserConfig): void { } if (d.groups) { - const a = d.groups() + const a = d.groups for (const d of a) { if (!d.title) { throw new Error("No title") @@ -185,10 +235,7 @@ export function eleventySitemap(uc: UserConfig): void { } if (d.group) { - const n = d.group() - if (n) { - c.set(p.id, n) - } + c.set(p.id, d.group) } } diff --git a/packages/eleventy-sitemap/package.json b/packages/eleventy-sitemap/package.json index 00fa6a49a..4358c1d4b 100644 --- a/packages/eleventy-sitemap/package.json +++ b/packages/eleventy-sitemap/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "@onlyoffice/eleventy-types": "workspace:^", + "@onlyoffice/objects": "workspace:^", "typescript": "^5.4.5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8cba3d458..d3116464d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -815,6 +815,9 @@ importers: '@onlyoffice/eleventy-types': specifier: workspace:^ version: link:../eleventy-types + '@onlyoffice/objects': + specifier: workspace:^ + version: link:../objects typescript: specifier: ^5.4.5 version: 5.4.5 diff --git a/site/generations/library.ts b/site/generations/library.ts index 94fdc1558..d29da263b 100644 --- a/site/generations/library.ts +++ b/site/generations/library.ts @@ -1,3 +1,4 @@ +import {SitemapDatum} from "@onlyoffice/eleventy-sitemap" import {type Data} from "@onlyoffice/eleventy-types" import {type Declaration, type Reference, type Token} from "@onlyoffice/library-declaration" import {type Resource} from "@onlyoffice/library-resource" @@ -22,50 +23,47 @@ export function data({list, retrieve}: Resource): Data { return `${p}/index` }, - sitemap(data) { - const s = data.defaultSitemap(data) + onRetrieve(r: Reference): Declaration | undefined { + return retrieve(r.id) + }, - s.groups = function groups() { - const [d]: Declaration[] = data.pagination.items - if (d.kind !== "class") { - return [] + eleventyComputed: { + title(data) { + if (!data || !data.pagination || !data.pagination.items) { + throw new Error("No pagination") } - return [ - {title: "Constructors"}, - {title: "Events"}, - {title: "Methods"}, - {title: "Properties"}, - ] - } + return data.pagination.items[0].title + }, + + sitemap(data) { + const a = data.defaultSitemap(data) + const b = new SitemapDatum() - s.group = function group() { const [d]: Declaration[] = data.pagination.items switch (d.kind) { + case "class": + b.groups = [ + {title: "Constructors"}, + {title: "Events"}, + {title: "Methods"}, + {title: "Properties"}, + ] + break case "constructor": - return "Constructors" + b.group = "Constructors" + break case "event": - return "Events" + b.group = "Events" + break case "method": - return "Methods" + b.group = "Methods" + break case "property": - return "Properties" + b.group = "Properties" + break } - return "" - } - return s - }, - - onRetrieve(r: Reference): Declaration | undefined { - return retrieve(r.id) - }, - - eleventyComputed: { - title(data) { - if (!data || !data.pagination || !data.pagination.items) { - throw new Error("No pagination") - } - return data.pagination.items[0].title + return SitemapDatum.merge(a, b) }, onLink(data) { diff --git a/site/pages/pages.data.ts b/site/pages/pages.data.ts index 47cc08e04..bb775b94e 100644 --- a/site/pages/pages.data.ts +++ b/site/pages/pages.data.ts @@ -1,4 +1,4 @@ -import {type SitemapData} from "@onlyoffice/eleventy-sitemap" +import {type SitemapData, SitemapDatum} from "@onlyoffice/eleventy-sitemap" import {type Data} from "@onlyoffice/eleventy-types" import {cutPrefix, cutSuffix} from "@onlyoffice/strings" import {slug} from "github-slugger" @@ -59,17 +59,13 @@ export function data(): Data { }, defaultSitemap(d) { - return { - title: d.title, - url: d.page?.url, - path: d.page?.inputPath, - order: d.order || 0, - data: d, - } - }, - - sitemap(d) { - return d.defaultSitemap(d) + const m = new SitemapDatum() + m.title = d.title + m.url = d.page?.url + m.path = d.page?.inputPath + m.order = d.order || 0 + m.data = d + return m }, defaultHome(d) { @@ -117,6 +113,15 @@ export function data(): Data { return `${data.layout}.tsx` }, + sitemap(d) { + const a = d.defaultSitemap(d) + const b = d.sitemap + if (!b) { + return a + } + return SitemapDatum.merge(a, b) + }, + home(d) { const a = d.defaultHome(d) const b = d.home From d1577cbe589709a0b93ad675b9f1467e37edbd2f Mon Sep 17 00:00:00 2001 From: vanyauhalin Date: Fri, 12 Jul 2024 13:57:46 +0400 Subject: [PATCH 12/25] add the items property to the eleventy pagination object --- packages/eleventy-types/lib/main.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/eleventy-types/lib/main.ts b/packages/eleventy-types/lib/main.ts index 90bb44712..bb4e1e35f 100644 --- a/packages/eleventy-types/lib/main.ts +++ b/packages/eleventy-types/lib/main.ts @@ -173,6 +173,8 @@ export interface Data { * {@link https://www.11ty.dev/docs/pagination/ Eleventy Reference} */ export interface Pagination { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + items?: any[] data?: string size?: number addAllPagesToCollections?: boolean From 9ad4b2a68ce714a5309adb89425ba74848af1e86 Mon Sep 17 00:00:00 2001 From: vanyauhalin Date: Fri, 12 Jul 2024 13:58:17 +0400 Subject: [PATCH 13/25] add the layout property to the eleventy computed object --- packages/eleventy-types/lib/main.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/eleventy-types/lib/main.ts b/packages/eleventy-types/lib/main.ts index bb4e1e35f..6b2a7cafb 100644 --- a/packages/eleventy-types/lib/main.ts +++ b/packages/eleventy-types/lib/main.ts @@ -185,6 +185,11 @@ export interface Pagination { */ export interface EleventyComputed { [k: string]: unknown + + /** + * {@link https://www.11ty.dev/docs/layouts/ Eleventy Reference} + */ + layout?(data: Data): string | undefined } /** From 80a57094c5fe11d3520c10145e850eeb6de5bcd0 Mon Sep 17 00:00:00 2001 From: vanyauhalin Date: Fri, 12 Jul 2024 14:17:41 +0400 Subject: [PATCH 14/25] allow the return of an undefined from computed properties --- site/internal/global-navigation.tsx | 2 +- site/internal/home.tsx | 2 +- site/internal/part.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/site/internal/global-navigation.tsx b/site/internal/global-navigation.tsx index 91f5ea728..2dea94eb0 100644 --- a/site/internal/global-navigation.tsx +++ b/site/internal/global-navigation.tsx @@ -15,7 +15,7 @@ declare module "@onlyoffice/eleventy-types" { } interface EleventyComputed { - globalNavigation?(data: Data): GlobalNavigationData + globalNavigation?(data: Data): GlobalNavigationData | undefined } } diff --git a/site/internal/home.tsx b/site/internal/home.tsx index d8ba63ab7..72859ff0b 100644 --- a/site/internal/home.tsx +++ b/site/internal/home.tsx @@ -26,7 +26,7 @@ declare module "@onlyoffice/eleventy-types" { } interface EleventyComputed { - home?(data: Data): HomeData + home?(data: Data): HomeData | undefined } } diff --git a/site/internal/part.tsx b/site/internal/part.tsx index bd47a088d..0ce5ac2fb 100644 --- a/site/internal/part.tsx +++ b/site/internal/part.tsx @@ -18,7 +18,7 @@ declare module "@onlyoffice/eleventy-types" { } interface EleventyComputed { - part?(data: Data): PartData + part?(data: Data): PartData | undefined } } From 786b6079b87b11ddc4f757e9b9db7c47d0aa353a Mon Sep 17 00:00:00 2001 From: vanyauhalin Date: Fri, 12 Jul 2024 14:21:22 +0400 Subject: [PATCH 15/25] normalize the general data of pages --- site/generations/library.ts | 10 +- site/generations/page.ts | 211 ++++++++++++++++++++++++++++++++++++ site/pages/pages.data.ts | 160 +-------------------------- 3 files changed, 222 insertions(+), 159 deletions(-) create mode 100644 site/generations/page.ts diff --git a/site/generations/library.ts b/site/generations/library.ts index d29da263b..3623c529d 100644 --- a/site/generations/library.ts +++ b/site/generations/library.ts @@ -36,7 +36,15 @@ export function data({list, retrieve}: Resource): Data { }, sitemap(data) { - const a = data.defaultSitemap(data) + if (!data.pagination || !data.pagination.items) { + return + } + + const a = data.defaultSitemap + if (!a) { + return + } + const b = new SitemapDatum() const [d]: Declaration[] = data.pagination.items diff --git a/site/generations/page.ts b/site/generations/page.ts new file mode 100644 index 000000000..910780e2e --- /dev/null +++ b/site/generations/page.ts @@ -0,0 +1,211 @@ +import {type SitemapData, SitemapDatum} from "@onlyoffice/eleventy-sitemap" +import {type Data} from "@onlyoffice/eleventy-types" +import {cutSuffix} from "@onlyoffice/strings" +import {slug} from "github-slugger" +import {type GlobalNavigationData, GlobalNavigationDatum} from "../internal/global-navigation.tsx" +import {type HelpData, HelpDatum} from "../internal/help.tsx" +import {type HomeData, HomeDatum} from "../internal/home.tsx" +import {type PartData, PartDatum} from "../internal/part.tsx" + +declare module "@onlyoffice/eleventy-types" { + interface Data { + slug?(data: Data): string | undefined + crosslink?(data: Data, slug: string): string | undefined + + icon?: string + title?: string + description?: string + summary?: string + order?: number + items?: unknown[] + + defaultSitemap?: SitemapData + defaultGlobalNavigation?: GlobalNavigationData + defaultHelp?: HelpData + defaultPart?: PartData + defaultHome?: HomeData + } + + interface EleventyComputed { + icon?(data: Data): string | undefined + title?(data: Data): string | undefined + description?(data: Data): string | undefined + summary?(data: Data): string | undefined + order?(data: Data): number | undefined + items?(data: Data): unknown[] | undefined + + defaultSitemap?(data: Data): SitemapData | undefined + defaultGlobalNavigation?(data: Data): GlobalNavigationData | undefined + defaultHelp?(data: Data): HelpData | undefined + defaultPart?(data: Data): PartData | undefined + defaultHome?(data: Data): HomeData | undefined + } +} + +export function data(): Data { + return { + permalink(d) { + if (!d.page) { + return + } + let p = d.page.filePathStem + if (d.slug) { + p = cutSuffix(p, d.page.fileSlug) + p += d.slug(d) + } + p = p.split("/").map((s) => slug(s)).join("/") + p += `.${d.page.outputFileExtension}` + return p + }, + + crosslink(d, s) { + if (!d.page) { + return + } + let p = d.page.filePathStem + p = cutSuffix(p, d.page.fileSlug) + p += s + p = p.split("/").map((s) => slug(s)).join("/") + return p + }, + + layout: "chapter", + + eleventyComputed: { + title(d) { + if (d.title) { + return d.title + } + if (d.page) { + return d.page.fileSlug + } + }, + + layout(d) { + if (d.layout) { + return `${d.layout}.tsx` + } + }, + + sitemap(d) { + const a = d.defaultSitemap + if (!a) { + return + } + const b = d.sitemap + if (!b) { + return a + } + return SitemapDatum.merge(a, b) + }, + + defaultSitemap(d) { + const m = new SitemapDatum() + if (d.title) { + m.title = d.title + } + if (d.page) { + m.url = d.page.url + m.path = d.page.inputPath + } + if (d.order) { + m.order = d.order + } + m.data = d + return m + }, + + globalNavigation(d) { + const a = d.defaultGlobalNavigation + if (!a) { + return + } + const b = d.globalNavigation + if (!b) { + return a + } + return GlobalNavigationDatum.merge(a, b) + }, + + defaultGlobalNavigation(d) { + const m = new GlobalNavigationDatum() + if (d.icon) { + m.icon = d.icon + } + if (d.title) { + m.title = d.title + } + if (d.page) { + // Remove the leading dot to pass this path to the Link component. + m.path = d.page.inputPath.slice(1) + } + return m + }, + + help(d) { + const a = d.defaultHelp + if (!a) { + return + } + const b = d.help + if (!b) { + // Do not return the default help data to force the Help component to + // try find the help data in the tree. + return + } + return HelpDatum.merge(a, b) + }, + + defaultHelp(d) { + const m = new HelpDatum() + if (d.title) { + m.title = d.title + } + return m + }, + + part(d) { + const a = d.defaultPart + if (!a) { + return + } + const b = d.part + if (!b) { + return a + } + return PartDatum.merge(a, b) + }, + + defaultPart(d) { + const m = new PartDatum() + if (d.title) { + m.title = d.title + } + if (d.description) { + m.description = d.description + } + return m + }, + + home(d) { + const a = d.defaultHome + if (!a) { + return + } + const b = d.home + if (!b) { + return a + } + return HomeDatum.merge(a, b) + }, + + defaultHome(d) { + const m = new HomeDatum() + if (d.title) { + m.title = d.title + } + return m + }, + }, + } +} diff --git a/site/pages/pages.data.ts b/site/pages/pages.data.ts index bb775b94e..02e0caed3 100644 --- a/site/pages/pages.data.ts +++ b/site/pages/pages.data.ts @@ -1,162 +1,6 @@ -import {type SitemapData, SitemapDatum} from "@onlyoffice/eleventy-sitemap" import {type Data} from "@onlyoffice/eleventy-types" -import {cutPrefix, cutSuffix} from "@onlyoffice/strings" -import {slug} from "github-slugger" -import {type GlobalNavigationData, GlobalNavigationDatum} from "@/internal/global-navigation.tsx" -import {type HelpData, HelpDatum} from "@/internal/help.tsx" -import {type HomeData, HomeDatum} from "@/internal/home.tsx" -import {type PartData, PartDatum} from "@/internal/part.tsx" - -declare module "@onlyoffice/eleventy-types" { - interface Data { - title?: string - items?: any[] - crosslink?(data: Data, s: string): string - slug?(data: Data): string - defaultSitemap?(d: Data): SitemapData - defaultHome?(data: Data): HomeData - defaultPart?(data: Data): PartData - defaultHelp?(data: Data): HelpData - defaultGlobalNavigation?(data: Data): GlobalNavigationData - } - - interface EleventyComputed { - title?(data?: Data): string | undefined - layout?(data?: Data): string | undefined - } - - interface Pagination { - items?: any[] - } -} +import * as g from "@/generations/page.ts" export function data(): Data { - return { - layout: "chapter", - - permalink(data) { - let p = data.page.filePathStem - p = cutPrefix(p, "/pages") - - if (data.slug) { - p = cutSuffix(p, data.page.fileSlug) - p += data.slug(data) - } - - p = p.split("/").map((s) => slug(s)).join("/") - p += `.${data.page.outputFileExtension}` - - return p - }, - - crosslink(data, s) { - let p = data.page.filePathStem - p = cutPrefix(p, "/pages") - p = cutSuffix(p, data.page.fileSlug) - p += s - p = p.split("/").map((s) => slug(s)).join("/") - return p - }, - - defaultSitemap(d) { - const m = new SitemapDatum() - m.title = d.title - m.url = d.page?.url - m.path = d.page?.inputPath - m.order = d.order || 0 - m.data = d - return m - }, - - defaultHome(d) { - const m = new HomeDatum() - m.title = d.title - return m - }, - - defaultPart(d) { - const m = new PartDatum() - m.title = d.title - m.description = d.description - return m - }, - - defaultHelp(d) { - const m = new HelpDatum() - m.title = d.title - return m - }, - - defaultGlobalNavigation(d) { - const m = new GlobalNavigationDatum() - m.icon = d.icon - m.title = d.title - m.path = d.page.inputPath.slice(1) - return m - }, - - eleventyComputed: { - title(data) { - if (!data) { - return undefined - } - if (!data.title) { - return data.page.fileSlug - } - return data.title - }, - - layout(data) { - if (!data || !data.layout) { - return undefined - } - return `${data.layout}.tsx` - }, - - sitemap(d) { - const a = d.defaultSitemap(d) - const b = d.sitemap - if (!b) { - return a - } - return SitemapDatum.merge(a, b) - }, - - home(d) { - const a = d.defaultHome(d) - const b = d.home - if (!b) { - return a - } - return HomeDatum.merge(a, b) - }, - - part(d) { - const a = d.defaultPart(d) - const b = d.part - if (!b) { - return a - } - return PartDatum.merge(a, b) - }, - - help(d) { - const a = d.defaultHelp(d) - const b = d.help - if (!b) { - return - } - return HelpDatum.merge(a, b) - }, - - globalNavigation(d) { - const a = d.defaultGlobalNavigation(d) - const b = d.globalNavigation - if (!b) { - return a - } - return GlobalNavigationDatum.merge(a, b) - }, - }, - } + return g.data() } From cb006bea07adc736208a55a9e434ab0fab3b40f4 Mon Sep 17 00:00:00 2001 From: vanyauhalin Date: Fri, 12 Jul 2024 15:55:19 +0400 Subject: [PATCH 16/25] merge the table of contents layout with the chapter one --- site/generations/page.ts | 23 ++ site/internal/chapter.tsx | 275 ++++++++++++++++++ site/layouts/chapter.tsx | 223 +------------- site/layouts/table-of-contents.tsx | 16 - site/pages/Search/index.tsx | 12 +- .../API Backend/API Backend/index.md | 3 +- .../API Backend/Get Started/index.md | 3 +- .../API Backend/More Information/index.md | 3 +- site/pages/Workspace/API Backend/index.md | 5 +- 9 files changed, 319 insertions(+), 244 deletions(-) create mode 100644 site/internal/chapter.tsx delete mode 100644 site/layouts/table-of-contents.tsx diff --git a/site/generations/page.ts b/site/generations/page.ts index 910780e2e..2d80d279f 100644 --- a/site/generations/page.ts +++ b/site/generations/page.ts @@ -2,6 +2,7 @@ import {type SitemapData, SitemapDatum} from "@onlyoffice/eleventy-sitemap" import {type Data} from "@onlyoffice/eleventy-types" import {cutSuffix} from "@onlyoffice/strings" import {slug} from "github-slugger" +import {type ChapterData, ChapterDatum} from "../internal/chapter.tsx" import {type GlobalNavigationData, GlobalNavigationDatum} from "../internal/global-navigation.tsx" import {type HelpData, HelpDatum} from "../internal/help.tsx" import {type HomeData, HomeDatum} from "../internal/home.tsx" @@ -22,6 +23,7 @@ declare module "@onlyoffice/eleventy-types" { defaultSitemap?: SitemapData defaultGlobalNavigation?: GlobalNavigationData defaultHelp?: HelpData + defaultChapter?: ChapterData defaultPart?: PartData defaultHome?: HomeData } @@ -37,6 +39,7 @@ declare module "@onlyoffice/eleventy-types" { defaultSitemap?(data: Data): SitemapData | undefined defaultGlobalNavigation?(data: Data): GlobalNavigationData | undefined defaultHelp?(data: Data): HelpData | undefined + defaultChapter?(data: Data): ChapterData | undefined defaultPart?(data: Data): PartData | undefined defaultHome?(data: Data): HomeData | undefined } @@ -164,6 +167,26 @@ export function data(): Data { return m }, + chapter(d) { + const a = d.defaultChapter + if (!a) { + return + } + const b = d.chapter + if (!b) { + return a + } + return ChapterDatum.merge(a, b) + }, + + defaultChapter(d) { + const m = new ChapterDatum() + if (d.title) { + m.title = d.title + } + return m + }, + part(d) { const a = d.defaultPart if (!a) { diff --git a/site/internal/chapter.tsx b/site/internal/chapter.tsx new file mode 100644 index 000000000..57d66390b --- /dev/null +++ b/site/internal/chapter.tsx @@ -0,0 +1,275 @@ +import {Sitemap, type SitemapEntity} from "@onlyoffice/eleventy-sitemap" +import {type ChildrenIncludable} from "@onlyoffice/preact-types" +import { + Chapter as SChapter, + ChapterContent, + ChapterNavigation as SChapterNavigation, + SearchClear, + SearchContainer, + SearchField, + SearchHidable, + SearchOutput, + SearchPlaceholder, + SearchTemplate, +} from "@onlyoffice/site-kit" +import { + Breadcrumb as UBreadcrumb, + BreadcrumbCrumb, + Content, +} from "@onlyoffice/ui-kit" +import {Fragment, type JSX, h} from "preact" +import {Tree} from "../components/tree/tree.ts" +import {TableOfContents} from "./table-of-contents.tsx" + +declare module "@onlyoffice/eleventy-types" { + interface Data { + chapter?: ChapterData + } + + interface EleventyComputed { + chapter?(data: Data): ChapterData | undefined + } +} + +export interface ChapterData { + title?: string + tableOfContents?: boolean +} + +export class ChapterDatum implements ChapterData { + title = "" + tableOfContents = false + + static merge(a: ChapterData, b: ChapterData): ChapterData { + const c = new ChapterDatum() + + if (b.title) { + c.title = b.title + } else if (a.title) { + c.title = a.title + } + + if (b.tableOfContents) { + c.tableOfContents = b.tableOfContents + } else if (a.tableOfContents) { + c.tableOfContents = a.tableOfContents + } + + return c + } +} + +export interface ChapterProperties extends ChildrenIncludable { + url: string +} + +export function Chapter(p: ChapterProperties): JSX.Element { + const s = Sitemap.shared + + const ue = s.find(p.url, "url") + if (!ue) { + throw new Error(`Entity not found: ${p.url}`) + } + if (ue.type !== "page") { + throw new Error(`Current entity is not a page: ${ue.type} (${ue.id})`) + } + + const ud = ue.data.chapter + if (!ud) { + throw new Error(`Chapter data not found: ${ue.url} (${ue.id})`) + } + + const ut = s.trace(ue) + if (ut.length < 3) { + throw new Error(`Chapter layout requires at least three levels: ${ue.url} (${ue.id})`) + } + + const [, pi, hi] = ut + + const pe = s.find(pi, "id") + if (!pe) { + throw new Error(`Entity not found: ${pi}`) + } + if (pe.type !== "page") { + throw new Error(`Part entity is not a page: ${pe.type} (${pe.id})`) + } + + const he = s.find(hi, "id") + if (!he) { + throw new Error(`Entity not found: ${hi}`) + } + if (he.type !== "page") { + throw new Error(`Chapter entity is not a page: ${he.type} (${he.id})`) + } + + const hd = he.data.chapter + if (!hd) { + throw new Error(`Chapter data not found: ${he.url} (${he.id})`) + } + + return + + + Type / to search + + + +
  • + +

    +
  • +
    +
    + +
    + + + + +

    {ud.title}

    + {p.children} + {ud.tableOfContents && } +
    +
    + + +

    Results

    +
      +
      +
      +
      +
      +} + +export interface ChapterNavigationProperties { + level: number + url: string +} + +export function ChapterNavigation(p: ChapterNavigationProperties): JSX.Element | null { + const s = Sitemap.shared + + let l = p.level + let e = s.find("/", "url") + while (true) { + if (!e || l === 0) { + break + } + for (const id of e.children) { + const c = s.find(id, "id") + if (!c) { + continue + } + let u = "" + if (c.type === "group") { + const b = s.find(c.parent, "id") + if (!b || b.type !== "page") { + continue + } + u = b.url + } else if (c.type === "page") { + u = c.url + } else { + // @ts-expect-error + throw new Error(`Unexpected entity type: ${c.type}`) + } + if (p.url.startsWith(u)) { + e = c + l -= 1 + break + } + } + } + + if (!e) { + return null + } + + return + {e.children.map((id) => { + const e = s.find(id, "id") + if (!e || e.type !== "page") { + return null + } + return + {e.title} + + + })} + + + function Sub({e}: {e: SitemapEntity}): JSX.Element | null { + return <>{e.children.map((id) => { + const e = s.find(id, "id") + if (!e) { + return null + } + if (e.type === "group") { + if (e.children.length === 0) { + return null + } + const r = s.find(e.parent, "id") + if (!r) { + return null + } + if (r.type !== "page") { + throw new Error(`Nested group is not supported: ${e.id}`) + } + const b = s.find(p.url, "url") + if (!b) { + return null + } + return + {e.title} + + + } + if (e.type === "page") { + return + {e.title} + {e.children.length !== 0 && } + + } + // @ts-expect-error + throw new Error(`Unexpected entity type: ${e.type}`) + })} + } +} + +export interface BreadcrumbProperties { + url: string +} + +export function Breadcrumb(p: BreadcrumbProperties): JSX.Element | null { + const a: JSX.Element[] = [] + const s = Sitemap.shared + + let e = s.find(p.url, "url") + while (true) { + while (e && e.type === "group") { + e = s.find(e.parent, "id") + } + if (!e || e.url === "/") { + break + } + a.unshift({e.title}) + e = s.find(e.parent, "id") + } + + if (a.length === 0) { + return null + } + + return {a} +} diff --git a/site/layouts/chapter.tsx b/site/layouts/chapter.tsx index 7f597ad3b..d54a8cdc2 100644 --- a/site/layouts/chapter.tsx +++ b/site/layouts/chapter.tsx @@ -1,226 +1,13 @@ -import {Sitemap, type SitemapEntity} from "@onlyoffice/eleventy-sitemap" import {type Context, type Data} from "@onlyoffice/eleventy-types" -import { - Chapter, - ChapterContent, - ChapterNavigation, - SearchClear, - SearchContainer, - SearchField, - SearchHidable, - SearchOutput, - SearchPlaceholder, - SearchTemplate, - Help -} from "@onlyoffice/site-kit" -import {GithubIcon} from "@onlyoffice/ui-icons/rich/24.tsx" -import {Breadcrumb, BreadcrumbCrumb, Content} from "@onlyoffice/ui-kit" -import {Fragment, type JSX, h} from "preact" -import {Tree} from "../components/tree/tree.ts" +import {type JSX, h} from "preact" +import {Chapter} from "../internal/chapter.tsx" export function data(): Data { return { - layout: "page" + layout: "page", } } -export function render({content, ...ctx}: Context): JSX.Element { - const s = Sitemap.shared - - const pe = s.find(ctx.page.url, "url") - if (!pe || pe.type !== "page") { - throw new Error(`Page not found: ${ctx.page.url}`) - } - - const pt = s.trace(pe) - if (pt.length < 3) { - throw new Error(`Chapter layout requires at least three levels: ${pe.url}`) - } - - const [, rd, cd] = pt - - const re = s.find(rd, "id") - if (!re || re.type !== "page") { - throw new Error(`Part not found: ${rd}`) - } - - const ce = s.find(cd, "id") - if (!ce || ce.type !== "page") { - throw new Error(`Chapter not found: ${cd}`) - } - - return - - - Type / to search - - - -
    1. - -

      -
    2. -
      -
      - -
      - - - - - {ctx.title &&

      {ctx.title}

      } - {content} -
      -
      - - -

      Results

      -
        -
        -
        - {/* todo */} - {/* - -

        Get Help

        -
          -
        • If you have any questions about ONLYOFFICE DocSpace, try the FAQ section first.
        • -
        • You can request a feature or report a bug by posting an issue on GitHub.
        • -
        • You can also ask our developers on ONLYOFFICE forum (registration required).
        • -
        -
        */} -
        -
        -} - -export interface InternalChapterNavigationProperties { - level: number - url: string -} - -export function InternalChapterNavigation(p: InternalChapterNavigationProperties): JSX.Element | null { - const s = Sitemap.shared - - let l = p.level - let e = s.find("/", "url") - while (true) { - if (!e || l === 0) { - break - } - for (const id of e.children) { - const c = s.find(id, "id") - if (!c) { - continue - } - let u = "" - if (c.type === "group") { - const b = s.find(c.parent, "id") - if (!b || b.type !== "page") { - continue - } - u = b.url - } else if (c.type === "page") { - u = c.url - } else { - // @ts-expect-error - throw new Error(`Unexpected entity type: ${c.type}`) - } - if (p.url.startsWith(u)) { - e = c - l -= 1 - break - } - } - } - - if (!e) { - return null - } - - return - {e.children.map((id) => { - const e = s.find(id, "id") - if (!e || e.type !== "page") { - return null - } - return - {e.title} - - - })} - - - function Sub({e}: {e: SitemapEntity}): JSX.Element | null { - return <>{e.children.map((id) => { - const e = s.find(id, "id") - if (!e) { - return null - } - if (e.type === "group") { - if (e.children.length === 0) { - return null - } - const r = s.find(e.parent, "id") - if (!r) { - return null - } - if (r.type !== "page") { - throw new Error(`Nested group is not supported: ${e.id}`) - } - const b = s.find(p.url, "url") - if (!b) { - return null - } - return - {e.title} - - - } - if (e.type === "page") { - return - {e.title} - {e.children.length !== 0 && } - - } - // @ts-expect-error - throw new Error(`Unexpected entity type: ${e.type}`) - })} - } -} - -export interface InternalBreadcrumbProperties { - url: string -} - -export function InternalBreadcrumb(p: InternalBreadcrumbProperties): JSX.Element | null { - const a: JSX.Element[] = [] - const s = Sitemap.shared - - let e = s.find(p.url, "url") - while (true) { - while (e && e.type === "group") { - e = s.find(e.parent, "id") - } - if (!e || e.url === "/") { - break - } - a.unshift({e.title}) - e = s.find(e.parent, "id") - } - - if (a.length === 0) { - return null - } - - return {a} +export function render(c: Context): JSX.Element { + return {c.content} } diff --git a/site/layouts/table-of-contents.tsx b/site/layouts/table-of-contents.tsx deleted file mode 100644 index 5e584cbd7..000000000 --- a/site/layouts/table-of-contents.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import {type Context, type Data} from "@onlyoffice/eleventy-types" -import {Fragment, type JSX, h} from "preact" -import {TableOfContents} from "@/internal/table-of-contents.tsx" - -export function data(): Data { - return { - layout: "chapter", - } -} - -export function render(ctx: Context): JSX.Element { - return <> - {ctx.content} - - -} diff --git a/site/pages/Search/index.tsx b/site/pages/Search/index.tsx index b7f0ee66b..916f2339e 100644 --- a/site/pages/Search/index.tsx +++ b/site/pages/Search/index.tsx @@ -2,7 +2,7 @@ import {type Context, type Data} from "@onlyoffice/eleventy-types" import { Chapter, ChapterContent, - ChapterNavigation, + ChapterNavigation as SChapterNavigation, SearchClear, SearchContainer, SearchField, @@ -12,7 +12,7 @@ import { } from "@onlyoffice/site-kit" import {Content} from "@onlyoffice/ui-kit" import {type JSX, h} from "preact" -import {InternalChapterNavigation, InternalBreadcrumb} from "../../layouts/chapter.tsx" +import {ChapterNavigation, Breadcrumb} from "../../internal/chapter.tsx" export function data(): Data { return { @@ -23,7 +23,7 @@ export function data(): Data { export function render({content, ...ctx}: Context): JSX.Element { return - + Type / to search @@ -35,10 +35,10 @@ export function render({content, ...ctx}: Context): JSX.Element { - - + + - +