diff --git a/.github/workflows/baseline-superdoc.yml b/.github/workflows/baseline-superdoc.yml index b77f7902af..f4ccb412bd 100644 --- a/.github/workflows/baseline-superdoc.yml +++ b/.github/workflows/baseline-superdoc.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 ref: ${{ github.sha }} @@ -90,7 +90,7 @@ jobs: - name: Setup Node.js if: steps.version.outputs.skip != 'true' - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc cache: pnpm @@ -98,7 +98,7 @@ jobs: - name: Cache apt packages if: steps.version.outputs.skip != 'true' - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/apt-cache key: apt-canvas-${{ runner.os }}-v1 @@ -131,7 +131,7 @@ jobs: - name: Cache Playwright browsers if: steps.version.outputs.skip != 'true' - uses: actions/cache@v4 + uses: actions/cache@v5 id: playwright-cache with: path: ~/.cache/ms-playwright diff --git a/.github/workflows/ci-demos.yml b/.github/workflows/ci-demos.yml new file mode 100644 index 0000000000..c2e4fb6b9a --- /dev/null +++ b/.github/workflows/ci-demos.yml @@ -0,0 +1,52 @@ +name: CI Demos + +on: + pull_request: + paths: + - 'demos/**' + workflow_dispatch: + +jobs: + smoke-test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + demo: + - cdn + - custom-mark + - custom-node + - docx-from-html + - docxtemplater + - fields + - grading-papers + # - html-editor # broken: imports unpublished superdoc/super-editor/style.css subpath + - linked-sections + - loading-from-json + - nextjs-ssr + - react + # - replace-content # broken: runtime nextSibling error in SuperDoc + - text-selection + - toolbar + - typescript + - vanilla + - vue + steps: + - uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + + - name: Install demo dependencies + working-directory: demos/${{ matrix.demo }} + run: npm install + + - name: Install smoke test dependencies + working-directory: demos/__tests__ + run: npm install && npx playwright install chromium + + - name: Run smoke test + working-directory: demos/__tests__ + run: DEMO=${{ matrix.demo }} npx playwright test diff --git a/.github/workflows/ci-docs.yml b/.github/workflows/ci-docs.yml index 9c865512ea..e4a6e21b0e 100644 --- a/.github/workflows/ci-docs.yml +++ b/.github/workflows/ci-docs.yml @@ -10,13 +10,13 @@ jobs: validate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup pnpm uses: pnpm/action-setup@v4 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: '20' cache: 'pnpm' diff --git a/.github/workflows/ci-esign.yml b/.github/workflows/ci-esign.yml index 0d8e7af1fb..a862da376e 100644 --- a/.github/workflows/ci-esign.yml +++ b/.github/workflows/ci-esign.yml @@ -17,11 +17,11 @@ jobs: validate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version-file: .nvmrc cache: pnpm diff --git a/.github/workflows/ci-examples.yml b/.github/workflows/ci-examples.yml new file mode 100644 index 0000000000..7ce9fe2baa --- /dev/null +++ b/.github/workflows/ci-examples.yml @@ -0,0 +1,109 @@ +name: CI Examples + +on: + pull_request: + paths: + - 'examples/**' + workflow_dispatch: + +jobs: + getting-started: + runs-on: ubuntu-latest + strategy: + matrix: + example: [react, vue, vanilla, cdn] + steps: + - uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + + - name: Install example dependencies + if: matrix.example != 'cdn' + working-directory: examples/getting-started/${{ matrix.example }} + run: npm install + + - name: Install smoke test dependencies + working-directory: examples/__tests__ + run: npm install && npx playwright install chromium + + - name: Run smoke test + working-directory: examples/__tests__ + run: EXAMPLE=${{ matrix.example }} npx playwright test + + collaboration: + runs-on: ubuntu-latest + strategy: + matrix: + example: [superdoc-yjs, hocuspocus, liveblocks] + steps: + - uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + + - name: Create .env for cloud providers + working-directory: examples/collaboration/${{ matrix.example }} + run: | + if [ "${{ matrix.example }}" = "liveblocks" ]; then + echo "VITE_LIVEBLOCKS_PUBLIC_KEY=${{ secrets.VITE_LIVEBLOCKS_PUBLIC_KEY }}" > .env + fi + + - name: Install example dependencies + working-directory: examples/collaboration/${{ matrix.example }} + run: npm install + + - name: Install smoke test dependencies + working-directory: examples/__tests__ + run: npm install && npx playwright install chromium + + - name: Run smoke test + working-directory: examples/__tests__ + run: EXAMPLE=collaboration/${{ matrix.example }} npx playwright test + + features: + runs-on: ubuntu-latest + strategy: + matrix: + example: [track-changes, ai-redlining, comments, custom-toolbar] + steps: + - uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + + - name: Install example dependencies + working-directory: examples/features/${{ matrix.example }} + run: npm install + + - name: Install smoke test dependencies + working-directory: examples/__tests__ + run: npm install && npx playwright install chromium + + - name: Run smoke test + working-directory: examples/__tests__ + run: EXAMPLE=features/${{ matrix.example }} npx playwright test + + headless: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + + - name: Install dependencies + working-directory: examples/headless/ai-redlining + run: npm install + + - name: Run headless tests + working-directory: examples/headless/ai-redlining + run: npx tsx src/index.test.ts diff --git a/.github/workflows/ci-superdoc.yml b/.github/workflows/ci-superdoc.yml index d6ab03e4be..ff7a48bc63 100644 --- a/.github/workflows/ci-superdoc.yml +++ b/.github/workflows/ci-superdoc.yml @@ -9,26 +9,26 @@ on: paths-ignore: - '.github/workflows/**' - 'apps/docs/**' + - 'demos/**' + - 'examples/**' - 'packages/template-builder/**' - 'packages/esign/**' - '**/*.md' + workflow_dispatch: concurrency: - group: ci-superdoc-${{ github.event.pull_request.number }} + group: ci-superdoc-${{ github.event.pull_request.number || github.run_id }} cancel-in-progress: true jobs: - validate-commits: + validate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha }} + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version-file: .nvmrc cache: pnpm @@ -48,172 +48,23 @@ jobs: - name: Install dependencies run: pnpm install - - name: Validate commits - run: | - BASE=$(git merge-base ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }}) - pnpx commitlint \ - --from "$BASE" \ - --to ${{ github.event.pull_request.head.sha }} + - name: Lint + run: pnpm run lint - run-unit-tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} + - name: Format check + run: pnpm run format --check - - uses: pnpm/action-setup@v4 - - - uses: actions/setup-node@v4 - with: - node-version-file: .nvmrc - cache: pnpm - - - name: Install canvas system dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - build-essential \ - libcairo2-dev \ - libpango1.0-dev \ - libjpeg-dev \ - libgif-dev \ - librsvg2-dev \ - libpixman-1-dev - - - name: Install dependencies - run: pnpm install - - - name: Build SuperDoc + - name: Build run: pnpm run build - name: Validate command types run: node scripts/validate-command-types.mjs + - name: Typecheck + run: pnpm run type-check + - name: Run unit tests run: pnpm test - name: Run slow tests run: pnpm test:slow - - # run-e2e-tests: - # runs-on: ubuntu-latest - # container: - # image: node:20 - - # steps: - # - name: Checkout - # uses: actions/checkout@v4 - # with: - # ref: ${{ github.event.pull_request.head.sha }} - - # - uses: pnpm/action-setup@v4 - - # - uses: actions/setup-node@v4 - # with: - # node-version-file: .nvmrc - # cache: pnpm - - # - name: Install canvas system dependencies - # run: | - # apt-get update - # apt-get install -y \ - # build-essential \ - # libcairo2-dev \ - # libpango1.0-dev \ - # libjpeg-dev \ - # libgif-dev \ - # librsvg2-dev \ - # libpixman-1-dev - - # - name: Install dependencies - # run: pnpm install - - # - name: Build SuperDoc - # run: | - # pnpm run build - - # - name: Install Playwright browsers - # run: | - # cd e2e-tests - # pnpm exec playwright install --with-deps chromium - - # - name: Run e2e tests - # id: run-e2e-tests - # if: always() - # run: cd e2e-tests && pnpm test - - # - name: Upload e2e test results - # id: upload_artifact - # if: always() && steps.run-e2e-tests.outcome == 'failure' - # uses: actions/upload-artifact@v4 - # with: - # name: e2e-tests - # path: e2e-tests/test-results - # retention-days: 15 - - run-examples-visual-tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - - uses: pnpm/action-setup@v4 - - - uses: actions/setup-node@v4 - with: - node-version-file: .nvmrc - cache: pnpm - - - name: Get Playwright version - id: playwright-version - run: | - cd examples/tests - echo "version=$(pnpm ls @playwright/test --json | jq -r '.dependencies["@playwright/test"].version')" >> $GITHUB_OUTPUT - - - name: Cache Playwright browsers - uses: actions/cache@v4 - id: playwright-cache - with: - path: ~/.cache/ms-playwright - key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }} - - - name: Install dependencies - run: pnpm install - - - name: Pack SuperDoc - run: pnpm run --filter superdoc pack - - - name: Setup visual tests - run: | - cd examples/tests - pnpm install - pnpm install -g http-server - - - name: Install Playwright browsers - if: steps.playwright-cache.outputs.cache-hit != 'true' - run: | - cd examples/tests - pnpm exec playwright install --with-deps chromium - - - name: Install only system deps if cache hit - if: steps.playwright-cache.outputs.cache-hit == 'true' - run: | - cd examples/tests - pnpm exec playwright install-deps chromium - - - name: Run visual tests - id: run-visual-tests - run: | - cd examples/tests - pnpm test - - - name: Upload visual test results - id: upload_artifact - if: always() && steps.run-visual-tests.outcome == 'failure' - uses: actions/upload-artifact@v4 - with: - name: visual-tests - path: examples/tests/test-results - retention-days: 15 diff --git a/.github/workflows/ci-template-builder.yml b/.github/workflows/ci-template-builder.yml index fe39ff2d3f..2ec59d0c2e 100644 --- a/.github/workflows/ci-template-builder.yml +++ b/.github/workflows/ci-template-builder.yml @@ -17,11 +17,11 @@ jobs: validate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version-file: .nvmrc cache: pnpm diff --git a/.github/workflows/deploy-demos.yml b/.github/workflows/deploy-demos.yml index 7247897298..126634d08f 100644 --- a/.github/workflows/deploy-demos.yml +++ b/.github/workflows/deploy-demos.yml @@ -21,11 +21,11 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version-file: .nvmrc cache: pnpm @@ -59,7 +59,7 @@ jobs: echo '' > ./demos-dist/index.html - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v4 with: path: ./demos-dist diff --git a/.github/workflows/deploy-esign-proxy.yml b/.github/workflows/deploy-esign-proxy.yml index 8319c09435..9024a66445 100644 --- a/.github/workflows/deploy-esign-proxy.yml +++ b/.github/workflows/deploy-esign-proxy.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Auth to Google Cloud uses: google-github-actions/auth@v2 diff --git a/.github/workflows/manual-patch-release.yml b/.github/workflows/manual-patch-release.yml index 630764c9bb..5deef21406 100644 --- a/.github/workflows/manual-patch-release.yml +++ b/.github/workflows/manual-patch-release.yml @@ -22,7 +22,7 @@ jobs: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 ref: ${{ github.event.inputs.ref || github.ref }} @@ -30,14 +30,14 @@ jobs: - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # Pinned commit hash for @v4 - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 with: node-version-file: .nvmrc cache: pnpm registry-url: 'https://registry.npmjs.org' - name: Cache apt packages - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/apt-cache key: apt-canvas-${{ runner.os }}-v1 diff --git a/.github/workflows/release-superdoc.yml b/.github/workflows/release-superdoc.yml index 4567479223..4ccd3a9d98 100644 --- a/.github/workflows/release-superdoc.yml +++ b/.github/workflows/release-superdoc.yml @@ -51,7 +51,7 @@ jobs: - uses: oven-sh/setup-bun@v2 - name: Cache apt packages - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/apt-cache key: apt-canvas-${{ runner.os }}-v1 diff --git a/.github/workflows/spec-review.yml b/.github/workflows/spec-review.yml index ffa070a0c3..ced5d2d330 100644 --- a/.github/workflows/spec-review.yml +++ b/.github/workflows/spec-review.yml @@ -22,10 +22,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 # shallow-clone as default + uses: actions/checkout@v6 # shallow-clone as default - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: '20' diff --git a/.github/workflows/sync-patches.yml b/.github/workflows/sync-patches.yml index d845ca098e..87075ceabf 100644 --- a/.github/workflows/sync-patches.yml +++ b/.github/workflows/sync-patches.yml @@ -22,7 +22,7 @@ jobs: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 token: ${{ steps.generate_token.outputs.token }} diff --git a/apps/docs/__tests__/lib/extract.ts b/apps/docs/__tests__/lib/extract.ts index a64eb84b12..c57ee43397 100644 --- a/apps/docs/__tests__/lib/extract.ts +++ b/apps/docs/__tests__/lib/extract.ts @@ -31,7 +31,6 @@ const SKIP_IMPORTS = [ '@liveblocks/', '@hocuspocus/', '@tiptap/', - '@y-sweet/', 'hocuspocus', 'fastify', 'express', diff --git a/apps/docs/core/superdoc/configuration.mdx b/apps/docs/core/superdoc/configuration.mdx index fa9bf3f0bd..ec90f44063 100644 --- a/apps/docs/core/superdoc/configuration.mdx +++ b/apps/docs/core/superdoc/configuration.mdx @@ -106,6 +106,7 @@ new SuperDoc({ - `viewing` - Read-only display - `suggesting` - Track changes enabled + See the [Track Changes extension](/extensions/track-changes) for accept/reject commands, and the [runnable example](https://github.com/superdoc-dev/superdoc/tree/main/examples/features/track-changes) for a complete workflow. @@ -196,8 +197,8 @@ new SuperDoc({ Toolbar configuration - - Toolbar container + + CSS selector for the toolbar container (e.g. `'#toolbar'`) Layout groups @@ -262,9 +263,8 @@ new SuperDoc({ Show document rulers - - DOM element for toolbar - Alternative to `modules.toolbar.selector` + + CSS selector for the toolbar container (e.g. `'#toolbar'`). Shorthand for `modules.toolbar.selector`. ## Advanced options diff --git a/apps/docs/core/superdoc/methods.mdx b/apps/docs/core/superdoc/methods.mdx index 6d3589dde4..6de48e7ad4 100644 --- a/apps/docs/core/superdoc/methods.mdx +++ b/apps/docs/core/superdoc/methods.mdx @@ -28,7 +28,7 @@ Export the document with various options. Base filename - Auto-download file + Automatically trigger a browser file download. Set to `false` to get a `Blob` back without downloading. Field highlight color diff --git a/apps/docs/core/supereditor/configuration.mdx b/apps/docs/core/supereditor/configuration.mdx index 5776292c85..ab94cf797b 100644 --- a/apps/docs/core/supereditor/configuration.mdx +++ b/apps/docs/core/supereditor/configuration.mdx @@ -202,8 +202,8 @@ const editor = await Editor.open(docxFile, { Convert DOCX files server-side without a browser using JSDOM. - See the [headless-converter - example](https://github.com/Harbour-Enterprises/superdoc/tree/main/examples/advanced/headless-converter) + See the [headless + example](https://github.com/Harbour-Enterprises/superdoc/tree/main/examples/headless) for a complete implementation. diff --git a/apps/docs/core/supereditor/methods.mdx b/apps/docs/core/supereditor/methods.mdx index 084eb87795..a855e204f0 100644 --- a/apps/docs/core/supereditor/methods.mdx +++ b/apps/docs/core/supereditor/methods.mdx @@ -180,6 +180,8 @@ const blob = await editor.exportDocument({ isFinalDoc: true }); Lower-level export with additional control. +**Returns:** `Promise` in the browser, `Promise` in headless/Node.js mode. + diff --git a/apps/docs/docs.json b/apps/docs/docs.json index 51318e68d1..e756f1c15e 100644 --- a/apps/docs/docs.json +++ b/apps/docs/docs.json @@ -166,6 +166,7 @@ "extensions/text-indent", "extensions/text-style", "extensions/text-transform", + "extensions/track-changes", "extensions/underline" ] } @@ -204,10 +205,8 @@ "group": "Collaboration", "pages": [ "guides/collaboration/liveblocks", - "guides/collaboration/tiptap-cloud", "guides/collaboration/superdoc-yjs", - "guides/collaboration/hocuspocus", - "guides/collaboration/y-sweet" + "guides/collaboration/hocuspocus" ] }, { @@ -371,10 +370,6 @@ "source": "/modules/collaboration/cloud/liveblocks", "destination": "/guides/collaboration/liveblocks" }, - { - "source": "/modules/collaboration/cloud/tiptap-cloud", - "destination": "/guides/collaboration/tiptap-cloud" - }, { "source": "/modules/collaboration/self-hosted/superdoc-yjs", "destination": "/guides/collaboration/superdoc-yjs" @@ -383,10 +378,6 @@ "source": "/modules/collaboration/self-hosted/hocuspocus", "destination": "/guides/collaboration/hocuspocus" }, - { - "source": "/modules/collaboration/self-hosted/y-sweet", - "destination": "/guides/collaboration/y-sweet" - }, { "source": "/modules/collaboration/self-hosted/overview", "destination": "/guides/collaboration/self-hosted-overview" diff --git a/apps/docs/extensions/track-changes.mdx b/apps/docs/extensions/track-changes.mdx index c7a9c657cc..af98f4e0f0 100644 --- a/apps/docs/extensions/track-changes.mdx +++ b/apps/docs/extensions/track-changes.mdx @@ -283,6 +283,16 @@ const superdoc = new SuperDoc({ ``` +## Full example + + + Runnable example with mode switching, accept/reject, and comments sidebar + + ## Source code import { SourceCodeLink } from '/snippets/components/source-code-link.jsx' diff --git a/apps/docs/getting-started/ai-agents.mdx b/apps/docs/getting-started/ai-agents.mdx index 9404e9e455..3d99e8cfc6 100644 --- a/apps/docs/getting-started/ai-agents.mdx +++ b/apps/docs/getting-started/ai-agents.mdx @@ -107,4 +107,20 @@ https://docs.superdoc.dev/llms-full.txt // Complete documentation > Content formats, export options, and round-trip behavior + + + React app: upload a DOCX, LLM reviews it, tracked changes in the UI + + + + Node.js script: open DOCX, LLM reviews, export redlined document + diff --git a/apps/docs/getting-started/frameworks/react.mdx b/apps/docs/getting-started/frameworks/react.mdx index 91a1895701..9453e32f52 100644 --- a/apps/docs/getting-started/frameworks/react.mdx +++ b/apps/docs/getting-started/frameworks/react.mdx @@ -265,4 +265,4 @@ function useSuperDoc(config) { - [Vue Integration](/getting-started/frameworks/vue) - Vue setup - [API Reference](/core/superdoc/configuration) - Configuration options -- [Examples](https://github.com/superdoc-dev/superdoc/tree/main/examples/react-example) - Working examples +- [Examples](https://github.com/superdoc-dev/superdoc/tree/main/examples/getting-started/react) - Working examples diff --git a/apps/docs/getting-started/frameworks/vue.mdx b/apps/docs/getting-started/frameworks/vue.mdx index 3f16c54761..aeded7bfdb 100644 --- a/apps/docs/getting-started/frameworks/vue.mdx +++ b/apps/docs/getting-started/frameworks/vue.mdx @@ -204,4 +204,4 @@ defineExpose({ - [React Integration](/getting-started/frameworks/react) - React setup - [API Reference](/core/superdoc/configuration) - Configuration options -- [Examples](https://github.com/superdoc-dev/superdoc/tree/main/examples/vue-example) - Working examples \ No newline at end of file +- [Examples](https://github.com/superdoc-dev/superdoc/tree/main/examples/getting-started/vue) - Working examples \ No newline at end of file diff --git a/apps/docs/getting-started/import-export.mdx b/apps/docs/getting-started/import-export.mdx index efec519b29..b474482b31 100644 --- a/apps/docs/getting-started/import-export.mdx +++ b/apps/docs/getting-started/import-export.mdx @@ -110,6 +110,10 @@ const superdoc = new SuperDoc({ ### DOCX export + + `superdoc.export()` **triggers a browser download by default**. If you need the raw `Blob` (e.g. to upload to a server), pass `triggerDownload: false` — otherwise you'll get a duplicate download. + + ```javascript Usage // Download as .docx file diff --git a/apps/docs/guides/collaboration/self-hosted-overview.mdx b/apps/docs/guides/collaboration/self-hosted-overview.mdx index 6292021e13..47849de98e 100644 --- a/apps/docs/guides/collaboration/self-hosted-overview.mdx +++ b/apps/docs/guides/collaboration/self-hosted-overview.mdx @@ -40,7 +40,6 @@ flowchart TB |--------|----------|------------| | [SuperDoc Yjs](/guides/collaboration/superdoc-yjs) | New projects, recommended | 30 mins | | [Hocuspocus](/guides/collaboration/hocuspocus) | TipTap ecosystem users | 30 mins | -| [Y-Sweet](/guides/collaboration/y-sweet) | High performance, easy deployment | 30 mins | ## Quick comparison @@ -74,20 +73,6 @@ flowchart TB [Get Started](/guides/collaboration/hocuspocus) - - **Jamsocket's Yjs server** - - - High performance, Rust-based - - Easy to deploy with `npx` - - Optional managed cloud via Jamsocket - - ```bash - npx y-sweet@latest serve - ``` - - [Get Started](/guides/collaboration/y-sweet) - - ## Client connection options @@ -170,14 +155,6 @@ The provider-agnostic approach gives you more control but requires managing the Alternative using TipTap's server - - High performance Rust-based server - - - TipTap Cloud requires a TipTap Pro subscription. See [TipTap - pricing](https://tiptap.dev/pricing) for details. - - -## Setup - -### 1. Get your credentials - -1. Sign up for [TipTap Pro](https://tiptap.dev/pricing) -2. Create a new project in the dashboard -3. Get your **App ID** and **Token** - -### 2. Install dependencies - -```bash -npm install @tiptap-pro/provider yjs -``` - -### 3. Implementation - -```javascript -import { TiptapCollabProvider } from "@tiptap-pro/provider"; -import * as Y from "yjs"; -import { SuperDoc } from "superdoc"; - -const APP_ID = "your-app-id"; -const TOKEN = "your-token"; -const DOCUMENT_NAME = "my-document"; - -// Create Yjs document -const ydoc = new Y.Doc(); - -// Create TipTap Cloud provider -const provider = new TiptapCollabProvider({ - appId: APP_ID, - name: DOCUMENT_NAME, - document: ydoc, - token: TOKEN, -}); - -// Wait for sync -provider.on("synced", () => { - const superdoc = new SuperDoc({ - selector: "#editor", - documentMode: "editing", - user: { - name: "John Smith", - email: "john@example.com", - }, - modules: { - collaboration: { ydoc, provider }, - }, - }); -}); -``` - -## React example - -```tsx -import { useEffect, useRef, useState } from "react"; -import { TiptapCollabProvider } from "@tiptap-pro/provider"; -import * as Y from "yjs"; -import { SuperDoc } from "superdoc"; -import "superdoc/style.css"; - -const APP_ID = import.meta.env.VITE_TIPTAP_APP_ID; -const TOKEN = import.meta.env.VITE_TIPTAP_TOKEN; - -export default function Editor() { - const superdocRef = useRef(null); - const [users, setUsers] = useState([]); - - useEffect(() => { - if (!APP_ID || !TOKEN) return; - - const ydoc = new Y.Doc(); - const provider = new TiptapCollabProvider({ - appId: APP_ID, - name: "document-123", - document: ydoc, - token: TOKEN, - }); - - provider.on("synced", () => { - superdocRef.current = new SuperDoc({ - selector: "#superdoc", - documentMode: "editing", - user: { - name: `User ${Math.floor(Math.random() * 1000)}`, - email: "user@example.com", - }, - modules: { - collaboration: { ydoc, provider }, - }, - onAwarenessUpdate: ({ states }) => { - setUsers(states.filter((s) => s.user)); - }, - }); - }); - - return () => { - superdocRef.current?.destroy(); - provider.destroy(); - }; - }, []); - - return ( -
-
- {users.map((u, i) => ( - - {u.user?.name} - - ))} -
-
-
- ); -} -``` - -## Configuration - - - Your TipTap Cloud application ID - - - - Document name (room identifier) - - - - The Yjs document instance - - - - Authentication token from TipTap dashboard - - -## Events - -```javascript -// Connection status -provider.on("status", ({ status }) => { - // 'connecting' | 'connected' | 'disconnected' - console.log("Connection status:", status); -}); - -// Sync status -provider.on("synced", () => { - console.log("Document synced"); -}); - -// Authentication error -provider.on("authenticationFailed", ({ reason }) => { - console.error("Auth failed:", reason); -}); -``` - -## Cleanup - -```javascript -// Always clean up -provider.destroy(); -superdoc.destroy(); -``` - -## Resources - - - - Official documentation - - - - Complete source code - - - -## Next steps - - - - All SuperDoc collaboration options - - - - Run your own collaboration server - - diff --git a/apps/docs/guides/collaboration/y-sweet.mdx b/apps/docs/guides/collaboration/y-sweet.mdx deleted file mode 100644 index 13121623db..0000000000 --- a/apps/docs/guides/collaboration/y-sweet.mdx +++ /dev/null @@ -1,275 +0,0 @@ ---- -title: Y-Sweet -sidebarTitle: Y-Sweet -keywords: "y-sweet collaboration, jamsocket yjs, self-hosted collaboration, yjs sync server" ---- - -[Y-Sweet](https://docs.jamsocket.com/y-sweet) is a standalone Yjs sync server built by [Jamsocket](https://jamsocket.com/). It's designed for high performance and easy deployment. - -## Architecture - -```mermaid -flowchart LR - Client["Client (React):3000"] --> Auth["Auth Server (Express):3001"] - Auth --> YSweet["Y-Sweet Server:8080"] -``` - -- **Y-Sweet Server** (port 8080): Handles real-time Yjs sync -- **Auth Server** (port 3001): Issues client tokens via Y-Sweet SDK -- **Client** (port 3000): Your app with SuperDoc - -## Setup - -### 1. Start Y-Sweet server - -The easiest way to run Y-Sweet locally: - -```bash -npx y-sweet@latest serve -``` - -To persist data to disk: - -```bash -npx y-sweet@latest serve ./data -``` - -### 2. Create auth server - -Y-Sweet requires a backend to issue authentication tokens. - -```bash -npm install @y-sweet/sdk express cors -``` - -```javascript -import express from "express"; -import cors from "cors"; -import { DocumentManager } from "@y-sweet/sdk"; - -const CONNECTION_STRING = - process.env.CONNECTION_STRING || "ys://127.0.0.1:8080"; -const PORT = process.env.PORT || 3001; - -const app = express(); -app.use(cors()); -app.use(express.json()); - -const manager = new DocumentManager(CONNECTION_STRING); - -app.post("/api/auth", async (req, res) => { - try { - const { docId } = req.body; - const clientToken = await manager.getOrCreateDocAndToken(docId); - res.json(clientToken); - } catch (error) { - console.error("Auth error:", error); - res.status(500).json({ error: "Failed to get auth token" }); - } -}); - -app.listen(PORT, () => { - console.log(`Auth server running on http://localhost:${PORT}`); -}); -``` - -### 3. Client setup - -```bash -npm install @y-sweet/client yjs -``` - -```javascript -import { createYjsProvider } from "@y-sweet/client"; -import * as Y from "yjs"; -import { SuperDoc } from "superdoc"; - -const AUTH_ENDPOINT = "http://localhost:3001/api/auth"; -const DOC_ID = "my-document"; - -const ydoc = new Y.Doc(); -const provider = createYjsProvider(ydoc, DOC_ID, AUTH_ENDPOINT); - -provider.on("sync", (synced) => { - if (!synced) return; - - const superdoc = new SuperDoc({ - selector: "#editor", - documentMode: "editing", - user: { - name: "John Smith", - email: "john@example.com", - }, - modules: { - collaboration: { ydoc, provider }, - }, - }); -}); -``` - -## React example - -```tsx -import { useEffect, useRef, useState } from "react"; -import { createYjsProvider } from "@y-sweet/client"; -import * as Y from "yjs"; -import { SuperDoc } from "superdoc"; -import "superdoc/style.css"; - -const AUTH_ENDPOINT = "http://localhost:3001/api/auth"; -const DOC_ID = "my-document"; - -export default function Editor() { - const superdocRef = useRef(null); - const [users, setUsers] = useState([]); - - useEffect(() => { - const ydoc = new Y.Doc(); - const provider = createYjsProvider(ydoc, DOC_ID, AUTH_ENDPOINT); - - provider.on("sync", (synced: boolean) => { - if (!synced) return; - - superdocRef.current = new SuperDoc({ - selector: "#superdoc", - documentMode: "editing", - user: { - name: `User ${Math.floor(Math.random() * 1000)}`, - email: "user@example.com", - }, - modules: { - collaboration: { ydoc, provider }, - }, - onAwarenessUpdate: ({ states }) => { - setUsers(states.filter((s) => s.user)); - }, - }); - }); - - return () => { - superdocRef.current?.destroy(); - provider.destroy(); - }; - }, []); - - return ( -
-
- {users.map((u, i) => ( - - {u.user?.name} - - ))} -
-
-
- ); -} -``` - -## Configuration - -### Environment variables - -**Auth Server:** - -```bash -CONNECTION_STRING=ys://127.0.0.1:8080 # Y-Sweet server URL -PORT=3001 # Auth server port -``` - -**Client:** - -```bash -VITE_Y_SWEET_AUTH_ENDPOINT=http://localhost:3001/api/auth -VITE_DOC_ID=my-document -``` - -## Events - -```javascript -// Sync status -provider.on("sync", (synced) => { - if (synced) { - console.log("Document synced"); - } -}); - -// Connection status -provider.on("status", ({ status }) => { - // 'connecting' | 'connected' | 'disconnected' - console.log("Connection:", status); -}); -``` - -## Cleanup - -```javascript -// Always clean up -provider.destroy(); -superdoc.destroy(); -``` - -## Production deployment - -### Docker - -```dockerfile -FROM node:18-alpine - -WORKDIR /app -COPY package*.json ./ -RUN npm ci --production - -COPY . . - -EXPOSE 3001 -CMD ["node", "server.js"] -``` - -### Running Y-Sweet in production - -For production, consider: - -1. **Self-hosted**: Run Y-Sweet on your own infrastructure -2. **Jamsocket Cloud**: Use [Jamsocket's managed Y-Sweet](https://jamsocket.com/) for automatic scaling - -## Resources - - - - Official documentation - - - - Complete source code - - - -## Next steps - - - - All SuperDoc collaboration options - - - - Managed collaboration solutions - - diff --git a/apps/docs/modules/collaboration/overview.mdx b/apps/docs/modules/collaboration/overview.mdx index d29bba4903..aa71692516 100644 --- a/apps/docs/modules/collaboration/overview.mdx +++ b/apps/docs/modules/collaboration/overview.mdx @@ -22,7 +22,6 @@ Enable multiple users to edit the same document simultaneously with real-time co | Provider | Best For | Pricing | |----------|----------|---------| | [Liveblocks](/guides/collaboration/liveblocks) | Quick start, any framework | Free tier available | - | [TipTap Cloud](/guides/collaboration/tiptap-cloud) | TipTap Pro users | Requires subscription | @@ -33,7 +32,6 @@ Enable multiple users to edit the same document simultaneously with real-time co |--------|----------| | [SuperDoc Yjs](/guides/collaboration/superdoc-yjs) | Recommended — the official package | | [Hocuspocus](/guides/collaboration/hocuspocus) | TipTap ecosystem users | - | [Y-Sweet](/guides/collaboration/y-sweet) | High performance, easy deployment | diff --git a/apps/docs/modules/comments.mdx b/apps/docs/modules/comments.mdx index 6517eca9cc..8f36be58b3 100644 --- a/apps/docs/modules/comments.mdx +++ b/apps/docs/modules/comments.mdx @@ -639,3 +639,13 @@ onCommentsUpdate: ({ type, comment, meta }) => { + +## Full example + + + Runnable example: threaded comments with resolve workflow and event log + diff --git a/apps/docs/modules/overview.mdx b/apps/docs/modules/overview.mdx index 68ceb04051..2459ecb8a7 100644 --- a/apps/docs/modules/overview.mdx +++ b/apps/docs/modules/overview.mdx @@ -37,3 +37,16 @@ const superdoc = new SuperDoc({ Each module is configured via `modules.` in the [SuperDoc configuration](/core/superdoc/configuration). See individual module pages for all available options. + +## Related features + +These features are configured at the top level rather than through `modules`, but are commonly used alongside modules. + + + + Accept/reject workflow with `documentMode: 'suggesting'` + + + Headless mode for server-side document processing and LLM workflows + + diff --git a/apps/docs/modules/toolbar.mdx b/apps/docs/modules/toolbar.mdx index 984175d081..6c5b1ba477 100644 --- a/apps/docs/modules/toolbar.mdx +++ b/apps/docs/modules/toolbar.mdx @@ -17,8 +17,8 @@ const superdoc = new SuperDoc({ ## Configuration - - Container element for toolbar + + CSS selector for the toolbar container (e.g. `'#toolbar'`). Must be a string selector, not a DOM element reference. @@ -594,3 +594,13 @@ const superdoc = new SuperDoc({ ``` + +## Full example + + + Runnable example: custom button groups, excluded items, and a custom clear-formatting button + diff --git a/apps/docs/scripts/validate-code-imports.ts b/apps/docs/scripts/validate-code-imports.ts index 3100ddb359..1e0ef2c13e 100644 --- a/apps/docs/scripts/validate-code-imports.ts +++ b/apps/docs/scripts/validate-code-imports.ts @@ -54,9 +54,7 @@ const PREFIX_EXTERNAL_IMPORTS = [ 'fs/', '@hocuspocus/', '@tiptap/', - '@tiptap-pro/', '@liveblocks/', - '@y-sweet/', '@fastify/', '@aws-sdk/', 'next/', diff --git a/demos/.gitignore b/demos/.gitignore new file mode 100644 index 0000000000..934668777a --- /dev/null +++ b/demos/.gitignore @@ -0,0 +1,5 @@ +# Ignore lock files +*-lock* + +# Ignore npmrc files +*.npmrc \ No newline at end of file diff --git a/demos/README.md b/demos/README.md new file mode 100644 index 0000000000..ce39947df6 --- /dev/null +++ b/demos/README.md @@ -0,0 +1,5 @@ +# SuperDoc Demos + +Feature demos used by the [SuperDoc demo gallery](https://superdoc.dev). Each demo includes a thumbnail, optional video, and is embeddable via StackBlitz. + +> **Looking for getting-started examples?** See the [`examples/`](../examples/) folder instead. diff --git a/demos/__tests__/package.json b/demos/__tests__/package.json new file mode 100644 index 0000000000..8169e9ccb5 --- /dev/null +++ b/demos/__tests__/package.json @@ -0,0 +1,12 @@ +{ + "name": "superdoc-demo-smoke-tests", + "private": true, + "type": "module", + "scripts": { + "test": "playwright test", + "test:install": "playwright install chromium" + }, + "devDependencies": { + "@playwright/test": "^1.50.0" + } +} diff --git a/demos/__tests__/playwright.config.ts b/demos/__tests__/playwright.config.ts new file mode 100644 index 0000000000..e2cf1b0b68 --- /dev/null +++ b/demos/__tests__/playwright.config.ts @@ -0,0 +1,44 @@ +import { defineConfig, devices } from '@playwright/test'; +import { existsSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +// DEMO env var: "react", "vue", "cdn", "nextjs-ssr", etc. +const demo = process.env.DEMO || 'react'; + +// Demos are flat: demos// +const demoPath = `../${demo}`; + +// Port mapping for non-Vite demos (these use their framework's default port) +const portMap: Record = { + cdn: 8080, + 'grading-papers': 3000, + 'nextjs-ssr': 3000, +}; +const port = portMap[demo] ?? 5173; + +// Detect package manager: use npm if demo has local node_modules, pnpm otherwise +const demoAbsPath = resolve(__dirname, demoPath); +const hasLocalNodeModules = existsSync(resolve(demoAbsPath, 'node_modules', '.bin')); +const run = hasLocalNodeModules ? `npm run --prefix ${demoPath}` : `pnpm --dir ${demoPath} run`; + +// Vite demos accept --port; mapped demos use their default port +const command = portMap[demo] ? `${run} dev` : `${run} dev -- --port ${port}`; + +export default defineConfig({ + testDir: '.', + retries: 1, + timeout: 30_000, + webServer: { + command, + url: `http://localhost:${port}`, + timeout: 30_000, + reuseExistingServer: !process.env.CI, + }, + use: { + baseURL: `http://localhost:${port}`, + }, + projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }], +}); diff --git a/demos/__tests__/smoke.spec.ts b/demos/__tests__/smoke.spec.ts new file mode 100644 index 0000000000..725e672972 --- /dev/null +++ b/demos/__tests__/smoke.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('demo loads without errors', async ({ page }) => { + const errors: string[] = []; + + page.on('pageerror', (err) => errors.push(err.message)); + page.on('console', (msg) => { + if (msg.type() === 'error') errors.push(msg.text()); + }); + + await page.goto('/'); + await expect(page.locator('body')).toBeVisible(); + + // Give the app a moment to initialize (SuperDoc is async) + await page.waitForTimeout(2000); + + expect(errors).toEqual([]); +}); diff --git a/demos/cdn/README.md b/demos/cdn/README.md new file mode 100644 index 0000000000..93551cddae --- /dev/null +++ b/demos/cdn/README.md @@ -0,0 +1,5 @@ +# SuperDoc - From CDN example + +This is a very basic example of loading SuperDoc from CDN without any bundlers. + +Note: You can test this locally by using something like ```npx http-server``` \ No newline at end of file diff --git a/examples/getting-started/cdn/demo-config.json b/demos/cdn/demo-config.json similarity index 100% rename from examples/getting-started/cdn/demo-config.json rename to demos/cdn/demo-config.json diff --git a/examples/advanced/docx-from-html/demo-thumbnail.png b/demos/cdn/demo-thumbnail.png similarity index 100% rename from examples/advanced/docx-from-html/demo-thumbnail.png rename to demos/cdn/demo-thumbnail.png diff --git a/examples/getting-started/cdn/demo-video.mp4 b/demos/cdn/demo-video.mp4 similarity index 100% rename from examples/getting-started/cdn/demo-video.mp4 rename to demos/cdn/demo-video.mp4 diff --git a/examples/getting-started/cdn/file-upload.css b/demos/cdn/file-upload.css similarity index 100% rename from examples/getting-started/cdn/file-upload.css rename to demos/cdn/file-upload.css diff --git a/examples/getting-started/cdn/file-upload.js b/demos/cdn/file-upload.js similarity index 100% rename from examples/getting-started/cdn/file-upload.js rename to demos/cdn/file-upload.js diff --git a/demos/cdn/index.html b/demos/cdn/index.html new file mode 100644 index 0000000000..2fc37d71ee --- /dev/null +++ b/demos/cdn/index.html @@ -0,0 +1,56 @@ + + + + + + + + + SuperDoc - CDN example + + + + +
SuperDoc - CDN example
+
+ + + + +
+ + + + + + diff --git a/examples/getting-started/cdn/package.json b/demos/cdn/package.json similarity index 100% rename from examples/getting-started/cdn/package.json rename to demos/cdn/package.json diff --git a/examples/advanced/replace-content/public/sample.docx b/demos/cdn/sample.docx similarity index 100% rename from examples/advanced/replace-content/public/sample.docx rename to demos/cdn/sample.docx diff --git a/examples/integrations/chrome-extension/chrome-extension/README.md b/demos/chrome-extension/chrome-extension/README.md similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/README.md rename to demos/chrome-extension/chrome-extension/README.md diff --git a/examples/integrations/chrome-extension/chrome-extension/background.js b/demos/chrome-extension/chrome-extension/background.js similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/background.js rename to demos/chrome-extension/chrome-extension/background.js diff --git a/examples/integrations/chrome-extension/chrome-extension/content.js b/demos/chrome-extension/chrome-extension/content.js similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/content.js rename to demos/chrome-extension/chrome-extension/content.js diff --git a/examples/integrations/chrome-extension/chrome-extension/dist/docx-validator.bundle.js b/demos/chrome-extension/chrome-extension/dist/docx-validator.bundle.js similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/dist/docx-validator.bundle.js rename to demos/chrome-extension/chrome-extension/dist/docx-validator.bundle.js diff --git a/examples/integrations/chrome-extension/chrome-extension/dist/docx-validator.bundle.js.LICENSE.txt b/demos/chrome-extension/chrome-extension/dist/docx-validator.bundle.js.LICENSE.txt similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/dist/docx-validator.bundle.js.LICENSE.txt rename to demos/chrome-extension/chrome-extension/dist/docx-validator.bundle.js.LICENSE.txt diff --git a/examples/integrations/chrome-extension/chrome-extension/docx-validator.js b/demos/chrome-extension/chrome-extension/docx-validator.js similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/docx-validator.js rename to demos/chrome-extension/chrome-extension/docx-validator.js diff --git a/examples/integrations/chrome-extension/chrome-extension/icons/icon-128x128-disabled.png b/demos/chrome-extension/chrome-extension/icons/icon-128x128-disabled.png similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/icons/icon-128x128-disabled.png rename to demos/chrome-extension/chrome-extension/icons/icon-128x128-disabled.png diff --git a/examples/integrations/chrome-extension/chrome-extension/icons/icon-128x128.png b/demos/chrome-extension/chrome-extension/icons/icon-128x128.png similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/icons/icon-128x128.png rename to demos/chrome-extension/chrome-extension/icons/icon-128x128.png diff --git a/examples/integrations/chrome-extension/chrome-extension/icons/icon-16x16-disabled.png b/demos/chrome-extension/chrome-extension/icons/icon-16x16-disabled.png similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/icons/icon-16x16-disabled.png rename to demos/chrome-extension/chrome-extension/icons/icon-16x16-disabled.png diff --git a/examples/integrations/chrome-extension/chrome-extension/icons/icon-16x16.png b/demos/chrome-extension/chrome-extension/icons/icon-16x16.png similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/icons/icon-16x16.png rename to demos/chrome-extension/chrome-extension/icons/icon-16x16.png diff --git a/examples/integrations/chrome-extension/chrome-extension/icons/icon-19x19-disabled.png b/demos/chrome-extension/chrome-extension/icons/icon-19x19-disabled.png similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/icons/icon-19x19-disabled.png rename to demos/chrome-extension/chrome-extension/icons/icon-19x19-disabled.png diff --git a/examples/integrations/chrome-extension/chrome-extension/icons/icon-19x19.png b/demos/chrome-extension/chrome-extension/icons/icon-19x19.png similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/icons/icon-19x19.png rename to demos/chrome-extension/chrome-extension/icons/icon-19x19.png diff --git a/examples/integrations/chrome-extension/chrome-extension/icons/icon-48x48-disabled.png b/demos/chrome-extension/chrome-extension/icons/icon-48x48-disabled.png similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/icons/icon-48x48-disabled.png rename to demos/chrome-extension/chrome-extension/icons/icon-48x48-disabled.png diff --git a/examples/integrations/chrome-extension/chrome-extension/icons/icon-48x48.png b/demos/chrome-extension/chrome-extension/icons/icon-48x48.png similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/icons/icon-48x48.png rename to demos/chrome-extension/chrome-extension/icons/icon-48x48.png diff --git a/examples/advanced/docx-from-html/public/logo.webp b/demos/chrome-extension/chrome-extension/icons/logo.webp similarity index 100% rename from examples/advanced/docx-from-html/public/logo.webp rename to demos/chrome-extension/chrome-extension/icons/logo.webp diff --git a/examples/integrations/chrome-extension/chrome-extension/lib/style.css b/demos/chrome-extension/chrome-extension/lib/style.css similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/lib/style.css rename to demos/chrome-extension/chrome-extension/lib/style.css diff --git a/examples/integrations/chrome-extension/chrome-extension/lib/superdoc.umd.js b/demos/chrome-extension/chrome-extension/lib/superdoc.umd.js similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/lib/superdoc.umd.js rename to demos/chrome-extension/chrome-extension/lib/superdoc.umd.js diff --git a/examples/integrations/chrome-extension/chrome-extension/manifest.json b/demos/chrome-extension/chrome-extension/manifest.json similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/manifest.json rename to demos/chrome-extension/chrome-extension/manifest.json diff --git a/examples/integrations/chrome-extension/chrome-extension/modal.css b/demos/chrome-extension/chrome-extension/modal.css similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/modal.css rename to demos/chrome-extension/chrome-extension/modal.css diff --git a/examples/integrations/chrome-extension/chrome-extension/modal.html b/demos/chrome-extension/chrome-extension/modal.html similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/modal.html rename to demos/chrome-extension/chrome-extension/modal.html diff --git a/examples/integrations/chrome-extension/chrome-extension/package.json b/demos/chrome-extension/chrome-extension/package.json similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/package.json rename to demos/chrome-extension/chrome-extension/package.json diff --git a/examples/integrations/chrome-extension/chrome-extension/popup.html b/demos/chrome-extension/chrome-extension/popup.html similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/popup.html rename to demos/chrome-extension/chrome-extension/popup.html diff --git a/examples/integrations/chrome-extension/chrome-extension/popup.js b/demos/chrome-extension/chrome-extension/popup.js similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/popup.js rename to demos/chrome-extension/chrome-extension/popup.js diff --git a/examples/integrations/chrome-extension/chrome-extension/test_docs/Lunch Haiku (5).docx b/demos/chrome-extension/chrome-extension/test_docs/Lunch Haiku (5).docx similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/test_docs/Lunch Haiku (5).docx rename to demos/chrome-extension/chrome-extension/test_docs/Lunch Haiku (5).docx diff --git a/examples/integrations/chrome-extension/chrome-extension/test_docs/Mutual NDA_draft (1).docx b/demos/chrome-extension/chrome-extension/test_docs/Mutual NDA_draft (1).docx similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/test_docs/Mutual NDA_draft (1).docx rename to demos/chrome-extension/chrome-extension/test_docs/Mutual NDA_draft (1).docx diff --git a/examples/integrations/chrome-extension/chrome-extension/test_docs/Nda Formatted Doc MS WORD.docx b/demos/chrome-extension/chrome-extension/test_docs/Nda Formatted Doc MS WORD.docx similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/test_docs/Nda Formatted Doc MS WORD.docx rename to demos/chrome-extension/chrome-extension/test_docs/Nda Formatted Doc MS WORD.docx diff --git a/examples/integrations/chrome-extension/chrome-extension/test_docs/Nda Formatted Doc.docx b/demos/chrome-extension/chrome-extension/test_docs/Nda Formatted Doc.docx similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/test_docs/Nda Formatted Doc.docx rename to demos/chrome-extension/chrome-extension/test_docs/Nda Formatted Doc.docx diff --git a/examples/integrations/chrome-extension/chrome-extension/test_docs/nda_formatted_doc (1).md b/demos/chrome-extension/chrome-extension/test_docs/nda_formatted_doc (1).md similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/test_docs/nda_formatted_doc (1).md rename to demos/chrome-extension/chrome-extension/test_docs/nda_formatted_doc (1).md diff --git a/examples/integrations/chrome-extension/chrome-extension/test_docs/sdpr (23).docx b/demos/chrome-extension/chrome-extension/test_docs/sdpr (23).docx similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/test_docs/sdpr (23).docx rename to demos/chrome-extension/chrome-extension/test_docs/sdpr (23).docx diff --git a/examples/integrations/chrome-extension/chrome-extension/tester.html b/demos/chrome-extension/chrome-extension/tester.html similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/tester.html rename to demos/chrome-extension/chrome-extension/tester.html diff --git a/examples/integrations/chrome-extension/chrome-extension/webpack.config.js b/demos/chrome-extension/chrome-extension/webpack.config.js similarity index 100% rename from examples/integrations/chrome-extension/chrome-extension/webpack.config.js rename to demos/chrome-extension/chrome-extension/webpack.config.js diff --git a/examples/integrations/chrome-extension/demo-config.json b/demos/chrome-extension/demo-config.json similarity index 100% rename from examples/integrations/chrome-extension/demo-config.json rename to demos/chrome-extension/demo-config.json diff --git a/examples/integrations/chrome-extension/demo-thumbnail.png b/demos/chrome-extension/demo-thumbnail.png similarity index 100% rename from examples/integrations/chrome-extension/demo-thumbnail.png rename to demos/chrome-extension/demo-thumbnail.png diff --git a/examples/integrations/chrome-extension/demo-video.mp4 b/demos/chrome-extension/demo-video.mp4 similarity index 100% rename from examples/integrations/chrome-extension/demo-video.mp4 rename to demos/chrome-extension/demo-video.mp4 diff --git a/examples/advanced/docx-from-html/.gitignore b/demos/custom-mark/.gitignore similarity index 100% rename from examples/advanced/docx-from-html/.gitignore rename to demos/custom-mark/.gitignore diff --git a/demos/custom-mark/README.md b/demos/custom-mark/README.md new file mode 100644 index 0000000000..768cd9e239 --- /dev/null +++ b/demos/custom-mark/README.md @@ -0,0 +1,9 @@ +# SuperDoc: Creating a Custom Mark + +An example of creating a custom Mark to use with SuperDoc. + +[We create a custom mark here](https://github.com/superdoc-dev/superdoc/blob/main/demos/custom-mark/src/custom-mark.js) — note the custom command `setMyCustomMark`, which can be called from `superdoc.activeEditor.commands`. + +[Then we pass it into the editor via the `editorExtensions` key](https://github.com/superdoc-dev/superdoc/blob/main/demos/custom-mark/src/App.vue) + +This example also shows one way to export the docx to a blob whenever the content changes in the editor. diff --git a/examples/advanced/docx-from-html/public/sample-document.docx b/demos/custom-mark/assets/sample-document.docx similarity index 100% rename from examples/advanced/docx-from-html/public/sample-document.docx rename to demos/custom-mark/assets/sample-document.docx diff --git a/examples/customization/custom-mark/demo-config.json b/demos/custom-mark/demo-config.json similarity index 100% rename from examples/customization/custom-mark/demo-config.json rename to demos/custom-mark/demo-config.json diff --git a/examples/customization/custom-mark/demo-thumbnail.png b/demos/custom-mark/demo-thumbnail.png similarity index 100% rename from examples/customization/custom-mark/demo-thumbnail.png rename to demos/custom-mark/demo-thumbnail.png diff --git a/examples/customization/custom-mark/demo-video.mp4 b/demos/custom-mark/demo-video.mp4 similarity index 100% rename from examples/customization/custom-mark/demo-video.mp4 rename to demos/custom-mark/demo-video.mp4 diff --git a/examples/customization/custom-mark/index.html b/demos/custom-mark/index.html similarity index 100% rename from examples/customization/custom-mark/index.html rename to demos/custom-mark/index.html diff --git a/examples/advanced/docx-from-html/package.json b/demos/custom-mark/package.json similarity index 90% rename from examples/advanced/docx-from-html/package.json rename to demos/custom-mark/package.json index df6ef44cfc..2591a50bea 100644 --- a/examples/advanced/docx-from-html/package.json +++ b/demos/custom-mark/package.json @@ -9,7 +9,7 @@ "preview": "vite preview" }, "dependencies": { - "superdoc": "0.20.0-next.13", + "superdoc": "latest", "vue": "^3.5.13" }, "devDependencies": { diff --git a/examples/customization/custom-mark/public/circle-chevron-down-solid.svg b/demos/custom-mark/public/circle-chevron-down-solid.svg similarity index 100% rename from examples/customization/custom-mark/public/circle-chevron-down-solid.svg rename to demos/custom-mark/public/circle-chevron-down-solid.svg diff --git a/examples/customization/custom-mark/public/headphones-solid.svg b/demos/custom-mark/public/headphones-solid.svg similarity index 100% rename from examples/customization/custom-mark/public/headphones-solid.svg rename to demos/custom-mark/public/headphones-solid.svg diff --git a/examples/advanced/fields/public/logo.webp b/demos/custom-mark/public/logo.webp similarity index 100% rename from examples/advanced/fields/public/logo.webp rename to demos/custom-mark/public/logo.webp diff --git a/examples/advanced/docx-from-html/public/superdoc-logo.png b/demos/custom-mark/public/superdoc-logo.png similarity index 100% rename from examples/advanced/docx-from-html/public/superdoc-logo.png rename to demos/custom-mark/public/superdoc-logo.png diff --git a/examples/customization/custom-mark/src/App.vue b/demos/custom-mark/src/App.vue similarity index 100% rename from examples/customization/custom-mark/src/App.vue rename to demos/custom-mark/src/App.vue diff --git a/examples/advanced/fields/src/UploadFile.vue b/demos/custom-mark/src/UploadFile.vue similarity index 100% rename from examples/advanced/fields/src/UploadFile.vue rename to demos/custom-mark/src/UploadFile.vue diff --git a/examples/customization/custom-mark/src/custom-mark.js b/demos/custom-mark/src/custom-mark.js similarity index 100% rename from examples/customization/custom-mark/src/custom-mark.js rename to demos/custom-mark/src/custom-mark.js diff --git a/examples/advanced/docx-from-html/src/main.js b/demos/custom-mark/src/main.js similarity index 100% rename from examples/advanced/docx-from-html/src/main.js rename to demos/custom-mark/src/main.js diff --git a/examples/advanced/fields/src/style.css b/demos/custom-mark/src/style.css similarity index 100% rename from examples/advanced/fields/src/style.css rename to demos/custom-mark/src/style.css diff --git a/examples/advanced/docx-from-html/vite.config.js b/demos/custom-mark/vite.config.js similarity index 100% rename from examples/advanced/docx-from-html/vite.config.js rename to demos/custom-mark/vite.config.js diff --git a/examples/advanced/fields/.gitignore b/demos/custom-node/.gitignore similarity index 100% rename from examples/advanced/fields/.gitignore rename to demos/custom-node/.gitignore diff --git a/demos/custom-node/README.md b/demos/custom-node/README.md new file mode 100644 index 0000000000..ac3b0e64b3 --- /dev/null +++ b/demos/custom-node/README.md @@ -0,0 +1,7 @@ +# SuperDoc: Creating a Custom Node + +An example of creating a custom node to use with SuperDoc. + +[We create a custom node here](https://github.com/superdoc-dev/superdoc/blob/main/demos/custom-node/src/custom-node.js) — note the custom command `insertCustomNode`, which can be called from `editor.commands.insertCustomNode`. + +[Then we pass it into the editor via the `editorExtensions` key](https://github.com/superdoc-dev/superdoc/blob/main/demos/custom-node/src/App.vue) diff --git a/examples/customization/custom-node/demo-config.json b/demos/custom-node/demo-config.json similarity index 100% rename from examples/customization/custom-node/demo-config.json rename to demos/custom-node/demo-config.json diff --git a/examples/advanced/fields/demo-thumbnail.png b/demos/custom-node/demo-thumbnail.png similarity index 100% rename from examples/advanced/fields/demo-thumbnail.png rename to demos/custom-node/demo-thumbnail.png diff --git a/examples/advanced/fields/demo-video.mp4 b/demos/custom-node/demo-video.mp4 similarity index 100% rename from examples/advanced/fields/demo-video.mp4 rename to demos/custom-node/demo-video.mp4 diff --git a/examples/advanced/fields/index.html b/demos/custom-node/index.html similarity index 100% rename from examples/advanced/fields/index.html rename to demos/custom-node/index.html diff --git a/examples/customization/custom-mark/package.json b/demos/custom-node/package.json similarity index 90% rename from examples/customization/custom-mark/package.json rename to demos/custom-node/package.json index df6ef44cfc..2591a50bea 100644 --- a/examples/customization/custom-mark/package.json +++ b/demos/custom-node/package.json @@ -9,7 +9,7 @@ "preview": "vite preview" }, "dependencies": { - "superdoc": "0.20.0-next.13", + "superdoc": "latest", "vue": "^3.5.13" }, "devDependencies": { diff --git a/examples/advanced/linked-sections/public/logo.webp b/demos/custom-node/public/logo.webp similarity index 100% rename from examples/advanced/linked-sections/public/logo.webp rename to demos/custom-node/public/logo.webp diff --git a/examples/advanced/fields/public/sample-document.docx b/demos/custom-node/public/sample-document.docx similarity index 100% rename from examples/advanced/fields/public/sample-document.docx rename to demos/custom-node/public/sample-document.docx diff --git a/examples/customization/custom-node/src/App.vue b/demos/custom-node/src/App.vue similarity index 100% rename from examples/customization/custom-node/src/App.vue rename to demos/custom-node/src/App.vue diff --git a/examples/advanced/linked-sections/src/UploadFile.vue b/demos/custom-node/src/UploadFile.vue similarity index 100% rename from examples/advanced/linked-sections/src/UploadFile.vue rename to demos/custom-node/src/UploadFile.vue diff --git a/examples/customization/custom-node/src/custom-node.js b/demos/custom-node/src/custom-node.js similarity index 100% rename from examples/customization/custom-node/src/custom-node.js rename to demos/custom-node/src/custom-node.js diff --git a/examples/advanced/fields/src/main.js b/demos/custom-node/src/main.js similarity index 100% rename from examples/advanced/fields/src/main.js rename to demos/custom-node/src/main.js diff --git a/examples/advanced/linked-sections/src/style.css b/demos/custom-node/src/style.css similarity index 100% rename from examples/advanced/linked-sections/src/style.css rename to demos/custom-node/src/style.css diff --git a/examples/advanced/fields/vite.config.js b/demos/custom-node/vite.config.js similarity index 100% rename from examples/advanced/fields/vite.config.js rename to demos/custom-node/vite.config.js diff --git a/examples/advanced/html-editor/.gitignore b/demos/docx-from-html/.gitignore similarity index 100% rename from examples/advanced/html-editor/.gitignore rename to demos/docx-from-html/.gitignore diff --git a/demos/docx-from-html/README.md b/demos/docx-from-html/README.md new file mode 100644 index 0000000000..fa29f456fa --- /dev/null +++ b/demos/docx-from-html/README.md @@ -0,0 +1,7 @@ +# SuperDoc: Init a DOCX from HTML Content + +An example of initializing SuperDoc with HTML content. + +This will load a DOCX file (or a blank document), replacing the main contents with the provided HTML. + +In the example we pass `document: sample-document.docx` to load a template with a header and footer. You can omit this key to start with a blank document. diff --git a/examples/advanced/docx-from-html/demo-config.json b/demos/docx-from-html/demo-config.json similarity index 100% rename from examples/advanced/docx-from-html/demo-config.json rename to demos/docx-from-html/demo-config.json diff --git a/examples/getting-started/cdn/demo-thumbnail.png b/demos/docx-from-html/demo-thumbnail.png similarity index 100% rename from examples/getting-started/cdn/demo-thumbnail.png rename to demos/docx-from-html/demo-thumbnail.png diff --git a/examples/advanced/docx-from-html/demo-video.mp4 b/demos/docx-from-html/demo-video.mp4 similarity index 100% rename from examples/advanced/docx-from-html/demo-video.mp4 rename to demos/docx-from-html/demo-video.mp4 diff --git a/examples/advanced/docx-from-html/index.html b/demos/docx-from-html/index.html similarity index 100% rename from examples/advanced/docx-from-html/index.html rename to demos/docx-from-html/index.html diff --git a/examples/advanced/fields/package.json b/demos/docx-from-html/package.json similarity index 90% rename from examples/advanced/fields/package.json rename to demos/docx-from-html/package.json index df6ef44cfc..2591a50bea 100644 --- a/examples/advanced/fields/package.json +++ b/demos/docx-from-html/package.json @@ -9,7 +9,7 @@ "preview": "vite preview" }, "dependencies": { - "superdoc": "0.20.0-next.13", + "superdoc": "latest", "vue": "^3.5.13" }, "devDependencies": { diff --git a/examples/collaboration/basic/public/logo.webp b/demos/docx-from-html/public/logo.webp similarity index 100% rename from examples/collaboration/basic/public/logo.webp rename to demos/docx-from-html/public/logo.webp diff --git a/examples/customization/custom-mark/assets/sample-document.docx b/demos/docx-from-html/public/sample-document.docx similarity index 100% rename from examples/customization/custom-mark/assets/sample-document.docx rename to demos/docx-from-html/public/sample-document.docx diff --git a/examples/advanced/html-editor/public/superdoc-logo.png b/demos/docx-from-html/public/superdoc-logo.png similarity index 100% rename from examples/advanced/html-editor/public/superdoc-logo.png rename to demos/docx-from-html/public/superdoc-logo.png diff --git a/examples/advanced/docx-from-html/src/App.vue b/demos/docx-from-html/src/App.vue similarity index 100% rename from examples/advanced/docx-from-html/src/App.vue rename to demos/docx-from-html/src/App.vue diff --git a/examples/advanced/html-editor/src/main.js b/demos/docx-from-html/src/main.js similarity index 100% rename from examples/advanced/html-editor/src/main.js rename to demos/docx-from-html/src/main.js diff --git a/examples/advanced/docx-from-html/src/style.css b/demos/docx-from-html/src/style.css similarity index 100% rename from examples/advanced/docx-from-html/src/style.css rename to demos/docx-from-html/src/style.css diff --git a/examples/advanced/html-editor/vite.config.js b/demos/docx-from-html/vite.config.js similarity index 100% rename from examples/advanced/html-editor/vite.config.js rename to demos/docx-from-html/vite.config.js diff --git a/examples/advanced/docxtemplater/README.md b/demos/docxtemplater/README.md similarity index 100% rename from examples/advanced/docxtemplater/README.md rename to demos/docxtemplater/README.md diff --git a/examples/advanced/docxtemplater/demo-config.json b/demos/docxtemplater/demo-config.json similarity index 100% rename from examples/advanced/docxtemplater/demo-config.json rename to demos/docxtemplater/demo-config.json diff --git a/examples/advanced/docxtemplater/demo-thumbnail.png b/demos/docxtemplater/demo-thumbnail.png similarity index 100% rename from examples/advanced/docxtemplater/demo-thumbnail.png rename to demos/docxtemplater/demo-thumbnail.png diff --git a/examples/advanced/docxtemplater/demo-video.mp4 b/demos/docxtemplater/demo-video.mp4 similarity index 100% rename from examples/advanced/docxtemplater/demo-video.mp4 rename to demos/docxtemplater/demo-video.mp4 diff --git a/examples/advanced/docxtemplater/demo.gif b/demos/docxtemplater/demo.gif similarity index 100% rename from examples/advanced/docxtemplater/demo.gif rename to demos/docxtemplater/demo.gif diff --git a/examples/advanced/docxtemplater/index.html b/demos/docxtemplater/index.html similarity index 100% rename from examples/advanced/docxtemplater/index.html rename to demos/docxtemplater/index.html diff --git a/examples/advanced/docxtemplater/jsconfig.json b/demos/docxtemplater/jsconfig.json similarity index 100% rename from examples/advanced/docxtemplater/jsconfig.json rename to demos/docxtemplater/jsconfig.json diff --git a/examples/advanced/docxtemplater/package.json b/demos/docxtemplater/package.json similarity index 96% rename from examples/advanced/docxtemplater/package.json rename to demos/docxtemplater/package.json index 9e7e9e8b26..e7ec058d41 100644 --- a/examples/advanced/docxtemplater/package.json +++ b/demos/docxtemplater/package.json @@ -14,7 +14,7 @@ "@fortawesome/free-regular-svg-icons": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/vue-fontawesome": "^3.0.8", - "superdoc": "0.20.0-next.13", + "superdoc": "latest", "docxtemplater": "^3.59.0", "pizzip": "^3.1.8", "prismjs": "^1.29.0", diff --git a/examples/advanced/docxtemplater/public/favicon.ico b/demos/docxtemplater/public/favicon.ico similarity index 100% rename from examples/advanced/docxtemplater/public/favicon.ico rename to demos/docxtemplater/public/favicon.ico diff --git a/examples/advanced/docxtemplater/src/App.vue b/demos/docxtemplater/src/App.vue similarity index 100% rename from examples/advanced/docxtemplater/src/App.vue rename to demos/docxtemplater/src/App.vue diff --git a/examples/advanced/docxtemplater/src/assets/template.docx b/demos/docxtemplater/src/assets/template.docx similarity index 100% rename from examples/advanced/docxtemplater/src/assets/template.docx rename to demos/docxtemplater/src/assets/template.docx diff --git a/examples/advanced/docxtemplater/src/components/DocumentTab.vue b/demos/docxtemplater/src/components/DocumentTab.vue similarity index 100% rename from examples/advanced/docxtemplater/src/components/DocumentTab.vue rename to demos/docxtemplater/src/components/DocumentTab.vue diff --git a/examples/advanced/docxtemplater/src/fileProcessing.js b/demos/docxtemplater/src/fileProcessing.js similarity index 100% rename from examples/advanced/docxtemplater/src/fileProcessing.js rename to demos/docxtemplater/src/fileProcessing.js diff --git a/examples/advanced/docxtemplater/src/main.js b/demos/docxtemplater/src/main.js similarity index 100% rename from examples/advanced/docxtemplater/src/main.js rename to demos/docxtemplater/src/main.js diff --git a/examples/advanced/docxtemplater/src/utils.js b/demos/docxtemplater/src/utils.js similarity index 100% rename from examples/advanced/docxtemplater/src/utils.js rename to demos/docxtemplater/src/utils.js diff --git a/examples/advanced/docxtemplater/vite.config.js b/demos/docxtemplater/vite.config.js similarity index 100% rename from examples/advanced/docxtemplater/vite.config.js rename to demos/docxtemplater/vite.config.js diff --git a/examples/advanced/linked-sections/.gitignore b/demos/fields/.gitignore similarity index 100% rename from examples/advanced/linked-sections/.gitignore rename to demos/fields/.gitignore diff --git a/demos/fields/README.md b/demos/fields/README.md new file mode 100644 index 0000000000..0ba5db9d4d --- /dev/null +++ b/demos/fields/README.md @@ -0,0 +1,6 @@ +# SuperDoc: Fields + +An example of using fields with SuperDoc. + +- Shows basic drag-and-drop of fields +- Shows field replacement use case diff --git a/examples/advanced/fields/demo-config.json b/demos/fields/demo-config.json similarity index 100% rename from examples/advanced/fields/demo-config.json rename to demos/fields/demo-config.json diff --git a/examples/customization/custom-node/demo-thumbnail.png b/demos/fields/demo-thumbnail.png similarity index 100% rename from examples/customization/custom-node/demo-thumbnail.png rename to demos/fields/demo-thumbnail.png diff --git a/examples/customization/custom-node/demo-video.mp4 b/demos/fields/demo-video.mp4 similarity index 100% rename from examples/customization/custom-node/demo-video.mp4 rename to demos/fields/demo-video.mp4 diff --git a/examples/advanced/linked-sections/index.html b/demos/fields/index.html similarity index 100% rename from examples/advanced/linked-sections/index.html rename to demos/fields/index.html diff --git a/examples/advanced/html-editor/package.json b/demos/fields/package.json similarity index 90% rename from examples/advanced/html-editor/package.json rename to demos/fields/package.json index df6ef44cfc..2591a50bea 100644 --- a/examples/advanced/html-editor/package.json +++ b/demos/fields/package.json @@ -9,7 +9,7 @@ "preview": "vite preview" }, "dependencies": { - "superdoc": "0.20.0-next.13", + "superdoc": "latest", "vue": "^3.5.13" }, "devDependencies": { diff --git a/examples/collaboration/production/client/public/logo.webp b/demos/fields/public/logo.webp similarity index 100% rename from examples/collaboration/production/client/public/logo.webp rename to demos/fields/public/logo.webp diff --git a/examples/collaboration/from-scratch/client/public/sample-document.docx b/demos/fields/public/sample-document.docx similarity index 100% rename from examples/collaboration/from-scratch/client/public/sample-document.docx rename to demos/fields/public/sample-document.docx diff --git a/examples/advanced/fields/src/App.vue b/demos/fields/src/App.vue similarity index 100% rename from examples/advanced/fields/src/App.vue rename to demos/fields/src/App.vue diff --git a/examples/collaboration/basic/src/UploadFile.vue b/demos/fields/src/UploadFile.vue similarity index 100% rename from examples/collaboration/basic/src/UploadFile.vue rename to demos/fields/src/UploadFile.vue diff --git a/examples/advanced/linked-sections/src/main.js b/demos/fields/src/main.js similarity index 100% rename from examples/advanced/linked-sections/src/main.js rename to demos/fields/src/main.js diff --git a/examples/customization/custom-mark/src/style.css b/demos/fields/src/style.css similarity index 100% rename from examples/customization/custom-mark/src/style.css rename to demos/fields/src/style.css diff --git a/examples/advanced/linked-sections/vite.config.js b/demos/fields/vite.config.js similarity index 100% rename from examples/advanced/linked-sections/vite.config.js rename to demos/fields/vite.config.js diff --git a/examples/advanced/grading-papers/.gitignore b/demos/grading-papers/.gitignore similarity index 100% rename from examples/advanced/grading-papers/.gitignore rename to demos/grading-papers/.gitignore diff --git a/examples/advanced/grading-papers/README.md b/demos/grading-papers/README.md similarity index 100% rename from examples/advanced/grading-papers/README.md rename to demos/grading-papers/README.md diff --git a/examples/advanced/grading-papers/app/globals.css b/demos/grading-papers/app/globals.css similarity index 100% rename from examples/advanced/grading-papers/app/globals.css rename to demos/grading-papers/app/globals.css diff --git a/examples/advanced/grading-papers/app/grading/[id]/_doc-links.js b/demos/grading-papers/app/grading/[id]/_doc-links.js similarity index 100% rename from examples/advanced/grading-papers/app/grading/[id]/_doc-links.js rename to demos/grading-papers/app/grading/[id]/_doc-links.js diff --git a/examples/advanced/grading-papers/app/grading/[id]/loading.tsx b/demos/grading-papers/app/grading/[id]/loading.tsx similarity index 100% rename from examples/advanced/grading-papers/app/grading/[id]/loading.tsx rename to demos/grading-papers/app/grading/[id]/loading.tsx diff --git a/examples/advanced/grading-papers/app/grading/[id]/page.tsx b/demos/grading-papers/app/grading/[id]/page.tsx similarity index 100% rename from examples/advanced/grading-papers/app/grading/[id]/page.tsx rename to demos/grading-papers/app/grading/[id]/page.tsx diff --git a/examples/advanced/grading-papers/app/layout.tsx b/demos/grading-papers/app/layout.tsx similarity index 100% rename from examples/advanced/grading-papers/app/layout.tsx rename to demos/grading-papers/app/layout.tsx diff --git a/examples/advanced/grading-papers/app/loading.tsx b/demos/grading-papers/app/loading.tsx similarity index 100% rename from examples/advanced/grading-papers/app/loading.tsx rename to demos/grading-papers/app/loading.tsx diff --git a/examples/advanced/grading-papers/app/page.tsx b/demos/grading-papers/app/page.tsx similarity index 100% rename from examples/advanced/grading-papers/app/page.tsx rename to demos/grading-papers/app/page.tsx diff --git a/examples/advanced/grading-papers/assets/docs/assign-1.docx b/demos/grading-papers/assets/docs/assign-1.docx similarity index 100% rename from examples/advanced/grading-papers/assets/docs/assign-1.docx rename to demos/grading-papers/assets/docs/assign-1.docx diff --git a/examples/advanced/grading-papers/assets/docs/assign-1.js b/demos/grading-papers/assets/docs/assign-1.js similarity index 100% rename from examples/advanced/grading-papers/assets/docs/assign-1.js rename to demos/grading-papers/assets/docs/assign-1.js diff --git a/examples/advanced/grading-papers/assets/docs/assign-2.docx b/demos/grading-papers/assets/docs/assign-2.docx similarity index 100% rename from examples/advanced/grading-papers/assets/docs/assign-2.docx rename to demos/grading-papers/assets/docs/assign-2.docx diff --git a/examples/advanced/grading-papers/assets/docs/assign-2.js b/demos/grading-papers/assets/docs/assign-2.js similarity index 100% rename from examples/advanced/grading-papers/assets/docs/assign-2.js rename to demos/grading-papers/assets/docs/assign-2.js diff --git a/examples/advanced/grading-papers/assets/docs/assign-3.docx b/demos/grading-papers/assets/docs/assign-3.docx similarity index 100% rename from examples/advanced/grading-papers/assets/docs/assign-3.docx rename to demos/grading-papers/assets/docs/assign-3.docx diff --git a/examples/advanced/grading-papers/assets/docs/assign-3.js b/demos/grading-papers/assets/docs/assign-3.js similarity index 100% rename from examples/advanced/grading-papers/assets/docs/assign-3.js rename to demos/grading-papers/assets/docs/assign-3.js diff --git a/examples/advanced/grading-papers/assets/docs/assign-4.docx b/demos/grading-papers/assets/docs/assign-4.docx similarity index 100% rename from examples/advanced/grading-papers/assets/docs/assign-4.docx rename to demos/grading-papers/assets/docs/assign-4.docx diff --git a/examples/advanced/grading-papers/assets/docs/assign-4.js b/demos/grading-papers/assets/docs/assign-4.js similarity index 100% rename from examples/advanced/grading-papers/assets/docs/assign-4.js rename to demos/grading-papers/assets/docs/assign-4.js diff --git a/examples/advanced/grading-papers/assets/docs/assign-5.docx b/demos/grading-papers/assets/docs/assign-5.docx similarity index 100% rename from examples/advanced/grading-papers/assets/docs/assign-5.docx rename to demos/grading-papers/assets/docs/assign-5.docx diff --git a/examples/advanced/grading-papers/assets/docs/assign-5.js b/demos/grading-papers/assets/docs/assign-5.js similarity index 100% rename from examples/advanced/grading-papers/assets/docs/assign-5.js rename to demos/grading-papers/assets/docs/assign-5.js diff --git a/examples/advanced/grading-papers/components.json b/demos/grading-papers/components.json similarity index 100% rename from examples/advanced/grading-papers/components.json rename to demos/grading-papers/components.json diff --git a/examples/advanced/grading-papers/components/theme-provider.tsx b/demos/grading-papers/components/theme-provider.tsx similarity index 100% rename from examples/advanced/grading-papers/components/theme-provider.tsx rename to demos/grading-papers/components/theme-provider.tsx diff --git a/examples/advanced/grading-papers/components/ui/accordion.tsx b/demos/grading-papers/components/ui/accordion.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/accordion.tsx rename to demos/grading-papers/components/ui/accordion.tsx diff --git a/examples/advanced/grading-papers/components/ui/alert-dialog.tsx b/demos/grading-papers/components/ui/alert-dialog.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/alert-dialog.tsx rename to demos/grading-papers/components/ui/alert-dialog.tsx diff --git a/examples/advanced/grading-papers/components/ui/alert.tsx b/demos/grading-papers/components/ui/alert.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/alert.tsx rename to demos/grading-papers/components/ui/alert.tsx diff --git a/examples/advanced/grading-papers/components/ui/aspect-ratio.tsx b/demos/grading-papers/components/ui/aspect-ratio.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/aspect-ratio.tsx rename to demos/grading-papers/components/ui/aspect-ratio.tsx diff --git a/examples/advanced/grading-papers/components/ui/avatar.tsx b/demos/grading-papers/components/ui/avatar.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/avatar.tsx rename to demos/grading-papers/components/ui/avatar.tsx diff --git a/examples/advanced/grading-papers/components/ui/badge.tsx b/demos/grading-papers/components/ui/badge.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/badge.tsx rename to demos/grading-papers/components/ui/badge.tsx diff --git a/examples/advanced/grading-papers/components/ui/breadcrumb.tsx b/demos/grading-papers/components/ui/breadcrumb.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/breadcrumb.tsx rename to demos/grading-papers/components/ui/breadcrumb.tsx diff --git a/examples/advanced/grading-papers/components/ui/button.tsx b/demos/grading-papers/components/ui/button.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/button.tsx rename to demos/grading-papers/components/ui/button.tsx diff --git a/examples/advanced/grading-papers/components/ui/calendar.tsx b/demos/grading-papers/components/ui/calendar.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/calendar.tsx rename to demos/grading-papers/components/ui/calendar.tsx diff --git a/examples/advanced/grading-papers/components/ui/card.tsx b/demos/grading-papers/components/ui/card.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/card.tsx rename to demos/grading-papers/components/ui/card.tsx diff --git a/examples/advanced/grading-papers/components/ui/carousel.tsx b/demos/grading-papers/components/ui/carousel.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/carousel.tsx rename to demos/grading-papers/components/ui/carousel.tsx diff --git a/examples/advanced/grading-papers/components/ui/chart.tsx b/demos/grading-papers/components/ui/chart.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/chart.tsx rename to demos/grading-papers/components/ui/chart.tsx diff --git a/examples/advanced/grading-papers/components/ui/checkbox.tsx b/demos/grading-papers/components/ui/checkbox.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/checkbox.tsx rename to demos/grading-papers/components/ui/checkbox.tsx diff --git a/examples/advanced/grading-papers/components/ui/collapsible.tsx b/demos/grading-papers/components/ui/collapsible.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/collapsible.tsx rename to demos/grading-papers/components/ui/collapsible.tsx diff --git a/examples/advanced/grading-papers/components/ui/command.tsx b/demos/grading-papers/components/ui/command.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/command.tsx rename to demos/grading-papers/components/ui/command.tsx diff --git a/examples/advanced/grading-papers/components/ui/context-menu.tsx b/demos/grading-papers/components/ui/context-menu.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/context-menu.tsx rename to demos/grading-papers/components/ui/context-menu.tsx diff --git a/examples/advanced/grading-papers/components/ui/dialog.tsx b/demos/grading-papers/components/ui/dialog.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/dialog.tsx rename to demos/grading-papers/components/ui/dialog.tsx diff --git a/examples/advanced/grading-papers/components/ui/drawer.tsx b/demos/grading-papers/components/ui/drawer.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/drawer.tsx rename to demos/grading-papers/components/ui/drawer.tsx diff --git a/examples/advanced/grading-papers/components/ui/dropdown-menu.tsx b/demos/grading-papers/components/ui/dropdown-menu.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/dropdown-menu.tsx rename to demos/grading-papers/components/ui/dropdown-menu.tsx diff --git a/examples/advanced/grading-papers/components/ui/form.tsx b/demos/grading-papers/components/ui/form.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/form.tsx rename to demos/grading-papers/components/ui/form.tsx diff --git a/examples/advanced/grading-papers/components/ui/hover-card.tsx b/demos/grading-papers/components/ui/hover-card.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/hover-card.tsx rename to demos/grading-papers/components/ui/hover-card.tsx diff --git a/examples/advanced/grading-papers/components/ui/input-otp.tsx b/demos/grading-papers/components/ui/input-otp.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/input-otp.tsx rename to demos/grading-papers/components/ui/input-otp.tsx diff --git a/examples/advanced/grading-papers/components/ui/input.tsx b/demos/grading-papers/components/ui/input.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/input.tsx rename to demos/grading-papers/components/ui/input.tsx diff --git a/examples/advanced/grading-papers/components/ui/label.tsx b/demos/grading-papers/components/ui/label.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/label.tsx rename to demos/grading-papers/components/ui/label.tsx diff --git a/examples/advanced/grading-papers/components/ui/menubar.tsx b/demos/grading-papers/components/ui/menubar.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/menubar.tsx rename to demos/grading-papers/components/ui/menubar.tsx diff --git a/examples/advanced/grading-papers/components/ui/navigation-menu.tsx b/demos/grading-papers/components/ui/navigation-menu.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/navigation-menu.tsx rename to demos/grading-papers/components/ui/navigation-menu.tsx diff --git a/examples/advanced/grading-papers/components/ui/pagination.tsx b/demos/grading-papers/components/ui/pagination.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/pagination.tsx rename to demos/grading-papers/components/ui/pagination.tsx diff --git a/examples/advanced/grading-papers/components/ui/popover.tsx b/demos/grading-papers/components/ui/popover.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/popover.tsx rename to demos/grading-papers/components/ui/popover.tsx diff --git a/examples/advanced/grading-papers/components/ui/progress.tsx b/demos/grading-papers/components/ui/progress.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/progress.tsx rename to demos/grading-papers/components/ui/progress.tsx diff --git a/examples/advanced/grading-papers/components/ui/radio-group.tsx b/demos/grading-papers/components/ui/radio-group.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/radio-group.tsx rename to demos/grading-papers/components/ui/radio-group.tsx diff --git a/examples/advanced/grading-papers/components/ui/resizable.tsx b/demos/grading-papers/components/ui/resizable.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/resizable.tsx rename to demos/grading-papers/components/ui/resizable.tsx diff --git a/examples/advanced/grading-papers/components/ui/scroll-area.tsx b/demos/grading-papers/components/ui/scroll-area.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/scroll-area.tsx rename to demos/grading-papers/components/ui/scroll-area.tsx diff --git a/examples/advanced/grading-papers/components/ui/select.tsx b/demos/grading-papers/components/ui/select.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/select.tsx rename to demos/grading-papers/components/ui/select.tsx diff --git a/examples/advanced/grading-papers/components/ui/separator.tsx b/demos/grading-papers/components/ui/separator.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/separator.tsx rename to demos/grading-papers/components/ui/separator.tsx diff --git a/examples/advanced/grading-papers/components/ui/sheet.tsx b/demos/grading-papers/components/ui/sheet.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/sheet.tsx rename to demos/grading-papers/components/ui/sheet.tsx diff --git a/examples/advanced/grading-papers/components/ui/sidebar.tsx b/demos/grading-papers/components/ui/sidebar.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/sidebar.tsx rename to demos/grading-papers/components/ui/sidebar.tsx diff --git a/examples/advanced/grading-papers/components/ui/skeleton.tsx b/demos/grading-papers/components/ui/skeleton.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/skeleton.tsx rename to demos/grading-papers/components/ui/skeleton.tsx diff --git a/examples/advanced/grading-papers/components/ui/slider.tsx b/demos/grading-papers/components/ui/slider.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/slider.tsx rename to demos/grading-papers/components/ui/slider.tsx diff --git a/examples/advanced/grading-papers/components/ui/sonner.tsx b/demos/grading-papers/components/ui/sonner.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/sonner.tsx rename to demos/grading-papers/components/ui/sonner.tsx diff --git a/examples/advanced/grading-papers/components/ui/switch.tsx b/demos/grading-papers/components/ui/switch.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/switch.tsx rename to demos/grading-papers/components/ui/switch.tsx diff --git a/examples/advanced/grading-papers/components/ui/table.tsx b/demos/grading-papers/components/ui/table.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/table.tsx rename to demos/grading-papers/components/ui/table.tsx diff --git a/examples/advanced/grading-papers/components/ui/tabs.tsx b/demos/grading-papers/components/ui/tabs.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/tabs.tsx rename to demos/grading-papers/components/ui/tabs.tsx diff --git a/examples/advanced/grading-papers/components/ui/textarea.tsx b/demos/grading-papers/components/ui/textarea.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/textarea.tsx rename to demos/grading-papers/components/ui/textarea.tsx diff --git a/examples/advanced/grading-papers/components/ui/toast.tsx b/demos/grading-papers/components/ui/toast.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/toast.tsx rename to demos/grading-papers/components/ui/toast.tsx diff --git a/examples/advanced/grading-papers/components/ui/toaster.tsx b/demos/grading-papers/components/ui/toaster.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/toaster.tsx rename to demos/grading-papers/components/ui/toaster.tsx diff --git a/examples/advanced/grading-papers/components/ui/toggle-group.tsx b/demos/grading-papers/components/ui/toggle-group.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/toggle-group.tsx rename to demos/grading-papers/components/ui/toggle-group.tsx diff --git a/examples/advanced/grading-papers/components/ui/toggle.tsx b/demos/grading-papers/components/ui/toggle.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/toggle.tsx rename to demos/grading-papers/components/ui/toggle.tsx diff --git a/examples/advanced/grading-papers/components/ui/tooltip.tsx b/demos/grading-papers/components/ui/tooltip.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/tooltip.tsx rename to demos/grading-papers/components/ui/tooltip.tsx diff --git a/examples/advanced/grading-papers/components/ui/use-mobile.tsx b/demos/grading-papers/components/ui/use-mobile.tsx similarity index 100% rename from examples/advanced/grading-papers/components/ui/use-mobile.tsx rename to demos/grading-papers/components/ui/use-mobile.tsx diff --git a/examples/advanced/grading-papers/components/ui/use-toast.ts b/demos/grading-papers/components/ui/use-toast.ts similarity index 100% rename from examples/advanced/grading-papers/components/ui/use-toast.ts rename to demos/grading-papers/components/ui/use-toast.ts diff --git a/examples/advanced/grading-papers/demo-config.json b/demos/grading-papers/demo-config.json similarity index 100% rename from examples/advanced/grading-papers/demo-config.json rename to demos/grading-papers/demo-config.json diff --git a/examples/advanced/grading-papers/demo-thumbnail.png b/demos/grading-papers/demo-thumbnail.png similarity index 100% rename from examples/advanced/grading-papers/demo-thumbnail.png rename to demos/grading-papers/demo-thumbnail.png diff --git a/examples/advanced/grading-papers/hooks/use-mobile.tsx b/demos/grading-papers/hooks/use-mobile.tsx similarity index 100% rename from examples/advanced/grading-papers/hooks/use-mobile.tsx rename to demos/grading-papers/hooks/use-mobile.tsx diff --git a/examples/advanced/grading-papers/hooks/use-toast.ts b/demos/grading-papers/hooks/use-toast.ts similarity index 100% rename from examples/advanced/grading-papers/hooks/use-toast.ts rename to demos/grading-papers/hooks/use-toast.ts diff --git a/examples/advanced/grading-papers/lib/utils.ts b/demos/grading-papers/lib/utils.ts similarity index 100% rename from examples/advanced/grading-papers/lib/utils.ts rename to demos/grading-papers/lib/utils.ts diff --git a/examples/advanced/grading-papers/next.config.mjs b/demos/grading-papers/next.config.mjs similarity index 100% rename from examples/advanced/grading-papers/next.config.mjs rename to demos/grading-papers/next.config.mjs diff --git a/examples/advanced/grading-papers/package.json b/demos/grading-papers/package.json similarity index 98% rename from examples/advanced/grading-papers/package.json rename to demos/grading-papers/package.json index e97de1cb37..ac0fb6781d 100644 --- a/examples/advanced/grading-papers/package.json +++ b/demos/grading-papers/package.json @@ -9,7 +9,7 @@ "lint": "next lint" }, "dependencies": { - "superdoc": "0.20.0-next.13", + "superdoc": "latest", "@hocuspocus/provider": "latest", "@hookform/resolvers": "^3.9.1", "@radix-ui/react-accordion": "1.2.2", diff --git a/examples/advanced/grading-papers/pnpm-lock.yaml b/demos/grading-papers/pnpm-lock.yaml similarity index 100% rename from examples/advanced/grading-papers/pnpm-lock.yaml rename to demos/grading-papers/pnpm-lock.yaml diff --git a/examples/advanced/grading-papers/postcss.config.mjs b/demos/grading-papers/postcss.config.mjs similarity index 100% rename from examples/advanced/grading-papers/postcss.config.mjs rename to demos/grading-papers/postcss.config.mjs diff --git a/examples/advanced/grading-papers/public/assign-1.docx b/demos/grading-papers/public/assign-1.docx similarity index 100% rename from examples/advanced/grading-papers/public/assign-1.docx rename to demos/grading-papers/public/assign-1.docx diff --git a/examples/advanced/grading-papers/public/assign-2.docx b/demos/grading-papers/public/assign-2.docx similarity index 100% rename from examples/advanced/grading-papers/public/assign-2.docx rename to demos/grading-papers/public/assign-2.docx diff --git a/examples/advanced/grading-papers/public/assign-3.docx b/demos/grading-papers/public/assign-3.docx similarity index 100% rename from examples/advanced/grading-papers/public/assign-3.docx rename to demos/grading-papers/public/assign-3.docx diff --git a/examples/advanced/grading-papers/public/assign-4.docx b/demos/grading-papers/public/assign-4.docx similarity index 100% rename from examples/advanced/grading-papers/public/assign-4.docx rename to demos/grading-papers/public/assign-4.docx diff --git a/examples/advanced/grading-papers/public/assign-5.docx b/demos/grading-papers/public/assign-5.docx similarity index 100% rename from examples/advanced/grading-papers/public/assign-5.docx rename to demos/grading-papers/public/assign-5.docx diff --git a/examples/advanced/grading-papers/public/jamie_smith_bio220_research_proposal.docx b/demos/grading-papers/public/jamie_smith_bio220_research_proposal.docx similarity index 100% rename from examples/advanced/grading-papers/public/jamie_smith_bio220_research_proposal.docx rename to demos/grading-papers/public/jamie_smith_bio220_research_proposal.docx diff --git a/examples/advanced/grading-papers/public/placeholder-logo.png b/demos/grading-papers/public/placeholder-logo.png similarity index 100% rename from examples/advanced/grading-papers/public/placeholder-logo.png rename to demos/grading-papers/public/placeholder-logo.png diff --git a/examples/advanced/grading-papers/public/placeholder-logo.svg b/demos/grading-papers/public/placeholder-logo.svg similarity index 100% rename from examples/advanced/grading-papers/public/placeholder-logo.svg rename to demos/grading-papers/public/placeholder-logo.svg diff --git a/examples/advanced/grading-papers/public/placeholder-user.jpg b/demos/grading-papers/public/placeholder-user.jpg similarity index 100% rename from examples/advanced/grading-papers/public/placeholder-user.jpg rename to demos/grading-papers/public/placeholder-user.jpg diff --git a/examples/advanced/grading-papers/public/placeholder.jpg b/demos/grading-papers/public/placeholder.jpg similarity index 100% rename from examples/advanced/grading-papers/public/placeholder.jpg rename to demos/grading-papers/public/placeholder.jpg diff --git a/examples/advanced/grading-papers/public/placeholder.svg b/demos/grading-papers/public/placeholder.svg similarity index 100% rename from examples/advanced/grading-papers/public/placeholder.svg rename to demos/grading-papers/public/placeholder.svg diff --git a/examples/advanced/grading-papers/public/~$alex.docx b/demos/grading-papers/public/~$alex.docx similarity index 100% rename from examples/advanced/grading-papers/public/~$alex.docx rename to demos/grading-papers/public/~$alex.docx diff --git a/examples/advanced/grading-papers/styles/globals.css b/demos/grading-papers/styles/globals.css similarity index 100% rename from examples/advanced/grading-papers/styles/globals.css rename to demos/grading-papers/styles/globals.css diff --git a/examples/advanced/grading-papers/tailwind.config.ts b/demos/grading-papers/tailwind.config.ts similarity index 100% rename from examples/advanced/grading-papers/tailwind.config.ts rename to demos/grading-papers/tailwind.config.ts diff --git a/examples/advanced/grading-papers/tsconfig.json b/demos/grading-papers/tsconfig.json similarity index 100% rename from examples/advanced/grading-papers/tsconfig.json rename to demos/grading-papers/tsconfig.json diff --git a/examples/collaboration/basic/.gitignore b/demos/html-editor/.gitignore similarity index 100% rename from examples/collaboration/basic/.gitignore rename to demos/html-editor/.gitignore diff --git a/examples/advanced/html-editor/.vscode/extensions.json b/demos/html-editor/.vscode/extensions.json similarity index 100% rename from examples/advanced/html-editor/.vscode/extensions.json rename to demos/html-editor/.vscode/extensions.json diff --git a/demos/html-editor/README.md b/demos/html-editor/README.md new file mode 100644 index 0000000000..61e988975c --- /dev/null +++ b/demos/html-editor/README.md @@ -0,0 +1,5 @@ +# SuperDoc: Rich-Text HTML Editor + +An example of initializing the SuperEditor class directly to create a rich-text editor for HTML editing with Vue. + +> Using the Editor class directly means you must initialize the toolbar manually. diff --git a/examples/advanced/html-editor/demo-config.json b/demos/html-editor/demo-config.json similarity index 100% rename from examples/advanced/html-editor/demo-config.json rename to demos/html-editor/demo-config.json diff --git a/examples/advanced/html-editor/demo-thumbnail.png b/demos/html-editor/demo-thumbnail.png similarity index 100% rename from examples/advanced/html-editor/demo-thumbnail.png rename to demos/html-editor/demo-thumbnail.png diff --git a/examples/advanced/html-editor/demo-video.mp4 b/demos/html-editor/demo-video.mp4 similarity index 100% rename from examples/advanced/html-editor/demo-video.mp4 rename to demos/html-editor/demo-video.mp4 diff --git a/examples/advanced/html-editor/index.html b/demos/html-editor/index.html similarity index 100% rename from examples/advanced/html-editor/index.html rename to demos/html-editor/index.html diff --git a/demos/html-editor/package.json b/demos/html-editor/package.json new file mode 100644 index 0000000000..2591a50bea --- /dev/null +++ b/demos/html-editor/package.json @@ -0,0 +1,19 @@ +{ + "name": "vue-html-editor", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "superdoc": "latest", + "vue": "^3.5.13" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.2", + "vite": "^6.3.1" + } +} diff --git a/examples/customization/custom-mark/public/superdoc-logo.png b/demos/html-editor/public/superdoc-logo.png similarity index 100% rename from examples/customization/custom-mark/public/superdoc-logo.png rename to demos/html-editor/public/superdoc-logo.png diff --git a/examples/advanced/html-editor/src/App.vue b/demos/html-editor/src/App.vue similarity index 100% rename from examples/advanced/html-editor/src/App.vue rename to demos/html-editor/src/App.vue diff --git a/examples/collaboration/basic/src/main.js b/demos/html-editor/src/main.js similarity index 100% rename from examples/collaboration/basic/src/main.js rename to demos/html-editor/src/main.js diff --git a/examples/advanced/html-editor/src/style.css b/demos/html-editor/src/style.css similarity index 100% rename from examples/advanced/html-editor/src/style.css rename to demos/html-editor/src/style.css diff --git a/examples/collaboration/basic/vite.config.js b/demos/html-editor/vite.config.js similarity index 100% rename from examples/collaboration/basic/vite.config.js rename to demos/html-editor/vite.config.js diff --git a/examples/collaboration/production/client/.gitignore b/demos/linked-sections/.gitignore similarity index 100% rename from examples/collaboration/production/client/.gitignore rename to demos/linked-sections/.gitignore diff --git a/demos/linked-sections/README.md b/demos/linked-sections/README.md new file mode 100644 index 0000000000..d23a0a2ab5 --- /dev/null +++ b/demos/linked-sections/README.md @@ -0,0 +1,3 @@ +# SuperDoc: Linked Sections + +An example of using the editor section nodes to create linked document sections. diff --git a/examples/advanced/linked-sections/demo-config.json b/demos/linked-sections/demo-config.json similarity index 100% rename from examples/advanced/linked-sections/demo-config.json rename to demos/linked-sections/demo-config.json diff --git a/examples/advanced/linked-sections/demo-thumbnail.png b/demos/linked-sections/demo-thumbnail.png similarity index 100% rename from examples/advanced/linked-sections/demo-thumbnail.png rename to demos/linked-sections/demo-thumbnail.png diff --git a/examples/advanced/linked-sections/demo-video.mp4 b/demos/linked-sections/demo-video.mp4 similarity index 100% rename from examples/advanced/linked-sections/demo-video.mp4 rename to demos/linked-sections/demo-video.mp4 diff --git a/examples/collaboration/basic/index.html b/demos/linked-sections/index.html similarity index 100% rename from examples/collaboration/basic/index.html rename to demos/linked-sections/index.html diff --git a/examples/advanced/linked-sections/package.json b/demos/linked-sections/package.json similarity index 90% rename from examples/advanced/linked-sections/package.json rename to demos/linked-sections/package.json index b0349da1c4..1e67ae7484 100644 --- a/examples/advanced/linked-sections/package.json +++ b/demos/linked-sections/package.json @@ -9,7 +9,7 @@ "preview": "vite preview" }, "dependencies": { - "superdoc": "0.20.0-next.13", + "superdoc": "latest", "vue": "^3.5.13" }, "devDependencies": { diff --git a/examples/collaboration/superdoc/public/logo.webp b/demos/linked-sections/public/logo.webp similarity index 100% rename from examples/collaboration/superdoc/public/logo.webp rename to demos/linked-sections/public/logo.webp diff --git a/examples/advanced/linked-sections/src/App.vue b/demos/linked-sections/src/App.vue similarity index 100% rename from examples/advanced/linked-sections/src/App.vue rename to demos/linked-sections/src/App.vue diff --git a/examples/collaboration/production/client/src/UploadFile.vue b/demos/linked-sections/src/UploadFile.vue similarity index 100% rename from examples/collaboration/production/client/src/UploadFile.vue rename to demos/linked-sections/src/UploadFile.vue diff --git a/examples/collaboration/superdoc/src/main.js b/demos/linked-sections/src/main.js similarity index 100% rename from examples/collaboration/superdoc/src/main.js rename to demos/linked-sections/src/main.js diff --git a/examples/customization/custom-node/src/style.css b/demos/linked-sections/src/style.css similarity index 100% rename from examples/customization/custom-node/src/style.css rename to demos/linked-sections/src/style.css diff --git a/examples/collaboration/superdoc/vite.config.js b/demos/linked-sections/vite.config.js similarity index 100% rename from examples/collaboration/superdoc/vite.config.js rename to demos/linked-sections/vite.config.js diff --git a/examples/advanced/loading-from-json/demo-config.json b/demos/loading-from-json/demo-config.json similarity index 100% rename from examples/advanced/loading-from-json/demo-config.json rename to demos/loading-from-json/demo-config.json diff --git a/examples/advanced/loading-from-json/demo-thumbnail.png b/demos/loading-from-json/demo-thumbnail.png similarity index 100% rename from examples/advanced/loading-from-json/demo-thumbnail.png rename to demos/loading-from-json/demo-thumbnail.png diff --git a/examples/advanced/loading-from-json/demo-video.mp4 b/demos/loading-from-json/demo-video.mp4 similarity index 100% rename from examples/advanced/loading-from-json/demo-video.mp4 rename to demos/loading-from-json/demo-video.mp4 diff --git a/examples/advanced/loading-from-json/index.html b/demos/loading-from-json/index.html similarity index 100% rename from examples/advanced/loading-from-json/index.html rename to demos/loading-from-json/index.html diff --git a/examples/advanced/loading-from-json/package.json b/demos/loading-from-json/package.json similarity index 86% rename from examples/advanced/loading-from-json/package.json rename to demos/loading-from-json/package.json index 0049f92216..1c17c8ec6e 100644 --- a/examples/advanced/loading-from-json/package.json +++ b/demos/loading-from-json/package.json @@ -7,7 +7,7 @@ "dev": "vite" }, "dependencies": { - "superdoc": "0.20.0-next.13" + "superdoc": "latest" }, "devDependencies": { "vite": "^4.4.6" diff --git a/examples/advanced/loading-from-json/src/main.js b/demos/loading-from-json/src/main.js similarity index 100% rename from examples/advanced/loading-from-json/src/main.js rename to demos/loading-from-json/src/main.js diff --git a/examples/advanced/loading-from-json/src/style.css b/demos/loading-from-json/src/style.css similarity index 100% rename from examples/advanced/loading-from-json/src/style.css rename to demos/loading-from-json/src/style.css diff --git a/examples/advanced/loading-from-json/vite.config.js b/demos/loading-from-json/vite.config.js similarity index 100% rename from examples/advanced/loading-from-json/vite.config.js rename to demos/loading-from-json/vite.config.js diff --git a/examples/integrations/nextjs-ssr/.gitignore b/demos/nextjs-ssr/.gitignore similarity index 100% rename from examples/integrations/nextjs-ssr/.gitignore rename to demos/nextjs-ssr/.gitignore diff --git a/examples/integrations/nextjs-ssr/README.md b/demos/nextjs-ssr/README.md similarity index 100% rename from examples/integrations/nextjs-ssr/README.md rename to demos/nextjs-ssr/README.md diff --git a/examples/integrations/nextjs-ssr/demo-config.json b/demos/nextjs-ssr/demo-config.json similarity index 100% rename from examples/integrations/nextjs-ssr/demo-config.json rename to demos/nextjs-ssr/demo-config.json diff --git a/examples/integrations/nextjs-ssr/demo-thumbnail.png b/demos/nextjs-ssr/demo-thumbnail.png similarity index 100% rename from examples/integrations/nextjs-ssr/demo-thumbnail.png rename to demos/nextjs-ssr/demo-thumbnail.png diff --git a/examples/integrations/nextjs-ssr/demo-video.mp4 b/demos/nextjs-ssr/demo-video.mp4 similarity index 100% rename from examples/integrations/nextjs-ssr/demo-video.mp4 rename to demos/nextjs-ssr/demo-video.mp4 diff --git a/examples/integrations/nextjs-ssr/jsconfig.json b/demos/nextjs-ssr/jsconfig.json similarity index 100% rename from examples/integrations/nextjs-ssr/jsconfig.json rename to demos/nextjs-ssr/jsconfig.json diff --git a/examples/integrations/nextjs-ssr/next.config.mjs b/demos/nextjs-ssr/next.config.mjs similarity index 100% rename from examples/integrations/nextjs-ssr/next.config.mjs rename to demos/nextjs-ssr/next.config.mjs diff --git a/examples/integrations/nextjs-ssr/package.json b/demos/nextjs-ssr/package.json similarity index 89% rename from examples/integrations/nextjs-ssr/package.json rename to demos/nextjs-ssr/package.json index fd9cd083f9..bc007a9d68 100644 --- a/examples/integrations/nextjs-ssr/package.json +++ b/demos/nextjs-ssr/package.json @@ -9,7 +9,7 @@ "lint": "next lint" }, "dependencies": { - "superdoc": "0.20.0-next.13", + "superdoc": "latest", "next": "15.3.3", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/examples/integrations/nextjs-ssr/public/file.svg b/demos/nextjs-ssr/public/file.svg similarity index 100% rename from examples/integrations/nextjs-ssr/public/file.svg rename to demos/nextjs-ssr/public/file.svg diff --git a/examples/integrations/nextjs-ssr/public/globe.svg b/demos/nextjs-ssr/public/globe.svg similarity index 100% rename from examples/integrations/nextjs-ssr/public/globe.svg rename to demos/nextjs-ssr/public/globe.svg diff --git a/examples/integrations/nextjs-ssr/public/next.svg b/demos/nextjs-ssr/public/next.svg similarity index 100% rename from examples/integrations/nextjs-ssr/public/next.svg rename to demos/nextjs-ssr/public/next.svg diff --git a/examples/customization/custom-node/public/sample-document.docx b/demos/nextjs-ssr/public/sample-document.docx similarity index 100% rename from examples/customization/custom-node/public/sample-document.docx rename to demos/nextjs-ssr/public/sample-document.docx diff --git a/examples/advanced/text-selection/public/sample.docx b/demos/nextjs-ssr/public/sample.docx similarity index 100% rename from examples/advanced/text-selection/public/sample.docx rename to demos/nextjs-ssr/public/sample.docx diff --git a/examples/integrations/nextjs-ssr/public/vercel.svg b/demos/nextjs-ssr/public/vercel.svg similarity index 100% rename from examples/integrations/nextjs-ssr/public/vercel.svg rename to demos/nextjs-ssr/public/vercel.svg diff --git a/examples/integrations/nextjs-ssr/public/window.svg b/demos/nextjs-ssr/public/window.svg similarity index 100% rename from examples/integrations/nextjs-ssr/public/window.svg rename to demos/nextjs-ssr/public/window.svg diff --git a/examples/integrations/nextjs-ssr/src/app/SuperDoc/superdoc.css b/demos/nextjs-ssr/src/app/SuperDoc/superdoc.css similarity index 100% rename from examples/integrations/nextjs-ssr/src/app/SuperDoc/superdoc.css rename to demos/nextjs-ssr/src/app/SuperDoc/superdoc.css diff --git a/examples/integrations/nextjs-ssr/src/app/SuperDoc/superdoc.js b/demos/nextjs-ssr/src/app/SuperDoc/superdoc.js similarity index 100% rename from examples/integrations/nextjs-ssr/src/app/SuperDoc/superdoc.js rename to demos/nextjs-ssr/src/app/SuperDoc/superdoc.js diff --git a/examples/integrations/nextjs-ssr/src/app/favicon.ico b/demos/nextjs-ssr/src/app/favicon.ico similarity index 100% rename from examples/integrations/nextjs-ssr/src/app/favicon.ico rename to demos/nextjs-ssr/src/app/favicon.ico diff --git a/examples/integrations/nextjs-ssr/src/app/globals.css b/demos/nextjs-ssr/src/app/globals.css similarity index 100% rename from examples/integrations/nextjs-ssr/src/app/globals.css rename to demos/nextjs-ssr/src/app/globals.css diff --git a/examples/integrations/nextjs-ssr/src/app/layout.js b/demos/nextjs-ssr/src/app/layout.js similarity index 100% rename from examples/integrations/nextjs-ssr/src/app/layout.js rename to demos/nextjs-ssr/src/app/layout.js diff --git a/examples/integrations/nextjs-ssr/src/app/page.js b/demos/nextjs-ssr/src/app/page.js similarity index 100% rename from examples/integrations/nextjs-ssr/src/app/page.js rename to demos/nextjs-ssr/src/app/page.js diff --git a/examples/integrations/nextjs-ssr/src/app/page.module.css b/demos/nextjs-ssr/src/app/page.module.css similarity index 100% rename from examples/integrations/nextjs-ssr/src/app/page.module.css rename to demos/nextjs-ssr/src/app/page.module.css diff --git a/examples/integrations/nodejs/.gitignore b/demos/nodejs/.gitignore similarity index 100% rename from examples/integrations/nodejs/.gitignore rename to demos/nodejs/.gitignore diff --git a/demos/nodejs/README.md b/demos/nodejs/README.md new file mode 100644 index 0000000000..ae58d7eacc --- /dev/null +++ b/demos/nodejs/README.md @@ -0,0 +1,30 @@ +# SuperDoc: Node.js Example + +A headless Node.js example using SuperDoc's Editor class with Express. + +> Requires Node >= 20. Earlier versions are missing the `File` object. If you must use Node < 20, see the file polyfill in this example. + +## Quick start + +```bash +npm install && npm run dev +``` + +Runs an Express server at `http://localhost:3000` with a single root endpoint that returns a `.docx` file. + +## Usage + +``` +# Returns the unchanged .docx template +http://localhost:3000 + +# Insert text +http://localhost:3000?text=hello world! + +# Insert HTML +http://localhost:3000?html=

I am a paragraph

I AM BOLD!

+``` + +## Additional docs + +See the [SuperDoc docs](https://docs.superdoc.dev/core/supereditor/methods) for all available editor commands and hooks. diff --git a/examples/integrations/nodejs/demo-config.json b/demos/nodejs/demo-config.json similarity index 100% rename from examples/integrations/nodejs/demo-config.json rename to demos/nodejs/demo-config.json diff --git a/examples/integrations/nodejs/demo-thumbnail.png b/demos/nodejs/demo-thumbnail.png similarity index 100% rename from examples/integrations/nodejs/demo-thumbnail.png rename to demos/nodejs/demo-thumbnail.png diff --git a/examples/integrations/nodejs/demo-video.mp4 b/demos/nodejs/demo-video.mp4 similarity index 100% rename from examples/integrations/nodejs/demo-video.mp4 rename to demos/nodejs/demo-video.mp4 diff --git a/examples/integrations/nodejs/document-b64.js b/demos/nodejs/document-b64.js similarity index 100% rename from examples/integrations/nodejs/document-b64.js rename to demos/nodejs/document-b64.js diff --git a/examples/integrations/nodejs/document.js b/demos/nodejs/document.js similarity index 100% rename from examples/integrations/nodejs/document.js rename to demos/nodejs/document.js diff --git a/examples/integrations/nodejs/file-polyfill.js b/demos/nodejs/file-polyfill.js similarity index 100% rename from examples/integrations/nodejs/file-polyfill.js rename to demos/nodejs/file-polyfill.js diff --git a/examples/integrations/nodejs/package.json b/demos/nodejs/package.json similarity index 90% rename from examples/integrations/nodejs/package.json rename to demos/nodejs/package.json index 062af3ba9e..303743bf8b 100644 --- a/examples/integrations/nodejs/package.json +++ b/demos/nodejs/package.json @@ -12,7 +12,7 @@ }, "description": "", "dependencies": { - "superdoc": "0.20.0-next.13", + "superdoc": "latest", "express": "^4.21.2" }, "devDependencies": { diff --git a/examples/integrations/nodejs/sample-document.docx b/demos/nodejs/sample-document.docx similarity index 100% rename from examples/integrations/nodejs/sample-document.docx rename to demos/nodejs/sample-document.docx diff --git a/examples/integrations/nodejs/server.js b/demos/nodejs/server.js similarity index 100% rename from examples/integrations/nodejs/server.js rename to demos/nodejs/server.js diff --git a/examples/getting-started/react/demo-config.json b/demos/react/demo-config.json similarity index 100% rename from examples/getting-started/react/demo-config.json rename to demos/react/demo-config.json diff --git a/examples/advanced/replace-content/demo-thumbnail.png b/demos/react/demo-thumbnail.png similarity index 100% rename from examples/advanced/replace-content/demo-thumbnail.png rename to demos/react/demo-thumbnail.png diff --git a/examples/getting-started/react/demo-video.mp4 b/demos/react/demo-video.mp4 similarity index 100% rename from examples/getting-started/react/demo-video.mp4 rename to demos/react/demo-video.mp4 diff --git a/examples/advanced/replace-content/index.html b/demos/react/index.html similarity index 100% rename from examples/advanced/replace-content/index.html rename to demos/react/index.html diff --git a/demos/react/package.json b/demos/react/package.json new file mode 100644 index 0000000000..4d1fa19235 --- /dev/null +++ b/demos/react/package.json @@ -0,0 +1,19 @@ +{ + "name": "react-superdoc-example", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "styled-jsx": "^5.1.7", + "superdoc": "latest" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.0.4", + "vite": "^6.2.0" + } +} diff --git a/examples/getting-started/cdn/sample.docx b/demos/react/public/sample.docx similarity index 100% rename from examples/getting-started/cdn/sample.docx rename to demos/react/public/sample.docx diff --git a/examples/getting-started/react/src/App.jsx b/demos/react/src/App.jsx similarity index 100% rename from examples/getting-started/react/src/App.jsx rename to demos/react/src/App.jsx diff --git a/examples/advanced/text-selection/src/components/DocumentEditor.jsx b/demos/react/src/components/DocumentEditor.jsx similarity index 100% rename from examples/advanced/text-selection/src/components/DocumentEditor.jsx rename to demos/react/src/components/DocumentEditor.jsx diff --git a/examples/getting-started/react/src/main.jsx b/demos/react/src/main.jsx similarity index 100% rename from examples/getting-started/react/src/main.jsx rename to demos/react/src/main.jsx diff --git a/examples/advanced/text-selection/vite.config.js b/demos/react/vite.config.js similarity index 100% rename from examples/advanced/text-selection/vite.config.js rename to demos/react/vite.config.js diff --git a/examples/advanced/replace-content/README.md b/demos/replace-content/README.md similarity index 100% rename from examples/advanced/replace-content/README.md rename to demos/replace-content/README.md diff --git a/examples/advanced/replace-content/demo-config.json b/demos/replace-content/demo-config.json similarity index 100% rename from examples/advanced/replace-content/demo-config.json rename to demos/replace-content/demo-config.json diff --git a/examples/advanced/text-selection/demo-thumbnail.png b/demos/replace-content/demo-thumbnail.png similarity index 100% rename from examples/advanced/text-selection/demo-thumbnail.png rename to demos/replace-content/demo-thumbnail.png diff --git a/examples/advanced/replace-content/demo-video.mp4 b/demos/replace-content/demo-video.mp4 similarity index 100% rename from examples/advanced/replace-content/demo-video.mp4 rename to demos/replace-content/demo-video.mp4 diff --git a/examples/advanced/text-selection/index.html b/demos/replace-content/index.html similarity index 100% rename from examples/advanced/text-selection/index.html rename to demos/replace-content/index.html diff --git a/examples/advanced/replace-content/package.json b/demos/replace-content/package.json similarity index 90% rename from examples/advanced/replace-content/package.json rename to demos/replace-content/package.json index 0511016810..0c06583abb 100644 --- a/examples/advanced/replace-content/package.json +++ b/demos/replace-content/package.json @@ -7,7 +7,7 @@ "dev": "vite" }, "dependencies": { - "superdoc": "0.20.0-next.13", + "superdoc": "latest", "highlight.js": "^11.11.1", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/examples/getting-started/react/public/sample.docx b/demos/replace-content/public/sample.docx similarity index 100% rename from examples/getting-started/react/public/sample.docx rename to demos/replace-content/public/sample.docx diff --git a/examples/advanced/replace-content/src/App.jsx b/demos/replace-content/src/App.jsx similarity index 100% rename from examples/advanced/replace-content/src/App.jsx rename to demos/replace-content/src/App.jsx diff --git a/examples/advanced/replace-content/src/components/DocumentEditor.jsx b/demos/replace-content/src/components/DocumentEditor.jsx similarity index 100% rename from examples/advanced/replace-content/src/components/DocumentEditor.jsx rename to demos/replace-content/src/components/DocumentEditor.jsx diff --git a/examples/advanced/replace-content/src/main.jsx b/demos/replace-content/src/main.jsx similarity index 100% rename from examples/advanced/replace-content/src/main.jsx rename to demos/replace-content/src/main.jsx diff --git a/examples/advanced/replace-content/vite.config.js b/demos/replace-content/vite.config.js similarity index 100% rename from examples/advanced/replace-content/vite.config.js rename to demos/replace-content/vite.config.js diff --git a/examples/integrations/slack-redlining/README.md b/demos/slack-redlining/README.md similarity index 100% rename from examples/integrations/slack-redlining/README.md rename to demos/slack-redlining/README.md diff --git a/examples/integrations/slack-redlining/cloud-function/package.json b/demos/slack-redlining/cloud-function/package.json similarity index 93% rename from examples/integrations/slack-redlining/cloud-function/package.json rename to demos/slack-redlining/cloud-function/package.json index fd6b6b759a..c3fee8aa17 100644 --- a/examples/integrations/slack-redlining/cloud-function/package.json +++ b/demos/slack-redlining/cloud-function/package.json @@ -13,7 +13,7 @@ "description": "", "dependencies": { "@google-cloud/storage": "^7.16.0", - "superdoc": "0.20.0-next.13", + "superdoc": "latest", "busboy": "0.3.0", "dotenv": "^16.5.0", "express": "^4.21.2", diff --git a/examples/integrations/slack-redlining/cloud-function/server.js b/demos/slack-redlining/cloud-function/server.js similarity index 100% rename from examples/integrations/slack-redlining/cloud-function/server.js rename to demos/slack-redlining/cloud-function/server.js diff --git a/examples/integrations/slack-redlining/cloud-function/utils.js b/demos/slack-redlining/cloud-function/utils.js similarity index 100% rename from examples/integrations/slack-redlining/cloud-function/utils.js rename to demos/slack-redlining/cloud-function/utils.js diff --git a/examples/integrations/slack-redlining/demo-config.json b/demos/slack-redlining/demo-config.json similarity index 100% rename from examples/integrations/slack-redlining/demo-config.json rename to demos/slack-redlining/demo-config.json diff --git a/examples/integrations/slack-redlining/demo-thumbnail.png b/demos/slack-redlining/demo-thumbnail.png similarity index 100% rename from examples/integrations/slack-redlining/demo-thumbnail.png rename to demos/slack-redlining/demo-thumbnail.png diff --git a/examples/integrations/slack-redlining/demo-video.mp4 b/demos/slack-redlining/demo-video.mp4 similarity index 100% rename from examples/integrations/slack-redlining/demo-video.mp4 rename to demos/slack-redlining/demo-video.mp4 diff --git a/examples/integrations/slack-redlining/screenshot.png b/demos/slack-redlining/screenshot.png similarity index 100% rename from examples/integrations/slack-redlining/screenshot.png rename to demos/slack-redlining/screenshot.png diff --git a/examples/integrations/slack-redlining/zap.json b/demos/slack-redlining/zap.json similarity index 100% rename from examples/integrations/slack-redlining/zap.json rename to demos/slack-redlining/zap.json diff --git a/demos/text-selection/README.md b/demos/text-selection/README.md new file mode 100644 index 0000000000..f99f7f993d --- /dev/null +++ b/demos/text-selection/README.md @@ -0,0 +1,6 @@ +# SuperDoc - Programmatic Text Selection Example + +This React-based example shows how SuperDoc can select text in a document relative to the cursor's position. + +- [Based on character count](https://github.com/superdoc-dev/superdoc/blob/main/demos/text-selection/src/App.jsx) +- [Or, just grab the whole line](https://github.com/superdoc-dev/superdoc/blob/main/demos/text-selection/src/App.jsx) diff --git a/examples/advanced/text-selection/demo-config.json b/demos/text-selection/demo-config.json similarity index 100% rename from examples/advanced/text-selection/demo-config.json rename to demos/text-selection/demo-config.json diff --git a/examples/getting-started/react/demo-thumbnail.png b/demos/text-selection/demo-thumbnail.png similarity index 100% rename from examples/getting-started/react/demo-thumbnail.png rename to demos/text-selection/demo-thumbnail.png diff --git a/examples/advanced/text-selection/demo-video.mp4 b/demos/text-selection/demo-video.mp4 similarity index 100% rename from examples/advanced/text-selection/demo-video.mp4 rename to demos/text-selection/demo-video.mp4 diff --git a/demos/text-selection/index.html b/demos/text-selection/index.html new file mode 100644 index 0000000000..5a4e3e6da2 --- /dev/null +++ b/demos/text-selection/index.html @@ -0,0 +1,12 @@ + + + + + + SuperDoc React Example + + +
+ + + \ No newline at end of file diff --git a/examples/advanced/text-selection/package.json b/demos/text-selection/package.json similarity index 91% rename from examples/advanced/text-selection/package.json rename to demos/text-selection/package.json index 21db4ceebf..0d7947410b 100644 --- a/examples/advanced/text-selection/package.json +++ b/demos/text-selection/package.json @@ -10,7 +10,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "styled-jsx": "^5.1.7", - "superdoc": "0.20.0-next.13" + "superdoc": "latest" }, "devDependencies": { "@vitejs/plugin-react": "^4.0.4", diff --git a/examples/integrations/nextjs-ssr/public/sample.docx b/demos/text-selection/public/sample.docx similarity index 100% rename from examples/integrations/nextjs-ssr/public/sample.docx rename to demos/text-selection/public/sample.docx diff --git a/examples/advanced/text-selection/src/App.jsx b/demos/text-selection/src/App.jsx similarity index 100% rename from examples/advanced/text-selection/src/App.jsx rename to demos/text-selection/src/App.jsx diff --git a/examples/getting-started/react/src/components/DocumentEditor.jsx b/demos/text-selection/src/components/DocumentEditor.jsx similarity index 100% rename from examples/getting-started/react/src/components/DocumentEditor.jsx rename to demos/text-selection/src/components/DocumentEditor.jsx diff --git a/examples/advanced/text-selection/src/main.jsx b/demos/text-selection/src/main.jsx similarity index 100% rename from examples/advanced/text-selection/src/main.jsx rename to demos/text-selection/src/main.jsx diff --git a/examples/getting-started/react/vite.config.js b/demos/text-selection/vite.config.js similarity index 100% rename from examples/getting-started/react/vite.config.js rename to demos/text-selection/vite.config.js diff --git a/demos/toolbar/README.md b/demos/toolbar/README.md new file mode 100644 index 0000000000..17a575a47f --- /dev/null +++ b/demos/toolbar/README.md @@ -0,0 +1,7 @@ +# SuperDoc: Customizing the Toolbar + +An example of how to add a custom button to the SuperDoc toolbar. This custom button inserts a random cat GIF into the document. + +[We define the custom button in the `modules.toolbar.customButtons` option](https://github.com/superdoc-dev/superdoc/blob/main/demos/toolbar/src/main.js) + +The button's action is to insert a custom `catNode`. [The custom node and its Prosemirror click-handler plugin are defined in the same file](https://github.com/superdoc-dev/superdoc/blob/main/demos/toolbar/src/main.js). diff --git a/examples/customization/toolbar/demo-config.json b/demos/toolbar/demo-config.json similarity index 100% rename from examples/customization/toolbar/demo-config.json rename to demos/toolbar/demo-config.json diff --git a/examples/customization/toolbar/demo-thumbnail.png b/demos/toolbar/demo-thumbnail.png similarity index 100% rename from examples/customization/toolbar/demo-thumbnail.png rename to demos/toolbar/demo-thumbnail.png diff --git a/examples/customization/toolbar/demo-video.mp4 b/demos/toolbar/demo-video.mp4 similarity index 100% rename from examples/customization/toolbar/demo-video.mp4 rename to demos/toolbar/demo-video.mp4 diff --git a/examples/customization/toolbar/index.html b/demos/toolbar/index.html similarity index 100% rename from examples/customization/toolbar/index.html rename to demos/toolbar/index.html diff --git a/examples/customization/toolbar/package.json b/demos/toolbar/package.json similarity index 86% rename from examples/customization/toolbar/package.json rename to demos/toolbar/package.json index 31ca71a448..5436efd00b 100644 --- a/examples/customization/toolbar/package.json +++ b/demos/toolbar/package.json @@ -7,7 +7,7 @@ "dev": "vite" }, "dependencies": { - "superdoc": "0.20.0-next.13" + "superdoc": "latest" }, "devDependencies": { "vite": "^4.4.6" diff --git a/examples/customization/toolbar/src/icon_cat.svg b/demos/toolbar/src/icon_cat.svg similarity index 100% rename from examples/customization/toolbar/src/icon_cat.svg rename to demos/toolbar/src/icon_cat.svg diff --git a/examples/customization/toolbar/src/main.js b/demos/toolbar/src/main.js similarity index 100% rename from examples/customization/toolbar/src/main.js rename to demos/toolbar/src/main.js diff --git a/examples/customization/toolbar/src/style.css b/demos/toolbar/src/style.css similarity index 100% rename from examples/customization/toolbar/src/style.css rename to demos/toolbar/src/style.css diff --git a/examples/customization/toolbar/vite.config.js b/demos/toolbar/vite.config.js similarity index 100% rename from examples/customization/toolbar/vite.config.js rename to demos/toolbar/vite.config.js diff --git a/examples/collaboration/superdoc/.gitignore b/demos/typescript/.gitignore similarity index 100% rename from examples/collaboration/superdoc/.gitignore rename to demos/typescript/.gitignore diff --git a/examples/getting-started/typescript/demo-config.json b/demos/typescript/demo-config.json similarity index 100% rename from examples/getting-started/typescript/demo-config.json rename to demos/typescript/demo-config.json diff --git a/examples/getting-started/typescript/demo-thumbnail.png b/demos/typescript/demo-thumbnail.png similarity index 100% rename from examples/getting-started/typescript/demo-thumbnail.png rename to demos/typescript/demo-thumbnail.png diff --git a/examples/getting-started/typescript/demo-video.mp4 b/demos/typescript/demo-video.mp4 similarity index 100% rename from examples/getting-started/typescript/demo-video.mp4 rename to demos/typescript/demo-video.mp4 diff --git a/examples/getting-started/typescript/eslint.config.js b/demos/typescript/eslint.config.js similarity index 100% rename from examples/getting-started/typescript/eslint.config.js rename to demos/typescript/eslint.config.js diff --git a/examples/getting-started/typescript/index.html b/demos/typescript/index.html similarity index 100% rename from examples/getting-started/typescript/index.html rename to demos/typescript/index.html diff --git a/examples/getting-started/typescript/package.json b/demos/typescript/package.json similarity index 95% rename from examples/getting-started/typescript/package.json rename to demos/typescript/package.json index c446024c6b..3f4f22a38a 100644 --- a/examples/getting-started/typescript/package.json +++ b/demos/typescript/package.json @@ -13,7 +13,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "styled-jsx": "^5.1.7", - "superdoc": "0.20.0-next.13" + "superdoc": "latest" }, "devDependencies": { "@eslint/js": "^9.19.0", diff --git a/examples/getting-started/typescript/src/App.tsx b/demos/typescript/src/App.tsx similarity index 100% rename from examples/getting-started/typescript/src/App.tsx rename to demos/typescript/src/App.tsx diff --git a/examples/getting-started/typescript/src/components/DocumentEditor.tsx b/demos/typescript/src/components/DocumentEditor.tsx similarity index 100% rename from examples/getting-started/typescript/src/components/DocumentEditor.tsx rename to demos/typescript/src/components/DocumentEditor.tsx diff --git a/examples/getting-started/typescript/src/main.tsx b/demos/typescript/src/main.tsx similarity index 100% rename from examples/getting-started/typescript/src/main.tsx rename to demos/typescript/src/main.tsx diff --git a/examples/getting-started/typescript/src/vite-env.d.ts b/demos/typescript/src/vite-env.d.ts similarity index 100% rename from examples/getting-started/typescript/src/vite-env.d.ts rename to demos/typescript/src/vite-env.d.ts diff --git a/examples/getting-started/typescript/tsconfig.app.json b/demos/typescript/tsconfig.app.json similarity index 100% rename from examples/getting-started/typescript/tsconfig.app.json rename to demos/typescript/tsconfig.app.json diff --git a/examples/getting-started/typescript/tsconfig.json b/demos/typescript/tsconfig.json similarity index 100% rename from examples/getting-started/typescript/tsconfig.json rename to demos/typescript/tsconfig.json diff --git a/examples/getting-started/typescript/tsconfig.node.json b/demos/typescript/tsconfig.node.json similarity index 100% rename from examples/getting-started/typescript/tsconfig.node.json rename to demos/typescript/tsconfig.node.json diff --git a/examples/getting-started/typescript/vite.config.ts b/demos/typescript/vite.config.ts similarity index 100% rename from examples/getting-started/typescript/vite.config.ts rename to demos/typescript/vite.config.ts diff --git a/examples/getting-started/vanilla/demo-config.json b/demos/vanilla/demo-config.json similarity index 100% rename from examples/getting-started/vanilla/demo-config.json rename to demos/vanilla/demo-config.json diff --git a/examples/getting-started/vanilla/demo-thumbnail.png b/demos/vanilla/demo-thumbnail.png similarity index 100% rename from examples/getting-started/vanilla/demo-thumbnail.png rename to demos/vanilla/demo-thumbnail.png diff --git a/examples/getting-started/vanilla/demo-video.mp4 b/demos/vanilla/demo-video.mp4 similarity index 100% rename from examples/getting-started/vanilla/demo-video.mp4 rename to demos/vanilla/demo-video.mp4 diff --git a/demos/vanilla/index.html b/demos/vanilla/index.html new file mode 100644 index 0000000000..1d5401ef6c --- /dev/null +++ b/demos/vanilla/index.html @@ -0,0 +1,27 @@ + + + + + + SuperDoc Vanilla Example + + +
+
+

SuperDoc Example

+ + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/demos/vanilla/package.json b/demos/vanilla/package.json new file mode 100644 index 0000000000..1c17c8ec6e --- /dev/null +++ b/demos/vanilla/package.json @@ -0,0 +1,15 @@ +{ + "name": "vanilla-superdoc-example", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite" + }, + "dependencies": { + "superdoc": "latest" + }, + "devDependencies": { + "vite": "^4.4.6" + } +} diff --git a/demos/vanilla/src/main.js b/demos/vanilla/src/main.js new file mode 100644 index 0000000000..961aa3bf3e --- /dev/null +++ b/demos/vanilla/src/main.js @@ -0,0 +1,46 @@ +import { SuperDoc } from 'superdoc'; +import 'superdoc/style.css'; +import './style.css'; + +// Initialize SuperDoc +let editor = null; + +function initializeEditor(file = null) { + // Cleanup previous instance if it exists + if (editor) { + editor = null; + } + + editor = new SuperDoc({ + selector: '#superdoc', + toolbar: '#superdoc-toolbar', + document: file, // URL, File or document config + documentMode: 'editing', + pagination: true, + rulers: true, + onReady: (event) => { + console.log('SuperDoc is ready', event); + }, + onEditorCreate: (event) => { + console.log('Editor is created', event); + }, + }); +} + +// Setup file input handling +const fileInput = document.getElementById('fileInput'); +const loadButton = document.getElementById('loadButton'); + +loadButton.addEventListener('click', () => { + fileInput.click(); +}); + +fileInput.addEventListener('change', (event) => { + const file = event.target.files?.[0]; + if (file) { + initializeEditor(file); + } +}); + +// Initialize empty editor on page load +initializeEditor(); \ No newline at end of file diff --git a/examples/getting-started/vanilla/src/style.css b/demos/vanilla/src/style.css similarity index 100% rename from examples/getting-started/vanilla/src/style.css rename to demos/vanilla/src/style.css diff --git a/examples/getting-started/vanilla/vite.config.js b/demos/vanilla/vite.config.js similarity index 100% rename from examples/getting-started/vanilla/vite.config.js rename to demos/vanilla/vite.config.js diff --git a/examples/getting-started/vue/demo-config.json b/demos/vue/demo-config.json similarity index 100% rename from examples/getting-started/vue/demo-config.json rename to demos/vue/demo-config.json diff --git a/examples/getting-started/vue/demo-thumbnail.png b/demos/vue/demo-thumbnail.png similarity index 100% rename from examples/getting-started/vue/demo-thumbnail.png rename to demos/vue/demo-thumbnail.png diff --git a/examples/getting-started/vue/demo-video.mp4 b/demos/vue/demo-video.mp4 similarity index 100% rename from examples/getting-started/vue/demo-video.mp4 rename to demos/vue/demo-video.mp4 diff --git a/examples/collaboration/from-scratch/client/index.html b/demos/vue/index.html similarity index 82% rename from examples/collaboration/from-scratch/client/index.html rename to demos/vue/index.html index 7f1784f816..64e6bb7806 100644 --- a/examples/collaboration/from-scratch/client/index.html +++ b/demos/vue/index.html @@ -3,7 +3,7 @@ - SuperDoc: Collaboration from scratch + SuperDoc Vue Example
diff --git a/examples/collaboration/from-scratch/client/package.json b/demos/vue/package.json similarity index 91% rename from examples/collaboration/from-scratch/client/package.json rename to demos/vue/package.json index e3e9c706ea..e758cea902 100644 --- a/examples/collaboration/from-scratch/client/package.json +++ b/demos/vue/package.json @@ -7,6 +7,7 @@ "dev": "vite" }, "dependencies": { + "superdoc": "latest", "vue": "^3.5.13" }, "devDependencies": { diff --git a/demos/vue/src/App.vue b/demos/vue/src/App.vue new file mode 100644 index 0000000000..906cc772de --- /dev/null +++ b/demos/vue/src/App.vue @@ -0,0 +1,79 @@ + + + + + \ No newline at end of file diff --git a/examples/getting-started/vue/src/components/DocumentEditor.vue b/demos/vue/src/components/DocumentEditor.vue similarity index 100% rename from examples/getting-started/vue/src/components/DocumentEditor.vue rename to demos/vue/src/components/DocumentEditor.vue diff --git a/examples/getting-started/vue/src/main.js b/demos/vue/src/main.js similarity index 100% rename from examples/getting-started/vue/src/main.js rename to demos/vue/src/main.js diff --git a/examples/getting-started/vue/vite.config.js b/demos/vue/vite.config.js similarity index 100% rename from examples/getting-started/vue/vite.config.js rename to demos/vue/vite.config.js diff --git a/examples/integrations/word-addin/.gitignore b/demos/word-addin/.gitignore similarity index 100% rename from examples/integrations/word-addin/.gitignore rename to demos/word-addin/.gitignore diff --git a/examples/integrations/word-addin/MS-Word-Add-in-Sample.code-workspace b/demos/word-addin/MS-Word-Add-in-Sample.code-workspace similarity index 100% rename from examples/integrations/word-addin/MS-Word-Add-in-Sample.code-workspace rename to demos/word-addin/MS-Word-Add-in-Sample.code-workspace diff --git a/examples/integrations/word-addin/README.md b/demos/word-addin/README.md similarity index 100% rename from examples/integrations/word-addin/README.md rename to demos/word-addin/README.md diff --git a/examples/integrations/word-addin/assets/icon-128.png b/demos/word-addin/assets/icon-128.png similarity index 100% rename from examples/integrations/word-addin/assets/icon-128.png rename to demos/word-addin/assets/icon-128.png diff --git a/examples/integrations/word-addin/assets/icon-128x128.png b/demos/word-addin/assets/icon-128x128.png similarity index 100% rename from examples/integrations/word-addin/assets/icon-128x128.png rename to demos/word-addin/assets/icon-128x128.png diff --git a/examples/integrations/word-addin/assets/icon-16.png b/demos/word-addin/assets/icon-16.png similarity index 100% rename from examples/integrations/word-addin/assets/icon-16.png rename to demos/word-addin/assets/icon-16.png diff --git a/examples/integrations/word-addin/assets/icon-16x16.png b/demos/word-addin/assets/icon-16x16.png similarity index 100% rename from examples/integrations/word-addin/assets/icon-16x16.png rename to demos/word-addin/assets/icon-16x16.png diff --git a/examples/integrations/word-addin/assets/icon-32.png b/demos/word-addin/assets/icon-32.png similarity index 100% rename from examples/integrations/word-addin/assets/icon-32.png rename to demos/word-addin/assets/icon-32.png diff --git a/examples/integrations/word-addin/assets/icon-32x32.png b/demos/word-addin/assets/icon-32x32.png similarity index 100% rename from examples/integrations/word-addin/assets/icon-32x32.png rename to demos/word-addin/assets/icon-32x32.png diff --git a/examples/integrations/word-addin/assets/icon-64.png b/demos/word-addin/assets/icon-64.png similarity index 100% rename from examples/integrations/word-addin/assets/icon-64.png rename to demos/word-addin/assets/icon-64.png diff --git a/examples/integrations/word-addin/assets/icon-64x64.png b/demos/word-addin/assets/icon-64x64.png similarity index 100% rename from examples/integrations/word-addin/assets/icon-64x64.png rename to demos/word-addin/assets/icon-64x64.png diff --git a/examples/integrations/word-addin/assets/icon-80.png b/demos/word-addin/assets/icon-80.png similarity index 100% rename from examples/integrations/word-addin/assets/icon-80.png rename to demos/word-addin/assets/icon-80.png diff --git a/examples/integrations/word-addin/assets/icon-80x80.png b/demos/word-addin/assets/icon-80x80.png similarity index 100% rename from examples/integrations/word-addin/assets/icon-80x80.png rename to demos/word-addin/assets/icon-80x80.png diff --git a/examples/integrations/word-addin/assets/logo-filled.png b/demos/word-addin/assets/logo-filled.png similarity index 100% rename from examples/integrations/word-addin/assets/logo-filled.png rename to demos/word-addin/assets/logo-filled.png diff --git a/examples/integrations/word-addin/assets/logo.png b/demos/word-addin/assets/logo.png similarity index 100% rename from examples/integrations/word-addin/assets/logo.png rename to demos/word-addin/assets/logo.png diff --git a/examples/integrations/word-addin/assets/sample-document.docx b/demos/word-addin/assets/sample-document.docx similarity index 100% rename from examples/integrations/word-addin/assets/sample-document.docx rename to demos/word-addin/assets/sample-document.docx diff --git a/examples/integrations/word-addin/babel.config.json b/demos/word-addin/babel.config.json similarity index 100% rename from examples/integrations/word-addin/babel.config.json rename to demos/word-addin/babel.config.json diff --git a/examples/integrations/word-addin/demo-config.json b/demos/word-addin/demo-config.json similarity index 100% rename from examples/integrations/word-addin/demo-config.json rename to demos/word-addin/demo-config.json diff --git a/examples/integrations/word-addin/demo-thumbnail.png b/demos/word-addin/demo-thumbnail.png similarity index 100% rename from examples/integrations/word-addin/demo-thumbnail.png rename to demos/word-addin/demo-thumbnail.png diff --git a/examples/integrations/word-addin/demo-video.mp4 b/demos/word-addin/demo-video.mp4 similarity index 100% rename from examples/integrations/word-addin/demo-video.mp4 rename to demos/word-addin/demo-video.mp4 diff --git a/examples/integrations/word-addin/manifest.xml b/demos/word-addin/manifest.xml similarity index 100% rename from examples/integrations/word-addin/manifest.xml rename to demos/word-addin/manifest.xml diff --git a/examples/integrations/word-addin/package.json b/demos/word-addin/package.json similarity index 100% rename from examples/integrations/word-addin/package.json rename to demos/word-addin/package.json diff --git a/examples/integrations/word-addin/server/.env.example b/demos/word-addin/server/.env.example similarity index 100% rename from examples/integrations/word-addin/server/.env.example rename to demos/word-addin/server/.env.example diff --git a/examples/integrations/word-addin/server/package.json b/demos/word-addin/server/package.json similarity index 100% rename from examples/integrations/word-addin/server/package.json rename to demos/word-addin/server/package.json diff --git a/examples/integrations/word-addin/server/public/editor.css b/demos/word-addin/server/public/editor.css similarity index 100% rename from examples/integrations/word-addin/server/public/editor.css rename to demos/word-addin/server/public/editor.css diff --git a/examples/integrations/word-addin/server/public/editor.html b/demos/word-addin/server/public/editor.html similarity index 100% rename from examples/integrations/word-addin/server/public/editor.html rename to demos/word-addin/server/public/editor.html diff --git a/examples/integrations/word-addin/server/public/editor.js b/demos/word-addin/server/public/editor.js similarity index 100% rename from examples/integrations/word-addin/server/public/editor.js rename to demos/word-addin/server/public/editor.js diff --git a/examples/integrations/word-addin/server/server.js b/demos/word-addin/server/server.js similarity index 100% rename from examples/integrations/word-addin/server/server.js rename to demos/word-addin/server/server.js diff --git a/examples/integrations/word-addin/src/auth-dialog/auth-dialog.css b/demos/word-addin/src/auth-dialog/auth-dialog.css similarity index 100% rename from examples/integrations/word-addin/src/auth-dialog/auth-dialog.css rename to demos/word-addin/src/auth-dialog/auth-dialog.css diff --git a/examples/integrations/word-addin/src/auth-dialog/auth-dialog.html b/demos/word-addin/src/auth-dialog/auth-dialog.html similarity index 100% rename from examples/integrations/word-addin/src/auth-dialog/auth-dialog.html rename to demos/word-addin/src/auth-dialog/auth-dialog.html diff --git a/examples/integrations/word-addin/src/auth-dialog/auth-dialog.js b/demos/word-addin/src/auth-dialog/auth-dialog.js similarity index 100% rename from examples/integrations/word-addin/src/auth-dialog/auth-dialog.js rename to demos/word-addin/src/auth-dialog/auth-dialog.js diff --git a/examples/integrations/word-addin/src/auth0-config.js.example b/demos/word-addin/src/auth0-config.js.example similarity index 100% rename from examples/integrations/word-addin/src/auth0-config.js.example rename to demos/word-addin/src/auth0-config.js.example diff --git a/examples/integrations/word-addin/src/server-domain.js.example b/demos/word-addin/src/server-domain.js.example similarity index 100% rename from examples/integrations/word-addin/src/server-domain.js.example rename to demos/word-addin/src/server-domain.js.example diff --git a/examples/integrations/word-addin/src/taskpane/taskpane.css b/demos/word-addin/src/taskpane/taskpane.css similarity index 100% rename from examples/integrations/word-addin/src/taskpane/taskpane.css rename to demos/word-addin/src/taskpane/taskpane.css diff --git a/examples/integrations/word-addin/src/taskpane/taskpane.html b/demos/word-addin/src/taskpane/taskpane.html similarity index 100% rename from examples/integrations/word-addin/src/taskpane/taskpane.html rename to demos/word-addin/src/taskpane/taskpane.html diff --git a/examples/integrations/word-addin/src/taskpane/taskpane.js b/demos/word-addin/src/taskpane/taskpane.js similarity index 100% rename from examples/integrations/word-addin/src/taskpane/taskpane.js rename to demos/word-addin/src/taskpane/taskpane.js diff --git a/examples/integrations/word-addin/webpack.config.js b/demos/word-addin/webpack.config.js similarity index 100% rename from examples/integrations/word-addin/webpack.config.js rename to demos/word-addin/webpack.config.js diff --git a/examples/.gitignore b/examples/.gitignore index 934668777a..2286166f02 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -1,5 +1,5 @@ -# Ignore lock files +node_modules *-lock* - -# Ignore npmrc files -*.npmrc \ No newline at end of file +*.npmrc +test-results +playwright-report \ No newline at end of file diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000000..1898c019fb --- /dev/null +++ b/examples/README.md @@ -0,0 +1,38 @@ +# SuperDoc Examples + +Minimal, self-contained examples showing how to use SuperDoc. + +## Getting Started + +| Example | Description | +|---------|-------------| +| [react](./getting-started/react) | React + TypeScript with Vite | +| [vue](./getting-started/vue) | Vue 3 + TypeScript with Vite | +| [vanilla](./getting-started/vanilla) | Plain JavaScript with Vite | +| [cdn](./getting-started/cdn) | Zero build tools — just an HTML file | + +## Features + +| Example | Description | Docs | +|---------|-------------|------| +| [track-changes](./features/track-changes) | Accept/reject workflow with suggesting mode | [Track Changes](https://docs.superdoc.dev/extensions/track-changes) | +| [ai-redlining](./features/ai-redlining) | LLM-powered document review with tracked changes | [AI Agents](https://docs.superdoc.dev/getting-started/ai-agents) | +| [comments](./features/comments) | Threaded comments with resolve workflow and event log | [Comments](https://docs.superdoc.dev/modules/comments) | +| [custom-toolbar](./features/custom-toolbar) | Custom button groups, excluded items, and custom buttons | [Toolbar](https://docs.superdoc.dev/modules/toolbar) | +| [collaboration](./collaboration) | Real-time editing with various Yjs providers | [Guides](https://docs.superdoc.dev/guides) | +| [headless](./headless) | Server-side AI redlining with Node.js | [AI Agents](https://docs.superdoc.dev/getting-started/ai-agents) | + +## Running an example + +```bash +cd +npm install +npm run dev +``` + +For the CDN example, just open `index.html` or run `npx serve .`. + +## Documentation + +- [Getting Started](https://docs.superdoc.dev/getting-started/installation) +- [Configuration](https://docs.superdoc.dev/core/superdoc/configuration) diff --git a/examples/__tests__/package.json b/examples/__tests__/package.json new file mode 100644 index 0000000000..d6265d6824 --- /dev/null +++ b/examples/__tests__/package.json @@ -0,0 +1,13 @@ +{ + "name": "superdoc-example-smoke-tests", + "private": true, + "type": "module", + "scripts": { + "test": "playwright test", + "test:install": "playwright install chromium" + }, + "devDependencies": { + "@playwright/test": "^1.50.0", + "serve": "^14.2.0" + } +} diff --git a/examples/__tests__/playwright.config.ts b/examples/__tests__/playwright.config.ts new file mode 100644 index 0000000000..8cbdfbae64 --- /dev/null +++ b/examples/__tests__/playwright.config.ts @@ -0,0 +1,63 @@ +import { defineConfig, devices } from '@playwright/test'; +import { existsSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +// EXAMPLE can be: +// "react", "vue", "vanilla", "cdn" (getting-started) +// "collaboration/superdoc-yjs", "collaboration/hocuspocus", etc. +const example = process.env.EXAMPLE || 'react'; + +// Resolve example path — getting-started examples use short names +const isGettingStarted = !example.includes('/'); +const examplePath = isGettingStarted + ? `../getting-started/${example}` + : `../${example}`; + +// Collaboration examples that use concurrently (server + client). +// These run `npm run dev` which starts both processes — don't append --port. +const useConcurrently = [ + 'collaboration/hocuspocus', + 'collaboration/superdoc-yjs', +]; + +// Port mapping — must match vite.config or server defaults +const portMap: Record = { + cdn: 3000, + 'collaboration/hocuspocus': 3000, +}; +const port = portMap[example] ?? 5173; + +// Detect package manager: use pnpm if the example has no local node_modules +// (pnpm hoists to workspace root), otherwise use npm (CI installs per-example) +const exampleAbsPath = resolve(__dirname, examplePath); +const hasLocalNodeModules = existsSync(resolve(exampleAbsPath, 'node_modules', '.bin')); +const run = hasLocalNodeModules ? `npm run --prefix ${examplePath}` : `pnpm --dir ${examplePath} run`; + +// Start command +const isCdn = example === 'cdn'; +const command = isCdn + ? `npx serve ${examplePath} -l ${port}` + : useConcurrently.includes(example) + ? `${run} dev` + : `${run} dev -- --port ${port}`; + +export default defineConfig({ + testDir: '.', + retries: 1, + timeout: 30_000, + webServer: { + command, + url: `http://localhost:${port}`, + timeout: 30_000, + reuseExistingServer: !process.env.CI, + }, + use: { + baseURL: `http://localhost:${port}`, + }, + projects: [ + { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, + ], +}); diff --git a/examples/__tests__/smoke.spec.ts b/examples/__tests__/smoke.spec.ts new file mode 100644 index 0000000000..754cd95ae3 --- /dev/null +++ b/examples/__tests__/smoke.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('example loads without errors', async ({ page }) => { + const errors: string[] = []; + + page.on('pageerror', (err) => errors.push(err.message)); + page.on('console', (msg) => { + if (msg.type() === 'error') errors.push(msg.text()); + }); + + await page.goto('/'); + await expect(page.locator('body')).toBeVisible(); + + // Give the app a moment to initialize (SuperDoc is async) + await page.waitForTimeout(2000); + + expect(errors).toEqual([]); +}); diff --git a/examples/advanced/README.md b/examples/advanced/README.md deleted file mode 100644 index daee9d73a3..0000000000 --- a/examples/advanced/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Advanced Examples - -Advanced examples demonstrating complex SuperDoc features and use cases. - -## Examples - -| Example | Description | -|---------|-------------| -| [docx-from-html](./docx-from-html) | Generate DOCX files from HTML content | -| [html-editor](./html-editor) | HTML-based editor mode | -| [linked-sections](./linked-sections) | Link editor sections across documents | -| [fields](./fields) | Work with document fields (merge fields, form fields) | -| [loading-from-json](./loading-from-json) | Load documents from JSON format | -| [replace-content](./replace-content) | Programmatically replace document content | -| [text-selection](./text-selection) | Programmatic text selection APIs | -| [docxtemplater](./docxtemplater) | Template-based document generation | -| [grading-papers](./grading-papers) | Document review and annotation use case | -| [headless-converter](./headless-converter) | Convert DOCX to HTML/JSON/text/Markdown (Node.js) | - -## Running an Example - -```bash -cd -npm install -npm run dev -``` diff --git a/examples/advanced/docx-from-html/README.md b/examples/advanced/docx-from-html/README.md deleted file mode 100644 index 6f9c5ffe56..0000000000 --- a/examples/advanced/docx-from-html/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# SuperDoc -## SuperDoc: Init a docx from HTML content - -An example of initializing SuperDoc with HTML content. - -This example will initialize a docx file in SuperDoc (either a blank file or your own file), replacing the file's main contents with the HTML provided. - -Note: In the example we pass in `document: sample-document.docx` in the config, which loads our sample docx containing a header and footer. The inner contents are replaced with the HTML. You can simply **omit** this key and SuperDoc will initialize a blank document for your HTML. diff --git a/examples/advanced/fields/README.md b/examples/advanced/fields/README.md deleted file mode 100644 index 2a2029e1f9..0000000000 --- a/examples/advanced/fields/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# SuperDoc -## SuperDoc: Basic example of using fields - -An example of using fields -Note: Requires `SuperDoc 0.11.8` or later - -- Shows basic drag-and-drop of fields -- Shows field replacement use case \ No newline at end of file diff --git a/examples/advanced/headless-converter/README.md b/examples/advanced/headless-converter/README.md deleted file mode 100644 index 2afd3c816f..0000000000 --- a/examples/advanced/headless-converter/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Headless Converter - -Convert DOCX files to HTML, JSON, text, or Markdown using SuperEditor in headless mode. - -## Setup - -```bash -npm install -``` - -## Usage - -```bash -npx tsx src/index.ts [--format html|json|text|md] -``` - -Examples: -```bash -npx tsx src/index.ts document.docx # text (default) -npx tsx src/index.ts document.docx --format html # HTML -npx tsx src/index.ts document.docx --format json # ProseMirror JSON -npx tsx src/index.ts document.docx --format md # Markdown -``` diff --git a/examples/advanced/headless-converter/package.json b/examples/advanced/headless-converter/package.json deleted file mode 100644 index a862e33fc2..0000000000 --- a/examples/advanced/headless-converter/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "headless-converter", - "private": true, - "version": "1.0.0", - "type": "module", - "scripts": { - "start": "tsx src/index.ts" - }, - "dependencies": { - "superdoc": "latest", - "jsdom": "^27.3.0" - }, - "devDependencies": { - "@types/jsdom": "^27.0.0", - "@types/node": "^25.0.2", - "tsx": "^4.21.0", - "typescript": "^5.9.3" - } -} \ No newline at end of file diff --git a/examples/advanced/headless-converter/sample.docx b/examples/advanced/headless-converter/sample.docx deleted file mode 100644 index 2d36e1d9b1..0000000000 Binary files a/examples/advanced/headless-converter/sample.docx and /dev/null differ diff --git a/examples/advanced/headless-converter/src/index.ts b/examples/advanced/headless-converter/src/index.ts deleted file mode 100644 index 1d9971063a..0000000000 --- a/examples/advanced/headless-converter/src/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Headless Converter - * - * Convert DOCX to HTML, JSON, text, or Markdown using SuperEditor. - * Usage: npx tsx src/index.ts [--format html|json|text|md] - */ - -import { readFile } from 'fs/promises'; -import { JSDOM } from 'jsdom'; -import { Editor, getStarterExtensions } from 'superdoc/super-editor'; - -type OutputFormat = 'html' | 'json' | 'text' | 'md'; - -async function processDocx(filePath: string, format: OutputFormat) { - const buffer = await readFile(filePath); - - const { window } = new JSDOM(''); - const { document } = window; - - const [content, media, mediaFiles, fonts] = await Editor.loadXmlData(buffer, true); - - const editor = new Editor({ - mode: 'docx', - documentId: 'headless', - element: document.createElement('div'), - extensions: getStarterExtensions(), - fileSource: buffer, - content, - media, - mediaFiles, - fonts, - isHeadless: true, - mockDocument: document, - mockWindow: window, - }); - - switch (format) { - case 'html': - console.log(editor.getHTML()); - break; - case 'json': - console.log(JSON.stringify(editor.getJSON(), null, 2)); - break; - case 'md': - console.log(await editor.getMarkdown()); - break; - case 'text': - default: - console.log(editor.state.doc.textContent); - } - - editor.destroy(); -} - -// Parse args -const args = process.argv.slice(2); -const formatIndex = args.findIndex((a) => a === '--format' || a === '-f'); -const format = formatIndex !== -1 ? (args[formatIndex + 1] as OutputFormat) : 'text'; -const inputPath = args.find((a) => !a.startsWith('-') && a !== format); - -if (!inputPath) { - console.error('Usage: npx tsx src/index.ts [--format html|json|text|md]'); - process.exit(1); -} - -processDocx(inputPath, format).catch((err) => { - console.error('Error:', err.message); - process.exit(1); -}); diff --git a/examples/advanced/html-editor/README.md b/examples/advanced/html-editor/README.md deleted file mode 100644 index e216f901a5..0000000000 --- a/examples/advanced/html-editor/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# SuperDoc -## Rich-text editing with SuperEditor and Vue - -An example of initializing the Editor.js class directly and creating a rich-text editor for HTML editing. - -Note: Using the Editor.js class directly means you must initialize the toolbar manually. diff --git a/examples/advanced/linked-sections/README.md b/examples/advanced/linked-sections/README.md deleted file mode 100644 index 13cded9c0b..0000000000 --- a/examples/advanced/linked-sections/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# SuperDoc -## SuperDoc: Example of using editor sections - -An example of using the editor section nodes -Note: Requires `SuperDoc 0.15` or later diff --git a/examples/advanced/text-selection/README.md b/examples/advanced/text-selection/README.md deleted file mode 100644 index d11109a7b3..0000000000 --- a/examples/advanced/text-selection/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# SuperDoc - Programmatic Text Selection Example - -See it here: https://www.superdoc.dev/?demo=programmatic-text-selection - -This React-based example shows how SuperDoc can select text in a document relative to the cursor's position. - -- Based on character count: https://github.com/superdoc-dev/superdoc/blob/develop/examples/advanced/text-selection/src/App.jsx#L30 -- Or, just grab the whole line: https://github.com/superdoc-dev/superdoc/blob/develop/examples/advanced/text-selection/src/App.jsx#L43 \ No newline at end of file diff --git a/examples/ai/README.md b/examples/ai/README.md deleted file mode 100644 index a55e2436e5..0000000000 --- a/examples/ai/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# AI Examples - -Examples for integrating AI capabilities with SuperDoc. - -## Examples - -| Example | Description | -|---------|-------------| -| [quickstart](./quickstart) | Quick start guide for SuperDoc AI features | -| [prompts](./prompts) | Example prompts for AI-powered document operations | - -## Running an Example - -```bash -cd -npm install -npm run dev -``` diff --git a/examples/ai/prompts/claude/prompt.txt b/examples/ai/prompts/claude/prompt.txt deleted file mode 100644 index 8e9f1c7e53..0000000000 --- a/examples/ai/prompts/claude/prompt.txt +++ /dev/null @@ -1,84 +0,0 @@ -Create a simple document editor using SuperDoc and the following code below: - - - - - - - - - - - - SuperDoc - CDN example - - - - -
SuperDoc - CDN example
-
- - - - -
- - - - - \ No newline at end of file diff --git a/examples/ai/tool-calls/.env.example b/examples/ai/tool-calls/.env.example deleted file mode 100644 index dcb1f1a747..0000000000 --- a/examples/ai/tool-calls/.env.example +++ /dev/null @@ -1,4 +0,0 @@ -# Copy this file to ".env" and fill in your API key before running the demo. -# VITE_OPENAI_API_KEY=sk-your-openai-key -# VITE_OPENAI_MODEL=gpt-4o-mini -# VITE_PROXY_URL=proxy.url diff --git a/examples/ai/tool-calls/README.md b/examples/ai/tool-calls/README.md deleted file mode 100644 index 2f90ef21e6..0000000000 --- a/examples/ai/tool-calls/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# SuperDoc AI Quickstart - -Launch a live SuperDoc editor with AI helpers. This example shows how to wire up -`@superdoc-dev/ai` with a fresh SuperDoc instance, register a few buttons, and stream results back into the -document. - -## Prerequisites - -- Node.js 18+ -- An OpenAI API key (or update the provider section in `src/main.js` for another provider) - -## Run the demo - -```bash -cd examples/ai/quickstart -cp .env.example .env # add your OpenAI key + preferred model -npm install -npm run dev -``` - -Vite will print a local URL (defaults to ). Open it in your browser and use the action chips at -the top of the editor to generate, locate, or revise content while the status pill reports progress. - -## What this example covers - -- creating a SuperDoc instance with a ready-to-edit document -- initializing `AIActions` after the editor mounts -- streaming UI feedback via the built-in callbacks -- wiring multiple AI actions (`insertContent`, `insertTrackedChange`, `highlight`, `replace`, `replaceAll`, `find`, - `findAll`, `insertComment`, `insertComments`) through a shared handler -- safely handling missing API keys and error states - -## Key files - -- `src/main.js` – core integration logic -- `src/style.css` – quick styling for the layout and status panel -- `.env.example` – environment variables expected by the example - -## Switching providers - -OpenAI is used here, but you can swap in another provider by changing the `provider` block inside -`initializeAI()` in `src/main.js`. For example, to call a custom HTTP endpoint: - -```js -aiInstance = new AIActions(superdoc, { - user: { displayName: 'SuperDoc AI Assistant' }, - provider: { - type: 'http', - url: import.meta.env.VITE_AI_HTTP_URL, - headers: { - Authorization: `Bearer ${import.meta.env.VITE_AI_HTTP_TOKEN}` - } - } -}); -``` - -Restart the dev server after updating environment variables so Vite can pick them up. diff --git a/examples/ai/tool-calls/demo-config.json b/examples/ai/tool-calls/demo-config.json deleted file mode 100644 index 61e707d7e6..0000000000 --- a/examples/ai/tool-calls/demo-config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "dirname": "superdoc-ai-tool-calls", - "tags": [ - "vue", - "agentic", - "ai" - ], - "title": "AI Actions" -} \ No newline at end of file diff --git a/examples/ai/tool-calls/demo-thumbnail.png b/examples/ai/tool-calls/demo-thumbnail.png deleted file mode 100644 index 9267c8c718..0000000000 Binary files a/examples/ai/tool-calls/demo-thumbnail.png and /dev/null differ diff --git a/examples/ai/tool-calls/demo-video.mp4 b/examples/ai/tool-calls/demo-video.mp4 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/ai/tool-calls/index.html b/examples/ai/tool-calls/index.html deleted file mode 100644 index f4e3d24431..0000000000 --- a/examples/ai/tool-calls/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - SuperDoc AI Quickstart - Vue - - - -
- - - diff --git a/examples/ai/tool-calls/package.json b/examples/ai/tool-calls/package.json deleted file mode 100644 index c98250875c..0000000000 --- a/examples/ai/tool-calls/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "superdoc-ai-quickstart", - "private": true, - "version": "0.0.1", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "@vitejs/plugin-vue": "^6.0.2", - "@superdoc-dev/ai": "^0.1.6", - "superdoc": "^1.0.0-next.1", - "vue": "^3.5.24" - }, - "devDependencies": { - "vite": "^6.3.5" - } -} \ No newline at end of file diff --git a/examples/ai/tool-calls/public/default.docx b/examples/ai/tool-calls/public/default.docx deleted file mode 100644 index 7082071ac4..0000000000 Binary files a/examples/ai/tool-calls/public/default.docx and /dev/null differ diff --git a/examples/ai/tool-calls/src/App.vue b/examples/ai/tool-calls/src/App.vue deleted file mode 100644 index 8daa14de99..0000000000 --- a/examples/ai/tool-calls/src/App.vue +++ /dev/null @@ -1,359 +0,0 @@ - - - \ No newline at end of file diff --git a/examples/ai/tool-calls/src/actions.js b/examples/ai/tool-calls/src/actions.js deleted file mode 100644 index 64c7b87e91..0000000000 --- a/examples/ai/tool-calls/src/actions.js +++ /dev/null @@ -1,220 +0,0 @@ - -// Available actions dictionary - matches the planner's built-in tools -const actionsDictionary = { - 'findAll': { - label: 'Find All', - description: 'Locate all occurrences of content matching the instruction', - method: (ai, prompt) => ai.action.findAll(prompt) - }, - 'highlight': { - label: 'Highlight', - description: 'Visually highlight text without changing it. Use for: drawing attention to issues, marking items for discussion, indicating areas of concern.', - method: (ai, prompt) => ai.action.highlight(prompt) - }, - 'replaceAll': { - label: 'Replace All', - description: 'DIRECT batch editing (no tracking). Use ONLY when user explicitly wants all instances changed immediately AND the user does NOT provide exact find/replace text pairs.', - method: (ai, prompt) => ai.action.replaceAll(prompt) - }, - 'literalReplace': { - label: 'Literal Replace', - description: 'PREFERRED for explicit find-and-replace operations. Use when the user provides both the exact text to find AND the exact replacement text.', - method: (ai, prompt) => { - // For literal replace, we need to parse the prompt differently - // This is a simplified version - in practice, the planner would handle this - const parts = prompt.split(/ to | with /i); - if (parts.length >= 2) { - const findText = parts[0].trim(); - const replaceText = parts.slice(1).join(' ').trim(); - return ai.action.literalReplace(findText, replaceText, { trackChanges: false }); - } - throw new Error('Literal replace requires format: "find X to replace with Y"'); - } - }, - 'insertTrackedChanges': { - label: 'Insert Tracked Changes', - description: 'PRIMARY TOOL for suggesting multiple edits. Creates tracked changes across multiple locations. Use for: batch corrections, applying consistent changes, multiple editing suggestions.', - method: (ai, prompt) => ai.action.insertTrackedChanges(prompt) - }, - 'insertComments': { - label: 'Insert Comments', - description: 'PRIMARY TOOL for providing feedback in multiple locations when location criteria are complex or require AI interpretation. Use for: comprehensive document review, multiple questions, batch feedback.', - method: (ai, prompt) => ai.action.insertComments(prompt) - }, - 'literalInsertComment': { - label: 'Literal Insert Comment', - description: 'PREFERRED for explicit find-and-add-comment operations. Use when the user provides both the exact text to find AND the exact comment text to add.', - method: (ai, prompt) => { - // Simplified version - planner would handle this better - const parts = prompt.split(/ with comment | add comment /i); - if (parts.length >= 2) { - const findText = parts[0].trim(); - const commentText = parts.slice(1).join(' ').trim(); - return ai.action.literalInsertComment(findText, commentText); - } - throw new Error('Literal insert comment requires format: "find X with comment Y"'); - } - }, - 'summarize': { - label: 'Summarize', - description: 'Generate a summary or clarification of content. Use for: creating executive summaries, explaining complex sections, condensing information.', - method: (ai, prompt) => ai.action.summarize(prompt) - }, - 'insertContent': { - label: 'Insert Content', - description: 'Draft and insert new content relative to the current selection. Use for inserting headings, lists, clauses, or replacing selected text.', - method: (ai, prompt) => ai.action.insertContent(prompt) - }, -} - -// Export the available actions list as a static array -export const availableActions = Object.keys(actionsDictionary) - .sort() - .map(key => ({ - key, - ...actionsDictionary[key] - })) - -// Action handler class to manage dependencies and reduce parameter passing -export class ActionHandler { - constructor(aiInstance, logger) { - this.availableActions = availableActions - this.aiInstance = aiInstance - this.logger = logger - } - - // Build tools array for OpenAI function calling - buildToolsArray() { - const convertToOpenAITool = action => ({ - type: "function", - function: { - name: action.key, - description: action.description, - parameters: { - type: "object", - properties: { - prompt: { - type: "string", - description: "The specific instruction or prompt for this action" - } - }, - required: ["prompt"] - } - } - }) - - return this.availableActions.map(convertToOpenAITool) - } - - // Make API request to proxy - async callOpenAI(prompt, tools) { - const proxyUrl = import.meta.env.VITE_PROXY_URL - if (!proxyUrl) { - throw new Error('VITE_PROXY_URL not configured') - } - - const response = await fetch(proxyUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - messages: [{ role: 'user', content: prompt }], - tools: tools, - tool_choice: "auto", - temperature: 0.7, - max_tokens: 1000 - }) - }) - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`) - } - - return await response.json() - } - - // Execute a single tool call - async executeToolCall(toolCall) { - const functionName = toolCall.function.name - const args = JSON.parse(toolCall.function.arguments) - - const action = this.availableActions.find(a => a.key === functionName) - - if (!action?.method) { - this.logger.addActionLog(functionName, args.prompt) - this.logger.updateCurrentLog({ status: 'Error', error: 'Tool not found' }) - return `✗ ${functionName}: Tool not found` - } - - try { - this.logger.addActionLog(action.label, args.prompt) - console.log(`Executing ${functionName} with prompt:`, args.prompt) - - await action.method(this.aiInstance.value, args.prompt) - - this.logger.updateCurrentLog({ status: 'Done.' }) - return `✓ ${action.label}: "${args.prompt}"` - - } catch (error) { - console.error(`Error executing ${functionName}:`, error) - this.logger.updateCurrentLog({ status: 'Error', error: error.message }) - return `✗ ${action.label}: Error - ${error.message}` - } - } - - - // Execute tool calls from open prompt response - async executeToolCallsFromPrompt(toolCalls) { - const toolResults = [] - - for (const toolCall of toolCalls) { - const result = await this.executeToolCall(toolCall) - toolResults.push(result) - } - - this.logger.updateOriginalLog(toolCalls.length) - - const result = `Function calls executed:\n${toolResults.join('\n')}` - console.log('Open prompt result:', result) - return result - } - - // Standalone function to handle open prompt AI calls - async handleOpenPrompt(prompt) { - try { - this.logger.updateCurrentLog({ status: 'Processing open prompt...' }) - - const tools = this.buildToolsArray() - const data = await this.callOpenAI(prompt, tools) - const message = data.choices?.[0]?.message - - if (message?.tool_calls?.length > 0) { - console.log('Function calls requested:', message.tool_calls) - this.logger.updateCurrentLog({ status: `Executing ${message.tool_calls.length} function call(s)...` }) - - // Return the tool calls for the application to handle - return { - type: 'tool_calls', - toolCalls: message.tool_calls, - count: message.tool_calls.length - } - - } else { - // No tools were run - const result = message?.content || data.content || JSON.stringify(data) - console.log('Open prompt result:', result) - this.logger.updateNoToolsRun(result) - - return { - type: 'text_response', - content: result - } - } - - } catch (error) { - this.logger.handleError(error) - throw error - } - } -} \ No newline at end of file diff --git a/examples/ai/tool-calls/src/components/Sidebar.vue b/examples/ai/tool-calls/src/components/Sidebar.vue deleted file mode 100644 index 9df57ce03e..0000000000 --- a/examples/ai/tool-calls/src/components/Sidebar.vue +++ /dev/null @@ -1,169 +0,0 @@ - - - \ No newline at end of file diff --git a/examples/ai/tool-calls/src/logging.js b/examples/ai/tool-calls/src/logging.js deleted file mode 100644 index c99e452245..0000000000 --- a/examples/ai/tool-calls/src/logging.js +++ /dev/null @@ -1,95 +0,0 @@ -import { ref } from 'vue' - -// Helper function for text truncation -function truncateText(text, maxLength) { - if (!text) return '' - return text.length > maxLength ? text.substring(0, maxLength) + '...' : text -} - -export class ActionLogger { - constructor() { - this.actionLogs = ref([]) - this.currentLogId = 0 - this.currentOriginalLogId = null - } - - addActionLog(action, prompt) { - const log = { - id: this.currentLogId++, - action: action, - prompt: prompt, - status: 'Starting...', - partialResult: null, - fullResult: null, - error: null - } - this.actionLogs.value.push(log) - this.currentOriginalLogId = log.id - return log - } - - updateCurrentLog(updates) { - if (this.actionLogs.value.length > 0) { - const currentLog = this.actionLogs.value[this.actionLogs.value.length - 1] - Object.assign(currentLog, updates) - } - } - - updateLogById(logId, updates) { - const logIndex = this.actionLogs.value.findIndex(log => log.id === logId) - if (logIndex !== -1) { - Object.assign(this.actionLogs.value[logIndex], updates) - } - } - - updateStreamingResult(context) { - this.updateCurrentLog({ - status: 'Streaming...', - partialResult: truncateText(context.partialResult, 100) - }) - } - - updateStreamingEnd(context) { - this.updateCurrentLog({ - status: 'Done.', - fullResult: truncateText(String(context.fullResult), 100) - }) - } - - updateError(error) { - this.updateCurrentLog({ - status: 'Error', - error: error.message - }) - } - - updateNoToolsRun(result) { - this.updateCurrentLog({ - status: 'No tools were run.', - fullResult: truncateText(String(result), 100) - }) - } - - updateOriginalLog(toolCallsCount) { - if (!this.currentOriginalLogId) return - - const originalLogIndex = this.actionLogs.value.findIndex(log => log.id === this.currentOriginalLogId) - if (originalLogIndex !== -1) { - this.actionLogs.value[originalLogIndex].status = `All ${toolCallsCount} function calls completed.` - } - } - - handleError(error) { - console.error('Open prompt error:', error) - - if (this.currentOriginalLogId) { - const originalLogIndex = this.actionLogs.value.findIndex(log => log.id === this.currentOriginalLogId) - if (originalLogIndex !== -1) { - this.actionLogs.value[originalLogIndex].status = 'Open prompt failed.' - this.actionLogs.value[originalLogIndex].error = error.message - } - } else { - this.updateCurrentLog({ status: 'Open prompt failed.', error: error.message }) - } - } -} \ No newline at end of file diff --git a/examples/ai/tool-calls/src/main.js b/examples/ai/tool-calls/src/main.js deleted file mode 100644 index 01433bca2a..0000000000 --- a/examples/ai/tool-calls/src/main.js +++ /dev/null @@ -1,4 +0,0 @@ -import { createApp } from 'vue' -import App from './App.vue' - -createApp(App).mount('#app') diff --git a/examples/ai/tool-calls/src/style.css b/examples/ai/tool-calls/src/style.css deleted file mode 100644 index c1193abdf2..0000000000 --- a/examples/ai/tool-calls/src/style.css +++ /dev/null @@ -1,501 +0,0 @@ -* { - box-sizing: border-box; -} - -body { - margin: 0; - font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - background: #f6f8fb; - color: #0f172a; -} - -.page { - min-height: 100vh; - display: grid; - grid-template-columns: minmax(0, 1fr); - background: linear-gradient(180deg, rgba(248, 250, 252, 0.8), rgba(241, 245, 249, 0.95)); - /* padding: 3rem 1.5rem; */ -} - -.page-shell { - padding: 1em; - display: flex; - flex-direction: column; - gap: 1.75rem; - align-items: center; -} - -.page-header { - width: 100%; - margin-bottom: 1.5rem; -} - -.page-header h1 { - margin: 0 0 0.35rem; - font-size: 1.6rem; - color: #0f172a; -} - -.page-header > div:first-child { - /* display: flex; */ - flex-direction: column; -} - -.page-header p { - margin: 0; - line-height: 1.45; - color: #475569; -} - -.main-layout { - width: 100%; -} - -.document-area { - background: rgba(255, 255, 255, 0.88); - border-radius: 1rem; - box-shadow: 0 20px 45px rgba(15, 23, 42, 0.12); - padding: 1.5rem; -} - -.document-content { - display: flex; - gap: 1.5rem; -} - -.superdoc-section { - flex: 1; -} - -.sidebar { - width: 400px; - flex-shrink: 0; - position: relative; -} - -.sidebar.collapsed { - width: 30px; - overflow: hidden; -} - -.collapse-toggle { - position: absolute; - left: -30px; - top: 80px; - width: 30px; - height: 60px; - background: white; - border: 1px solid rgba(37, 99, 235, 0.2); - border-right: none; - border-radius: 5px 0 0 5px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - z-index: 10; -} - -.sidebar.collapsed .collapse-toggle { - left: 0; - border: 1px solid rgba(37, 99, 235, 0.2); - border-radius: 5px; -} - -.collapse-toggle:hover { - background-color: #f9fafb; -} - -.collapse-toggle .chevron { - color: #25282d; - font-size: 24px; - font-weight: bold; - line-height: 1; -} - -.history-section { - background: white; - border: 1px solid rgba(37, 99, 235, 0.2); - border-radius: 8px; - margin-bottom: 1rem; - height: 300px; - display: flex; - flex-direction: column; -} - -.section-header { - padding: 0.75rem 1rem; - border-bottom: 1px solid #e5e7eb; -} - -.section-header span { - font-size: 0.7rem; - letter-spacing: 0.08em; - text-transform: uppercase; - color: #3b82f6; - font-weight: 600; -} - -.history-content { - flex: 1; - padding: 0.75rem 1rem; - overflow-y: auto; - font-size: 0.875rem; -} - -.history-placeholder { - color: #9ca3af; - font-style: italic; - text-align: center; - padding: 2rem 1rem; - font-size: 0.875rem; -} - -.history-item { - color: #374151; - font-size: 0.8rem; - border-bottom: 1px solid #f3f4f6; - padding-bottom: 0.5rem; - margin-bottom: 0.5rem; -} - -.history-item:last-child { - border-bottom: none; - margin-bottom: 0; - padding-bottom: 0; -} - -.history-action { - font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; - margin-bottom: 0.25rem; -} - -.history-status { - font-size: 0.75rem; - color: #6b7280; - font-style: italic; - display: flex; - align-items: center; - gap: 0.5rem; -} - -.spinner { - width: 0.75rem; - height: 0.75rem; - border: 1px solid #d1d5db; - border-top: 1px solid #6366f1; - border-radius: 50%; - animation: spin 1s linear infinite; - flex-shrink: 0; -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -.controls-section { - background: white; - border: 1px solid rgba(37, 99, 235, 0.2); - border-radius: 8px; - padding: 1rem; -} - -.mode-toggle { - display: flex; - margin-bottom: 1rem; - border: 1px solid #d1d5db; - border-radius: 8px; - overflow: hidden; -} - -.mode-toggle-btn { - flex: 1; - padding: 0.75rem 1rem; - border: none; - background: white; - font-size: 0.875rem; - font-weight: 500; - color: #6b7280; - cursor: pointer; - transition: all 0.2s ease; -} - -.mode-toggle-btn:first-child { - border-right: 1px solid #d1d5db; -} - -.mode-toggle-btn.active { - background: #6366f1; - color: white; -} - -.mode-toggle-btn:hover:not(.active) { - background: #f3f4f6; -} - -.custom-dropdown:disabled { - opacity: 0.8; - cursor: default; -} - -.custom-dropdown small { - font-weight: 400; - opacity: 0.8; -} - - - -.control-row { - display: flex; - gap: 1rem; - align-items: flex-start; -} - -.dropdown-container { - position: relative; - flex: 1; -} - -.custom-dropdown { - width: 100%; - min-width: 200px; - padding: 0.75rem 1rem; - border: 2px solid #d1d5db; - border-radius: 8px; - background: white; - font-size: 1rem; - font-weight: 500; - color: #374151; - cursor: pointer; - display: flex; - align-items: center; - justify-content: space-between; - transition: border-color 0.2s ease, box-shadow 0.2s ease; -} - -.custom-dropdown:hover { - border-color: #9ca3af; -} - -.custom-dropdown:focus { - outline: none; - border-color: #6366f1; - box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); -} - -.custom-dropdown.is-open { - border-color: #6366f1; - box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); -} - -.dropdown-arrow { - font-size: 0.875rem; - color: #6b7280; - transition: transform 0.2s ease; -} - -.custom-dropdown.is-open .dropdown-arrow { - transform: rotate(180deg); -} - -.dropdown-menu { - position: absolute; - top: 100%; - left: 0; - right: 0; - margin-top: 0.25rem; - background: white; - border: 1px solid #e5e7eb; - border-radius: 8px; - box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); - z-index: 50; - max-height: 300px; - overflow-y: auto; -} - -.dropdown-item { - width: 100%; - padding: 0.75rem 1rem; - border: none; - background: transparent; - font-size: 0.95rem; - color: #374151; - text-align: left; - cursor: pointer; - transition: background-color 0.15s ease; - display: block; -} - -.dropdown-item:hover { - background-color: #f3f4f6; -} - -.dropdown-item:focus { - outline: none; - background-color: #e0e7ff; -} - -.dropdown-item.is-selected { - background-color: #e0e7ff; - color: #6366f1; - font-weight: 600; -} - -.dropdown-item:first-child { - border-radius: 8px 8px 0 0; -} - -.dropdown-item:last-child { - border-radius: 0 0 8px 8px; -} - -.run-button { - padding: 0.75rem 1.5rem; - border: none; - border-radius: 8px; - background: linear-gradient(135deg, #6366f1, #8b5cf6); - color: white; - font-size: 1rem; - font-weight: 600; - cursor: pointer; - display: flex; - align-items: center; - gap: 0.5rem; - transition: all 0.2s ease; - white-space: nowrap; -} - -.run-button:hover:not(:disabled) { - background: linear-gradient(135deg, #5b5de3, #7c3aed); - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3); -} - -.run-button:focus { - outline: none; - box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.3); -} - -.run-button:disabled { - opacity: 0.6; - cursor: not-allowed; - transform: none; - box-shadow: none; -} - -.prompt-container { - margin-top: 1rem; -} - -.prompt-textarea { - width: 100%; - min-height: 100px; - padding: 1rem; - border: 2px solid #d1d5db; - border-radius: 8px; - font-size: 1rem; - font-family: inherit; - line-height: 1.5; - color: #374151; - background: white; - resize: vertical; - transition: border-color 0.2s ease, box-shadow 0.2s ease; -} - -.prompt-hint { - font-size: 0.75rem; - color: #6b7280; - margin-top: 0.5rem; - text-align: right; - font-style: italic; -} - -.prompt-textarea::placeholder { - color: #9ca3af; - font-style: italic; -} - -.prompt-textarea:focus { - outline: none; - border-color: #6366f1; - box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); -} - -@media (max-width: 640px) { - .control-row { - flex-direction: column; - gap: 0.75rem; - } - - .custom-dropdown { - min-width: unset; - } -} - -#superdoc { - width: 100%; - min-height: 500px; - background: white; - border-radius: 0.75rem; - box-shadow: 0 4px 12px rgba(15, 23, 42, 0.06); - border: 1px solid rgba(0, 0, 0, 0.05); - overflow: hidden; -} - -.superdoc { - justify-content: center; -} - -@media (max-width: 520px) { - .prompt-inline { - width: min(320px, calc(100vw - 2rem)); - } -} - -.import-export-buttons { - display: flex; - gap: 0.5rem; - justify-content: flex-end; - margin-bottom: 0.75rem; -} - -.toolbar-container { - display: flex; - align-items: flex-start; -} - -.import-btn, -.export-btn { - display: flex; - align-items: center; - gap: 0.375rem; - padding: 0.5rem 0.75rem; - border: 1px solid #d1d5db; - border-radius: 6px; - background: white; - font-size: 0.875rem; - font-weight: 500; - color: #374151; - cursor: pointer; - transition: all 0.2s ease; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.import-btn:hover, -.export-btn:hover { - background: #f9fafb; - border-color: #9ca3af; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); -} - -.import-btn:focus, -.export-btn:focus { - outline: none; - border-color: #6366f1; - box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); -} - -.import-btn i, -.export-btn i { - font-size: 0.75rem; -} diff --git a/examples/ai/tool-calls/vite.config.js b/examples/ai/tool-calls/vite.config.js deleted file mode 100644 index aa447e42e5..0000000000 --- a/examples/ai/tool-calls/vite.config.js +++ /dev/null @@ -1,9 +0,0 @@ -import { defineConfig } from 'vite'; -import vue from '@vitejs/plugin-vue'; - -export default defineConfig({ - plugins: [vue()], - optimizeDeps: { - include: ['superdoc', '@superdoc-dev/ai'] - } -}); diff --git a/examples/collaboration/README.md b/examples/collaboration/README.md index 3542eb5d5d..bb45830b04 100644 --- a/examples/collaboration/README.md +++ b/examples/collaboration/README.md @@ -1,40 +1,16 @@ # Collaboration Examples -Real-time collaboration examples using Yjs for multi-user document editing. +Real-time collaboration examples using Yjs. Each example maps to a guide in the [docs](https://docs.superdoc.dev/guides). -## Cloud Providers (No Server Required) +## Cloud Providers -| Provider | Description | -| -------------------------- | ---------------------------------------- | -| [Liveblocks](./liveblocks) | Managed cloud - just add API key | -| [TipTap Cloud](./tiptap) | Managed Hocuspocus (requires TipTap Pro) | +| Provider | Docs | +| ------------------------------ | ------------------------------------------------------------------ | +| [Liveblocks](./liveblocks) | [Guide](https://docs.superdoc.dev/guides/liveblocks) | -## Self-Hosted Examples +## Self-Hosted -| Example | Description | -| ---------------------------------- | ---------------------------------------------------------- | -| [Hocuspocus](./hocuspocus) | Self-hosted Hocuspocus server | -| [basic](./basic) | Simple collaboration setup with y-websocket | -| [superdoc](./superdoc) | Full SuperDoc collaboration with awareness features | -| [from-scratch](./from-scratch) | Build collaboration from scratch with client and server | -| [production](./production) | Production-ready setup with persistence and authentication | -| [fastify-server](./fastify-server) | Fastify-based collaboration server | -| [y-sweet](./y-sweet) | Y-Sweet collaboration server | - -## Running an Example - -Most collaboration examples require running both a server and client: - -```bash -# Terminal 1 - Server -cd /server -npm install -npm run dev - -# Terminal 2 - Client -cd /client -npm install -npm run dev -``` - -See individual example READMEs for specific instructions. +| Example | Docs | +| ---------------------------------- | ------------------------------------------------------------------ | +| [SuperDoc Yjs](./superdoc-yjs) | [Guide](https://docs.superdoc.dev/guides/superdoc-yjs) | +| [Hocuspocus](./hocuspocus) | [Guide](https://docs.superdoc.dev/guides/hocuspocus) | diff --git a/examples/collaboration/basic/README.md b/examples/collaboration/basic/README.md deleted file mode 100644 index 5926686286..0000000000 --- a/examples/collaboration/basic/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Using collaboration with Editor.js - -A basic example of using collaboration with Editor.js - -## Getting started -For this example, we recommend you run the provided ```fastify``` example in the examples directory. -With two terminals: - -Terminal 1 (in this folder): The frontend using Editor.js directly -``` -npm install && npm run dev -``` - -Terminal 2 (in ../fastify folder): The backend -``` -npm install && npm run dev -``` - -Point your browser (2 tabs) to: ```http://localhost:5173``` - -This will run both a basic Editor.js frontend, and a Fastify server running ```superdoc-yjs-collaboration``` and a single websocket endpoint. The editors should be collaborating and [look something like this](https://cdn.zappy.app/02b7c71ea638080bee27cdd4740060de.mp4) diff --git a/examples/collaboration/basic/package.json b/examples/collaboration/basic/package.json deleted file mode 100644 index 69c5ee2cec..0000000000 --- a/examples/collaboration/basic/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "vue-editor-js-collab", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "superdoc": "^0.26.0", - "vue": "^3.5.13", - "yjs": "^13.6.8" - }, - "devDependencies": { - "@vitejs/plugin-vue": "^5.2.2", - "vite": "^6.0.0" - } -} diff --git a/examples/collaboration/basic/src/App.vue b/examples/collaboration/basic/src/App.vue deleted file mode 100644 index 985da8282e..0000000000 --- a/examples/collaboration/basic/src/App.vue +++ /dev/null @@ -1,111 +0,0 @@ - - - - - diff --git a/examples/collaboration/fastify-server/README.md b/examples/collaboration/fastify-server/README.md deleted file mode 100644 index 2016250c7e..0000000000 --- a/examples/collaboration/fastify-server/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# SuperDoc Collaboration Example (Fastify) - -This example demonstrates how to set up a Fastify server with WebSocket support to run the custom SuperDoc Collaboration service using Yjs. - -## Prerequisites - -* Node.js v18+ or higher (Node v20+ recommended) -* npm or yarn -* The `@harbour-enterprises/superdoc-yjs-collaboration` package built and linked locally (or installed from npm) - -## Installation - -1. **Install dependencies** in the example directory: - - ```bash - cd examples/collaboration/fastify-server - npm install - ``` - -2. **Link or install** the collaboration library: - - * **Using `npm link`** (local development): - - ```bash - # in your main package folder - npm run build # ensure dist/ is built - npm link - - # in the example folder - npm link @harbour-enterprises/superdoc-yjs-collaboration - ``` - - * **Or as a file dependency** in `package.json`: - - ```json - "dependencies": { - "@harbour-enterprises/superdoc-yjs-collaboration": "file:../../" - } - ``` - -## Running the Example - -The `dev` script watches both your library and the example and restarts the server on changes. - -```bash -npm run dev -``` - -Or start the example alone (after building the library): - -```bash -cd examples/collaboration/fastify-server -npm start -``` - -The server listens on port `3000` by default. - -## Endpoints - -* **`GET /`** - - * Simple HTTP endpoint. Returns a greeting string. - -* **`GET /collaboration/:documentId`** - - * WebSocket endpoint to join a Yjs-powered collaborative session. - * Replace `:documentId` with your document identifier (e.g. `my-doc-id`). diff --git a/examples/collaboration/fastify-server/package.json b/examples/collaboration/fastify-server/package.json deleted file mode 100644 index 120c6c0314..0000000000 --- a/examples/collaboration/fastify-server/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "fastify-esm-example", - "version": "1.0.0", - "type": "module", - "scripts": { - "dev": "tsx watch index.ts", - "start": "tsx index.ts", - "build": "tsc", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@superdoc-dev/superdoc-yjs-collaboration": "^1.0.0", - "@fastify/websocket": "^11.1.0", - "fastify": "^5.3.3", - "y-protocols": "^1.0.6", - "y-websocket": "^3.0.0", - "yjs": "^13.6.27" - }, - "devDependencies": { - "@types/node": "^24.10.0", - "@types/ws": "^8.18.1", - "nodemon": "^3.1.10", - "tsx": "^4.20.6", - "typescript": "^5.9.3" - } -} diff --git a/examples/collaboration/fastify-server/tsconfig.json b/examples/collaboration/fastify-server/tsconfig.json deleted file mode 100644 index a2fff94db6..0000000000 --- a/examples/collaboration/fastify-server/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "include": ["./**/*.ts"], - "exclude": ["node_modules", "dist"], - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "lib": ["ES2022", "DOM"], - "outDir": "./dist", - "rootDir": "./", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": false, - "forceConsistentCasingInFileNames": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "allowSyntheticDefaultImports": true, - "resolveJsonModule": true - } -} \ No newline at end of file diff --git a/examples/collaboration/from-scratch/.gitignore b/examples/collaboration/from-scratch/.gitignore deleted file mode 100644 index 737bc52b50..0000000000 --- a/examples/collaboration/from-scratch/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules/ -dist/ -package-lock.json \ No newline at end of file diff --git a/examples/collaboration/from-scratch/README.md b/examples/collaboration/from-scratch/README.md deleted file mode 100644 index 57de36d7f4..0000000000 --- a/examples/collaboration/from-scratch/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# SuperDoc collaboration from scratch - -This example shows how to create an absolutely minimal collaboration service for SuperDoc using utilities from the Yjs library directly. -It does not use our own **superdoc-yjs-library** (contact us q@sueprdoc.dev for more info). - -This example is really an adaptation of [y-websocket-server](https://github.com/yjs/y-websocket-server/) from Yjs and shows how to use the basic utilities provided there to set up the socket connection. - -**⚠️ Warning:** This example is a very basic, minimal example of getting Yjs set up on the server, and connecting it to the client. It does not offer any security or auth features, etc. - -**Usage**: -``` -npm install -npm run dev -``` - -**Note**: Open two or more browser windows to http://localhost:5173/ (each one will refresh the document). Then, start collaborating! diff --git a/examples/collaboration/from-scratch/client/README.md b/examples/collaboration/from-scratch/client/README.md deleted file mode 100644 index bb8579c4d6..0000000000 --- a/examples/collaboration/from-scratch/client/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# SuperDoc collaboration from scratch - -This example shows how to create an absolutely minimal collaboration service for SuperDoc using utilities from the Yjs library directly. -It does not use our own [superdoc-yjs-library](https://www.npmjs.com/package/@harbour-enterprises/superdoc-yjs-collaboration) which makes initial set up easier. - -This example is really an adaptation of [y-websocket-server](https://github.com/yjs/y-websocket-server/) from Yjs and shows how to use the basic utilities provided there to set up the socket connection. - -**⚠️ Warning:** This example is a very basic, minimal example of getting Yjs set up on the server, and connecting it to the client. It does not offer any security or auth features, etc. - -**Usage**: -``` -npm install -npm run dev -``` - -**Note**: Open two or more browser windows to http://localhost:5173/ (each one will refresh the document). Then, start collaborating! diff --git a/examples/collaboration/from-scratch/client/src/App.vue b/examples/collaboration/from-scratch/client/src/App.vue deleted file mode 100644 index dc6c6738b0..0000000000 --- a/examples/collaboration/from-scratch/client/src/App.vue +++ /dev/null @@ -1,104 +0,0 @@ - - - - - \ No newline at end of file diff --git a/examples/collaboration/from-scratch/client/src/style.css b/examples/collaboration/from-scratch/client/src/style.css deleted file mode 100644 index ccbaccf286..0000000000 --- a/examples/collaboration/from-scratch/client/src/style.css +++ /dev/null @@ -1,46 +0,0 @@ -.app { - height: 100vh; - display: flex; - flex-direction: column; - font-family: Arial, Helvetica, sans-serif; -} - -header { - padding: 1rem; - background: #f5f5f5; - display: flex; - justify-content: center; - gap: 1rem; - flex-direction: column; -} - -h1 { - font-size: 20px; -} - -header h1, -header h2 { - margin: 0; -} - -header button { - padding: 0.5rem 1rem; - background: #1355ff; - color: white; - border: none; - border-radius: 4px; - cursor: pointer; -} - -header button:hover { - background: #0044ff; -} - -.hidden { - display: none; -} - -main { - flex: 1; - padding: 1rem; -} diff --git a/examples/collaboration/from-scratch/package.json b/examples/collaboration/from-scratch/package.json deleted file mode 100644 index 7cf8769119..0000000000 --- a/examples/collaboration/from-scratch/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "collaboration-from-scratch", - "version": "1.0.0", - "main": "index.js", - "workspaces": [ - "client", - "server" - ], - "scripts": { - "dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"", - "dev:server": "nodemon server.js --workspace server", - "dev:client": "npm run dev --workspace client", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "", - "license": "ISC", - "description": "", - "dependencies": { - "lib0": "^0.2.102", - "y-leveldb": "^0.1.0", - "y-protocols": "^1.0.5", - "yjs": "^13.6.27" - } -} diff --git a/examples/collaboration/from-scratch/server/callback.js b/examples/collaboration/from-scratch/server/callback.js deleted file mode 100644 index f3002bf94d..0000000000 --- a/examples/collaboration/from-scratch/server/callback.js +++ /dev/null @@ -1,75 +0,0 @@ -import http from 'http' -import * as number from 'lib0/number' - -const CALLBACK_URL = process.env.CALLBACK_URL ? new URL(process.env.CALLBACK_URL) : null -const CALLBACK_TIMEOUT = number.parseInt(process.env.CALLBACK_TIMEOUT || '5000') -const CALLBACK_OBJECTS = process.env.CALLBACK_OBJECTS ? JSON.parse(process.env.CALLBACK_OBJECTS) : {} - -export const isCallbackSet = !!CALLBACK_URL - -/** - * @param {import('./utils.js').WSSharedDoc} doc - */ -export const callbackHandler = (doc) => { - const room = doc.name - const dataToSend = { - room, - data: {} - } - const sharedObjectList = Object.keys(CALLBACK_OBJECTS) - sharedObjectList.forEach(sharedObjectName => { - const sharedObjectType = CALLBACK_OBJECTS[sharedObjectName] - dataToSend.data[sharedObjectName] = { - type: sharedObjectType, - content: getContent(sharedObjectName, sharedObjectType, doc).toJSON() - } - }) - CALLBACK_URL && callbackRequest(CALLBACK_URL, CALLBACK_TIMEOUT, dataToSend) -} - -/** - * @param {URL} url - * @param {number} timeout - * @param {Object} data - */ -const callbackRequest = (url, timeout, data) => { - data = JSON.stringify(data) - const options = { - hostname: url.hostname, - port: url.port, - path: url.pathname, - timeout, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(data) - } - } - const req = http.request(options) - req.on('timeout', () => { - console.warn('Callback request timed out.') - req.abort() - }) - req.on('error', (e) => { - console.error('Callback request error.', e) - req.abort() - }) - req.write(data) - req.end() -} - -/** - * @param {string} objName - * @param {string} objType - * @param {import('./utils.js').WSSharedDoc} doc - */ -const getContent = (objName, objType, doc) => { - switch (objType) { - case 'Array': return doc.getArray(objName) - case 'Map': return doc.getMap(objName) - case 'Text': return doc.getText(objName) - case 'XmlFragment': return doc.getXmlFragment(objName) - case 'XmlElement': return doc.getXmlElement(objName) - default : return {} - } -} \ No newline at end of file diff --git a/examples/collaboration/from-scratch/server/package.json b/examples/collaboration/from-scratch/server/package.json deleted file mode 100644 index 82e863a618..0000000000 --- a/examples/collaboration/from-scratch/server/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "server", - "version": "1.0.0", - "main": "server.js", - "type": "module", - "scripts": { - "dev": "nodemon server.js" - }, - "keywords": [], - "author": "", - "license": "ISC", - "description": "", - "dependencies": { - "@fastify/websocket": "^11.1.0", - "fastify": "^5.4.0" - }, - "devDependencies": { - "nodemon": "^3.1.10" - } -} diff --git a/examples/collaboration/from-scratch/server/server.js b/examples/collaboration/from-scratch/server/server.js deleted file mode 100644 index 8cf25a02ec..0000000000 --- a/examples/collaboration/from-scratch/server/server.js +++ /dev/null @@ -1,27 +0,0 @@ -import * as Y from 'yjs'; -import Fastify from 'fastify'; -import websocketPlugin from '@fastify/websocket'; -import { setupWSConnection } from './yjs-utils.js' - -const app = Fastify({ logger: false }); -app.register(websocketPlugin); - -app.register(async function (app) { - app.get('/collaboration/:documentId', { websocket: true }, (socket, request) => { - const { documentId } = request.params; - console.debug('WebSocket connection requested for document:', documentId); - - const options = { docName: documentId }; - const doc = setupWSConnection(socket, request.request, options); - - // You could add some listeners... - - /** Note: The update listener will get called a lot. You might want to debounce this. */ - doc.on('update', (update) => { - console.debug(`Document ${documentId} updated with size: ${update.byteLength} bytes`); - }); - - }) -}); - -app.listen({ port: 3050 }); \ No newline at end of file diff --git a/examples/collaboration/from-scratch/server/yjs-utils.js b/examples/collaboration/from-scratch/server/yjs-utils.js deleted file mode 100644 index 4a033767b5..0000000000 --- a/examples/collaboration/from-scratch/server/yjs-utils.js +++ /dev/null @@ -1,312 +0,0 @@ - -/** - * This file is taken from https://github.com/yjs/y-websocket-server/blob/main/src/utils.js - * And is part of the y-websocket-server package. - */ - -import * as Y from 'yjs' -import * as syncProtocol from 'y-protocols/sync' -import * as awarenessProtocol from 'y-protocols/awareness' - -import * as encoding from 'lib0/encoding' -import * as decoding from 'lib0/decoding' -import * as map from 'lib0/map' - -import * as eventloop from 'lib0/eventloop' -import { LeveldbPersistence } from 'y-leveldb' - -import { callbackHandler, isCallbackSet } from './callback.js' - -const CALLBACK_DEBOUNCE_WAIT = parseInt(process.env.CALLBACK_DEBOUNCE_WAIT || '2000') -const CALLBACK_DEBOUNCE_MAXWAIT = parseInt(process.env.CALLBACK_DEBOUNCE_MAXWAIT || '10000') - -const debouncer = eventloop.createDebouncer(CALLBACK_DEBOUNCE_WAIT, CALLBACK_DEBOUNCE_MAXWAIT) - -const wsReadyStateConnecting = 0 -const wsReadyStateOpen = 1 -const wsReadyStateClosing = 2 // eslint-disable-line -const wsReadyStateClosed = 3 // eslint-disable-line - -// disable gc when using snapshots! -const gcEnabled = process.env.GC !== 'false' && process.env.GC !== '0' -const persistenceDir = process.env.YPERSISTENCE -/** - * @type {{bindState: function(string,WSSharedDoc):void, writeState:function(string,WSSharedDoc):Promise, provider: any}|null} - */ -let persistence = null -if (typeof persistenceDir === 'string') { - console.info('Persisting documents to "' + persistenceDir + '"') - // @ts-ignore - const ldb = new LeveldbPersistence(persistenceDir) - persistence = { - provider: ldb, - bindState: async (docName, ydoc) => { - const persistedYdoc = await ldb.getYDoc(docName) - const newUpdates = Y.encodeStateAsUpdate(ydoc) - ldb.storeUpdate(docName, newUpdates) - Y.applyUpdate(ydoc, Y.encodeStateAsUpdate(persistedYdoc)) - ydoc.on('update', update => { - ldb.storeUpdate(docName, update) - }) - }, - writeState: async (_docName, _ydoc) => {} - } -} - -/** - * @param {{bindState: function(string,WSSharedDoc):void, - * writeState:function(string,WSSharedDoc):Promise,provider:any}|null} persistence_ - */ -export const setPersistence = persistence_ => { - persistence = persistence_ -} - -/** - * @return {null|{bindState: function(string,WSSharedDoc):void, - * writeState:function(string,WSSharedDoc):Promise}|null} used persistence layer - */ -export const getPersistence = () => persistence - -/** - * @type {Map} - */ -export const docs = new Map() - -const messageSync = 0 -const messageAwareness = 1 -// const messageAuth = 2 - -/** - * @param {Uint8Array} update - * @param {any} _origin - * @param {WSSharedDoc} doc - * @param {any} _tr - */ -const updateHandler = (update, _origin, doc, _tr) => { - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, messageSync) - syncProtocol.writeUpdate(encoder, update) - const message = encoding.toUint8Array(encoder) - doc.conns.forEach((_, conn) => send(doc, conn, message)) -} - -/** - * @type {(ydoc: Y.Doc) => Promise} - */ -let contentInitializor = _ydoc => Promise.resolve() - -/** - * This function is called once every time a Yjs document is created. You can - * use it to pull data from an external source or initialize content. - * - * @param {(ydoc: Y.Doc) => Promise} f - */ -export const setContentInitializor = (f) => { - contentInitializor = f -} - -export class WSSharedDoc extends Y.Doc { - /** - * @param {string} name - */ - constructor (name) { - super({ gc: gcEnabled }) - this.name = name - /** - * Maps from conn to set of controlled user ids. Delete all user ids from awareness when this conn is closed - * @type {Map>} - */ - this.conns = new Map() - /** - * @type {awarenessProtocol.Awareness} - */ - this.awareness = new awarenessProtocol.Awareness(this) - this.awareness.setLocalState(null) - /** - * @param {{ added: Array, updated: Array, removed: Array }} changes - * @param {Object | null} conn Origin is the connection that made the change - */ - const awarenessChangeHandler = ({ added, updated, removed }, conn) => { - const changedClients = added.concat(updated, removed) - if (conn !== null) { - const connControlledIDs = /** @type {Set} */ (this.conns.get(conn)) - if (connControlledIDs !== undefined) { - added.forEach(clientID => { connControlledIDs.add(clientID) }) - removed.forEach(clientID => { connControlledIDs.delete(clientID) }) - } - } - // broadcast awareness update - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, messageAwareness) - encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this.awareness, changedClients)) - const buff = encoding.toUint8Array(encoder) - this.conns.forEach((_, c) => { - send(this, c, buff) - }) - } - this.awareness.on('update', awarenessChangeHandler) - this.on('update', /** @type {any} */ (updateHandler)) - if (isCallbackSet) { - this.on('update', (_update, _origin, doc) => { - debouncer(() => callbackHandler(/** @type {WSSharedDoc} */ (doc))) - }) - } - - this.whenInitialized = contentInitializor(this) - } -} - -/** - * Gets a Y.Doc by name, whether in memory or on disk - * - * @param {string} docname - the name of the Y.Doc to find or create - * @param {boolean} gc - whether to allow gc on the doc (applies only when created) - * @return {WSSharedDoc} - */ -export const getYDoc = (docname, gc = true) => map.setIfUndefined(docs, docname, () => { - const doc = new WSSharedDoc(docname) - doc.gc = gc - if (persistence !== null) { - persistence.bindState(docname, doc) - } - docs.set(docname, doc) - return doc -}) - -/** - * @param {any} conn - * @param {WSSharedDoc} doc - * @param {Uint8Array} message - */ -const messageListener = (conn, doc, message) => { - try { - const encoder = encoding.createEncoder() - const decoder = decoding.createDecoder(message) - const messageType = decoding.readVarUint(decoder) - switch (messageType) { - case messageSync: - encoding.writeVarUint(encoder, messageSync) - syncProtocol.readSyncMessage(decoder, encoder, doc, conn) - - // If the `encoder` only contains the type of reply message and no - // message, there is no need to send the message. When `encoder` only - // contains the type of reply, its length is 1. - if (encoding.length(encoder) > 1) { - send(doc, conn, encoding.toUint8Array(encoder)) - } - break - case messageAwareness: { - awarenessProtocol.applyAwarenessUpdate(doc.awareness, decoding.readVarUint8Array(decoder), conn) - break - } - } - } catch (err) { - console.error(err) - // @ts-ignore - doc.emit('error', [err]) - } -} - -/** - * @param {WSSharedDoc} doc - * @param {any} conn - */ -const closeConn = (doc, conn) => { - if (doc.conns.has(conn)) { - /** - * @type {Set} - */ - // @ts-ignore - const controlledIds = doc.conns.get(conn) - doc.conns.delete(conn) - awarenessProtocol.removeAwarenessStates(doc.awareness, Array.from(controlledIds), null) - if (doc.conns.size === 0 && persistence !== null) { - // if persisted, we store state and destroy ydocument - persistence.writeState(doc.name, doc).then(() => { - doc.destroy() - }) - docs.delete(doc.name) - } - } - conn.close() -} - -/** - * @param {WSSharedDoc} doc - * @param {import('ws').WebSocket} conn - * @param {Uint8Array} m - */ -const send = (doc, conn, m) => { - if (conn.readyState !== wsReadyStateConnecting && conn.readyState !== wsReadyStateOpen) { - closeConn(doc, conn) - } - try { - conn.send(m, {}, err => { err != null && closeConn(doc, conn) }) - } catch (e) { - closeConn(doc, conn) - } -} - -const pingTimeout = 30000 - -/** - * @param {import('ws').WebSocket} conn - * @param {import('http').IncomingMessage} req - * @param {any} opts - */ -export const setupWSConnection = (conn, req, { docName = (req.url || '').slice(1).split('?')[0], gc = true } = {}) => { - conn.binaryType = 'arraybuffer' - // get doc, initialize if it does not exist yet - const doc = getYDoc(docName, gc) - doc.conns.set(conn, new Set()) - - // listen and reply to events - conn.on('message', /** @param {ArrayBuffer} message */ message => messageListener(conn, doc, new Uint8Array(message))) - - // Check if connection is still alive - let pongReceived = true - const pingInterval = setInterval(() => { - if (!pongReceived) { - if (doc.conns.has(conn)) { - closeConn(doc, conn) - } - clearInterval(pingInterval) - } else if (doc.conns.has(conn)) { - pongReceived = false - try { - conn.ping() - } catch (e) { - closeConn(doc, conn) - clearInterval(pingInterval) - } - } - }, pingTimeout); - - conn.on('close', () => { - closeConn(doc, conn) - clearInterval(pingInterval) - }); - - conn.on('pong', () => { - pongReceived = true - }); - - // put the following in a variables in a block so the interval handlers don't keep in in - // scope - { - // send sync step 1 - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, messageSync) - syncProtocol.writeSyncStep1(encoder, doc) - send(doc, conn, encoding.toUint8Array(encoder)) - const awarenessStates = doc.awareness.getStates() - if (awarenessStates.size > 0) { - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, messageAwareness) - encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(doc.awareness, Array.from(awarenessStates.keys()))) - send(doc, conn, encoding.toUint8Array(encoder)) - } - } - - return doc; -} diff --git a/examples/collaboration/hocuspocus/client/.env.example b/examples/collaboration/hocuspocus/.env.example similarity index 100% rename from examples/collaboration/hocuspocus/client/.env.example rename to examples/collaboration/hocuspocus/.env.example diff --git a/examples/collaboration/hocuspocus/README.md b/examples/collaboration/hocuspocus/README.md index 2cd590daab..a2f48f49bd 100644 --- a/examples/collaboration/hocuspocus/README.md +++ b/examples/collaboration/hocuspocus/README.md @@ -1,80 +1,14 @@ -# SuperDoc + Hocuspocus (Self-Hosted) +# SuperDoc + Hocuspocus -Real-time collaboration using self-hosted [Hocuspocus](https://hocuspocus.dev/) server. +Self-hosted collaboration using [Hocuspocus](https://hocuspocus.dev/). -## Quick Start +**Docs:** [Hocuspocus Guide](https://docs.superdoc.dev/guides/hocuspocus) -```bash -pnpm install -pnpm dev -``` - -This starts both the server (ws://localhost:1234) and client (http://localhost:3000). - -Open http://localhost:3000 in multiple tabs to test collaboration. - -## Manual Setup (Alternative) - -If you prefer running separately: - -```bash -# Terminal 1 - Server -cd server && pnpm install && pnpm dev - -# Terminal 2 - Client -cd client && pnpm install && pnpm dev -``` - -## How It Works - -```ts -import { HocuspocusProvider } from '@hocuspocus/provider'; -import * as Y from 'yjs'; - -const ydoc = new Y.Doc(); -const provider = new HocuspocusProvider({ - url: 'ws://localhost:1234', - name: 'room-id', - document: ydoc, -}); - -new SuperDoc({ - modules: { - collaboration: { ydoc, provider }, - }, -}); -``` - -## Adding Persistence - -To persist documents, add the Database extension to the server: +## Getting started ```bash -pnpm add @hocuspocus/extension-database level +npm install +npm run dev ``` -```js -import { Database } from '@hocuspocus/extension-database'; -import { Level } from 'level'; - -const db = new Level('./data'); - -const server = new Server({ - port: 1234, - extensions: [ - new Database({ - fetch: async ({ documentName }) => { - try { return await db.get(documentName); } - catch { return null; } - }, - store: async ({ documentName, state }) => { - await db.put(documentName, state); - }, - }), - ], -}); -``` - -## Managed Alternative - -For managed hosting, see [TipTap Cloud](../cloud/tiptap/). +This starts both the Hocuspocus server (ws://localhost:1234) and the React client (http://localhost:3000). Open two browser tabs to see real-time collaboration. diff --git a/examples/collaboration/hocuspocus/client/index.html b/examples/collaboration/hocuspocus/index.html similarity index 100% rename from examples/collaboration/hocuspocus/client/index.html rename to examples/collaboration/hocuspocus/index.html diff --git a/examples/collaboration/hocuspocus/package.json b/examples/collaboration/hocuspocus/package.json index 1c8154701e..f4a3026833 100644 --- a/examples/collaboration/hocuspocus/package.json +++ b/examples/collaboration/hocuspocus/package.json @@ -1,8 +1,27 @@ { "name": "superdoc-hocuspocus-example", "private": true, + "type": "module", "scripts": { - "dev": "pnpm -r --parallel dev", - "build": "pnpm --filter ./client build" + "dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"", + "dev:client": "vite", + "dev:server": "node server.js", + "build": "vite build" + }, + "dependencies": { + "@hocuspocus/provider": "^2.13.6", + "@hocuspocus/server": "^2.13.6", + "react": "^19.2.1", + "react-dom": "^19.2.1", + "superdoc": "latest", + "yjs": "13.6.19" + }, + "devDependencies": { + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.2", + "concurrently": "^9.0.0", + "typescript": "^5.9.3", + "vite": "^7.2.7" } } diff --git a/examples/collaboration/hocuspocus/pnpm-workspace.yaml b/examples/collaboration/hocuspocus/pnpm-workspace.yaml deleted file mode 100644 index b2eff6c81c..0000000000 --- a/examples/collaboration/hocuspocus/pnpm-workspace.yaml +++ /dev/null @@ -1,3 +0,0 @@ -packages: - - client - - server diff --git a/examples/collaboration/hocuspocus/server/server.js b/examples/collaboration/hocuspocus/server.js similarity index 78% rename from examples/collaboration/hocuspocus/server/server.js rename to examples/collaboration/hocuspocus/server.js index 0f058852b9..e4654daf95 100644 --- a/examples/collaboration/hocuspocus/server/server.js +++ b/examples/collaboration/hocuspocus/server.js @@ -1,6 +1,6 @@ -import { Server } from '@hocuspocus/server'; +import { Hocuspocus } from '@hocuspocus/server'; -const server = new Server({ +const server = new Hocuspocus({ port: 1234, async onConnect({ documentName }) { diff --git a/examples/collaboration/hocuspocus/server/package.json b/examples/collaboration/hocuspocus/server/package.json deleted file mode 100644 index a7ef83b6b3..0000000000 --- a/examples/collaboration/hocuspocus/server/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "hocuspocus-server", - "type": "module", - "scripts": { - "dev": "node server.js" - }, - "dependencies": { - "@hocuspocus/server": "^3.4.0" - } -} diff --git a/examples/collaboration/hocuspocus/client/src/App.tsx b/examples/collaboration/hocuspocus/src/App.tsx similarity index 100% rename from examples/collaboration/hocuspocus/client/src/App.tsx rename to examples/collaboration/hocuspocus/src/App.tsx diff --git a/examples/collaboration/hocuspocus/client/src/main.tsx b/examples/collaboration/hocuspocus/src/main.tsx similarity index 100% rename from examples/collaboration/hocuspocus/client/src/main.tsx rename to examples/collaboration/hocuspocus/src/main.tsx diff --git a/examples/collaboration/hocuspocus/client/vite.config.js b/examples/collaboration/hocuspocus/vite.config.js similarity index 100% rename from examples/collaboration/hocuspocus/client/vite.config.js rename to examples/collaboration/hocuspocus/vite.config.js diff --git a/examples/collaboration/liveblocks/.env.example b/examples/collaboration/liveblocks/.env.example index f96042ee8b..485dc0b356 100644 --- a/examples/collaboration/liveblocks/.env.example +++ b/examples/collaboration/liveblocks/.env.example @@ -1,2 +1,2 @@ -VITE_LIVEBLOCKS_PUBLIC_KEY=pk_your_public_key_here -VITE_ROOM_ID=superdoc-example-room +VITE_LIVEBLOCKS_PUBLIC_KEY=pk_xxx +VITE_ROOM_ID=superdoc-room diff --git a/examples/collaboration/liveblocks/package.json b/examples/collaboration/liveblocks/package.json index 730d980b86..22e125a397 100644 --- a/examples/collaboration/liveblocks/package.json +++ b/examples/collaboration/liveblocks/package.json @@ -10,8 +10,8 @@ "@liveblocks/yjs": "^3.11.0", "react": "^19.2.1", "react-dom": "^19.2.1", - "superdoc": "link:../../../../packages/superdoc", - "yjs": "13.6.27" + "superdoc": "latest", + "yjs": "13.6.19" }, "devDependencies": { "@types/react": "^19.2.7", diff --git a/examples/collaboration/production/README.md b/examples/collaboration/production/README.md deleted file mode 100644 index cbd1646efb..0000000000 --- a/examples/collaboration/production/README.md +++ /dev/null @@ -1,127 +0,0 @@ -# Collaboration in Production Example - -A real-time collaboration example using SuperDoc and Y.js WebSocket synchronization. - -## Overview - -This example demonstrates: -- **Real-time collaboration** with multiple users editing the same document -- **User presence** showing who's currently editing -- **WebSocket synchronization** using Y.js -- **Image upload** with synchronized media handling - -## Architecture - -``` -┌─────────────────┐ WebSocket ┌─────────────────┐ -│ │◄──────────────────────────►│ │ -│ Client (Vue) │ Y.js sync │ Server │ -│ + SuperDoc │ │ (Fastify) │ -│ │ │ │ -└─────────────────┘ └─────────────────┘ -``` - -**Client**: Vue 3 application with SuperDoc editor and Y.js WebSocket provider -**Server**: Fastify server with WebSocket support for Y.js synchronization - -**Note**: This example does not persist documents. All collaboration is in-memory and ephemeral. Documents reset when the server restarts. - -## Quick Start - -From this directory, run: - -```bash -npm install -npm run dev -``` - -This will: -1. Install dependencies for both client and server -2. Start the server on http://localhost:3050 -3. Start the client on http://localhost:5173 - -Open http://localhost:5173 in your browser. Open multiple tabs or share the URL to test collaboration. - -## Testing Collaboration - -1. Open http://localhost:5173 in multiple browser tabs -2. Each tab will connect as a different user (randomly generated) -3. Start typing in one tab - changes appear in all tabs in real-time -4. See user presence indicators showing who's editing -5. Upload images - they sync across all connected clients - -## Project Structure - -``` -collaboration-in-production/ -├── client/ # Vue 3 frontend -│ ├── src/ -│ │ ├── DocumentEditor.vue # Main editor component -│ │ ├── Health.vue # Server health check -│ │ ├── router.js # Vue Router config -│ │ └── App.vue # Root component -│ └── package.json -├── server/ # Fastify backend -│ ├── index.ts # Server entry point -│ ├── userGenerator.ts # Random user identity generator -│ ├── storage.ts # Storage interface (no-op) -│ └── package.json -└── package.json # Root orchestration -``` - -## How It Works - -### Server -- Fastify server with `@fastify/websocket` for WebSocket support -- Uses `@superdoc-dev/superdoc-yjs-collaboration` to manage Y.js document rooms -- Assigns random user identities (adjective + animal + ID) -- No persistence - all documents exist in memory only - -### Client -- SuperDoc editor initialized with Y.js binding -- WebSocket provider connects to server for real-time sync -- User presence UI shows connected collaborators -- Image uploads synchronized via Y.js media map - -### Synchronization Flow -1. Client creates Y.js document and connects WebSocket provider -2. Server manages document state and broadcasts updates to all connected clients -3. Y.js handles CRDT-based conflict resolution automatically -4. Changes propagate in real-time to all connected clients - -## Extending This Example - -To add persistence, you could: -- Store Y.js document updates in a database (PostgreSQL, Redis, etc.) -- Implement the storage interface in [server/storage.ts](server/storage.ts) -- Convert Y.js documents to/from your preferred format (DOCX, JSON, etc.) - -For production use, consider: -- Authentication and authorization -- Document access control -- Rate limiting -- Monitoring and logging -- Horizontal scaling with shared state (Redis, etc.) - -## Environment Variables - -### Server (.env) -``` -PORT=3050 # Server port (default: 3050) -``` - -### Client (.env) -``` -VITE_API_URL=http://localhost:3050 # Server HTTP URL -VITE_WS_URL=ws://localhost:3050 # Server WebSocket URL -``` - -## Troubleshooting - -**Connection refused**: Ensure the server is running on port 3050 -**Changes not syncing**: Check browser console for WebSocket errors -**Port already in use**: Change `PORT` in server/.env - -## License - -See the main SuperDoc repository for license information. diff --git a/examples/collaboration/production/client/.env.example b/examples/collaboration/production/client/.env.example deleted file mode 100644 index 347a4fd52b..0000000000 --- a/examples/collaboration/production/client/.env.example +++ /dev/null @@ -1,9 +0,0 @@ -# Vite Environment Variables (VITE_ prefix required for client access) -# Copy this file to .env and update the values for your environment - -# Server Configuration -# API server URL (HTTP) -VITE_API_URL=http://localhost:3050 - -# WebSocket server URL -VITE_WS_URL=ws://localhost:3050 \ No newline at end of file diff --git a/examples/collaboration/production/client/package.json b/examples/collaboration/production/client/package.json deleted file mode 100644 index 705c790146..0000000000 --- a/examples/collaboration/production/client/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "vue-editor-js-collab", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "superdoc": "^0.31.2", - "vue": "^3.5.25", - "vue-router": "^4.6.3", - "yjs": "13.6.19" - }, - "devDependencies": { - "@vitejs/plugin-vue": "^6.0.2", - "vite": "^7.2.4" - } -} diff --git a/examples/collaboration/production/client/public/default.docx b/examples/collaboration/production/client/public/default.docx deleted file mode 100644 index 219f008686..0000000000 Binary files a/examples/collaboration/production/client/public/default.docx and /dev/null differ diff --git a/examples/collaboration/production/client/src/App.vue b/examples/collaboration/production/client/src/App.vue deleted file mode 100644 index cc52f1800e..0000000000 --- a/examples/collaboration/production/client/src/App.vue +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/examples/collaboration/production/client/src/DocumentEditor.vue b/examples/collaboration/production/client/src/DocumentEditor.vue deleted file mode 100644 index d5cbfa4da1..0000000000 --- a/examples/collaboration/production/client/src/DocumentEditor.vue +++ /dev/null @@ -1,352 +0,0 @@ - - - - - diff --git a/examples/collaboration/production/client/src/Health.vue b/examples/collaboration/production/client/src/Health.vue deleted file mode 100644 index 17c3713972..0000000000 --- a/examples/collaboration/production/client/src/Health.vue +++ /dev/null @@ -1,85 +0,0 @@ - - - - - \ No newline at end of file diff --git a/examples/collaboration/production/client/src/main.js b/examples/collaboration/production/client/src/main.js deleted file mode 100644 index e3a485c500..0000000000 --- a/examples/collaboration/production/client/src/main.js +++ /dev/null @@ -1,8 +0,0 @@ -import { createApp } from 'vue' -import './style.css' -import App from './App.vue' -import router from './router' - -const app = createApp(App) -app.use(router) -app.mount('#app') diff --git a/examples/collaboration/production/client/src/router.js b/examples/collaboration/production/client/src/router.js deleted file mode 100644 index fa99535453..0000000000 --- a/examples/collaboration/production/client/src/router.js +++ /dev/null @@ -1,40 +0,0 @@ -import { createRouter, createWebHistory } from 'vue-router'; -import DocumentEditor from './DocumentEditor.vue'; -import Health from './Health.vue'; - -const routes = [ - { - path: '/', - redirect: '/random' - }, - { - path: '/doc/:documentId', - name: 'Document', - component: DocumentEditor, - beforeEnter: (to) => { - // Redirect if trying to access default document directly - if (to.params.documentId === 'default') { - return '/random'; - } - } - }, - { - path: '/random', - redirect: () => { - const randomId = Math.random().toString(36).substring(2, 15); - return `/doc/${randomId}`; - } - }, - { - path: '/health', - name: 'Health', - component: Health - } -]; - -const router = createRouter({ - history: createWebHistory(), - routes -}); - -export default router; diff --git a/examples/collaboration/production/client/src/style.css b/examples/collaboration/production/client/src/style.css deleted file mode 100644 index ee33f0cfa0..0000000000 --- a/examples/collaboration/production/client/src/style.css +++ /dev/null @@ -1,75 +0,0 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color: rgba(0, 0, 0, 0.87); - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 2.2em; - line-height: 1.1; -} - -#app { - /* max-width: 1280px; */ - margin: 0 auto; - /* padding: 2rem; */ -} - -.my-custom-mark-class { - border-bottom: 1px solid #646cff; -} - -.example-container { - padding: 0 1em; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -} -.editor-and-button { - display: flex; - flex-direction: row; - align-items: flex-start; - justify-content: center; -} -.editor-buttons { - display: flex; - flex-direction: column; -} -.editor-buttons button { - margin-bottom: 10px; -} -.custom-button { - padding: 8px 12px; - border-radius: 8px; - margin-left: 10px; - outline: none; - border: none; - background-color: #AECEE6; -} -.hidden { - display: none; -} \ No newline at end of file diff --git a/examples/collaboration/production/client/vite.config.js b/examples/collaboration/production/client/vite.config.js deleted file mode 100644 index 85ce67a80c..0000000000 --- a/examples/collaboration/production/client/vite.config.js +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'vite' -import vue from '@vitejs/plugin-vue' - -// https://vite.dev/config/ -export default defineConfig({ - plugins: [vue()] -}) diff --git a/examples/collaboration/production/package.json b/examples/collaboration/production/package.json deleted file mode 100644 index b81acd9053..0000000000 --- a/examples/collaboration/production/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "collaboration-in-production-example", - "version": "1.0.0", - "private": true, - "description": "Minimal collaboration example with SuperDoc and Y.js", - "scripts": { - "dev": "concurrently \"pnpm run dev --prefix server\" \"pnpm run dev --prefix client\" --names \"server,client\" --prefix-colors \"blue,green\"" - }, - "devDependencies": { - "concurrently": "^9.2.1" - } -} diff --git a/examples/collaboration/production/server/.env.example b/examples/collaboration/production/server/.env.example deleted file mode 100644 index f0d4e7ad27..0000000000 --- a/examples/collaboration/production/server/.env.example +++ /dev/null @@ -1,2 +0,0 @@ -# Server Configuration -PORT=3050 diff --git a/examples/collaboration/production/server/index.ts b/examples/collaboration/production/server/index.ts deleted file mode 100644 index 9c0c757215..0000000000 --- a/examples/collaboration/production/server/index.ts +++ /dev/null @@ -1,94 +0,0 @@ -import 'dotenv/config'; - -import Fastify from 'fastify'; -import websocketPlugin from '@fastify/websocket'; -import corsPlugin from '@fastify/cors'; -import { readFileSync } from 'fs'; - -import { CollaborationBuilder, LoadFn, AutoSaveFn } from '@superdoc-dev/superdoc-yjs-collaboration'; -import { encodeStateAsUpdate, Doc as YDoc } from 'yjs'; - -import { saveDocument, loadDocument } from './storage.js'; -import { generateUser } from './userGenerator.js'; - -const errorHandlers: Record void> = { - LoadError: (error: Error, socket: any) => { - console.log('Document load failed:', error.message); - socket.close(1011, 'Document unavailable'); - }, - SaveError: (error: Error, socket: any) => { - console.log('Document save failed:', error.message); - // Don't close connection for save errors, just log - }, - default: (error: Error, socket: any) => { - console.log('Something went wrong:', error.message); - socket.close(1011, 'Unknown error'); - }, -}; - -const fastify = Fastify({ logger: false }); -fastify.register(corsPlugin, { origin: true }); -fastify.register(websocketPlugin); - -const SuperDocCollaboration = new CollaborationBuilder() - .withName('SuperDoc Collaboration service') - .withDebounce(2000) - .onLoad((async (params) => { - try { - const state = await loadDocument(params.documentId); - return state; - } catch(error) { - const err = new Error('Failed to load document: ' + error); - err.name = 'LoadError'; - throw err; - } - }) as LoadFn) - .onAutoSave((async (params) => { - try { - const { documentId, document } = params; - if (!document) throw new Error('No document to save'); - - const state = encodeStateAsUpdate(document); - const success = await saveDocument(documentId, state); - - if (!success) throw new Error('Save returned false'); - } catch (error) { - const err = new Error('Failed to save document: ' + error); - err.name = 'SaveError'; - throw err; - } - }) as AutoSaveFn) - .build(); - -/** Health check endpoint */ -fastify.get('/health', async (request, reply) => { - return { status: 'ok', timestamp: new Date().toISOString() }; -}); - -/** Generate user info endpoint */ -fastify.get('/user', async (request, reply) => { - return generateUser(); -}); - -/** An example route for websocket collaboration connection */ -fastify.register(async function (fastify) { - fastify.get('/doc/:documentId', { websocket: true }, async (socket, request) => { - try { - await SuperDocCollaboration.welcome(socket as any, request as any); - } catch (error) { - const err = error as Error; - const errorHandler = errorHandlers[err.name] || errorHandlers.default; - errorHandler(err, socket); - } - }) -}); - -/** Start the server */ -const port = parseInt(process.env.PORT || '3050'); -const host = '0.0.0.0'; // Listen on all interfaces for Cloud Run -fastify.listen({ port, host }, (err: Error | null, address?: string): void => { - if (err) { - fastify.log.error(err); - process.exit(1); - } -}); \ No newline at end of file diff --git a/examples/collaboration/production/server/package.json b/examples/collaboration/production/server/package.json deleted file mode 100644 index 2d8798def0..0000000000 --- a/examples/collaboration/production/server/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "fastify-esm-example", - "version": "1.0.0", - "type": "module", - "scripts": { - "dev": "tsx watch index.ts", - "start": "node dist/index.js", - "build": "tsc", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@fastify/cors": "^11.1.0", - "@fastify/multipart": "^9.3.0", - "@fastify/websocket": "^11.2.0", - "@superdoc-dev/superdoc-yjs-collaboration": "^1.0.2", - "dotenv": "^17.2.3", - "fastify": "^5.6.2", - "y-protocols": "^1.0.6", - "y-websocket": "^3.0.0", - "yjs": "13.6.19" - }, - "devDependencies": { - "@types/node": "^24.10.1", - "@types/ws": "^8.18.1", - "tsx": "^4.20.6", - "typescript": "^5.9.3" - } -} diff --git a/examples/collaboration/production/server/storage-types.ts b/examples/collaboration/production/server/storage-types.ts deleted file mode 100644 index d7fe863dc8..0000000000 --- a/examples/collaboration/production/server/storage-types.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type StorageFunction = (id: string, file?: Uint8Array) => Promise; - -export interface StorageHandler { - save: StorageFunction; - load: StorageFunction; -} \ No newline at end of file diff --git a/examples/collaboration/production/server/storage.ts b/examples/collaboration/production/server/storage.ts deleted file mode 100644 index 9103f0fbda..0000000000 --- a/examples/collaboration/production/server/storage.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { StorageFunction } from './storage-types.js'; -import { Doc as YDoc, encodeStateAsUpdate } from 'yjs'; - -const blankDocxYdoc = new YDoc(); -const metaMap = blankDocxYdoc.getMap('meta'); - -// Add minimal DOCX structure that the client expects -metaMap.set('docx', []); - -export const loadDocument: StorageFunction = async (id: string) => { - // Return an empty Y.js document with minimal DOCX structure - return encodeStateAsUpdate(blankDocxYdoc); -}; - -export const saveDocument: StorageFunction = async (id: string, file?: Uint8Array) => { - // No-op - just return success - return true; -}; \ No newline at end of file diff --git a/examples/collaboration/production/server/tsconfig.json b/examples/collaboration/production/server/tsconfig.json deleted file mode 100644 index a2fff94db6..0000000000 --- a/examples/collaboration/production/server/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "include": ["./**/*.ts"], - "exclude": ["node_modules", "dist"], - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "lib": ["ES2022", "DOM"], - "outDir": "./dist", - "rootDir": "./", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": false, - "forceConsistentCasingInFileNames": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "allowSyntheticDefaultImports": true, - "resolveJsonModule": true - } -} \ No newline at end of file diff --git a/examples/collaboration/production/server/userGenerator.ts b/examples/collaboration/production/server/userGenerator.ts deleted file mode 100644 index a82447ea26..0000000000 --- a/examples/collaboration/production/server/userGenerator.ts +++ /dev/null @@ -1,25 +0,0 @@ -const ADJECTIVES = [ - 'Happy', 'Clever', 'Brave', 'Swift', 'Bright', 'Calm', 'Bold', 'Quick', - 'Wise', 'Cool', 'Kind', 'Smart', 'Wild', 'Free', 'Pure', 'Strong', - 'Gentle', 'Noble', 'Fierce', 'Sharp', 'Sleek', 'Vivid', 'Zesty' -]; - -const ANIMALS = [ - 'Tiger', 'Eagle', 'Wolf', 'Fox', 'Bear', 'Lion', 'Hawk', 'Deer', - 'Owl', 'Shark', 'Lynx', 'Otter', 'Raven', 'Falcon', 'Puma', 'Seal', - 'Whale', 'Cobra', 'Gecko', 'Moose', 'Bison', 'Crane', 'Viper' -]; - -function generateUser() { - const userId = Math.random().toString().substring(2, 6); - const adjective = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)]; - const animal = ANIMALS[Math.floor(Math.random() * ANIMALS.length)]; - const fullName = `${adjective} ${animal}-${userId}`; - - return { - name: fullName, - email: `${adjective.toLowerCase()}.${animal.toLowerCase()}${userId}@example.com` - }; -} - -export { ADJECTIVES, ANIMALS, generateUser }; \ No newline at end of file diff --git a/examples/customization/custom-mark/.gitignore b/examples/collaboration/superdoc-yjs/.gitignore similarity index 100% rename from examples/customization/custom-mark/.gitignore rename to examples/collaboration/superdoc-yjs/.gitignore diff --git a/examples/collaboration/superdoc-yjs/README.md b/examples/collaboration/superdoc-yjs/README.md new file mode 100644 index 0000000000..fb2eca351f --- /dev/null +++ b/examples/collaboration/superdoc-yjs/README.md @@ -0,0 +1,14 @@ +# SuperDoc + Yjs Collaboration + +Self-hosted collaboration using `@superdoc-dev/superdoc-yjs-collaboration` with a Fastify WebSocket server. + +**Docs:** [Self-hosted with SuperDoc Yjs](https://docs.superdoc.dev/guides/superdoc-yjs) + +## Getting started + +```bash +npm install +npm run dev +``` + +This starts both the Fastify collaboration server (port 3050) and the Vue client (port 5173). Open two browser tabs to see real-time collaboration. diff --git a/examples/collaboration/production/client/index.html b/examples/collaboration/superdoc-yjs/index.html similarity index 100% rename from examples/collaboration/production/client/index.html rename to examples/collaboration/superdoc-yjs/index.html diff --git a/examples/collaboration/superdoc-yjs/package.json b/examples/collaboration/superdoc-yjs/package.json new file mode 100644 index 0000000000..be56d746a6 --- /dev/null +++ b/examples/collaboration/superdoc-yjs/package.json @@ -0,0 +1,28 @@ +{ + "name": "superdoc-yjs-collaboration", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"", + "dev:client": "vite", + "dev:server": "tsx watch server.ts", + "build": "vite build" + }, + "dependencies": { + "superdoc": "latest", + "vue": "^3.5.13", + "@superdoc-dev/superdoc-yjs-collaboration": "^1.0.0", + "@fastify/websocket": "^11.1.0", + "fastify": "^5.3.3", + "y-protocols": "^1.0.6", + "y-websocket": "^3.0.0", + "yjs": "13.6.19" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.2", + "concurrently": "^9.0.0", + "tsx": "^4.20.6", + "vite": "^6.0.0" + } +} diff --git a/examples/customization/custom-mark/public/logo.webp b/examples/collaboration/superdoc-yjs/public/logo.webp similarity index 100% rename from examples/customization/custom-mark/public/logo.webp rename to examples/collaboration/superdoc-yjs/public/logo.webp diff --git a/examples/collaboration/basic/public/sample-document.docx b/examples/collaboration/superdoc-yjs/public/sample-document.docx similarity index 100% rename from examples/collaboration/basic/public/sample-document.docx rename to examples/collaboration/superdoc-yjs/public/sample-document.docx diff --git a/examples/collaboration/fastify-server/index.ts b/examples/collaboration/superdoc-yjs/server.ts similarity index 100% rename from examples/collaboration/fastify-server/index.ts rename to examples/collaboration/superdoc-yjs/server.ts diff --git a/examples/collaboration/superdoc/src/App.vue b/examples/collaboration/superdoc-yjs/src/App.vue similarity index 78% rename from examples/collaboration/superdoc/src/App.vue rename to examples/collaboration/superdoc-yjs/src/App.vue index b66ca3ff5b..c6a3c6b1d6 100644 --- a/examples/collaboration/superdoc/src/App.vue +++ b/examples/collaboration/superdoc-yjs/src/App.vue @@ -77,31 +77,4 @@ onBeforeUnmount(() => { border: 1px solid #ccc; border-radius: 8px; } -.fields > div { - margin-bottom: 10px; -} -textarea { - margin-left: 10px; -} -.my-custom-node-default-class { - background-color: #1355FF; - border-radius: 8px; - cursor: pointer; - color: white; - display: inline-block; - padding: 2px 8px; - font-size: 12px; -} -.my-custom-node-default-class:hover { - background-color: #0a3dff; -} -.draggable-field { - background-color: #1355FF; - border-radius: 8px; - cursor: pointer; - color: white; - display: inline-block; - padding: 2px 8px; - font-size: 12px; -} diff --git a/examples/customization/custom-mark/src/main.js b/examples/collaboration/superdoc-yjs/src/main.js similarity index 100% rename from examples/customization/custom-mark/src/main.js rename to examples/collaboration/superdoc-yjs/src/main.js diff --git a/examples/collaboration/basic/src/style.css b/examples/collaboration/superdoc-yjs/src/style.css similarity index 100% rename from examples/collaboration/basic/src/style.css rename to examples/collaboration/superdoc-yjs/src/style.css diff --git a/examples/customization/custom-mark/vite.config.js b/examples/collaboration/superdoc-yjs/vite.config.js similarity index 100% rename from examples/customization/custom-mark/vite.config.js rename to examples/collaboration/superdoc-yjs/vite.config.js diff --git a/examples/collaboration/superdoc/README.md b/examples/collaboration/superdoc/README.md deleted file mode 100644 index bf8c7354f7..0000000000 --- a/examples/collaboration/superdoc/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# SuperDoc collaboration example - -This example shows how to run the full SuperDoc experience (toolbar + editor) while connecting to the collaboration server that lives in `../fastify`. - -## Getting started -For this example, we recommend you run the provided ```fastify``` example in the examples directory. -With two terminals: - -Terminal 1 (in this folder): The SuperDoc frontend -``` -npm install && npm run dev -``` - -Terminal 2 (in ../fastify folder): The backend collaboration server -``` -npm install && npm run dev -``` - -Point your browser (2 tabs) to: ```http://localhost:5173``` - -This will run both a SuperDoc-powered frontend, and a Fastify server running ```superdoc-yjs-collaboration``` and a single websocket endpoint. The editors should be collaborating once both tabs have loaded the sample document. diff --git a/examples/collaboration/superdoc/index.html b/examples/collaboration/superdoc/index.html deleted file mode 100644 index 4807e41528..0000000000 --- a/examples/collaboration/superdoc/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - SuperDoc Examples - - -
- - - diff --git a/examples/collaboration/superdoc/package.json b/examples/collaboration/superdoc/package.json deleted file mode 100644 index 17894e4a7f..0000000000 --- a/examples/collaboration/superdoc/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "superdoc-collaboration", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "superdoc": "^0.26.0", - "vue": "^3.5.13" - }, - "devDependencies": { - "@vitejs/plugin-vue": "^5.2.2", - "vite": "^6.0.0" - } -} diff --git a/examples/collaboration/superdoc/public/sample-document.docx b/examples/collaboration/superdoc/public/sample-document.docx deleted file mode 100644 index 3dfdba3cb2..0000000000 Binary files a/examples/collaboration/superdoc/public/sample-document.docx and /dev/null differ diff --git a/examples/collaboration/superdoc/src/UploadFile.vue b/examples/collaboration/superdoc/src/UploadFile.vue deleted file mode 100644 index 86c92e293a..0000000000 --- a/examples/collaboration/superdoc/src/UploadFile.vue +++ /dev/null @@ -1,30 +0,0 @@ - - - diff --git a/examples/collaboration/superdoc/src/style.css b/examples/collaboration/superdoc/src/style.css deleted file mode 100644 index 30a0d26daf..0000000000 --- a/examples/collaboration/superdoc/src/style.css +++ /dev/null @@ -1,79 +0,0 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color: rgba(0, 0, 0, 0.87); - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 2.2em; - line-height: 1.1; -} - -#app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; -} - -.my-custom-mark-class { - border-bottom: 1px solid #646cff; -} - -.example-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -} -.editor-and-button { - display: flex; - flex-direction: row; - align-items: flex-start; - justify-content: center; -} -.editor-buttons { - display: flex; - flex-direction: column; -} -.editor-buttons button { - margin-bottom: 10px; -} -.custom-button { - padding: 8px 12px; - border-radius: 8px; - margin-left: 10px; - outline: none; - border: none; - background-color: #AECEE6; -} -.hidden { - display: none; -} - -p { - padding: 0; - margin: 0; -} \ No newline at end of file diff --git a/examples/collaboration/tiptap-cloud/.env.example b/examples/collaboration/tiptap-cloud/.env.example deleted file mode 100644 index 145b730e73..0000000000 --- a/examples/collaboration/tiptap-cloud/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -VITE_TIPTAP_APP_ID=your-app-id -VITE_TIPTAP_TOKEN=your-jwt-token -VITE_ROOM_ID=superdoc-room diff --git a/examples/collaboration/tiptap-cloud/.npmrc.example b/examples/collaboration/tiptap-cloud/.npmrc.example deleted file mode 100644 index 991dac62f8..0000000000 --- a/examples/collaboration/tiptap-cloud/.npmrc.example +++ /dev/null @@ -1,2 +0,0 @@ -@tiptap-pro:registry=https://registry.tiptap.dev/ -//registry.tiptap.dev/:_authToken=YOUR_NPM_TOKEN diff --git a/examples/collaboration/tiptap-cloud/README.md b/examples/collaboration/tiptap-cloud/README.md deleted file mode 100644 index 6a7ee04090..0000000000 --- a/examples/collaboration/tiptap-cloud/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# SuperDoc + TipTap Cloud - -Real-time collaboration using [TipTap Cloud](https://cloud.tiptap.dev/) (managed Hocuspocus). - -**Note:** Requires TipTap Pro subscription and access to their private npm registry. - -## Setup - -1. **Create `.npmrc`** from `.npmrc.example` with your NPM token from TipTap Cloud Settings - -2. **Create `.env`** from `.env.example`: - ``` - VITE_TIPTAP_APP_ID=your-app-id - VITE_TIPTAP_TOKEN=your-jwt-token - ``` - -3. **Install and run** - ```bash - pnpm install - pnpm dev - ``` - -4. Open http://localhost:3000 in multiple tabs - -## How It Works - -```ts -import { TiptapCollabProvider } from '@tiptap-pro/provider'; -import * as Y from 'yjs'; - -const ydoc = new Y.Doc(); -const provider = new TiptapCollabProvider({ - appId: 'your-app-id', - name: 'room-id', - document: ydoc, - token: 'your-token', -}); - -new SuperDoc({ - modules: { - collaboration: { ydoc, provider }, - }, -}); -``` - -## Self-Hosted Alternative - -For self-hosted deployments, see the [hocuspocus example](../../self-hosted/hocuspocus/). diff --git a/examples/collaboration/tiptap-cloud/index.html b/examples/collaboration/tiptap-cloud/index.html deleted file mode 100644 index 6540a3f1af..0000000000 --- a/examples/collaboration/tiptap-cloud/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - SuperDoc + TipTap Cloud - - - -
- - - diff --git a/examples/collaboration/tiptap-cloud/src/App.tsx b/examples/collaboration/tiptap-cloud/src/App.tsx deleted file mode 100644 index 7719f1f3dc..0000000000 --- a/examples/collaboration/tiptap-cloud/src/App.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; -import { TiptapCollabProvider } from '@tiptap-pro/provider'; -import * as Y from 'yjs'; -import 'superdoc/style.css'; -import { SuperDoc } from 'superdoc'; - -const APP_ID = import.meta.env.VITE_TIPTAP_APP_ID as string; -const TOKEN = import.meta.env.VITE_TIPTAP_TOKEN as string; -const ROOM_ID = (import.meta.env.VITE_ROOM_ID as string) || 'superdoc-room'; - -export default function App() { - const superdocRef = useRef(null); - const [users, setUsers] = useState([]); - - useEffect(() => { - if (!APP_ID || !TOKEN) { - console.error('Missing VITE_TIPTAP_APP_ID or VITE_TIPTAP_TOKEN'); - return; - } - - const ydoc = new Y.Doc(); - const provider = new TiptapCollabProvider({ - appId: APP_ID, - name: ROOM_ID, - document: ydoc, - token: TOKEN, - }); - - provider.on('synced', () => { - superdocRef.current = new SuperDoc({ - selector: '#superdoc', - documentMode: 'editing', - user: { name: `User ${Math.floor(Math.random() * 1000)}`, email: 'user@example.com' }, - modules: { - collaboration: { ydoc, provider }, - }, - onAwarenessUpdate: ({ states }: any) => setUsers(states.filter((s: any) => s.user)), - }); - }); - - return () => { - superdocRef.current?.destroy(); - provider.destroy(); - }; - }, []); - - return ( -
-
-

SuperDoc + TipTap Cloud

-
- {users.map((u, i) => ( - - {u.user?.name} - - ))} -
-
-
-
-
-
- ); -} diff --git a/examples/collaboration/y-sweet/README.md b/examples/collaboration/y-sweet/README.md deleted file mode 100644 index ee1c761fc9..0000000000 --- a/examples/collaboration/y-sweet/README.md +++ /dev/null @@ -1,102 +0,0 @@ -# SuperDoc + Y-Sweet Example - -This example demonstrates using SuperDoc with [Y-Sweet](https://docs.jamsocket.com/y-sweet) as a self-hosted collaboration provider. - -## Architecture - -``` -┌─────────────┐ ┌─────────────┐ ┌─────────────┐ -│ Client │────▶│ Auth Server │────▶│ Y-Sweet │ -│ (React) │ │ (Express) │ │ Server │ -└─────────────┘ └─────────────┘ └─────────────┘ - :3000 :3001 :8080 -``` - -- **Y-Sweet Server** (port 8080): The Yjs sync engine that handles real-time collaboration -- **Auth Server** (port 3001): Express server that authenticates clients and issues tokens -- **Client** (port 3000): React app with SuperDoc - -## Setup - -1. **Install dependencies** - ```bash - pnpm install - ``` - -2. **Start the Y-Sweet server** (in terminal 1) - ```bash - pnpm dev:y-sweet - ``` - This runs `npx y-sweet@latest serve` on port 8080. - -3. **Start the auth server and client** (in terminal 2) - ```bash - pnpm dev - ``` - This starts both the Express auth server (:3001) and Vite dev server (:3000). - -4. Open http://localhost:3000 in multiple browser tabs to test collaboration - -## How It Works - -### Auth Server (`server/server.js`) -```js -import { DocumentManager } from '@y-sweet/sdk'; - -const manager = new DocumentManager('ys://127.0.0.1:8080'); - -app.post('/api/auth', async (req, res) => { - const { docId } = req.body; - const clientToken = await manager.getOrCreateDocAndToken(docId); - res.json(clientToken); -}); -``` - -### Client (`client/src/App.tsx`) -```js -import { createYjsProvider } from '@y-sweet/client'; -import * as Y from 'yjs'; - -const ydoc = new Y.Doc(); -const provider = createYjsProvider(ydoc, 'doc-id', 'http://localhost:3001/api/auth'); - -provider.on('sync', (synced) => { - if (!synced) return; - - new SuperDoc({ - selector: '#superdoc', - modules: { - collaboration: { ydoc, provider }, - }, - }); -}); -``` - -## Configuration - -### Environment Variables - -**Server** (`server/.env`): -```bash -CONNECTION_STRING=ys://127.0.0.1:8080 # Y-Sweet server URL -PORT=3001 # Auth server port -``` - -**Client** (`client/.env`): -```bash -VITE_Y_SWEET_AUTH_ENDPOINT=http://localhost:3001/api/auth -VITE_DOC_ID=superdoc-example-room -``` - -### Persisting Data - -By default, Y-Sweet stores data in memory. To persist to disk: -```bash -npx y-sweet@latest serve ./data -``` - -## Resources - -- [Y-Sweet Documentation](https://docs.jamsocket.com/y-sweet) -- [Y-Sweet GitHub](https://github.com/jamsocket/y-sweet) -- [Y-Sweet SDK Reference](https://docs.y-sweet.dev/) diff --git a/examples/collaboration/y-sweet/client/index.html b/examples/collaboration/y-sweet/client/index.html deleted file mode 100644 index 9d93b2d41f..0000000000 --- a/examples/collaboration/y-sweet/client/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - SuperDoc + Y-Sweet - - - -
- - - diff --git a/examples/collaboration/y-sweet/client/src/App.tsx b/examples/collaboration/y-sweet/client/src/App.tsx deleted file mode 100644 index 5cc6f42504..0000000000 --- a/examples/collaboration/y-sweet/client/src/App.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; -import { createYjsProvider } from '@y-sweet/client'; -import * as Y from 'yjs'; -import 'superdoc/style.css'; -import { SuperDoc } from 'superdoc'; - -const AUTH_ENDPOINT = (import.meta.env.VITE_Y_SWEET_AUTH_ENDPOINT as string) || 'http://localhost:3001/api/auth'; -const DOC_ID = (import.meta.env.VITE_DOC_ID as string) || 'superdoc-room'; - -export default function App() { - const superdocRef = useRef(null); - const [users, setUsers] = useState([]); - - useEffect(() => { - const ydoc = new Y.Doc(); - const provider = createYjsProvider(ydoc, DOC_ID, AUTH_ENDPOINT); - - provider.on('sync', (synced: boolean) => { - if (!synced) return; - - superdocRef.current = new SuperDoc({ - selector: '#superdoc', - documentMode: 'editing', - user: { name: `User ${Math.floor(Math.random() * 1000)}`, email: 'user@example.com' }, - modules: { - collaboration: { ydoc, provider }, - }, - onAwarenessUpdate: ({ states }: any) => setUsers(states.filter((s: any) => s.user)), - }); - }); - - return () => { - superdocRef.current?.destroy(); - provider.destroy(); - }; - }, []); - - return ( -
-
-

SuperDoc + Y-Sweet

-
- {users.map((u, i) => ( - - {u.user?.name} - - ))} -
-
-
-
-
-
- ); -} diff --git a/examples/collaboration/y-sweet/client/src/main.tsx b/examples/collaboration/y-sweet/client/src/main.tsx deleted file mode 100644 index c244d7c7e2..0000000000 --- a/examples/collaboration/y-sweet/client/src/main.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import App from './App'; - -ReactDOM.createRoot(document.getElementById('root')).render(); diff --git a/examples/collaboration/y-sweet/package.json b/examples/collaboration/y-sweet/package.json deleted file mode 100644 index fc66f22377..0000000000 --- a/examples/collaboration/y-sweet/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "superdoc-y-sweet-example", - "private": true, - "scripts": { - "dev": "npx concurrently -n y-sweet,server,client -c blue,green,cyan \"npx y-sweet@latest serve\" \"pnpm --filter ./server dev\" \"pnpm --filter ./client dev\"", - "dev:y-sweet": "npx y-sweet@latest serve", - "build": "pnpm --filter ./client build" - } -} diff --git a/examples/collaboration/y-sweet/pnpm-workspace.yaml b/examples/collaboration/y-sweet/pnpm-workspace.yaml deleted file mode 100644 index b2eff6c81c..0000000000 --- a/examples/collaboration/y-sweet/pnpm-workspace.yaml +++ /dev/null @@ -1,3 +0,0 @@ -packages: - - client - - server diff --git a/examples/collaboration/y-sweet/server/package.json b/examples/collaboration/y-sweet/server/package.json deleted file mode 100644 index 777141b158..0000000000 --- a/examples/collaboration/y-sweet/server/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "y-sweet-server", - "type": "module", - "scripts": { - "dev": "node server.js" - }, - "dependencies": { - "@y-sweet/sdk": "^0.9.1", - "cors": "^2.8.5", - "express": "^5.2.1" - } -} diff --git a/examples/collaboration/y-sweet/server/server.js b/examples/collaboration/y-sweet/server/server.js deleted file mode 100644 index 493cf416ef..0000000000 --- a/examples/collaboration/y-sweet/server/server.js +++ /dev/null @@ -1,28 +0,0 @@ -import express from 'express'; -import cors from 'cors'; -import { DocumentManager } from '@y-sweet/sdk'; - -const CONNECTION_STRING = process.env.CONNECTION_STRING || 'ys://127.0.0.1:8080'; -const PORT = process.env.PORT || 3001; - -const app = express(); -app.use(cors()); -app.use(express.json()); - -const manager = new DocumentManager(CONNECTION_STRING); - -app.post('/api/auth', async (req, res) => { - try { - const { docId } = req.body; - const clientToken = await manager.getOrCreateDocAndToken(docId); - res.json(clientToken); - } catch (error) { - console.error('Auth error:', error); - res.status(500).json({ error: 'Failed to get auth token' }); - } -}); - -app.listen(PORT, () => { - console.log(`Auth server running on http://localhost:${PORT}`); - console.log(`Using Y-Sweet server at: ${CONNECTION_STRING}`); -}); diff --git a/examples/customization/README.md b/examples/customization/README.md deleted file mode 100644 index 9f0b55cbeb..0000000000 --- a/examples/customization/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Customization Examples - -Examples showing how to customize and extend SuperDoc's editor functionality. - -## Examples - -| Example | Description | -|---------|-------------| -| [toolbar](./toolbar) | Customize the editor toolbar | -| [custom-mark](./custom-mark) | Create custom text marks (formatting) | -| [custom-node](./custom-node) | Create custom node types | -| [custom-plugin](./custom-plugin) | Build custom editor plugins | -| [dynamic-content](./dynamic-content) | External plugin with dynamic content injection | - -## Running an Example - -```bash -cd -npm install -npm run dev -``` diff --git a/examples/customization/custom-mark/README.md b/examples/customization/custom-mark/README.md deleted file mode 100644 index 867ab7622f..0000000000 --- a/examples/customization/custom-mark/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# SuperDoc -## SuperDoc: Creating a custom mark - -An example of creating a custom Mark to use with SuperDoc. -Note: Requires `SuperDoc 0.10.15` or later - -[We create a custom mark here](https://github.com/superdoc-dev/superdoc/blob/main/examples/customization/custom-mark/src/custom-mark.js) -Note that we added a custom command to the mark, called setMyCustomMark. We can now insert this mark by calling this command from `superdoc.activeEditor.commands` - -[Then we can pass it into the editor via the `editorExtensions` key](https://github.com/superdoc-dev/superdoc/blob/main/examples/customization/custom-mark/src/App.vue#L20) - -## Exporting the docx -This example also shows one way to export the docx to a blob whenever the content changes in the editor diff --git a/examples/customization/custom-mark/src/UploadFile.vue b/examples/customization/custom-mark/src/UploadFile.vue deleted file mode 100644 index 86c92e293a..0000000000 --- a/examples/customization/custom-mark/src/UploadFile.vue +++ /dev/null @@ -1,30 +0,0 @@ - - - diff --git a/examples/customization/custom-node/.gitignore b/examples/customization/custom-node/.gitignore deleted file mode 100644 index a547bf36d8..0000000000 --- a/examples/customization/custom-node/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/examples/customization/custom-node/README.md b/examples/customization/custom-node/README.md deleted file mode 100644 index fc470c68ea..0000000000 --- a/examples/customization/custom-node/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# SuperDoc -## SuperDoc: Creating a custom node - -An example of creating a custom node to use with SuperDoc. -Note: Requires `SuperDoc 0.10.15` or later - -[We create a custom node here](https://github.com/superdoc-dev/superdoc/blob/develop/examples/customization/custom-node/src/custom-node.js#L62) -Note that we added a custom command to the node, called insertCustomNode. We can now insert this node by simply calling this command from editor.commands.insertCustomNode - -[Then we can pass it into the editor via the `editorExtensions` key](https://github.com/superdoc-dev/superdoc/blob/develop/examples/customization/custom-node/src/App.vue#L20) - diff --git a/examples/customization/custom-node/index.html b/examples/customization/custom-node/index.html deleted file mode 100644 index 4807e41528..0000000000 --- a/examples/customization/custom-node/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - SuperDoc Examples - - -
- - - diff --git a/examples/customization/custom-node/package.json b/examples/customization/custom-node/package.json deleted file mode 100644 index df6ef44cfc..0000000000 --- a/examples/customization/custom-node/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "vue-html-editor", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "superdoc": "0.20.0-next.13", - "vue": "^3.5.13" - }, - "devDependencies": { - "@vitejs/plugin-vue": "^5.2.2", - "vite": "^6.3.1" - } -} diff --git a/examples/customization/custom-node/public/logo.webp b/examples/customization/custom-node/public/logo.webp deleted file mode 100644 index f36a71b86d..0000000000 Binary files a/examples/customization/custom-node/public/logo.webp and /dev/null differ diff --git a/examples/customization/custom-node/src/UploadFile.vue b/examples/customization/custom-node/src/UploadFile.vue deleted file mode 100644 index 86c92e293a..0000000000 --- a/examples/customization/custom-node/src/UploadFile.vue +++ /dev/null @@ -1,30 +0,0 @@ - - - diff --git a/examples/customization/custom-node/src/main.js b/examples/customization/custom-node/src/main.js deleted file mode 100644 index 2425c0f745..0000000000 --- a/examples/customization/custom-node/src/main.js +++ /dev/null @@ -1,5 +0,0 @@ -import { createApp } from 'vue' -import './style.css' -import App from './App.vue' - -createApp(App).mount('#app') diff --git a/examples/customization/custom-node/vite.config.js b/examples/customization/custom-node/vite.config.js deleted file mode 100644 index bbcf80cca9..0000000000 --- a/examples/customization/custom-node/vite.config.js +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'vite' -import vue from '@vitejs/plugin-vue' - -// https://vite.dev/config/ -export default defineConfig({ - plugins: [vue()], -}) diff --git a/examples/customization/custom-plugin/.gitignore b/examples/customization/custom-plugin/.gitignore deleted file mode 100644 index a547bf36d8..0000000000 --- a/examples/customization/custom-plugin/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/examples/customization/custom-plugin/README.md b/examples/customization/custom-plugin/README.md deleted file mode 100644 index 3aa79f32d5..0000000000 --- a/examples/customization/custom-plugin/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Vue Custom Plugin Example - -This example shows how to use SuperDoc inside a Vue 3 + Vite application and register a custom extension that injects a ProseMirror plugin via `addPmPlugins()`. - -The plugin listens for selection changes, highlights the block node that currently contains the cursor, and emits a `custom-plugin:active-block` event. The host Vue component listens to that event and displays a short preview of the active block. - -## Getting Started - -```bash -npm install -npm run dev -``` - -Then open the printed local URL in your browser. Click around inside the editor to see the highlight and the metadata update. Use the **Import DOCX** button to load a document, or **Reset Document** to return to a blank state. - -## Key Files - -- `src/custom-plugin-extension.js` – defines the SuperDoc extension and ProseMirror plugin. -- `src/App.vue` – boots the editor, wires up the custom plugin events, and handles document uploading. diff --git a/examples/customization/custom-plugin/custom-plugin-extension.js b/examples/customization/custom-plugin/custom-plugin-extension.js deleted file mode 100644 index 53abc82656..0000000000 --- a/examples/customization/custom-plugin/custom-plugin-extension.js +++ /dev/null @@ -1 +0,0 @@ -export { customPluginExtension } from './src/custom-plugin-extension.js'; diff --git a/examples/customization/custom-plugin/demo-config.json b/examples/customization/custom-plugin/demo-config.json deleted file mode 100644 index 53429b4e21..0000000000 --- a/examples/customization/custom-plugin/demo-config.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "dirname": "vue-custom-plugin", - "tags": [ - "editing", - "viewing", - "vue", - "plugins" - ], - "title": "Custom ProseMirror Plugin" -} diff --git a/examples/customization/custom-plugin/index.html b/examples/customization/custom-plugin/index.html deleted file mode 100644 index 4807e41528..0000000000 --- a/examples/customization/custom-plugin/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - SuperDoc Examples - - -
- - - diff --git a/examples/customization/custom-plugin/package.json b/examples/customization/custom-plugin/package.json deleted file mode 100644 index 59b04aa81a..0000000000 --- a/examples/customization/custom-plugin/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "vue-custom-plugin", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "superdoc": "0.20.0-next.13", - "vue": "^3.5.13" - }, - "devDependencies": { - "@vitejs/plugin-vue": "^5.2.2", - "vite": "^6.3.1" - } -} diff --git a/examples/customization/custom-plugin/public/logo.webp b/examples/customization/custom-plugin/public/logo.webp deleted file mode 100644 index f36a71b86d..0000000000 Binary files a/examples/customization/custom-plugin/public/logo.webp and /dev/null differ diff --git a/examples/customization/custom-plugin/public/sample-document.docx b/examples/customization/custom-plugin/public/sample-document.docx deleted file mode 100644 index b88e504ef0..0000000000 Binary files a/examples/customization/custom-plugin/public/sample-document.docx and /dev/null differ diff --git a/examples/customization/custom-plugin/src/App.vue b/examples/customization/custom-plugin/src/App.vue deleted file mode 100644 index 3a56d7a359..0000000000 --- a/examples/customization/custom-plugin/src/App.vue +++ /dev/null @@ -1,213 +0,0 @@ - - - diff --git a/examples/customization/custom-plugin/src/custom-plugin-extension.js b/examples/customization/custom-plugin/src/custom-plugin-extension.js deleted file mode 100644 index 3d91268f5b..0000000000 --- a/examples/customization/custom-plugin/src/custom-plugin-extension.js +++ /dev/null @@ -1,189 +0,0 @@ -import { Extensions } from 'superdoc/super-editor'; -import { Plugin, PluginKey } from 'prosemirror-state'; - -const { Extension } = Extensions; - -const activeBlockPluginKey = new PluginKey('customPluginActiveBlock'); - -const ZERO_WIDTH_SPACE = /\u200B/g; - -/** - * Normalise text by stripping zero-width characters and collapsing - * whitespace so previews are deterministic. - */ -const collapseTextPreview = (text = '') => text.replace(ZERO_WIDTH_SPACE, '').replace(/\s+/g, ' ').trim(); - -/** - * Shallow compare two plain objects. Keeps block metadata change checks cheap. - */ -const shallowEqual = (a, b) => { - if (a === b) { - return true; - } - - const aObj = a || {}; - const bObj = b || {}; - const aKeys = Object.keys(aObj); - const bKeys = Object.keys(bObj); - - if (aKeys.length !== bKeys.length) { - return false; - } - - return aKeys.every((key) => aObj[key] === bObj[key]); -}; - -/** - * Tracks the block-level node under the user's most recent interaction and - * emits lightweight metadata so the host app can respond (e.g. by styling it). - */ -export const customPluginExtension = Extension.create({ - name: 'customPluginExtension', - - addPmPlugins() { - const extension = this; - // Tracks whether the next transaction originated from an explicit user gesture - // so we can ignore SuperDoc's bootstrapping transactions. - let pendingInteraction = false; - - const buildInitialState = (hasInteracted) => ({ - blockInfo: null, - hasInteracted, - }); - - const emitActiveBlock = (info) => { - extension.editor?.emit('custom-plugin:active-block', info ?? null); - }; - - return [ - new Plugin({ - key: activeBlockPluginKey, - state: { - init() { - return buildInitialState(false); - }, - - apply(tr, pluginState, _oldState, newState) { - const interactionMeta = tr.getMeta(activeBlockPluginKey); - const interactionOccurred = pendingInteraction === true || interactionMeta?.interaction === true; - const hasInteracted = pluginState.hasInteracted || interactionOccurred; - - pendingInteraction = false; - - const resetState = (hasInteractedFlag) => { - if (pluginState.blockInfo) { - emitActiveBlock(null); - } - return buildInitialState(hasInteractedFlag); - }; - - if (!hasInteracted) { - return resetState(false); - } - - // Bail early unless the document/selection actually changed, or the user explicitly interacted. - const shouldRecompute = - interactionOccurred || - tr.docChanged || - tr.selectionSet || - pluginState.blockInfo === null; - - if (!shouldRecompute) { - return { - ...pluginState, - hasInteracted, - }; - } - - const { selection } = newState; - const $from = selection?.$from; - - if (!$from) { - return resetState(hasInteracted); - } - - let blockDepth = null; - - // Walk from the selection root towards the document root and remember the deepest block node. - for (let depth = 1; depth <= $from.depth; depth += 1) { - const candidate = $from.node(depth); - if (candidate?.isBlock) { - blockDepth = depth; - } - } - - if (blockDepth === null) { - return resetState(hasInteracted); - } - - const blockNode = $from.node(blockDepth); - const blockPos = $from.before(blockDepth); - - if (typeof blockPos !== 'number') { - return resetState(hasInteracted); - } - - const blockStart = blockPos; - const blockEnd = blockStart + blockNode.nodeSize; - - const textPreview = collapseTextPreview(blockNode.textContent).slice(0, 120); - - // Captures the essentials the consuming app might want to render. - const blockInfo = { - pos: blockStart, - end: blockEnd, - size: blockNode.nodeSize, - type: blockNode.type.name, - depth: blockDepth, - isTextblock: blockNode.isTextblock, - textPreview, - attrs: { ...(blockNode.attrs || {}) }, - childCount: blockNode.childCount, - }; - - const blockChanged = - !pluginState.blockInfo || - pluginState.blockInfo.pos !== blockInfo.pos || - pluginState.blockInfo.end !== blockInfo.end || - pluginState.blockInfo.type !== blockInfo.type || - pluginState.blockInfo.textPreview !== blockInfo.textPreview || - pluginState.blockInfo.childCount !== blockInfo.childCount || - pluginState.blockInfo.size !== blockInfo.size || - !shallowEqual(pluginState.blockInfo.attrs, blockInfo.attrs); - - if (blockChanged) { - emitActiveBlock(blockInfo); - } - - return { - blockInfo, - hasInteracted, - }; - - }, - }, - props: { - handleDOMEvents: { - mousedown(view) { - pendingInteraction = true; - const tr = view.state.tr.setMeta(activeBlockPluginKey, { interaction: true }); - view.dispatch(tr); - return false; - }, - touchstart(view) { - pendingInteraction = true; - const tr = view.state.tr.setMeta(activeBlockPluginKey, { interaction: true }); - view.dispatch(tr); - return false; - }, - keydown() { - // Keyboard navigation updates run through the next transaction/selection change. - pendingInteraction = true; - return false; - }, - }, - }, - }), - ]; - }, -}); diff --git a/examples/customization/custom-plugin/src/main.js b/examples/customization/custom-plugin/src/main.js deleted file mode 100644 index 2425c0f745..0000000000 --- a/examples/customization/custom-plugin/src/main.js +++ /dev/null @@ -1,5 +0,0 @@ -import { createApp } from 'vue' -import './style.css' -import App from './App.vue' - -createApp(App).mount('#app') diff --git a/examples/customization/custom-plugin/src/style.css b/examples/customization/custom-plugin/src/style.css deleted file mode 100644 index cc99639e1b..0000000000 --- a/examples/customization/custom-plugin/src/style.css +++ /dev/null @@ -1,230 +0,0 @@ -:root { - font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - color: #0f172a; - background: linear-gradient(180deg, #f7f8ff 0%, #ffffff 55%, #f8fafc 100%); - line-height: 1.45; -} - -body { - margin: 0; - background: inherit; -} - -#app { - min-height: 100vh; - padding: 3rem 1.5rem 4rem; - box-sizing: border-box; - display: flex; - justify-content: center; -} - -.example-shell { - width: 100%; - max-width: 1400px; - display: flex; - flex-direction: column; - gap: 2rem; -} - -.hero, -.workspace, -.hint { - width: 100%; - max-width: 1200px; - margin: 0 auto; -} - -.hero { - background: linear-gradient(160deg, rgba(255, 255, 255, 0.94) 10%, rgba(226, 232, 240, 0.78) 100%); - color: #0f172a; - padding: 2.25rem; - border-radius: 22px; - box-shadow: 0 18px 40px rgba(15, 23, 42, 0.08); - position: relative; - overflow: hidden; -} - -.hero::after { - content: ''; - position: absolute; - inset: 14px; - border-radius: 16px; - border: 1px solid rgba(148, 163, 184, 0.2); - pointer-events: none; -} - -.hero__badge { - display: inline-flex; - align-items: center; - gap: 0.4rem; - padding: 0.4rem 0.85rem; - border-radius: 999px; - font-size: 0.82rem; - font-weight: 600; - background: rgba(79, 70, 229, 0.12); - color: #4f46e5; - margin-bottom: 1.2rem; - position: relative; - z-index: 1; -} - -.hero h1 { - margin: 0 0 0.65rem; - font-size: 2.1rem; - letter-spacing: -0.02em; - position: relative; - z-index: 1; -} - -.hero p { - margin: 0 0 1.8rem; - max-width: 58ch; - font-size: 1rem; - color: #1e293b; - position: relative; - z-index: 1; -} - -.hero__actions { - display: flex; - flex-wrap: wrap; - gap: 0.7rem; - position: relative; - z-index: 1; -} - -.action-button { - border: none; - cursor: pointer; - padding: 0.7rem 1.3rem; - border-radius: 999px; - font-size: 0.95rem; - font-weight: 600; - transition: transform 0.16s ease, box-shadow 0.16s ease, background 0.16s ease, color 0.16s ease; -} - -.action-button--primary { - background: linear-gradient(135deg, #a5b4fc, #7dd3fc); - color: #0f172a; - box-shadow: 0 14px 22px rgba(125, 211, 252, 0.28); -} - -.action-button--primary:hover { - transform: translateY(-1px); - box-shadow: 0 16px 26px rgba(125, 211, 252, 0.34); -} - -.action-button--ghost { - background: rgba(148, 163, 184, 0.18); - color: #0f172a; - box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.38); -} - -.action-button--ghost:hover { - background: rgba(148, 163, 184, 0.28); -} - -.workspace { - background: #ffffff; - border-radius: 22px; - box-shadow: 0 28px 55px rgba(15, 23, 42, 0.1); - padding: 2rem; - border: 1px solid rgba(148, 163, 184, 0.18); - display: flex; - flex-direction: column; - gap: 1.25rem; - align-items: center; -} - -.toolbar { - display: flex; - flex-wrap: wrap; - gap: 0.75rem; - min-height: 40px; -} - -.editor { - min-height: 440px; - border-radius: 16px; - overflow: hidden; - border: 1px solid rgba(148, 163, 184, 0.24); - background: linear-gradient(180deg, rgba(248, 250, 252, 0.92), rgba(248, 250, 252, 0.55)); - padding: 1.25rem 1.4rem; -} - -.editor :where(.superdoc__document, -.superdoc__sub-document) { - margin: 0 auto; -} - -.hint { - margin: 0; - color: #1e293b; - background: rgba(99, 102, 241, 0.12); - border-left: 4px solid rgba(79, 70, 229, 0.45); - padding: 1rem 1.25rem; - border-radius: 0 14px 14px 0; - box-shadow: 0 12px 24px rgba(15, 23, 42, 0.06); -} - -code { - font-family: 'Fira Code', 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; - background: rgba(15, 23, 42, 0.08); - padding: 0.1rem 0.35rem; - border-radius: 6px; -} - -/* Applied to the currently active block by custom-plugin-extension */ -.custom-plugin-highlight { - position: relative; - border-radius: 8px; - background: rgba(125, 211, 252, 0.25); - box-shadow: 0 0 0 1px rgba(14, 116, 144, 0.18), 0 6px 16px rgba(14, 116, 144, 0.15); - transition: background 0.18s ease, box-shadow 0.18s ease; -} - -.custom-plugin-highlight::after { - content: ''; - position: absolute; - inset: -4px; - border-radius: 12px; - border: 2px solid rgba(59, 130, 246, 0.28); - pointer-events: none; - opacity: 0.6; -} - -.custom-plugin-highlight--non-text { - background: rgba(125, 211, 252, 0.2); - box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.25), 0 8px 18px rgba(14, 116, 144, 0.22); -} - -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - border: 0; -} - -@media (max-width: 768px) { - #app { - padding: 2.5rem 1rem 3rem; - } - - .hero, - .workspace, - .hint { - max-width: none; - } - - .hero { - padding: 1.9rem; - } - - .workspace { - padding: 1.5rem; - } -} diff --git a/examples/customization/custom-plugin/vite.config.js b/examples/customization/custom-plugin/vite.config.js deleted file mode 100644 index bbcf80cca9..0000000000 --- a/examples/customization/custom-plugin/vite.config.js +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'vite' -import vue from '@vitejs/plugin-vue' - -// https://vite.dev/config/ -export default defineConfig({ - plugins: [vue()], -}) diff --git a/examples/customization/dynamic-content/.gitignore b/examples/customization/dynamic-content/.gitignore deleted file mode 100644 index 14e2b36fa1..0000000000 --- a/examples/customization/dynamic-content/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* -.vscode/ - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/examples/customization/dynamic-content/README.md b/examples/customization/dynamic-content/README.md deleted file mode 100644 index c04ed9ea5c..0000000000 --- a/examples/customization/dynamic-content/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# SuperDoc experiment - document sections, multi editor sync - -## Example of creating a custom external plugin for SuperDoc -## The plugin explores some dynamic content tracking, re-ordering with paragraph nodes - -To try it locally: -``` -npm install -npm run dev -``` - diff --git a/examples/customization/dynamic-content/index.html b/examples/customization/dynamic-content/index.html deleted file mode 100644 index d5dc692b0d..0000000000 --- a/examples/customization/dynamic-content/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - SuperDoc - External Plugin and Multi Editors - - -
- - - diff --git a/examples/customization/dynamic-content/package.json b/examples/customization/dynamic-content/package.json deleted file mode 100644 index 38b668b596..0000000000 --- a/examples/customization/dynamic-content/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "dynamic-content", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "prosemirror-model": "^1.25.2", - "prosemirror-state": "^1.4.3", - "prosemirror-view": "^1.38.1", - "superdoc": "^1.2.0-next.2", - "uuid": "^11.1.0", - "vue": "^3.5.13" - }, - "devDependencies": { - "@vitejs/plugin-vue": "^5.2.1", - "vite": "^6.2.0" - } -} diff --git a/examples/customization/dynamic-content/plugins/document-map/document-map.js b/examples/customization/dynamic-content/plugins/document-map/document-map.js deleted file mode 100644 index 73a4c0b020..0000000000 --- a/examples/customization/dynamic-content/plugins/document-map/document-map.js +++ /dev/null @@ -1,226 +0,0 @@ -import { Extensions } from 'superdoc/super-editor'; -import { Decoration, DecorationSet } from 'prosemirror-view'; -import { PluginKey } from 'prosemirror-state'; -import { DOMSerializer } from 'prosemirror-model'; - -const activeNodeDecorationKey = new PluginKey('activeNodeDecoration'); -const ContentMapPluginKey = new PluginKey('contentMapPlugin'); - -const documentHighlight = (item, state) => { - if (!item || !item.node || typeof item.pos !== 'number') { - return false; - } - const { pos, node } = item; - const nodeDeco = Decoration.inline(pos, pos + node.nodeSize, { - class: 'document-section-active' - }); - - const tr = state.tr.setMeta(activeNodeDecorationKey, { deco: nodeDeco }); - return tr; -} - -/** - * A basic document map extension for SuperDoc. - * It allows for selecting nodes, reordering them, and syncing content with a content editor. - * It is for demo and education purposes only. It is purposely simple. - * It does not handle all edge cases and is not production-ready. - * - * To extend SuperDoc, simply create a new extension: - */ -export const DocumentMapExtension = Extensions.Extension.create({ - - /** - * You can add custom commands here that will be available via editor.commands - */ - addCommands() { - return { - documentMapSelectNode: (item) => ({ state, dispatch }) => { - if (dispatch) { - const tr = documentHighlight(item, state); - dispatch(tr); - } - return true; - }, - documentMapReorder: ({ draggedIndex, targetIndex }) => ({ state, dispatch }) => { - const pluginState = ContentMapPluginKey.getState(state); - const docMap = pluginState.docMap; - - const draggedItem = docMap[draggedIndex]; - const targetItem = docMap[targetIndex]; - const { doc } = state; - let tr = state.tr; - - const paragraphNode = state.schema.nodes.paragraph.create(); - const isBefore = draggedItem.pos < targetItem.pos; - if (!isBefore) { - tr.delete(draggedItem.pos, Math.min(doc.content.size, draggedItem.pos + draggedItem.node.nodeSize)); - const insertPos = tr.mapping.map(targetItem.pos); - tr.insert(Math.max(insertPos - 1, 0), draggedItem.node); - tr.insert(Math.max(insertPos + draggedItem.node.nodeSize, 0), paragraphNode); - } else { - const insertPos = tr.mapping.map(targetItem.pos + targetItem.node.nodeSize - 1); - tr.insert(Math.max(insertPos, 0), paragraphNode); - tr.insert(Math.max(insertPos + paragraphNode.nodeSize, 0), draggedItem.node); - tr.delete(draggedItem.pos, Math.min(doc.content.size, draggedItem.pos + draggedItem.node.nodeSize)); - } - dispatch(tr); - return true; - }, - syncContent: ({ item, contentEditor }) => ({ tr, editor, state, dispatch }) => { - const contentJson = contentEditor.getJSON(); - const newNode = state.schema.nodeFromJSON(contentJson); - - const activeNode = state.doc.nodeAt(item.pos - 1); - tr.replaceWith(item.pos - 1, item.pos + activeNode.nodeSize - 1, newNode); - tr.setMeta(ContentMapPluginKey, { syncContent: true, item }); - dispatch(tr); - - const newItem = { ...item, node: newNode, json: newNode.toJSON() }; - const newTr = documentHighlight(newItem, state); - dispatch(newTr); - return true; - }, - updateDocumentMap: () => ({ tr, state, dispatch }) => { - return true; - } - }; - }, - - - /** - * Create a prosemirror plugin that will be added to the editor. - * See https://prosemirror.net/docs/ref/#state.Plugin_System - */ - addPmPlugins() { - const editor = this.editor; - const ContentMapPlugin = new Extensions.Plugin({ - key: ContentMapPluginKey, - state: { - // Plugin init should be pure - just return initial state, no side effects. - // View-dependent initialization belongs in the `view` callback below. - init(_, state) { - const initialDocMap = createMap(state, null); - return { - hasInitialized: false, - docMap: initialDocMap, - }; - }, - apply(tr, pluginState, oldState, newState) { - let activeItem; - - const docChanged = tr.docChanged || tr.selectionSet || tr.storedMarksSet; - if (!docChanged) { - return pluginState; - } - - const pluginMeta = tr.getMeta(ContentMapPluginKey); - if (pluginMeta?.syncContent) { - activeItem = pluginMeta?.item; - return pluginState; - } - - const decoMeta = tr.getMeta(activeNodeDecorationKey); - if (decoMeta?.deco) { - return pluginState; - } - - pluginState.docMap = createMap(newState, activeItem); - editor.emit('document-map-update', pluginState.docMap); - return pluginState; - }, - }, - // ProseMirror's `view` callback is the correct place for view-dependent initialization. - // It's called once when the view is created, with the view instance available. - view(editorView) { - // Add custom tracking IDs to paragraph nodes - const state = editorView.state; - const initialPositions = []; - state.doc.descendants((node, pos) => { - if (node.type.name === 'paragraph' && node.content.size > 0) { - initialPositions.push({ pos, node }); - } - }); - - if (initialPositions.length > 0) { - const tr = state.tr; - initialPositions.forEach(({ node, pos }) => { - const newAttrs = { - ...node.attrs, - extraAttrs: { - customTrackingId: `custom-id-${pos}`, - } - }; - tr.setNodeMarkup(pos, undefined, newAttrs); - }); - editorView.dispatch(tr); - } - - // Emit the initial document map after IDs are set - const docMap = createMap(editorView.state, null); - editor.emit('document-map-update', docMap); - - // Return update/destroy handlers (required by ProseMirror) - return { - update() {}, - destroy() {}, - }; - }, - }); - return [ContentMapPlugin]; - }, -}); - -/** - * Helper function to create the document map. - * It builds a tree of block nodes from the document. - * @param {Object} newState - The new state of the editor. - * @param {Object} activeItem - The currently active item in the document map. - * @returns {Array} - An array of block nodes with their properties. - */ -const createMap = (newState, activeItem) => { - const { doc } = newState; - const initialElements = buildBlockNodesTree(doc, activeItem); - return initialElements; -}; - -/** - * Recursively builds a tree of paragraph nodes from the ProseMirror document. - * It serializes each block node to HTML and includes its position, text content, and children. - * @param {Object} node - The ProseMirror node to process. - * @param {Object} activeItem - The currently active item in the document map. - * @param {number} pos - The current position in the document. - * @returns {Array} - An array of objects representing the block nodes. - */ -function buildBlockNodesTree(node, activeItem, pos = 0) { - const treeNodes = []; - node.forEach((child, offset) => { - const childPos = pos + offset + 1; - const isActive = childPos === activeItem?.pos; - - if (child.isBlock) { - if (child.type.name === 'paragraph') { - if (!child.textContent.trim()) { - return; - } - - const serializer = DOMSerializer.fromSchema(child.type.schema); - const fragment = serializer.serializeFragment(child.content); - const container = document.createElement('div'); - container.appendChild(fragment); - const html = container.innerHTML; - - treeNodes.push({ - id: childPos, - node: child, - json: child.toJSON(), - pos: childPos, - html, - isActive, - text: child.textContent.slice(0, 50), - children: buildBlockNodesTree(child, activeItem, childPos), - }); - } - } - }); - return treeNodes; -} diff --git a/examples/customization/dynamic-content/plugins/document-map/index.js b/examples/customization/dynamic-content/plugins/document-map/index.js deleted file mode 100644 index fbba0a35dc..0000000000 --- a/examples/customization/dynamic-content/plugins/document-map/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './document-map'; \ No newline at end of file diff --git a/examples/customization/dynamic-content/public/generic-license.docx b/examples/customization/dynamic-content/public/generic-license.docx deleted file mode 100644 index 5cd7e8e67d..0000000000 Binary files a/examples/customization/dynamic-content/public/generic-license.docx and /dev/null differ diff --git a/examples/customization/dynamic-content/public/logo.webp b/examples/customization/dynamic-content/public/logo.webp deleted file mode 100644 index f36a71b86d..0000000000 Binary files a/examples/customization/dynamic-content/public/logo.webp and /dev/null differ diff --git a/examples/customization/dynamic-content/public/superdoc-logo.png b/examples/customization/dynamic-content/public/superdoc-logo.png deleted file mode 100644 index 0e1a8349a3..0000000000 Binary files a/examples/customization/dynamic-content/public/superdoc-logo.png and /dev/null differ diff --git a/examples/customization/dynamic-content/src/App.vue b/examples/customization/dynamic-content/src/App.vue deleted file mode 100644 index 167c4ec829..0000000000 --- a/examples/customization/dynamic-content/src/App.vue +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - - diff --git a/examples/customization/dynamic-content/src/components/DocumentMap.vue b/examples/customization/dynamic-content/src/components/DocumentMap.vue deleted file mode 100644 index 3526e55fb0..0000000000 --- a/examples/customization/dynamic-content/src/components/DocumentMap.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - diff --git a/examples/customization/dynamic-content/src/components/DocumentMapItem.vue b/examples/customization/dynamic-content/src/components/DocumentMapItem.vue deleted file mode 100644 index 48b7d01004..0000000000 --- a/examples/customization/dynamic-content/src/components/DocumentMapItem.vue +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - - diff --git a/examples/customization/dynamic-content/src/main.js b/examples/customization/dynamic-content/src/main.js deleted file mode 100644 index 2425c0f745..0000000000 --- a/examples/customization/dynamic-content/src/main.js +++ /dev/null @@ -1,5 +0,0 @@ -import { createApp } from 'vue' -import './style.css' -import App from './App.vue' - -createApp(App).mount('#app') diff --git a/examples/customization/dynamic-content/src/style.css b/examples/customization/dynamic-content/src/style.css deleted file mode 100644 index a8fa646d59..0000000000 --- a/examples/customization/dynamic-content/src/style.css +++ /dev/null @@ -1,28 +0,0 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -body { - margin: 0; - display: flex; - justify-content: center; - min-width: 320px; - min-height: 100vh; -} - -* { - box-sizing: border-box; -} - -.document-map ul, -.document-map ol { - list-style: none; - padding-left: 8px; -} \ No newline at end of file diff --git a/examples/customization/dynamic-content/vite.config.js b/examples/customization/dynamic-content/vite.config.js deleted file mode 100644 index bbcf80cca9..0000000000 --- a/examples/customization/dynamic-content/vite.config.js +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'vite' -import vue from '@vitejs/plugin-vue' - -// https://vite.dev/config/ -export default defineConfig({ - plugins: [vue()], -}) diff --git a/examples/customization/toolbar/README.md b/examples/customization/toolbar/README.md deleted file mode 100644 index 85c46f5214..0000000000 --- a/examples/customization/toolbar/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# SuperDoc -## SuperDoc: Customizing the Toolbar - -An example of how to add a custom button to the SuperDoc toolbar. This custom button inserts a random cat GIF into the document which is fetched from [https://edgecats.net](https://edgecats.net). - -[We define the custom button in the `modules.toolbar.customButtons` option here](https://github.com/superdoc-dev/superdoc/blob/develop/examples/customization/toolbar/src/main.js#L122-L131) - -The button's action is to insert a custom `catNode`. [The custom node is defined here](https://github.com/superdoc-dev/superdoc/blob/develop/examples/customization/toolbar/src/main.js#L11) - -[The custom node is then passed to the editor via the `editorExtensions` option](https://github.com/superdoc-dev/superdoc/blob/develop/examples/customization/toolbar/src/main.js#L113) - -[Finally, we define a Prosemirror plugin that listens to click events on the custom node](https://github.com/superdoc-dev/superdoc/blob/develop/examples/customization/toolbar/src/main.js#L75-L92) diff --git a/examples/features/README.md b/examples/features/README.md new file mode 100644 index 0000000000..c173f6b36a --- /dev/null +++ b/examples/features/README.md @@ -0,0 +1,10 @@ +# Feature Examples + +Focused examples that each demonstrate a single SuperDoc feature. + +| Example | Description | Docs | +|---------|-------------|------| +| [track-changes](./track-changes) | Accept/reject workflow with suggesting mode | [Track Changes](https://docs.superdoc.dev/extensions/track-changes) | +| [ai-redlining](./ai-redlining) | LLM-powered document review with tracked changes | [AI Agents](https://docs.superdoc.dev/getting-started/ai-agents) | +| [comments](./comments) | Threaded comments with resolve workflow and event log | [Comments](https://docs.superdoc.dev/modules/comments) | +| [custom-toolbar](./custom-toolbar) | Custom button groups, excluded items, and custom buttons | [Toolbar](https://docs.superdoc.dev/modules/toolbar) | diff --git a/examples/features/ai-redlining/.env.example b/examples/features/ai-redlining/.env.example new file mode 100644 index 0000000000..67b89b3aa8 --- /dev/null +++ b/examples/features/ai-redlining/.env.example @@ -0,0 +1 @@ +VITE_OPENAI_API_KEY=sk-... diff --git a/examples/features/ai-redlining/index.html b/examples/features/ai-redlining/index.html new file mode 100644 index 0000000000..e77bc1d503 --- /dev/null +++ b/examples/features/ai-redlining/index.html @@ -0,0 +1,16 @@ + + + + + + SuperDoc — AI Redlining + + + +
+ + + diff --git a/examples/collaboration/y-sweet/client/package.json b/examples/features/ai-redlining/package.json similarity index 71% rename from examples/collaboration/y-sweet/client/package.json rename to examples/features/ai-redlining/package.json index b3dc11d25d..e087efc5a1 100644 --- a/examples/collaboration/y-sweet/client/package.json +++ b/examples/features/ai-redlining/package.json @@ -1,5 +1,5 @@ { - "name": "superdoc-y-sweet-client", + "name": "superdoc-ai-redlining-example", "private": true, "type": "module", "scripts": { @@ -7,11 +7,9 @@ "build": "vite build" }, "dependencies": { - "@y-sweet/client": "^0.9.1", "react": "^19.2.1", "react-dom": "^19.2.1", - "superdoc": "link:../../../../../packages/superdoc", - "yjs": "13.6.27" + "superdoc": "latest" }, "devDependencies": { "@types/react": "^19.2.7", diff --git a/examples/features/ai-redlining/src/App.tsx b/examples/features/ai-redlining/src/App.tsx new file mode 100644 index 0000000000..83c8730f9f --- /dev/null +++ b/examples/features/ai-redlining/src/App.tsx @@ -0,0 +1,121 @@ +import { useEffect, useRef, useState } from 'react'; +import { SuperDoc } from 'superdoc'; +import 'superdoc/style.css'; + +const API_KEY = import.meta.env.VITE_OPENAI_API_KEY as string; + +type Suggestion = { find: string; replace: string; comment: string }; + +export default function App() { + const [file, setFile] = useState(null); + const [reviewing, setReviewing] = useState(false); + const containerRef = useRef(null); + const commentsRef = useRef(null); + const superdocRef = useRef(null); + + useEffect(() => { + if (!file || !containerRef.current) return; + + superdocRef.current?.destroy(); + superdocRef.current = new SuperDoc({ + selector: containerRef.current, + document: file, + documentMode: 'suggesting', + user: { name: 'Jane Doe', email: 'jane@example.com' }, + modules: { + comments: { selector: commentsRef.current!, allowResolving: true }, + }, + }); + + return () => { + superdocRef.current?.destroy(); + superdocRef.current = null; + }; + }, [file]); + + const runAIReview = async () => { + const editor = superdocRef.current?.activeEditor; + if (!editor) return; + + setReviewing(true); + try { + const text = editor.state.doc.textContent; + const suggestions = await callLLM(text); + + for (const s of suggestions) { + const matches = editor.commands.search(s.find, { highlight: false }); + if (!matches.length) continue; + + editor.commands.insertTrackedChange({ + from: matches[0].from, + to: matches[0].to, + text: s.replace, + user: { name: 'AI Assistant', email: 'ai@superdoc.dev' }, + comment: s.comment, + }); + } + } finally { + setReviewing(false); + } + }; + + if (!API_KEY) { + return
Add VITE_OPENAI_API_KEY to .env — see .env.example
; + } + + return ( +
+
+ setFile(e.target.files?.[0] ?? null)} /> + +
+ +
+
+
+
+
+ ); +} + +/** + * Call OpenAI to get redlining suggestions for the document text. + * WARNING: This is a demo only. Never expose API keys in client-side code in production. + * Use a backend proxy to keep your key secret. + */ +async function callLLM(text: string): Promise { + const res = await fetch('https://api.openai.com/v1/chat/completions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${API_KEY}`, + }, + body: JSON.stringify({ + model: 'gpt-4o-mini', + temperature: 0.3, + response_format: { type: 'json_object' }, + messages: [ + { + role: 'system', + content: `You are a legal document reviewer. Given document text, return a JSON object with a "suggestions" array. Each suggestion has: +- "find": the exact text to replace (must match the document verbatim) +- "replace": the improved text +- "comment": a brief explanation of the change + +Return 3-5 suggestions max. Focus on clarity, precision, and legal best practices.`, + }, + { role: 'user', content: text.slice(0, 8000) }, + ], + }), + }); + + const data = await res.json(); + const content = data.choices?.[0]?.message?.content ?? '{}'; + return JSON.parse(content).suggestions ?? []; +} diff --git a/examples/collaboration/tiptap-cloud/src/main.tsx b/examples/features/ai-redlining/src/main.tsx similarity index 100% rename from examples/collaboration/tiptap-cloud/src/main.tsx rename to examples/features/ai-redlining/src/main.tsx diff --git a/examples/collaboration/y-sweet/client/vite.config.js b/examples/features/ai-redlining/vite.config.ts similarity index 83% rename from examples/collaboration/y-sweet/client/vite.config.js rename to examples/features/ai-redlining/vite.config.ts index 5327fa0380..0466183af6 100644 --- a/examples/collaboration/y-sweet/client/vite.config.js +++ b/examples/features/ai-redlining/vite.config.ts @@ -3,5 +3,4 @@ import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], - server: { port: 3000 }, }); diff --git a/examples/features/comments/index.html b/examples/features/comments/index.html new file mode 100644 index 0000000000..3e7c25a564 --- /dev/null +++ b/examples/features/comments/index.html @@ -0,0 +1,16 @@ + + + + + + SuperDoc — Comments + + + +
+ + + diff --git a/examples/collaboration/hocuspocus/client/package.json b/examples/features/comments/package.json similarity index 70% rename from examples/collaboration/hocuspocus/client/package.json rename to examples/features/comments/package.json index 4580d95749..a10a3cec93 100644 --- a/examples/collaboration/hocuspocus/client/package.json +++ b/examples/features/comments/package.json @@ -1,5 +1,5 @@ { - "name": "superdoc-hocuspocus-client", + "name": "superdoc-comments-example", "private": true, "type": "module", "scripts": { @@ -7,11 +7,9 @@ "build": "vite build" }, "dependencies": { - "@hocuspocus/provider": "^3.4.0", "react": "^19.2.1", "react-dom": "^19.2.1", - "superdoc": "link:../../../../packages/superdoc", - "yjs": "^13.6.27" + "superdoc": "latest" }, "devDependencies": { "@types/react": "^19.2.7", diff --git a/examples/features/comments/src/App.tsx b/examples/features/comments/src/App.tsx new file mode 100644 index 0000000000..139e7d6651 --- /dev/null +++ b/examples/features/comments/src/App.tsx @@ -0,0 +1,106 @@ +import { useEffect, useRef, useState } from 'react'; +import { SuperDoc } from 'superdoc'; +import 'superdoc/style.css'; + +type LogEntry = { time: string; type: string; detail: string }; + +export default function App() { + const [file, setFile] = useState(null); + const [log, setLog] = useState([]); + const containerRef = useRef(null); + const commentsRef = useRef(null); + const superdocRef = useRef(null); + + const addLog = (type: string, detail: string) => { + const time = new Date().toLocaleTimeString(); + setLog((prev) => [{ time, type, detail }, ...prev].slice(0, 50)); + }; + + useEffect(() => { + if (!file || !containerRef.current) return; + + superdocRef.current?.destroy(); + superdocRef.current = new SuperDoc({ + selector: containerRef.current, + document: file, + documentMode: 'editing', + user: { name: 'Jane Doe', email: 'jane@example.com' }, + modules: { + comments: { + selector: commentsRef.current!, + allowResolving: true, + }, + }, + onCommentsUpdate: ({ type, comment }: any) => { + const who = comment?.creatorName ?? ''; + const text = comment?.commentText?.replace(/<[^>]*>/g, '').slice(0, 40) ?? ''; + addLog(type, who ? `${who}: ${text}` : text); + }, + }); + + return () => { + superdocRef.current?.destroy(); + superdocRef.current = null; + }; + }, [file]); + + const addComment = () => { + const editor = superdocRef.current?.activeEditor; + if (!editor) return; + + const { from, to } = editor.state.selection; + if (from === to) { + alert('Select some text first, then click "Add Comment".'); + return; + } + + editor.commands.addComment('Please review this section.'); + }; + + const exportDocx = () => { + superdocRef.current?.export({ exportedName: 'commented' }); + }; + + return ( +
+
+ setFile(e.target.files?.[0] ?? null)} /> + + +
+ +
+
+
+
+ {log.length > 0 && ( +
+ Events + {log.map((entry, i) => ( +
+ {entry.time}{' '} + {entry.type}{' '} + {entry.detail} +
+ ))} +
+ )} +
+
+
+ ); +} + +const actionBtn = (color: string): React.CSSProperties => ({ + padding: '0.35rem 0.75rem', + border: 'none', + borderRadius: 4, + background: color, + color: '#fff', + cursor: 'pointer', + fontWeight: 500, +}); diff --git a/examples/features/comments/src/main.tsx b/examples/features/comments/src/main.tsx new file mode 100644 index 0000000000..c018515cd7 --- /dev/null +++ b/examples/features/comments/src/main.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/examples/collaboration/tiptap-cloud/vite.config.js b/examples/features/comments/vite.config.ts similarity index 83% rename from examples/collaboration/tiptap-cloud/vite.config.js rename to examples/features/comments/vite.config.ts index 5327fa0380..0466183af6 100644 --- a/examples/collaboration/tiptap-cloud/vite.config.js +++ b/examples/features/comments/vite.config.ts @@ -3,5 +3,4 @@ import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], - server: { port: 3000 }, }); diff --git a/examples/features/custom-toolbar/index.html b/examples/features/custom-toolbar/index.html new file mode 100644 index 0000000000..1a5c0f3c8a --- /dev/null +++ b/examples/features/custom-toolbar/index.html @@ -0,0 +1,16 @@ + + + + + + SuperDoc — Custom Toolbar + + + +
+ + + diff --git a/examples/collaboration/tiptap-cloud/package.json b/examples/features/custom-toolbar/package.json similarity index 70% rename from examples/collaboration/tiptap-cloud/package.json rename to examples/features/custom-toolbar/package.json index c4f286d516..433de34f0f 100644 --- a/examples/collaboration/tiptap-cloud/package.json +++ b/examples/features/custom-toolbar/package.json @@ -1,5 +1,5 @@ { - "name": "superdoc-tiptap-cloud-example", + "name": "superdoc-custom-toolbar-example", "private": true, "type": "module", "scripts": { @@ -7,11 +7,9 @@ "build": "vite build" }, "dependencies": { - "@tiptap-pro/provider": "^3.3.0", "react": "^19.2.1", "react-dom": "^19.2.1", - "superdoc": "link:../../../packages/superdoc", - "yjs": "^13.6.27" + "superdoc": "latest" }, "devDependencies": { "@types/react": "^19.2.7", diff --git a/examples/features/custom-toolbar/src/App.tsx b/examples/features/custom-toolbar/src/App.tsx new file mode 100644 index 0000000000..0460611fba --- /dev/null +++ b/examples/features/custom-toolbar/src/App.tsx @@ -0,0 +1,73 @@ +import { useEffect, useRef, useState } from 'react'; +import { SuperDoc } from 'superdoc'; +import 'superdoc/style.css'; + +export default function App() { + const [file, setFile] = useState(null); + const containerRef = useRef(null); + const superdocRef = useRef(null); + + useEffect(() => { + if (!file || !containerRef.current) return; + + superdocRef.current?.destroy(); + superdocRef.current = new SuperDoc({ + selector: containerRef.current, + document: file, + toolbar: '#toolbar', + modules: { + toolbar: { + // Arrange built-in buttons into groups + groups: { + left: ['undo', 'redo'], + center: [ + 'linkedStyles', + 'bold', + 'italic', + 'underline', + 'color', + 'highlight', + 'textAlign', + 'list', + 'numberedlist', + ], + right: ['zoom'], + }, + // Remove buttons you don't need + excludeItems: ['image', 'ruler', 'search', 'copyFormat', 'table'], + // Add a custom button + customButtons: [ + { + type: 'button', + name: 'clear', + tooltip: 'Clear formatting', + icon: eraserIcon, + group: 'center', + command: () => { + superdocRef.current?.activeEditor?.commands.clearFormat(); + }, + }, + ], + }, + }, + }); + + return () => { + superdocRef.current?.destroy(); + superdocRef.current = null; + }; + }, [file]); + + return ( +
+
+ setFile(e.target.files?.[0] ?? null)} /> +
+ +
+
+
+ ); +} + +const eraserIcon = ``; diff --git a/examples/features/custom-toolbar/src/main.tsx b/examples/features/custom-toolbar/src/main.tsx new file mode 100644 index 0000000000..c018515cd7 --- /dev/null +++ b/examples/features/custom-toolbar/src/main.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/examples/features/custom-toolbar/vite.config.ts b/examples/features/custom-toolbar/vite.config.ts new file mode 100644 index 0000000000..0466183af6 --- /dev/null +++ b/examples/features/custom-toolbar/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], +}); diff --git a/examples/features/track-changes/index.html b/examples/features/track-changes/index.html new file mode 100644 index 0000000000..242ec05802 --- /dev/null +++ b/examples/features/track-changes/index.html @@ -0,0 +1,16 @@ + + + + + + SuperDoc — Track Changes + + + +
+ + + diff --git a/examples/features/track-changes/package.json b/examples/features/track-changes/package.json new file mode 100644 index 0000000000..c03d152488 --- /dev/null +++ b/examples/features/track-changes/package.json @@ -0,0 +1,21 @@ +{ + "name": "superdoc-track-changes-example", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build" + }, + "dependencies": { + "react": "^19.2.1", + "react-dom": "^19.2.1", + "superdoc": "latest" + }, + "devDependencies": { + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.2", + "typescript": "^5.9.3", + "vite": "^7.2.7" + } +} diff --git a/examples/features/track-changes/src/App.tsx b/examples/features/track-changes/src/App.tsx new file mode 100644 index 0000000000..6a5e8c7742 --- /dev/null +++ b/examples/features/track-changes/src/App.tsx @@ -0,0 +1,90 @@ +import { useEffect, useRef, useState } from 'react'; +import { SuperDoc } from 'superdoc'; +import 'superdoc/style.css'; + +type DocumentMode = 'editing' | 'suggesting' | 'viewing'; + +export default function App() { + const [file, setFile] = useState(null); + const [mode, setMode] = useState('suggesting'); + const containerRef = useRef(null); + const commentsRef = useRef(null); + const superdocRef = useRef(null); + + useEffect(() => { + if (!file || !containerRef.current) return; + + superdocRef.current?.destroy(); + superdocRef.current = new SuperDoc({ + selector: containerRef.current, + document: file, + documentMode: mode, + user: { name: 'Jane Doe', email: 'jane@example.com' }, + modules: { + comments: { + selector: commentsRef.current!, + allowResolving: true, + }, + }, + }); + + return () => { + superdocRef.current?.destroy(); + superdocRef.current = null; + }; + }, [file]); + + const changeMode = (newMode: DocumentMode) => { + setMode(newMode); + superdocRef.current?.setDocumentMode(newMode); + }; + + const acceptAll = () => superdocRef.current?.activeEditor?.commands.acceptAllTrackedChanges(); + const rejectAll = () => superdocRef.current?.activeEditor?.commands.rejectAllTrackedChanges(); + + return ( +
+
+ setFile(e.target.files?.[0] ?? null)} /> + +
+ {(['editing', 'suggesting', 'viewing'] as const).map((m) => ( + + ))} +
+ + + +
+ +
+
+
+
+
+ ); +} + +const actionBtn = (color: string): React.CSSProperties => ({ + padding: '0.35rem 0.75rem', + border: `1px solid ${color}`, + borderRadius: 4, + background: 'white', + color, + cursor: 'pointer', + fontWeight: 500, +}); diff --git a/examples/features/track-changes/src/main.tsx b/examples/features/track-changes/src/main.tsx new file mode 100644 index 0000000000..9707d8270f --- /dev/null +++ b/examples/features/track-changes/src/main.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +); diff --git a/examples/features/track-changes/vite.config.ts b/examples/features/track-changes/vite.config.ts new file mode 100644 index 0000000000..0466183af6 --- /dev/null +++ b/examples/features/track-changes/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], +}); diff --git a/examples/getting-started/README.md b/examples/getting-started/README.md index f10052469b..1b94da540f 100644 --- a/examples/getting-started/README.md +++ b/examples/getting-started/README.md @@ -1,21 +1,20 @@ # Getting Started Examples -Quick start examples to get SuperDoc running in your preferred framework. +Minimal examples for integrating SuperDoc into your project. Each example loads a `.docx` file and renders it in the browser. -## Examples +| Example | Description | Docs | +|---------|-------------|------| +| [react](./react) | React + TypeScript with Vite | [Guide](https://docs.superdoc.dev/getting-started/frameworks/react) | +| [vue](./vue) | Vue 3 + TypeScript with Vite | [Guide](https://docs.superdoc.dev/getting-started/frameworks/vue) | +| [vanilla](./vanilla) | Plain JavaScript with Vite | [Guide](https://docs.superdoc.dev/getting-started/installation) | +| [cdn](./cdn) | Zero build tools — just an HTML file | [Guide](https://docs.superdoc.dev/getting-started/installation) | -| Example | Description | -|---------|-------------| -| [react](./react) | Basic React integration | -| [vue](./vue) | Basic Vue.js integration | -| [vanilla](./vanilla) | Plain JavaScript without frameworks | -| [typescript](./typescript) | TypeScript setup with type definitions | -| [cdn](./cdn) | Browser-based setup using CDN links | - -## Running an Example +## Running ```bash -cd +cd npm install npm run dev ``` + +For the CDN example, just open `index.html` or run `npx serve .`. diff --git a/examples/getting-started/cdn/README.md b/examples/getting-started/cdn/README.md index 93551cddae..fd63207b79 100644 --- a/examples/getting-started/cdn/README.md +++ b/examples/getting-started/cdn/README.md @@ -1,5 +1,14 @@ -# SuperDoc - From CDN example +# SuperDoc — CDN -This is a very basic example of loading SuperDoc from CDN without any bundlers. +Zero build tools. Open `index.html` in a browser or serve with any static server. -Note: You can test this locally by using something like ```npx http-server``` \ No newline at end of file +## Run + +```bash +npx serve . +``` + +## Learn more + +- [Vanilla JS Guide](https://docs.superdoc.dev/getting-started/frameworks/vanilla-js) +- [Configuration Reference](https://docs.superdoc.dev/core/superdoc/configuration) diff --git a/examples/getting-started/cdn/index.html b/examples/getting-started/cdn/index.html index 38b8a8e340..955216acea 100644 --- a/examples/getting-started/cdn/index.html +++ b/examples/getting-started/cdn/index.html @@ -1,56 +1,36 @@ - - - - - - - SuperDoc - CDN example - - - + + + + SuperDoc — CDN + + + + +
+ +
+
-
SuperDoc - CDN example
-
+ - - - + if (superdoc) superdoc.destroy(); + superdoc = new SuperDocLibrary.SuperDoc({ + selector: '#editor', + document: file, + }); + }); + + diff --git a/examples/getting-started/react/README.md b/examples/getting-started/react/README.md new file mode 100644 index 0000000000..e9e747fc1d --- /dev/null +++ b/examples/getting-started/react/README.md @@ -0,0 +1,15 @@ +# SuperDoc — React + +Minimal React + TypeScript example. + +## Run + +```bash +npm install +npm run dev +``` + +## Learn more + +- [React Integration Guide](https://docs.superdoc.dev/getting-started/frameworks/react) +- [Configuration Reference](https://docs.superdoc.dev/core/superdoc/configuration) diff --git a/examples/getting-started/react/index.html b/examples/getting-started/react/index.html index 5a4e3e6da2..094d07e62a 100644 --- a/examples/getting-started/react/index.html +++ b/examples/getting-started/react/index.html @@ -3,10 +3,10 @@ - SuperDoc React Example + SuperDoc — React
- + - \ No newline at end of file + diff --git a/examples/getting-started/react/package.json b/examples/getting-started/react/package.json index 698373a5db..af64b5c329 100644 --- a/examples/getting-started/react/package.json +++ b/examples/getting-started/react/package.json @@ -1,7 +1,6 @@ { - "name": "react-superdoc-example", + "name": "superdoc-react-example", "private": true, - "version": "0.0.1", "type": "module", "scripts": { "dev": "vite" @@ -9,11 +8,13 @@ "dependencies": { "react": "^19.0.0", "react-dom": "^19.0.0", - "styled-jsx": "^5.1.7", - "superdoc": "0.20.0-next.13" + "superdoc": "latest" }, "devDependencies": { - "@vitejs/plugin-react": "^4.0.4", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.7.0", "vite": "^6.2.0" } } diff --git a/examples/getting-started/react/src/App.tsx b/examples/getting-started/react/src/App.tsx new file mode 100644 index 0000000000..f0fd1f687d --- /dev/null +++ b/examples/getting-started/react/src/App.tsx @@ -0,0 +1,37 @@ +import { useEffect, useRef, useState, type ChangeEvent } from 'react'; +import { SuperDoc } from 'superdoc'; +import 'superdoc/style.css'; + +export default function App() { + const [file, setFile] = useState(null); + const containerRef = useRef(null); + const superdocRef = useRef(null); + + const handleFile = (e: ChangeEvent) => { + const selected = e.target.files?.[0]; + if (selected) setFile(selected); + }; + + useEffect(() => { + if (!containerRef.current) return; + + superdocRef.current = new SuperDoc({ + selector: containerRef.current, + document: file, + }); + + return () => { + superdocRef.current?.destroy(); + superdocRef.current = null; + }; + }, [file]); + + return ( + <> +
+ +
+
+ + ); +} diff --git a/examples/getting-started/react/src/main.tsx b/examples/getting-started/react/src/main.tsx new file mode 100644 index 0000000000..e0138a10cc --- /dev/null +++ b/examples/getting-started/react/src/main.tsx @@ -0,0 +1,4 @@ +import { createRoot } from 'react-dom/client'; +import App from './App'; + +createRoot(document.getElementById('root')!).render(); diff --git a/examples/getting-started/react/tsconfig.json b/examples/getting-started/react/tsconfig.json new file mode 100644 index 0000000000..4f344fd6f2 --- /dev/null +++ b/examples/getting-started/react/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/examples/getting-started/react/vite.config.ts b/examples/getting-started/react/vite.config.ts new file mode 100644 index 0000000000..0466183af6 --- /dev/null +++ b/examples/getting-started/react/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], +}); diff --git a/examples/getting-started/typescript/.gitignore b/examples/getting-started/typescript/.gitignore deleted file mode 100644 index a547bf36d8..0000000000 --- a/examples/getting-started/typescript/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/examples/getting-started/vanilla/README.md b/examples/getting-started/vanilla/README.md new file mode 100644 index 0000000000..16d7950f19 --- /dev/null +++ b/examples/getting-started/vanilla/README.md @@ -0,0 +1,15 @@ +# SuperDoc — Vanilla JS + +Minimal plain JavaScript example with Vite. + +## Run + +```bash +npm install +npm run dev +``` + +## Learn more + +- [Vanilla JS Guide](https://docs.superdoc.dev/getting-started/frameworks/vanilla-js) +- [Configuration Reference](https://docs.superdoc.dev/core/superdoc/configuration) diff --git a/examples/getting-started/vanilla/index.html b/examples/getting-started/vanilla/index.html index 1d5401ef6c..2ad2b88d97 100644 --- a/examples/getting-started/vanilla/index.html +++ b/examples/getting-started/vanilla/index.html @@ -1,27 +1,15 @@ - - - - SuperDoc Vanilla Example - - -
-
-

SuperDoc Example

- - -
-
-
-
-
+ + + + SuperDoc — Vanilla JS + + +
+
+
- - \ No newline at end of file + + diff --git a/examples/getting-started/vanilla/package.json b/examples/getting-started/vanilla/package.json index 0049f92216..cd2dae08b8 100644 --- a/examples/getting-started/vanilla/package.json +++ b/examples/getting-started/vanilla/package.json @@ -1,15 +1,14 @@ { - "name": "vanilla-superdoc-example", + "name": "superdoc-vanilla-example", "private": true, - "version": "0.0.1", "type": "module", "scripts": { "dev": "vite" }, "dependencies": { - "superdoc": "0.20.0-next.13" + "superdoc": "latest" }, "devDependencies": { - "vite": "^4.4.6" + "vite": "^6.2.0" } } diff --git a/examples/getting-started/vanilla/src/main.js b/examples/getting-started/vanilla/src/main.js index 961aa3bf3e..80f5b81d79 100644 --- a/examples/getting-started/vanilla/src/main.js +++ b/examples/getting-started/vanilla/src/main.js @@ -1,46 +1,17 @@ import { SuperDoc } from 'superdoc'; import 'superdoc/style.css'; -import './style.css'; -// Initialize SuperDoc -let editor = null; +let superdoc = new SuperDoc({ + selector: '#editor', +}); -function initializeEditor(file = null) { - // Cleanup previous instance if it exists - if (editor) { - editor = null; - } +document.getElementById('file-input').addEventListener('change', (e) => { + const file = e.target.files[0]; + if (!file) return; - editor = new SuperDoc({ - selector: '#superdoc', - toolbar: '#superdoc-toolbar', - document: file, // URL, File or document config - documentMode: 'editing', - pagination: true, - rulers: true, - onReady: (event) => { - console.log('SuperDoc is ready', event); - }, - onEditorCreate: (event) => { - console.log('Editor is created', event); - }, + superdoc?.destroy(); + superdoc = new SuperDoc({ + selector: '#editor', + document: file, }); -} - -// Setup file input handling -const fileInput = document.getElementById('fileInput'); -const loadButton = document.getElementById('loadButton'); - -loadButton.addEventListener('click', () => { - fileInput.click(); }); - -fileInput.addEventListener('change', (event) => { - const file = event.target.files?.[0]; - if (file) { - initializeEditor(file); - } -}); - -// Initialize empty editor on page load -initializeEditor(); \ No newline at end of file diff --git a/examples/getting-started/vue/README.md b/examples/getting-started/vue/README.md new file mode 100644 index 0000000000..52d1606ef2 --- /dev/null +++ b/examples/getting-started/vue/README.md @@ -0,0 +1,15 @@ +# SuperDoc — Vue + +Minimal Vue 3 + TypeScript example. + +## Run + +```bash +npm install +npm run dev +``` + +## Learn more + +- [Vue Integration Guide](https://docs.superdoc.dev/getting-started/frameworks/vue) +- [Configuration Reference](https://docs.superdoc.dev/core/superdoc/configuration) diff --git a/examples/getting-started/vue/index.html b/examples/getting-started/vue/index.html index 64e6bb7806..87dd1a6ade 100644 --- a/examples/getting-started/vue/index.html +++ b/examples/getting-started/vue/index.html @@ -1,12 +1,12 @@ - - - SuperDoc Vue Example + + + SuperDoc — Vue
- + - \ No newline at end of file + diff --git a/examples/getting-started/vue/package.json b/examples/getting-started/vue/package.json index ec33ff883d..a387668737 100644 --- a/examples/getting-started/vue/package.json +++ b/examples/getting-started/vue/package.json @@ -1,17 +1,17 @@ { - "name": "vue-superdoc-example", + "name": "superdoc-vue-example", "private": true, - "version": "0.0.1", "type": "module", "scripts": { "dev": "vite" }, "dependencies": { - "superdoc": "0.20.0-next.13", - "vue": "^3.5.13" + "superdoc": "latest", + "vue": "^3.5.0" }, "devDependencies": { - "@vitejs/plugin-vue": "^4.2.3", - "vite": "^4.4.6" + "@vitejs/plugin-vue": "^5.2.0", + "typescript": "^5.7.0", + "vite": "^6.2.0" } } diff --git a/examples/getting-started/vue/src/App.vue b/examples/getting-started/vue/src/App.vue index 906cc772de..6c794a0a61 100644 --- a/examples/getting-started/vue/src/App.vue +++ b/examples/getting-started/vue/src/App.vue @@ -1,79 +1,36 @@ - - - \ No newline at end of file +onMounted(initEditor); +watch(file, initEditor); + diff --git a/examples/collaboration/from-scratch/client/src/main.js b/examples/getting-started/vue/src/main.ts similarity index 80% rename from examples/collaboration/from-scratch/client/src/main.js rename to examples/getting-started/vue/src/main.ts index e0d8b5d603..684d04215d 100644 --- a/examples/collaboration/from-scratch/client/src/main.js +++ b/examples/getting-started/vue/src/main.ts @@ -1,4 +1,3 @@ -import './style.css'; import { createApp } from 'vue'; import App from './App.vue'; diff --git a/examples/getting-started/vue/tsconfig.json b/examples/getting-started/vue/tsconfig.json new file mode 100644 index 0000000000..2cac755e47 --- /dev/null +++ b/examples/getting-started/vue/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/examples/collaboration/from-scratch/client/vite.config.js b/examples/getting-started/vue/vite.config.ts similarity index 96% rename from examples/collaboration/from-scratch/client/vite.config.js rename to examples/getting-started/vue/vite.config.ts index 7fe948f660..1ebc4fc7dd 100644 --- a/examples/collaboration/from-scratch/client/vite.config.js +++ b/examples/getting-started/vue/vite.config.ts @@ -3,4 +3,4 @@ import vue from '@vitejs/plugin-vue'; export default defineConfig({ plugins: [vue()], -}); \ No newline at end of file +}); diff --git a/examples/headless/README.md b/examples/headless/README.md new file mode 100644 index 0000000000..eecf8cff70 --- /dev/null +++ b/examples/headless/README.md @@ -0,0 +1,7 @@ +# Headless Examples + +Server-side SuperDoc usage without a browser. + +| Example | Description | Docs | +|---------|-------------|------| +| [ai-redlining](./ai-redlining) | LLM reviews a DOCX and inserts tracked changes server-side | [AI Agents](https://docs.superdoc.dev/getting-started/ai-agents) | diff --git a/examples/headless/ai-redlining/.env.example b/examples/headless/ai-redlining/.env.example new file mode 100644 index 0000000000..ed6ed73ba0 --- /dev/null +++ b/examples/headless/ai-redlining/.env.example @@ -0,0 +1 @@ +OPENAI_API_KEY=sk-... diff --git a/examples/headless/ai-redlining/.gitignore b/examples/headless/ai-redlining/.gitignore new file mode 100644 index 0000000000..c2cb59fc97 --- /dev/null +++ b/examples/headless/ai-redlining/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +redlined.docx +.env diff --git a/examples/headless/ai-redlining/package.json b/examples/headless/ai-redlining/package.json new file mode 100644 index 0000000000..9c3fbbd99b --- /dev/null +++ b/examples/headless/ai-redlining/package.json @@ -0,0 +1,17 @@ +{ + "name": "headless-ai-redlining", + "private": true, + "type": "module", + "scripts": { + "start": "tsx --env-file=.env src/index.ts", + "test": "tsx src/index.test.ts" + }, + "dependencies": { + "superdoc": "latest" + }, + "devDependencies": { + "@types/node": "^22.15.0", + "tsx": "^4.21.0", + "typescript": "^5.9.3" + } +} diff --git a/examples/headless/ai-redlining/sample.docx b/examples/headless/ai-redlining/sample.docx new file mode 100644 index 0000000000..678c849b6d Binary files /dev/null and b/examples/headless/ai-redlining/sample.docx differ diff --git a/examples/headless/ai-redlining/src/index.test.ts b/examples/headless/ai-redlining/src/index.test.ts new file mode 100644 index 0000000000..1d18d5037b --- /dev/null +++ b/examples/headless/ai-redlining/src/index.test.ts @@ -0,0 +1,95 @@ +/** + * Tests for headless ai-redlining workflow. + * Verifies the core Editor.open → edit → export pipeline without an LLM call. + */ + +import { readFile, writeFile, unlink } from 'node:fs/promises'; +import { existsSync } from 'node:fs'; +import { Editor } from 'superdoc/super-editor'; + +const SAMPLE = new URL('../sample.docx', import.meta.url).pathname; +const OUTPUT = new URL('../test-output.docx', import.meta.url).pathname; + +async function test(name: string, fn: () => Promise) { + try { + await fn(); + console.log(` ✓ ${name}`); + } catch (err: any) { + console.error(` ✗ ${name}`); + console.error(` ${err.message}`); + process.exitCode = 1; + } +} + +async function run() { + console.log('headless ai-redlining tests\n'); + + await test('opens a DOCX headlessly', async () => { + const docx = await readFile(SAMPLE); + const editor = await Editor.open(docx, { documentMode: 'suggesting' }); + const text = editor.state.doc.textContent; + if (!text || text.length === 0) throw new Error('Document text is empty'); + editor.destroy(); + }); + + await test('search finds text in the document', async () => { + const docx = await readFile(SAMPLE); + const editor = await Editor.open(docx, { documentMode: 'suggesting' }); + const text = editor.state.doc.textContent; + // Search for the first word that's at least 3 chars + const word = text.match(/\b\w{3,}\b/)?.[0]; + if (!word) throw new Error('No searchable word found'); + const matches = editor.commands.search(word, { highlight: false }); + if (!matches.length) throw new Error(`search("${word}") returned no matches`); + if (matches[0].from == null || matches[0].to == null) throw new Error('Match missing from/to'); + editor.destroy(); + }); + + await test('inserts a tracked change', async () => { + const docx = await readFile(SAMPLE); + const editor = await Editor.open(docx, { documentMode: 'suggesting' }); + const text = editor.state.doc.textContent; + const word = text.match(/\b\w{3,}\b/)?.[0]; + if (!word) throw new Error('No searchable word found'); + const matches = editor.commands.search(word, { highlight: false }); + + editor.commands.insertTrackedChange({ + from: matches[0].from, + to: matches[0].to, + text: 'REPLACED', + user: { name: 'Test', email: 'test@test.com' }, + comment: 'Test change', + }); + + // Verify the replacement text is in the doc + const newText = editor.state.doc.textContent; + if (!newText.includes('REPLACED')) throw new Error('Tracked change text not found in document'); + editor.destroy(); + }); + + await test('exports a valid DOCX buffer', async () => { + const docx = await readFile(SAMPLE); + const editor = await Editor.open(docx, { documentMode: 'suggesting' }); + + const result = await editor.exportDocx(); + if (!result) throw new Error('exportDocx returned undefined'); + const buf = Buffer.isBuffer(result) ? result : Buffer.from(result as any); + if (!buf.length) throw new Error('Export produced empty buffer'); + if (buf.length < 100) throw new Error(`Output too small: ${buf.length} bytes`); + + // Verify it starts with PK (zip/docx magic bytes) + if (buf[0] !== 0x50 || buf[1] !== 0x4b) { + throw new Error('Output does not start with PK zip header'); + } + + await writeFile(OUTPUT, buf); + if (!existsSync(OUTPUT)) throw new Error('Output file was not written'); + await unlink(OUTPUT); + + editor.destroy(); + }); + + console.log(''); +} + +run(); diff --git a/examples/headless/ai-redlining/src/index.ts b/examples/headless/ai-redlining/src/index.ts new file mode 100644 index 0000000000..5ae61af56f --- /dev/null +++ b/examples/headless/ai-redlining/src/index.ts @@ -0,0 +1,101 @@ +/** + * Headless AI Redlining + * + * Open a DOCX, send its text to an LLM for review, insert tracked changes, + * and export the redlined document — all server-side, no browser needed. + * + * Usage: npx tsx src/index.ts [input.docx] [output.docx] + */ + +import { readFile, writeFile } from 'node:fs/promises'; +import { Editor } from 'superdoc/super-editor'; + +type Suggestion = { find: string; replace: string; comment: string }; + +async function main() { + const args = process.argv.slice(2); + const inputPath = args[0] || 'sample.docx'; + const outputPath = args[1] || 'redlined.docx'; + + const apiKey = process.env.OPENAI_API_KEY; + if (!apiKey) { + console.error('Set OPENAI_API_KEY in .env — see .env.example'); + process.exit(1); + } + + // 1. Open the document headlessly (no element = headless mode) + console.log(`Opening ${inputPath}...`); + const docx = await readFile(inputPath); + const editor = await Editor.open(docx, { documentMode: 'suggesting' }); + + // 2. Extract text and send to LLM + const text = editor.state.doc.textContent; + console.log(`Document has ${text.length} characters. Sending to LLM...`); + const suggestions = await callLLM(apiKey, text); + console.log(`Got ${suggestions.length} suggestions.`); + + // 3. Apply each suggestion as a tracked change + for (const s of suggestions) { + const matches = editor.commands.search(s.find, { highlight: false }); + if (!matches.length) { + console.log(` Skipped (not found): "${s.find.slice(0, 40)}..."`); + continue; + } + + editor.commands.insertTrackedChange({ + from: matches[0].from, + to: matches[0].to, + text: s.replace, + user: { name: 'AI Assistant', email: 'ai@superdoc.dev' }, + comment: s.comment, + }); + console.log(` Applied: "${s.find.slice(0, 40)}..." → "${s.replace.slice(0, 40)}..."`); + } + + // 4. Export the redlined document (headless mode returns a Buffer/Uint8Array) + const result = await editor.exportDocx(); + await writeFile(outputPath, Buffer.from(result as any)); + console.log(`Redlined document saved to ${outputPath}`); + + editor.destroy(); +} + +/** + * Call OpenAI to get redlining suggestions for the document text. + * WARNING: This is a demo only. In production, use proper secret management. + */ +async function callLLM(apiKey: string, text: string): Promise { + const res = await fetch('https://api.openai.com/v1/chat/completions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${apiKey}`, + }, + body: JSON.stringify({ + model: 'gpt-4o-mini', + temperature: 0.3, + response_format: { type: 'json_object' }, + messages: [ + { + role: 'system', + content: `You are a legal document reviewer. Given document text, return a JSON object with a "suggestions" array. Each suggestion has: +- "find": the exact text to replace (must match the document verbatim) +- "replace": the improved text +- "comment": a brief explanation of the change + +Return 3-5 suggestions max. Focus on clarity, precision, and legal best practices.`, + }, + { role: 'user', content: text.slice(0, 8000) }, + ], + }), + }); + + const data = await res.json(); + const content = data.choices?.[0]?.message?.content ?? '{}'; + return JSON.parse(content).suggestions ?? []; +} + +main().catch((err) => { + console.error('Error:', err.message); + process.exit(1); +}); diff --git a/examples/integrations/README.md b/examples/integrations/README.md deleted file mode 100644 index 06ff927330..0000000000 --- a/examples/integrations/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Integration Examples - -Examples showing how to integrate SuperDoc with various platforms and environments. - -## Examples - -| Example | Description | -|---------|-------------| -| [nextjs-ssr](./nextjs-ssr) | Next.js with server-side rendering | -| [nodejs](./nodejs) | Node.js backend for document processing | -| [chrome-extension](./chrome-extension) | Chrome browser extension | -| [word-addin](./word-addin) | Microsoft Word add-in | -| [slack-redlining](./slack-redlining) | Slack integration for document redlining | - -## Running an Example - -```bash -cd -npm install -npm run dev -``` - -See individual example READMEs for platform-specific setup instructions. diff --git a/examples/integrations/chrome-extension/chrome-extension/icons/logo.webp b/examples/integrations/chrome-extension/chrome-extension/icons/logo.webp deleted file mode 100644 index f36a71b86d..0000000000 Binary files a/examples/integrations/chrome-extension/chrome-extension/icons/logo.webp and /dev/null differ diff --git a/examples/integrations/nextjs-ssr/public/sample-document.docx b/examples/integrations/nextjs-ssr/public/sample-document.docx deleted file mode 100644 index b88e504ef0..0000000000 Binary files a/examples/integrations/nextjs-ssr/public/sample-document.docx and /dev/null differ diff --git a/examples/integrations/nodejs/README.md b/examples/integrations/nodejs/README.md deleted file mode 100644 index 28aa0901ac..0000000000 --- a/examples/integrations/nodejs/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Basic example of using SuperDoc in Node - -## Node version -Please use `Node >= 20`. In earlier versions, Node is missing the `File` object. -If you must use `Node < 20`, please create or inject the file polyfill (see example). - -## Quick start -``` -npm install && npm run dev -``` - -This will run a basic express server at `http://localhost:3000` with a single root endpoint. - -Point your browser or Postman GET request to: `http://localhost:3000`. The server will simply return an unchanged .docx template. -Now, you can add query params `text` or `html` to insert content into this document. - -## Basic example -``` - -Text only: http://localhost:3000?text=hello world! - -HTML only: http://localhost:3000?html=

I am a paragraph

I AM BOLD!

-``` - -## Additional docs -Please see [SuperDoc docs](https://docs.superdoc.dev/guide/components#superdoc) for additinoal editor commands and hooks. - -You can get a list of all available editor commands from editor.commands as well. For instnace, commands such as the examples below all will work while using the SuperDoc editor in the backend: -``` -editor.commands.toggleBold() - -editor.commands.toggleOrderedList() - -editor.commands.setColor() - -editor.commands.setFontSize() - -...etc - -``` - diff --git a/examples/pnpm-workspace.yaml b/examples/pnpm-workspace.yaml deleted file mode 100644 index 1736a0e370..0000000000 --- a/examples/pnpm-workspace.yaml +++ /dev/null @@ -1,17 +0,0 @@ -packages: - - '**/*' - - '!collaboration/tiptap-cloud' - -ignoredBuiltDependencies: - - '@microsoft/teamsapp-cli' - - core-js - - keytar - - leveldown - - sharp - - vue-demi - -onlyBuiltDependencies: - - esbuild - -overrides: - superdoc: file:../packages/superdoc/superdoc.tgz diff --git a/examples/shared/css/style.css b/examples/shared/css/style.css deleted file mode 100644 index 7274add928..0000000000 --- a/examples/shared/css/style.css +++ /dev/null @@ -1,74 +0,0 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color: rgba(0, 0, 0, 0.87); - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 2.2em; - line-height: 1.1; -} - -#app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; -} - -.my-custom-mark-class { - border-bottom: 1px solid #646cff; -} - -.example-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -} -.editor-and-button { - display: flex; - flex-direction: row; - align-items: flex-start; - justify-content: center; -} -.editor-buttons { - display: flex; - flex-direction: column; -} -.editor-buttons button { - margin-bottom: 10px; -} -.custom-button { - padding: 8px 12px; - border-radius: 8px; - margin-left: 10px; - outline: none; - border: none; - background-color: #AECEE6; -} -.hidden { - display: none; -} \ No newline at end of file diff --git a/examples/shared/data/sample-document.docx b/examples/shared/data/sample-document.docx deleted file mode 100644 index b88e504ef0..0000000000 Binary files a/examples/shared/data/sample-document.docx and /dev/null differ diff --git a/examples/shared/images/logo.webp b/examples/shared/images/logo.webp deleted file mode 100644 index f36a71b86d..0000000000 Binary files a/examples/shared/images/logo.webp and /dev/null differ diff --git a/examples/shared/images/superdoc-logo.png b/examples/shared/images/superdoc-logo.png deleted file mode 100644 index 0e1a8349a3..0000000000 Binary files a/examples/shared/images/superdoc-logo.png and /dev/null differ diff --git a/examples/shared/vue/UploadFile/UploadFile.vue b/examples/shared/vue/UploadFile/UploadFile.vue deleted file mode 100644 index 86c92e293a..0000000000 --- a/examples/shared/vue/UploadFile/UploadFile.vue +++ /dev/null @@ -1,30 +0,0 @@ - - - diff --git a/examples/tests/.gitignore b/examples/tests/.gitignore deleted file mode 100644 index 51957b8756..0000000000 --- a/examples/tests/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -playwright-report -!package-lock.json \ No newline at end of file diff --git a/examples/tests/Dockerfile b/examples/tests/Dockerfile deleted file mode 100644 index fdb6a9cfa4..0000000000 --- a/examples/tests/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -# syntax=docker/dockerfile:1 -FROM mcr.microsoft.com/playwright:v1.57.0-jammy -WORKDIR /app - -# Install a minimal set of fonts so UI typography matches local dev closer -RUN apt-get update && \ - apt-get install -y --no-install-recommends fonts-liberation fonts-dejavu-core fonts-noto-core && \ - rm -rf /var/lib/apt/lists/* - -ENV CI="true" -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -RUN corepack enable - -COPY --exclude=e2e-tests . . -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install -RUN pnpm run pack -RUN --mount=type=cache,id=pnpm,target=/pnpm/store cd examples && pnpm install --no-frozen-lockfile - -CMD ["bash", "-c", "pnpm run --prefix examples/tests test $PLAYWRIGHT_ARGS --workers=4"] diff --git a/examples/tests/README.md b/examples/tests/README.md deleted file mode 100644 index 06cc38de32..0000000000 --- a/examples/tests/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Demo gallery visual tests - -Playwright visual regression tests for the examples listed in `test-config.js`. - -## One-time setup - -```bash -pnpm --dir examples/tests run bootstrap -# If Playwright browsers are missing: -pnpm --dir examples/tests exec playwright install chromium -``` - -> `pnpm test` and `pnpm run update-snapshots` automatically run `pnpm run bootstrap` first. - -## Run the suite - -```bash -pnpm --dir examples/tests test -``` - -Quick filters: - -```bash -# Single project + test name match -pnpm --dir examples/tests test --project=chromium --grep dynamic-content -``` - -## Update snapshots - -```bash -pnpm --dir examples/tests run update-snapshots --project=chromium --grep dynamic-content -``` - -Or call Playwright directly: - -```bash -pnpm --dir examples/tests exec playwright test --update-snapshots --project=chromium --grep dynamic-content -``` - -Docker (matches CI): - -```bash -cd examples/tests -pnpm run docker:update-screenshots -``` diff --git a/examples/tests/package.json b/examples/tests/package.json deleted file mode 100644 index 90be55e00b..0000000000 --- a/examples/tests/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "demo-gallery-tests", - "version": "1.0.0", - "description": "Automated tests suites for all examples on the demo gallery", - "type": "module", - "main": "index.js", - "scripts": { - "bootstrap": "node ./scripts/bootstrap.js", - "pretest": "pnpm run bootstrap", - "preupdate-snapshots": "pnpm run bootstrap", - "docker:build": "docker build -t playwright-visual-tests -f Dockerfile ../..", - "docker:test": "docker run -e PLAYWRIGHT_ARGS=\"$PLAYWRIGHT_ARGS\" --rm -v ./test-results:/app/examples/tests/test-results -v ./tests.spec.js-snapshots:/app/examples/tests/tests.spec.js-snapshots -ti playwright-visual-tests", - "docker:update-screenshots": "PLAYWRIGHT_ARGS='--update-snapshots' pnpm run docker:test", - "test": "playwright test", - "update-snapshots": "playwright test --update-snapshots", - "update-screenshots": "pnpm run update-snapshots" - }, - "author": "", - "license": "ISC", - "devDependencies": { - "@playwright/test": "^1.55.0", - "playwright": "^1.55.0" - }, - "dependencies": { - "patch-package": "^8.0.1" - } -} diff --git a/examples/tests/playwright.config.js b/examples/tests/playwright.config.js deleted file mode 100644 index 2af2e7e9e6..0000000000 --- a/examples/tests/playwright.config.js +++ /dev/null @@ -1,46 +0,0 @@ -import { defineConfig, devices } from '@playwright/test'; -import testConfig from './test-config.js'; - -export default defineConfig({ - testMatch: '**/*.spec.js', - - // Run all tests in parallel. - fullyParallel: true, - - // Fail the build on CI if you accidentally left test.only in the source code. - forbidOnly: !!process.env.CI, - - // Retry on CI only. - retries: process.env.CI ? 2 : 0, - - // Opt out of parallel tests on CI. - workers: process.env.CI ? 1 : undefined, - - // Reporter to use - reporter: 'html', - - use: { - // Base URL to use in actions like `await page.goto('/')`. - baseURL: 'http://localhost:5173', - - // Collect trace when retrying the failed test. - trace: 'on-first-retry', - }, - // Configure projects for major browsers. - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, - }, - ], - - // Open every example in a separate server - webServer: testConfig.packages.map((packagePath, i) => { - return { - command: `pnpm run --prefix ../${packagePath} dev --port ${5173 + i}`, - url: `http://localhost:${5173 + i}`, - reuseExistingServer: !process.env.CI, - timeout: 120 * 1000, // 2 minutes timeout for server startup - } - }) -}); diff --git a/examples/tests/scripts/bootstrap.js b/examples/tests/scripts/bootstrap.js deleted file mode 100644 index c43ffa108f..0000000000 --- a/examples/tests/scripts/bootstrap.js +++ /dev/null @@ -1,36 +0,0 @@ -import { execSync } from 'node:child_process'; -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import testConfig from '../test-config.js'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const testsDir = path.resolve(__dirname, '..'); -const repoRoot = path.resolve(testsDir, '..'); - -const packages = [ - { name: 'demo-gallery-tests', dir: testsDir }, - ...testConfig.packages.map((relPath) => ({ - name: relPath, - dir: path.resolve(repoRoot, relPath), - })), -]; - -const hasNodeModules = (dir) => fs.existsSync(path.join(dir, 'node_modules')); - -const install = ({ name, dir }) => { - if (hasNodeModules(dir)) { - console.log(`✓ ${name} already installed`); - return; - } - - console.log(`→ Installing ${name} dependencies...`); - execSync('pnpm install --ignore-workspace', { cwd: dir, stdio: 'inherit' }); -}; - -packages.forEach(install); - -console.log( - 'Bootstrap complete. Install Playwright browsers if needed: pnpm --dir examples/tests exec playwright install chromium', -); diff --git a/examples/tests/test-config.js b/examples/tests/test-config.js deleted file mode 100644 index ea7986aa6f..0000000000 --- a/examples/tests/test-config.js +++ /dev/null @@ -1,19 +0,0 @@ -export default { - packages: [ - "getting-started/cdn", - "getting-started/react", - "getting-started/typescript", - "getting-started/vanilla", - "getting-started/vue", - // Customization - "customization/custom-mark", - "customization/dynamic-content", - // Integrations - "integrations/nextjs-ssr", - // Advanced - "advanced/docx-from-html", - "advanced/fields", - "advanced/linked-sections", - "advanced/text-selection", - ] -} diff --git a/examples/tests/tests.spec.js b/examples/tests/tests.spec.js deleted file mode 100644 index b54dd60f6d..0000000000 --- a/examples/tests/tests.spec.js +++ /dev/null @@ -1,25 +0,0 @@ -import { test, expect } from '@playwright/test'; -import testConfig from './test-config.js'; -const PORT = 5173; - -testConfig.packages.forEach((packagePath, i) => { - const name = packagePath.replace(/.*\//, ''); - test.describe(name, () => { - test('should open the main page', async ({ page }) => { - // Should open the main page - await page.goto(`http://localhost:${PORT + i}`); - - await page.waitForSelector('div.super-editor', { - timeout: 10_000, - }); - - const screenshotOptions = { - fullPage: true, - maxDiffPixelRatio: 0.05, // allow small visual drift across demos - }; - - // Compare the screenshot with the reference screenshot - await expect(page).toHaveScreenshot(screenshotOptions); - }); -}); -}); diff --git a/examples/tests/tests.spec.js-snapshots/cdn-should-open-the-main-page-1-chromium-darwin.png b/examples/tests/tests.spec.js-snapshots/cdn-should-open-the-main-page-1-chromium-darwin.png deleted file mode 100644 index 76d2646769..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/cdn-should-open-the-main-page-1-chromium-darwin.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/cdn-should-open-the-main-page-1-chromium-linux.png b/examples/tests/tests.spec.js-snapshots/cdn-should-open-the-main-page-1-chromium-linux.png deleted file mode 100644 index 0eb85f5013..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/cdn-should-open-the-main-page-1-chromium-linux.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/custom-mark-should-open-the-main-page-1-chromium-darwin.png b/examples/tests/tests.spec.js-snapshots/custom-mark-should-open-the-main-page-1-chromium-darwin.png deleted file mode 100644 index d288f69f3b..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/custom-mark-should-open-the-main-page-1-chromium-darwin.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/custom-mark-should-open-the-main-page-1-chromium-linux.png b/examples/tests/tests.spec.js-snapshots/custom-mark-should-open-the-main-page-1-chromium-linux.png deleted file mode 100644 index 01bdc6e1e5..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/custom-mark-should-open-the-main-page-1-chromium-linux.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/docx-from-html-should-open-the-main-page-1-chromium-darwin.png b/examples/tests/tests.spec.js-snapshots/docx-from-html-should-open-the-main-page-1-chromium-darwin.png deleted file mode 100644 index 2e2f8f9b03..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/docx-from-html-should-open-the-main-page-1-chromium-darwin.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/docx-from-html-should-open-the-main-page-1-chromium-linux.png b/examples/tests/tests.spec.js-snapshots/docx-from-html-should-open-the-main-page-1-chromium-linux.png deleted file mode 100644 index f9d4f6c3c3..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/docx-from-html-should-open-the-main-page-1-chromium-linux.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/dynamic-content-should-open-the-main-page-1-chromium-darwin.png b/examples/tests/tests.spec.js-snapshots/dynamic-content-should-open-the-main-page-1-chromium-darwin.png deleted file mode 100644 index b4fa565104..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/dynamic-content-should-open-the-main-page-1-chromium-darwin.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/dynamic-content-should-open-the-main-page-1-chromium-linux.png b/examples/tests/tests.spec.js-snapshots/dynamic-content-should-open-the-main-page-1-chromium-linux.png deleted file mode 100644 index 86d3fad373..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/dynamic-content-should-open-the-main-page-1-chromium-linux.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/fields-should-open-the-main-page-1-chromium-darwin.png b/examples/tests/tests.spec.js-snapshots/fields-should-open-the-main-page-1-chromium-darwin.png deleted file mode 100644 index e85078c67f..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/fields-should-open-the-main-page-1-chromium-darwin.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/fields-should-open-the-main-page-1-chromium-linux.png b/examples/tests/tests.spec.js-snapshots/fields-should-open-the-main-page-1-chromium-linux.png deleted file mode 100644 index bc5b91ec86..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/fields-should-open-the-main-page-1-chromium-linux.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/linked-sections-should-open-the-main-page-1-chromium-darwin.png b/examples/tests/tests.spec.js-snapshots/linked-sections-should-open-the-main-page-1-chromium-darwin.png deleted file mode 100644 index 22ad6cdb27..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/linked-sections-should-open-the-main-page-1-chromium-darwin.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/linked-sections-should-open-the-main-page-1-chromium-linux.png b/examples/tests/tests.spec.js-snapshots/linked-sections-should-open-the-main-page-1-chromium-linux.png deleted file mode 100644 index 55517df073..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/linked-sections-should-open-the-main-page-1-chromium-linux.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/nextjs-ssr-should-open-the-main-page-1-chromium-darwin.png b/examples/tests/tests.spec.js-snapshots/nextjs-ssr-should-open-the-main-page-1-chromium-darwin.png deleted file mode 100644 index 4af6ff60bb..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/nextjs-ssr-should-open-the-main-page-1-chromium-darwin.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/nextjs-ssr-should-open-the-main-page-1-chromium-linux.png b/examples/tests/tests.spec.js-snapshots/nextjs-ssr-should-open-the-main-page-1-chromium-linux.png deleted file mode 100644 index 2d7f6e6b05..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/nextjs-ssr-should-open-the-main-page-1-chromium-linux.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/react-should-open-the-main-page-1-chromium-darwin.png b/examples/tests/tests.spec.js-snapshots/react-should-open-the-main-page-1-chromium-darwin.png deleted file mode 100644 index 56f6066299..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/react-should-open-the-main-page-1-chromium-darwin.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/react-should-open-the-main-page-1-chromium-linux.png b/examples/tests/tests.spec.js-snapshots/react-should-open-the-main-page-1-chromium-linux.png deleted file mode 100644 index 722a15856f..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/react-should-open-the-main-page-1-chromium-linux.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/text-selection-should-open-the-main-page-1-chromium-darwin.png b/examples/tests/tests.spec.js-snapshots/text-selection-should-open-the-main-page-1-chromium-darwin.png deleted file mode 100644 index 8478996155..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/text-selection-should-open-the-main-page-1-chromium-darwin.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/text-selection-should-open-the-main-page-1-chromium-linux.png b/examples/tests/tests.spec.js-snapshots/text-selection-should-open-the-main-page-1-chromium-linux.png deleted file mode 100644 index 28a1609856..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/text-selection-should-open-the-main-page-1-chromium-linux.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/typescript-should-open-the-main-page-1-chromium-darwin.png b/examples/tests/tests.spec.js-snapshots/typescript-should-open-the-main-page-1-chromium-darwin.png deleted file mode 100644 index 5af355b419..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/typescript-should-open-the-main-page-1-chromium-darwin.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/typescript-should-open-the-main-page-1-chromium-linux.png b/examples/tests/tests.spec.js-snapshots/typescript-should-open-the-main-page-1-chromium-linux.png deleted file mode 100644 index aa20df336d..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/typescript-should-open-the-main-page-1-chromium-linux.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/vanilla-should-open-the-main-page-1-chromium-darwin.png b/examples/tests/tests.spec.js-snapshots/vanilla-should-open-the-main-page-1-chromium-darwin.png deleted file mode 100644 index 5af355b419..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/vanilla-should-open-the-main-page-1-chromium-darwin.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/vanilla-should-open-the-main-page-1-chromium-linux.png b/examples/tests/tests.spec.js-snapshots/vanilla-should-open-the-main-page-1-chromium-linux.png deleted file mode 100644 index aa20df336d..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/vanilla-should-open-the-main-page-1-chromium-linux.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/vue-should-open-the-main-page-1-chromium-darwin.png b/examples/tests/tests.spec.js-snapshots/vue-should-open-the-main-page-1-chromium-darwin.png deleted file mode 100644 index 26990cd8d1..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/vue-should-open-the-main-page-1-chromium-darwin.png and /dev/null differ diff --git a/examples/tests/tests.spec.js-snapshots/vue-should-open-the-main-page-1-chromium-linux.png b/examples/tests/tests.spec.js-snapshots/vue-should-open-the-main-page-1-chromium-linux.png deleted file mode 100644 index 02d548a6b3..0000000000 Binary files a/examples/tests/tests.spec.js-snapshots/vue-should-open-the-main-page-1-chromium-linux.png and /dev/null differ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2bcaad0f5d..a44228b885 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -422,7 +422,7 @@ importers: version: 14.0.3 mintlify: specifier: ^4.2.331 - version: 4.2.331(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.11))(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(react@19.2.3))(@types/node@22.19.2)(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(typescript@5.9.3) + version: 4.2.331(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.11))(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(react@19.2.3))(@types/node@22.19.8)(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(typescript@5.9.3) remark-mdx: specifier: ^3.1.1 version: 3.1.1 @@ -11139,128 +11139,128 @@ snapshots: '@inquirer/ansi@1.0.2': {} - '@inquirer/checkbox@4.3.2(@types/node@22.19.2)': + '@inquirer/checkbox@4.3.2(@types/node@22.19.8)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@22.19.2) + '@inquirer/core': 10.3.2(@types/node@22.19.8) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@22.19.2) + '@inquirer/type': 3.0.10(@types/node@22.19.8) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 22.19.2 + '@types/node': 22.19.8 - '@inquirer/confirm@5.1.21(@types/node@22.19.2)': + '@inquirer/confirm@5.1.21(@types/node@22.19.8)': dependencies: - '@inquirer/core': 10.3.2(@types/node@22.19.2) - '@inquirer/type': 3.0.10(@types/node@22.19.2) + '@inquirer/core': 10.3.2(@types/node@22.19.8) + '@inquirer/type': 3.0.10(@types/node@22.19.8) optionalDependencies: - '@types/node': 22.19.2 + '@types/node': 22.19.8 - '@inquirer/core@10.3.2(@types/node@22.19.2)': + '@inquirer/core@10.3.2(@types/node@22.19.8)': dependencies: '@inquirer/ansi': 1.0.2 '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@22.19.2) + '@inquirer/type': 3.0.10(@types/node@22.19.8) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 22.19.2 + '@types/node': 22.19.8 - '@inquirer/editor@4.2.23(@types/node@22.19.2)': + '@inquirer/editor@4.2.23(@types/node@22.19.8)': dependencies: - '@inquirer/core': 10.3.2(@types/node@22.19.2) - '@inquirer/external-editor': 1.0.3(@types/node@22.19.2) - '@inquirer/type': 3.0.10(@types/node@22.19.2) + '@inquirer/core': 10.3.2(@types/node@22.19.8) + '@inquirer/external-editor': 1.0.3(@types/node@22.19.8) + '@inquirer/type': 3.0.10(@types/node@22.19.8) optionalDependencies: - '@types/node': 22.19.2 + '@types/node': 22.19.8 - '@inquirer/expand@4.0.23(@types/node@22.19.2)': + '@inquirer/expand@4.0.23(@types/node@22.19.8)': dependencies: - '@inquirer/core': 10.3.2(@types/node@22.19.2) - '@inquirer/type': 3.0.10(@types/node@22.19.2) + '@inquirer/core': 10.3.2(@types/node@22.19.8) + '@inquirer/type': 3.0.10(@types/node@22.19.8) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 22.19.2 + '@types/node': 22.19.8 - '@inquirer/external-editor@1.0.3(@types/node@22.19.2)': + '@inquirer/external-editor@1.0.3(@types/node@22.19.8)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 22.19.2 + '@types/node': 22.19.8 '@inquirer/figures@1.0.15': {} - '@inquirer/input@4.3.1(@types/node@22.19.2)': + '@inquirer/input@4.3.1(@types/node@22.19.8)': dependencies: - '@inquirer/core': 10.3.2(@types/node@22.19.2) - '@inquirer/type': 3.0.10(@types/node@22.19.2) + '@inquirer/core': 10.3.2(@types/node@22.19.8) + '@inquirer/type': 3.0.10(@types/node@22.19.8) optionalDependencies: - '@types/node': 22.19.2 + '@types/node': 22.19.8 - '@inquirer/number@3.0.23(@types/node@22.19.2)': + '@inquirer/number@3.0.23(@types/node@22.19.8)': dependencies: - '@inquirer/core': 10.3.2(@types/node@22.19.2) - '@inquirer/type': 3.0.10(@types/node@22.19.2) + '@inquirer/core': 10.3.2(@types/node@22.19.8) + '@inquirer/type': 3.0.10(@types/node@22.19.8) optionalDependencies: - '@types/node': 22.19.2 + '@types/node': 22.19.8 - '@inquirer/password@4.0.23(@types/node@22.19.2)': + '@inquirer/password@4.0.23(@types/node@22.19.8)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@22.19.2) - '@inquirer/type': 3.0.10(@types/node@22.19.2) + '@inquirer/core': 10.3.2(@types/node@22.19.8) + '@inquirer/type': 3.0.10(@types/node@22.19.8) optionalDependencies: - '@types/node': 22.19.2 + '@types/node': 22.19.8 - '@inquirer/prompts@7.9.0(@types/node@22.19.2)': - dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@22.19.2) - '@inquirer/confirm': 5.1.21(@types/node@22.19.2) - '@inquirer/editor': 4.2.23(@types/node@22.19.2) - '@inquirer/expand': 4.0.23(@types/node@22.19.2) - '@inquirer/input': 4.3.1(@types/node@22.19.2) - '@inquirer/number': 3.0.23(@types/node@22.19.2) - '@inquirer/password': 4.0.23(@types/node@22.19.2) - '@inquirer/rawlist': 4.1.11(@types/node@22.19.2) - '@inquirer/search': 3.2.2(@types/node@22.19.2) - '@inquirer/select': 4.4.2(@types/node@22.19.2) + '@inquirer/prompts@7.9.0(@types/node@22.19.8)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@22.19.8) + '@inquirer/confirm': 5.1.21(@types/node@22.19.8) + '@inquirer/editor': 4.2.23(@types/node@22.19.8) + '@inquirer/expand': 4.0.23(@types/node@22.19.8) + '@inquirer/input': 4.3.1(@types/node@22.19.8) + '@inquirer/number': 3.0.23(@types/node@22.19.8) + '@inquirer/password': 4.0.23(@types/node@22.19.8) + '@inquirer/rawlist': 4.1.11(@types/node@22.19.8) + '@inquirer/search': 3.2.2(@types/node@22.19.8) + '@inquirer/select': 4.4.2(@types/node@22.19.8) optionalDependencies: - '@types/node': 22.19.2 + '@types/node': 22.19.8 - '@inquirer/rawlist@4.1.11(@types/node@22.19.2)': + '@inquirer/rawlist@4.1.11(@types/node@22.19.8)': dependencies: - '@inquirer/core': 10.3.2(@types/node@22.19.2) - '@inquirer/type': 3.0.10(@types/node@22.19.2) + '@inquirer/core': 10.3.2(@types/node@22.19.8) + '@inquirer/type': 3.0.10(@types/node@22.19.8) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 22.19.2 + '@types/node': 22.19.8 - '@inquirer/search@3.2.2(@types/node@22.19.2)': + '@inquirer/search@3.2.2(@types/node@22.19.8)': dependencies: - '@inquirer/core': 10.3.2(@types/node@22.19.2) + '@inquirer/core': 10.3.2(@types/node@22.19.8) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@22.19.2) + '@inquirer/type': 3.0.10(@types/node@22.19.8) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 22.19.2 + '@types/node': 22.19.8 - '@inquirer/select@4.4.2(@types/node@22.19.2)': + '@inquirer/select@4.4.2(@types/node@22.19.8)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@22.19.2) + '@inquirer/core': 10.3.2(@types/node@22.19.8) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@22.19.2) + '@inquirer/type': 3.0.10(@types/node@22.19.8) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 22.19.2 + '@types/node': 22.19.8 - '@inquirer/type@3.0.10(@types/node@22.19.2)': + '@inquirer/type@3.0.10(@types/node@22.19.8)': optionalDependencies: - '@types/node': 22.19.2 + '@types/node': 22.19.8 '@isaacs/balanced-match@4.0.1': {} @@ -11614,9 +11614,9 @@ snapshots: '@microsoft/tsdoc@0.16.0': {} - '@mintlify/cli@4.0.935(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.11))(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(react@19.2.3))(@types/node@22.19.2)(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(typescript@5.9.3)': + '@mintlify/cli@4.0.935(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.11))(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(react@19.2.3))(@types/node@22.19.8)(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(typescript@5.9.3)': dependencies: - '@inquirer/prompts': 7.9.0(@types/node@22.19.2) + '@inquirer/prompts': 7.9.0(@types/node@22.19.8) '@mintlify/common': 1.0.713(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.11))(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(react@19.2.3)(typescript@5.9.3) '@mintlify/link-rot': 3.0.872(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.11))(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(react@19.2.3)(typescript@5.9.3) '@mintlify/models': 0.0.268 @@ -11630,7 +11630,7 @@ snapshots: front-matter: 4.0.2 fs-extra: 11.2.0 ink: 6.3.0(@types/react@19.2.11)(react@19.2.3) - inquirer: 12.3.0(@types/node@22.19.2) + inquirer: 12.3.0(@types/node@22.19.8) js-yaml: 4.1.0 mdast-util-mdx-jsx: 3.2.0 react: 19.2.3 @@ -13434,6 +13434,14 @@ snapshots: optionalDependencies: vite: 7.3.1(@types/node@22.19.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) + '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@22.19.8)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.1(@types/node@22.19.8)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) + '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 @@ -16649,12 +16657,12 @@ snapshots: inline-style-parser@0.2.7: {} - inquirer@12.3.0(@types/node@22.19.2): + inquirer@12.3.0(@types/node@22.19.8): dependencies: - '@inquirer/core': 10.3.2(@types/node@22.19.2) - '@inquirer/prompts': 7.9.0(@types/node@22.19.2) - '@inquirer/type': 3.0.10(@types/node@22.19.2) - '@types/node': 22.19.2 + '@inquirer/core': 10.3.2(@types/node@22.19.8) + '@inquirer/prompts': 7.9.0(@types/node@22.19.8) + '@inquirer/type': 3.0.10(@types/node@22.19.8) + '@types/node': 22.19.8 ansi-escapes: 4.3.2 mute-stream: 2.0.0 run-async: 3.0.0 @@ -18360,9 +18368,9 @@ snapshots: minipass: 3.3.6 yallist: 4.0.0 - mintlify@4.2.331(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.11))(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(react@19.2.3))(@types/node@22.19.2)(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(typescript@5.9.3): + mintlify@4.2.331(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.11))(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(react@19.2.3))(@types/node@22.19.8)(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(typescript@5.9.3): dependencies: - '@mintlify/cli': 4.0.935(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.11))(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(react@19.2.3))(@types/node@22.19.2)(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(typescript@5.9.3) + '@mintlify/cli': 4.0.935(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.11))(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(react@19.2.3))(@types/node@22.19.8)(@types/react@19.2.11)(react-dom@19.2.0(react@19.2.3))(typescript@5.9.3) transitivePeerDependencies: - '@radix-ui/react-popover' - '@types/node' @@ -21556,7 +21564,7 @@ snapshots: dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@22.19.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@22.19.8)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4