diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b344aade8..cae802139 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -29,7 +29,7 @@ jobs: - name: Build Site timeout-minutes: 60 working-directory: site - run: /usr/bin/time -l pnpm build + run: /usr/bin/time --verbose pnpm build - name: Sync bucket run: | 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/packages/eleventy-types/lib/main.ts b/packages/eleventy-types/lib/main.ts index 7b090d276..6b2a7cafb 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 @@ -183,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 } /** @@ -207,7 +214,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 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/packages/site-config/lib/main.ts b/packages/site-config/lib/main.ts index 33e0af4c4..9c454b9d4 100644 --- a/packages/site-config/lib/main.ts +++ b/packages/site-config/lib/main.ts @@ -1,6 +1,5 @@ import {existsSync, readFileSync} from "node:fs" import {join} from "node:path" -import {cwd} from "node:process" import {type DocEditorConfigEvents, type DocEditorConfigurableOptions} from "@onlyoffice/document-server-types" import yaml from "yaml" @@ -17,23 +16,13 @@ export interface Configurable { } export class Config implements Configurable { + static shared: Configurable + baseUrl = "" server = new ServerConfig() playground = new PlaygroundConfig() - static #config: Configurable - static #done = false - - static read(m?: string): Configurable { - if (this.#done) { - return this.#config - } - this.#done = true - this.#config = this.load(cwd(), m) - return this.#config - } - - static load(d: string, m?: string): Configurable { + static read(d: string, m?: string): Configurable { // It is crucial to use synchronous operations. This will allow // configuration to be loaded within the Eleventy or JSX components. @@ -107,12 +96,10 @@ export class Config implements Configurable { static merge(a: Configurable, b: Configurable): Configurable { const co = new Config() - if (a.baseUrl && b.baseUrl) { + if (b.baseUrl) { co.baseUrl = b.baseUrl } else if (a.baseUrl) { co.baseUrl = a.baseUrl - } else if (b.baseUrl) { - co.baseUrl = b.baseUrl } co.server = ServerConfig.merge(a.server, b.server) @@ -160,12 +147,10 @@ export class ServerConfig implements ServerConfigurable { ): ServerConfigurable { const s = new ServerConfig() - if (a.baseUrl && b.baseUrl) { + if (b.baseUrl) { s.baseUrl = b.baseUrl } else if (a.baseUrl) { s.baseUrl = a.baseUrl - } else if (b.baseUrl) { - s.baseUrl = b.baseUrl } return s @@ -278,28 +263,26 @@ export class DocumentEditorConfig implements DocumentEditorConfigurable { ): DocumentEditorConfigurable { const de = new DocumentEditorConfig() - if (a.documentServerUrl && b.documentServerUrl) { + if (b.documentServerUrl) { de.documentServerUrl = b.documentServerUrl } else if (a.documentServerUrl) { de.documentServerUrl = a.documentServerUrl - } else if (b.documentServerUrl) { - de.documentServerUrl = b.documentServerUrl } if (a.config.length !== 0 && b.config.length !== 0) { throw new Error("Merging of config is not supported") - } else if (a.config.length !== 0) { - de.config = a.config } else if (b.config.length !== 0) { de.config = b.config + } else if (a.config.length !== 0) { + de.config = a.config } if (a.scenarios.length !== 0 && b.scenarios.length !== 0) { throw new Error("Merging of scenarios is not supported") - } else if (a.scenarios.length !== 0) { - de.scenarios = a.scenarios } else if (b.scenarios.length !== 0) { de.scenarios = b.scenarios + } else if (a.scenarios.length !== 0) { + de.scenarios = a.scenarios } return de @@ -494,3 +477,7 @@ interface InputScenarioConfig extends DocEditorConfigurableOptions { } type ScenarioConfigurable = InputScenario + +if (!Config.shared) { + Config.shared = new Config() +} diff --git a/packages/site-env/lib/main.ts b/packages/site-env/lib/main.ts new file mode 100644 index 000000000..d6f7b9aee --- /dev/null +++ b/packages/site-env/lib/main.ts @@ -0,0 +1,9 @@ +import {env} from "node:process" + +export function configMode(): string { + const m = env.SITE_CONFIG_MODE + if (m === undefined) { + return "" + } + return m +} diff --git a/packages/site-env/package.json b/packages/site-env/package.json new file mode 100644 index 000000000..093046461 --- /dev/null +++ b/packages/site-env/package.json @@ -0,0 +1,19 @@ +{ + "name": "@onlyoffice/site-env", + "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/site-env/tsconfig.json b/packages/site-env/tsconfig.json new file mode 100644 index 000000000..4385e0695 --- /dev/null +++ b/packages/site-env/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment" + }, + "include": ["lib", "index.d.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5a806c8ec..2cb55e27b 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 @@ -1223,6 +1226,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': @@ -1754,6 +1773,22 @@ importers: specifier: ^5.1.6 version: 5.1.6(@types/node@20.12.12)(lightningcss@1.24.1)(terser@5.31.0) + packages/site-env: + 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/site-help: dependencies: '@onlyoffice/preact-types': @@ -2701,6 +2736,9 @@ importers: '@onlyoffice/site-config': specifier: workspace:^ version: link:../packages/site-config + '@onlyoffice/site-env': + specifier: workspace:^ + version: link:../packages/site-env '@onlyoffice/site-kit': specifier: workspace:^ version: link:../packages/site-kit 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/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..be97f2556 100644 --- a/site/eleventy.config.ts +++ b/site/eleventy.config.ts @@ -1,5 +1,6 @@ // todo: normalize naming of eleventy, remark, and other plugins. +import {cwd} from "node:process" import {eleventyClean} from "@onlyoffice/eleventy-clean" import {isBuild} from "@onlyoffice/eleventy-env" import {eleventyEsbuild} from "@onlyoffice/eleventy-esbuild" @@ -10,13 +11,15 @@ import {eleventySitemap} from "@onlyoffice/eleventy-sitemap" import {eleventyStarryNight} from "@onlyoffice/eleventy-starry-night" import {type UserConfig} from "@onlyoffice/eleventy-types" import {eleventyYAML} from "@onlyoffice/eleventy-yaml" +import {configMode} from "@onlyoffice/site-env" 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" function config(uc: UserConfig): unknown { + Config.shared = Config.read(cwd(), configMode()) + uc.addPlugin(eleventyClean) uc.addPlugin(staticPlugin) uc.addPlugin(markupPlugin) @@ -40,14 +43,13 @@ function config(uc: UserConfig): unknown { sortAttributes: true }) - uc.addPlugin(navigationPlugin) uc.addPlugin(eleventyStarryNight) uc.addPlugin(eleventyYAML) uc.addPlugin(eleventySitemap) uc.addPlugin(eleventyEsbuild, () => { - const c = Config.read() + const c = Config.shared return { passthrough: { input: "assets/main.ts", diff --git a/site/generations/library.ts b/site/generations/library.ts index 94fdc1558..3623c529d 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,55 @@ 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 data.pagination.items[0].title + }, + + sitemap(data) { + if (!data.pagination || !data.pagination.items) { + return + } + + const a = data.defaultSitemap + if (!a) { + return } - return [ - {title: "Constructors"}, - {title: "Events"}, - {title: "Methods"}, - {title: "Properties"}, - ] - } - s.group = function group() { + const b = new SitemapDatum() + 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/generations/page.ts b/site/generations/page.ts new file mode 100644 index 000000000..2d80d279f --- /dev/null +++ b/site/generations/page.ts @@ -0,0 +1,234 @@ +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" +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 + defaultChapter?: ChapterData + 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 + defaultChapter?(data: Data): ChapterData | 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 + }, + + 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) { + 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/internal/chapter.tsx b/site/internal/chapter.tsx new file mode 100644 index 000000000..b458f57ec --- /dev/null +++ b/site/internal/chapter.tsx @@ -0,0 +1,285 @@ +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" +import {Help} from "./help.tsx" + +declare module "@onlyoffice/eleventy-types" { + interface Data { + chapter?: ChapterData + } + + interface EleventyComputed { + chapter?(data: Data): ChapterData | undefined + } +} + +export interface ChapterData { + title?: string + tableOfContents?: boolean + help?: boolean +} + +export class ChapterDatum implements ChapterData { + title = "" + tableOfContents = false + help = true + + 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 + } + + if (b.help) { + c.help = b.help + } else if (a.help) { + c.help = a.help + } + + 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 && } + {ud.help && } +
    +
    + + +

    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/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/global-navigation.tsx b/site/internal/global-navigation.tsx index c0193ae33..2dea94eb0 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 | undefined + } +} + +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/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/home.tsx b/site/internal/home.tsx new file mode 100644 index 000000000..72859ff0b --- /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 | undefined + } +} + +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/internal/image.tsx b/site/internal/image.tsx index a03deeaa9..036ba09da 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)) { - throw new Error("The 'src' attribute must be an absolute URL.") + if (typeof p.src !== "string") { + throw new Error("The 'src' attribute must be a string.") } - - if (isAbsolute(p.src)) { - p.src = `.${p.src}` + if (!URL.canParse(p.src) && !path.isAbsolute(p.src)) { + throw new Error("The 'src' attribute must be an absolute URL.") } - // 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) { @@ -46,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) { @@ -59,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) @@ -72,8 +75,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 +89,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 +167,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 diff --git a/site/internal/part.tsx b/site/internal/part.tsx new file mode 100644 index 000000000..0ce5ac2fb --- /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 | undefined + } +} + +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/internal/table-of-contents.tsx b/site/internal/table-of-contents.tsx index 61989ddf5..f944b9654 100644 --- a/site/internal/table-of-contents.tsx +++ b/site/internal/table-of-contents.tsx @@ -9,7 +9,7 @@ export interface TableOfContentsProperties { export function TableOfContents(p: TableOfContentsProperties): JSX.Element { const s = Sitemap.shared - const {depth = 1} = p + const {depth = -1} = p const e = s.find(p.url, "url") if (!e) { return <> 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/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/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/layouts/service-declaration.tsx b/site/layouts/service-declaration.tsx index 20ff256e2..8b45d4a40 100644 --- a/site/layouts/service-declaration.tsx +++ b/site/layouts/service-declaration.tsx @@ -17,7 +17,7 @@ export function render(ctx: Context): JSX.Element { switch (d.kind) { case "group": // todo: move to the ServiceDeclaration - return + return case "request": return - {ctx.content} - - -} diff --git a/site/package.json b/site/package.json index cb3554f49..f27873739 100644 --- a/site/package.json +++ b/site/package.json @@ -5,9 +5,15 @@ "scripts": { "demo:editor": "docker run --env JWT_HEADER=Authorization --env JWT_SECRET=your-256-bit-secret --publish 3000:80 onlyoffice/documentserver-de:8.0", "demo:server": "store-demo", - "build": "node --require esbuild-register --max-old-space-size=8192 ./node_modules/@11ty/eleventy/cmd.js --config ./eleventy.config.ts", + "build:dev": "pnpm build:do", + "build:do": "node --require esbuild-register --max-old-space-size=8192 ./node_modules/@11ty/eleventy/cmd.js --config ./eleventy.config.ts", + "build:prod": "SITE_CONFIG_MODE=production pnpm build:do", + "build": "pnpm build:prod", "preview": "eleventy-dev-server --dir dist", - "serve": "node --require esbuild-register --max-old-space-size=8192 ./node_modules/@11ty/eleventy/cmd.js --serve --quiet --config ./eleventy.config.ts" + "serve:dev": "pnpm serve:do", + "serve:do": "node --require esbuild-register --max-old-space-size=8192 ./node_modules/@11ty/eleventy/cmd.js --serve --quiet --config ./eleventy.config.ts", + "serve:prod": "SITE_CONFIG_MODE=production pnpm serve:do", + "serve": "pnpm serve:dev" }, "devDependencies": { "@11ty/eleventy": "^2.0.1", @@ -56,6 +62,7 @@ "@onlyoffice/service-declaration": "workspace:^", "@onlyoffice/service-resource": "workspace:^", "@onlyoffice/site-config": "workspace:^", + "@onlyoffice/site-env": "workspace:^", "@onlyoffice/site-kit": "workspace:^", "@onlyoffice/store-demo": "workspace:^", "@onlyoffice/strings": "workspace:^", diff --git a/site/pages/DocSpace/API Backend/API Backend/index.md b/site/pages/DocSpace/API Backend/API Backend/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/DocSpace/API Backend/API Backend/index.md +++ b/site/pages/DocSpace/API Backend/API Backend/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/DocSpace/API Backend/Get Started/index.md b/site/pages/DocSpace/API Backend/Get Started/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/DocSpace/API Backend/Get Started/index.md +++ b/site/pages/DocSpace/API Backend/Get Started/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/DocSpace/API Backend/More Information/index.md b/site/pages/DocSpace/API Backend/More Information/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/DocSpace/API Backend/More Information/index.md +++ b/site/pages/DocSpace/API Backend/More Information/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/DocSpace/API Backend/index.md b/site/pages/DocSpace/API Backend/index.md index dac8566ab..6d739d31b 100644 --- a/site/pages/DocSpace/API Backend/index.md +++ b/site/pages/DocSpace/API Backend/index.md @@ -2,4 +2,10 @@ 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 + +chapter: + tableOfContents: true --- diff --git a/site/pages/DocSpace/For Hosting Providers/For Hosting Providers/index.md b/site/pages/DocSpace/For Hosting Providers/For Hosting Providers/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/DocSpace/For Hosting Providers/For Hosting Providers/index.md +++ b/site/pages/DocSpace/For Hosting Providers/For Hosting Providers/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/DocSpace/For Hosting Providers/Get Started/index.md b/site/pages/DocSpace/For Hosting Providers/Get Started/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/DocSpace/For Hosting Providers/Get Started/index.md +++ b/site/pages/DocSpace/For Hosting Providers/Get Started/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/DocSpace/For Hosting Providers/More Information/index.md b/site/pages/DocSpace/For Hosting Providers/More Information/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/DocSpace/For Hosting Providers/More Information/index.md +++ b/site/pages/DocSpace/For Hosting Providers/More Information/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/DocSpace/For Hosting Providers/index.md b/site/pages/DocSpace/For Hosting Providers/index.md index dc6a094f5..996f91b6c 100644 --- a/site/pages/DocSpace/For Hosting Providers/index.md +++ b/site/pages/DocSpace/For Hosting Providers/index.md @@ -1,4 +1,10 @@ --- 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 + +chapter: + tableOfContents: true --- diff --git a/site/pages/DocSpace/JavaScript SDK/Get Started/index.md b/site/pages/DocSpace/JavaScript SDK/Get Started/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/DocSpace/JavaScript SDK/Get Started/index.md +++ b/site/pages/DocSpace/JavaScript SDK/Get Started/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/DocSpace/JavaScript SDK/JavaScript SDK/index.md b/site/pages/DocSpace/JavaScript SDK/JavaScript SDK/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/DocSpace/JavaScript SDK/JavaScript SDK/index.md +++ b/site/pages/DocSpace/JavaScript SDK/JavaScript SDK/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/DocSpace/JavaScript SDK/index.md b/site/pages/DocSpace/JavaScript SDK/index.md index bba13012e..09164513b 100644 --- a/site/pages/DocSpace/JavaScript SDK/index.md +++ b/site/pages/DocSpace/JavaScript SDK/index.md @@ -2,4 +2,10 @@ 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 + +chapter: + tableOfContents: true --- diff --git a/site/pages/DocSpace/Plugins SDK/Get Started/index.md b/site/pages/DocSpace/Plugins SDK/Get Started/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/DocSpace/Plugins SDK/Get Started/index.md +++ b/site/pages/DocSpace/Plugins SDK/Get Started/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/DocSpace/Plugins SDK/More Information/index.md b/site/pages/DocSpace/Plugins SDK/More Information/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/DocSpace/Plugins SDK/More Information/index.md +++ b/site/pages/DocSpace/Plugins SDK/More Information/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/DocSpace/Plugins SDK/Plugins SDK/index.md b/site/pages/DocSpace/Plugins SDK/Plugins SDK/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/DocSpace/Plugins SDK/Plugins SDK/index.md +++ b/site/pages/DocSpace/Plugins SDK/Plugins SDK/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/DocSpace/Plugins SDK/index.md b/site/pages/DocSpace/Plugins SDK/index.md index 7d4231e12..cad8cc5cd 100644 --- a/site/pages/DocSpace/Plugins SDK/index.md +++ b/site/pages/DocSpace/Plugins SDK/index.md @@ -2,4 +2,10 @@ 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 + +chapter: + tableOfContents: true --- 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/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/Additional API/index.md b/site/pages/Docs/Docs API/Additional API/index.md index fafcdae75..559209d69 100644 --- a/site/pages/Docs/Docs API/Additional API/index.md +++ b/site/pages/Docs/Docs API/Additional API/index.md @@ -1,3 +1,6 @@ --- order: -2 ---- \ No newline at end of file + +chapter: + tableOfContents: true +--- 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 81f87c3c7..8be84157d 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: diff --git a/site/pages/Docs/Docs API/Get Started/Try Docs/index.tsx b/site/pages/Docs/Docs API/Get Started/Try Docs/index.tsx index 1801ff3cf..6854e05e4 100644 --- a/site/pages/Docs/Docs API/Get Started/Try Docs/index.tsx +++ b/site/pages/Docs/Docs API/Get Started/Try Docs/index.tsx @@ -10,6 +10,6 @@ export function data(): Data { } export function render(): JSX.Element { - const c = Config.read() + const c = Config.shared return } diff --git a/site/pages/Docs/Docs API/Get Started/index.md b/site/pages/Docs/Docs API/Get Started/index.md index 20b35e069..43dae0488 100644 --- a/site/pages/Docs/Docs API/Get Started/index.md +++ b/site/pages/Docs/Docs API/Get Started/index.md @@ -1,3 +1,6 @@ --- order: -5 ---- \ No newline at end of file + +chapter: + tableOfContents: true +--- diff --git a/site/pages/Docs/Docs API/More Information/index.md b/site/pages/Docs/Docs API/More Information/index.md index 4c2122185..3bcfc542b 100644 --- a/site/pages/Docs/Docs API/More Information/index.md +++ b/site/pages/Docs/Docs API/More Information/index.md @@ -1,3 +1,6 @@ --- order: -1 ---- \ No newline at end of file + +chapter: + tableOfContents: true +--- diff --git a/site/pages/Docs/Docs API/Usage API/index.md b/site/pages/Docs/Docs API/Usage API/index.md index 88cca5d26..d3e4a10f9 100644 --- a/site/pages/Docs/Docs API/Usage API/index.md +++ b/site/pages/Docs/Docs API/Usage API/index.md @@ -1,3 +1,6 @@ --- order: -4 ---- \ No newline at end of file + +chapter: + tableOfContents: true +--- diff --git a/site/pages/Docs/Docs API/Using WOPI/index.md b/site/pages/Docs/Docs API/Using WOPI/index.md index 6e0eafc5e..7581974a7 100644 --- a/site/pages/Docs/Docs API/Using WOPI/index.md +++ b/site/pages/Docs/Docs API/Using WOPI/index.md @@ -1,3 +1,6 @@ --- order: -3 ---- \ No newline at end of file + +chapter: + tableOfContents: true +--- diff --git a/site/pages/Docs/Docs API/index.md b/site/pages/Docs/Docs API/index.md index 56acc2501..242e47328 100644 --- a/site/pages/Docs/Docs API/index.md +++ b/site/pages/Docs/Docs API/index.md @@ -2,4 +2,10 @@ 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 + +chapter: + tableOfContents: true --- diff --git a/site/pages/Docs/Document Builder/Builder App/index.md b/site/pages/Docs/Document Builder/Builder App/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/Docs/Document Builder/Builder App/index.md +++ b/site/pages/Docs/Document Builder/Builder App/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/Docs/Document Builder/Builder Framework/index.md b/site/pages/Docs/Document Builder/Builder Framework/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/Docs/Document Builder/Builder Framework/index.md +++ b/site/pages/Docs/Document Builder/Builder Framework/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/Docs/Document Builder/Builder Server/index.md b/site/pages/Docs/Document Builder/Builder Server/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/Docs/Document Builder/Builder Server/index.md +++ b/site/pages/Docs/Document Builder/Builder Server/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/Docs/Document Builder/Get Started/index.md b/site/pages/Docs/Document Builder/Get Started/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/Docs/Document Builder/Get Started/index.md +++ b/site/pages/Docs/Document Builder/Get Started/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/Docs/Document Builder/index.md b/site/pages/Docs/Document Builder/index.md index 933cf38cb..297d2ff83 100644 --- a/site/pages/Docs/Document Builder/index.md +++ b/site/pages/Docs/Document Builder/index.md @@ -2,4 +2,10 @@ 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 + +chapter: + tableOfContents: true --- diff --git a/site/pages/Docs/Office API/Get Started/index.md b/site/pages/Docs/Office API/Get Started/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/Docs/Office API/Get Started/index.md +++ b/site/pages/Docs/Office API/Get Started/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/Docs/Office API/More Information/index.md b/site/pages/Docs/Office API/More Information/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/Docs/Office API/More Information/index.md +++ b/site/pages/Docs/Office API/More Information/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/Docs/Office API/Office API/index.md b/site/pages/Docs/Office API/Office API/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/Docs/Office API/Office API/index.md +++ b/site/pages/Docs/Office API/Office API/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/Docs/Office API/index.md b/site/pages/Docs/Office API/index.md index 8424848fd..2839f2af7 100644 --- a/site/pages/Docs/Office API/index.md +++ b/site/pages/Docs/Office API/index.md @@ -2,4 +2,10 @@ 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 + +chapter: + tableOfContents: true --- diff --git a/site/pages/Docs/Plugin and Macros/Get Started/index.md b/site/pages/Docs/Plugin and Macros/Get Started/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/Docs/Plugin and Macros/Get Started/index.md +++ b/site/pages/Docs/Plugin and Macros/Get Started/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/Docs/Plugin and Macros/Macros/index.md b/site/pages/Docs/Plugin and Macros/Macros/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/Docs/Plugin and Macros/Macros/index.md +++ b/site/pages/Docs/Plugin and Macros/Macros/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/Docs/Plugin and Macros/More Information/index.md b/site/pages/Docs/Plugin and Macros/More Information/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/Docs/Plugin and Macros/More Information/index.md +++ b/site/pages/Docs/Plugin and Macros/More Information/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/Docs/Plugin and Macros/Plugins/index.md b/site/pages/Docs/Plugin and Macros/Plugins/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/Docs/Plugin and Macros/Plugins/index.md +++ b/site/pages/Docs/Plugin and Macros/Plugins/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/Docs/Plugin and Macros/Usage API/index.md b/site/pages/Docs/Plugin and Macros/Usage API/index.md index e69de29bb..a733a7fc5 100644 --- a/site/pages/Docs/Plugin and Macros/Usage API/index.md +++ b/site/pages/Docs/Plugin and Macros/Usage API/index.md @@ -0,0 +1,4 @@ +--- +chapter: + tableOfContents: true +--- diff --git a/site/pages/Docs/Plugin and Macros/index.md b/site/pages/Docs/Plugin and Macros/index.md index e93057b84..63f5dde32 100644 --- a/site/pages/Docs/Plugin and Macros/index.md +++ b/site/pages/Docs/Plugin and Macros/index.md @@ -2,4 +2,10 @@ 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 + +chapter: + tableOfContents: true --- 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/Robots/index.tsx b/site/pages/Robots/index.tsx index e7d8eedb6..4b55b35dd 100644 --- a/site/pages/Robots/index.tsx +++ b/site/pages/Robots/index.tsx @@ -12,7 +12,7 @@ export function data(): Data { } export function render(): string { - const c = Config.read() + const c = Config.shared const s = sitemap() return `Sitemap: ${c.baseUrl}${s.permalink}\nUser-agent: *` } 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 { - - + + - +