README.md
Rama main · réplica de lectura del repositorio original.
# Fileverse dDocs
[ddocs.new](http://ddocs.new/) is your privacy-focused, open-source alternative to Google Docs. It is an end-to-end encrypted document editor that is optimized for multiplayer collaboration without compromising on speed or people's control over their data.
## 𓆏 Features include:
- End-to-end encryption with post-quantum cryptography
- Real-time and async collaboration
- Suggestion mode (tracked changes) and commenting
- Markdown and LaTeX support
- MermaidJS diagram support
- Import/export of .docx, .md, .pdf, .html and more.
- Dark mode and color themes
- Offline/online editing (continue writing and opening older docs even with no WiFi)
- Cross-device syncing (desktop and mobile)
- Mobile optimized webapp & PWA
- Private access permissions enabled through zero-knowledge proofs
- Private social recovery of account/files, enabled through zero-knowledge proofs
- Version history
- End-to-end encrypted, programmable API optimised for agents (opt-in)
<img width="1574" height="981" alt="ddocsNew" src="https://github.com/user-attachments/assets/b7a344c1-8c86-4735-9181-d0c22a5c4ff6" />
This repository contains:
- `/package` – The core package code.
- Example & demo source code to showcase dDocs functionalities.
## Usage
### Prerequisites
Your app runs Tailwind CSS 3.4 and owns the `@tailwind base/components/utilities` entry. The package ships only editor CSS; preflight, utilities, the `@fileverse/ui` styles, and the KaTeX stylesheet come from your build.
### Install & import
```javascript
import { DdocEditor } from '@fileverse-dev/ddoc';
```
Load stylesheets in this order (Vite emits CSS in import order; in Next put the first two in the root layout and the last two in the editor route):
```javascript
import './globals.css'; // your tailwind entry
import '@fileverse/ui/styles/base'; // tokens and base rules
import 'katex/dist/katex.min.css'; // math; fonts are served as files
import '@fileverse-dev/ddoc/styles'; // editor CSS
```
### Sizing
`DdocEditor` fills its parent: `width: 100%; height: 100%`. The parent must have a definite height. The editor never reads the viewport, so a navbar above it or a footer below it is ordinary layout:
```tsx
<div style={{ height: '100dvh', display: 'flex', flexDirection: 'column' }}>
<div style={{ flex: 1, minHeight: 0 }}>
<DdocEditor renderNavbar={renderNavbar} />
</div>
<footer>…</footer>
</div>
```
If the editor renders with no height, its parent has none.
`renderNavbar` output is the editor's first row by default, rendered as is: the editor adds no styling, so return one element with your own background, padding and border. In flow it sits in an unstyled row that only animates the collapse. Without `renderNavbar` nothing is rendered. To place the navbar elsewhere, pass the element to render into. Hold it in state, not in a ref, so the editor re-renders when it attaches:
```tsx
const [navbarEl, setNavbarEl] = useState<HTMLElement | null>(null);
<header ref={setNavbarEl} />
<DdocEditor navbarContainer={navbarEl} renderNavbar={renderNavbar} />
```
The layout follows the editor's width, not the window's: in a narrow panel it uses its narrow layout. Known limits: one `DdocEditor` per page; in a narrow editor on a wide screen the comment sheet still covers the whole viewport.
### Peer Dependencies
This package requires the following peer dependencies to be installed in your project:
```bash
npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities @fileverse/ui @fileverse/crypto viem framer-motion frimousse
```
| Package | Version |
| -------------------- | ----------- |
| `@dnd-kit/core` | `>=6.3.1` |
| `@dnd-kit/sortable` | `>=10.0.0` |
| `@dnd-kit/utilities` | `>=3.2.2` |
| `@fileverse/ui` | `^5.4.2` |
| `@fileverse/crypto` | `>=0.0.21` |
| `viem` | `>=2.13.8` |
| `framer-motion` | `>=11.2.10` |
| `frimousse` | `>=0.3.0` |
The peer range is `@fileverse/ui@^5.4.2`. If you ever test a prerelease pairing, pin both packages exactly: a prerelease never satisfies a caret range.
These are externalized from the bundle to avoid duplication when your app already uses them. If you don't have them installed, npm (v7+) will auto-install them for you.
### Update Tailwind Config
```javascript
module.exports = {
presets: [require('@fileverse-dev/ddoc/tailwind')],
content: [
'./src/**/*.{js,ts,jsx,tsx}',
'./node_modules/@fileverse-dev/ddoc/dist/**/*.{js,mjs}',
'./node_modules/@fileverse/ui/dist/**/*.{js,mjs}',
],
};
```
Both dist globs are required, not the single `index.es.js` entry: the dist is chunked (`index.es.js`
statically imports `use-headless-editor-<hash>.mjs`), so a single-file entry leaves that chunk's
classes ungenerated.
The preset composes `@fileverse/ui/tailwind` (class-based dark mode, animate plugin, design-system classes) and adds the `mobile: 960px` screen the editor uses.
`tailwindcss@^3.4.0` is an optional peer: the preset runs inside your own Tailwind build, which also supplies the `postcss` and `postcss-js` the ui preset loads. Tailwind 4 is not supported.
### Migrating from 4.x
5.0 stops shipping preflight, Tailwind utilities, the ui stylesheet, the KaTeX stylesheet, and the `html`, `body`, and `*` resets. To upgrade:
1. Add the preset and the two `content` entries above.
2. Import `@fileverse/ui/styles/base` and `katex/dist/katex.min.css` yourself, in the order above.
3. The package no longer ships these global declarations:
- `* { margin: 0; padding: 0 }`
- `html, body { height: 100%; width: 100% }`
- `html, body { overflow: hidden }`
- `body { position: static; user-select: none; -moz-osx-font-smoothing: grayscale }`
- `*, ::before, ::after { border-color: hsl(var(--color-border-default)) }`
If your shell relied on `overflow: hidden` or `user-select: none`, add them to your own
global stylesheet. The rest are covered by Tailwind preflight or the `@fileverse/ui/styles/base`
sheet.
4. Remove any workaround that re-declared `mobile:` classes; the preset generates them.
5. Add `katex` to your own dependencies (`npm install katex@^0.16.11`); the package no longer guarantees it is hoisted.
6. If you styled anything from the old bundle by class name: `.custom-scrollbar` is now `.ddoc-scrollbar`, and `.highlight-comment-bg`, `.is-active`, `.custom-border-bg`, `.animate-fade-in-out`, `.placeholder-disabled`, `.tooltip:before` were removed as unused.
7. The composed `@fileverse/ui` preset defines `rounded-sm` as `calc(var(--radius) - 4px)` (4px)
instead of Tailwind's default 2px, so any surface using ddoc's `rounded-sm` utilities now
renders 4px corners.
8. Third-party CSS ddoc imports (the highlight.js theme, tippy) still ships global `.hljs*` and
`.tippy-box` selectors — this is outside the package's own selector-ownership guarantee.
9. List rules (`ul`, `ol`, and the nested-list counters) are now scoped to
`:where(.ProseMirror, .presentation-mode)` and no longer style `ol`/`ul` outside the editor or
presentation surfaces. If your app relied on ddoc's global list rules elsewhere, own them yourself.
You should now be set to use dDocs!
### Migrating from 6.x
7.0 changes how the editor is sized. No prop was removed.
- Give the editor a parent with a definite height (see Sizing). It no longer sizes itself from `100dvh`.
- Put your footer after the editor in normal flow. `footerHeight` has no effect and can be removed.
- The navbar and toolbars are no longer inside `#editor-canvas`. CSS that used `#editor-canvas` as their ancestor should use `.ddoc-editor-root`.
- The editor no longer wraps your navbar in `<nav id="Navbar">`. It renders what `renderNavbar` returns, so move the wrapper's styling (`color-bg-default p-2 flex gap-10 items-center justify-between w-full border-b color-border-default`) and the `id`, if you select on it, onto your own element. The Escape-returns-focus-to-editor handler went with it, and hiding now collapses an unstyled row (`.ddoc-navbar-row`) around your element. Without `renderNavbar` nothing is rendered. The navbar's height comes from its content; it was a fixed 64px, 46px below `lg`.
- `editorCanvasClassNames` should not set a height.
- `zoomLevel`, `isNavbarVisible` and `isPreviewMode` are optional. Every UI state can be left out, or controlled with a value and an `onXChange` callback. The `setX` props still work and are deprecated.
- Your Tailwind build needs no config change. The new `ddoc-*` variants (container-query breakpoints that follow the editor's width) come from the `@fileverse-dev/ddoc/tailwind` preset you already use.
Behaviour that changes without a code change on your side:
- UI features that did nothing without their `setX` prop now work on internal state. The Split View toggle shows whenever live collaboration is off (it needed `setIsSplitView` before), and the comment drawer, the outline and presentation mode open without a setter. To keep one fixed, pass its value with no callback, for example `isPresentationMode={false}`. For Split View, `isSplitView={false}` with no callback also hides the toolbar toggle.
- `onFocusModeChange` and the deprecated `onFocusMode` also fire when `isFocusMode` is not passed.
- `#editor-canvas` is now the document scroller (it carries `data-editor-scroll-container`), and the root (`.ddoc-editor-root`) is `overflow: hidden`.
- A navbar portalled with `navbarContainer` is positioned and layered by you; the editor only renders into the element. Focus mode and the toolbar's collapse toggle do not hide it: hide your own element from `onFocusModeChange` and `onNavbarVisibleChange` if you want that, or pass `showNavbarToggle={false}` to remove the collapse toggle.
- The toolbars no longer slide or fade when hidden; their rows are removed and the canvas takes the space. An in-flow navbar still slides up and fades over 300ms. For a portalled navbar the animation is yours: put `ddoc-navbar-row` on your element and add `ddoc-navbar-row--hidden` to collapse it.
- The mobile tab panel sits at the editor's bottom edge. It no longer adds `24px + env(safe-area-inset-bottom)`, so your footer must carry the safe-area inset.
- The editor's responsive classes use `:where()` in their window fallback, so they need Chrome 88+, Safari 14+ or Firefox 78+.
# dDocProps Interface
The `DdocProps` interface is a TypeScript interface that defines the properties for a page-related component. It includes properties for handling preview mode, managing publishing data, and optionally storing metadata and content associated with the page.
## Core Props
| Property | Type | Description |
| ------------------------ | --------------------------------------------- | ----------------------------------------------- |
| `initialContent` | `JSONContent` | Initial content of the editor |
| `onChange` | `(changes: JSONContent, chunk?: any) => void` | Callback triggered on editor content changes |
| `ref` | `React.RefObject` | Reference to access editor instance |
| `isPreviewMode` | `boolean` | Controls if editor is in preview/read-only mode. Optional, defaults to `false` |
| `editorCanvasClassNames` | `string` | Additional CSS classes for editor canvas |
| `ignoreCorruptedData` | `boolean` | Whether to ignore corrupted data during loading |
| `onInvalidContentError` | `(error: any) => void` | Callback for handling invalid content errors |
## Collaboration Props
| Property | Type | Description |
| ---------------------- | --------------------------------------------- | ------------------------------------------------------------------------- |
| `enableCollaboration` | `boolean` | Enables real-time collaboration features |
| `collaborationId` | `string` | Unique ID for collaboration session (required when collaboration enabled) |
| `username` | `string` | User's display name for collaboration |
| `setUsername` | `(username: string) => void` | Function to update username |
| `walletAddress` | `string` | User's wallet address |
| `onCollaboratorChange` | `(collaborators?: IDocCollabUsers[]) => void` | Callback when collaborators change |
| `enableIndexeddbSync` | `boolean` | Enables IndexedDB sync for offline support |
| `ddocId` | `string` | Unique document ID (required for IndexedDB sync) |
## UI/UX Props
Every UI state can be left out (the editor keeps it internally), or controlled with a value and an `onXChange` callback. The `setX` props still work and are deprecated.
| Property | Type | Description |
| --------------------------- | ----------------------------------------- | ----------------------------------------------------------------------------------- |
| `zoomLevel` | `string` | Current zoom level of the editor. Optional, defaults to `'1'` |
| `onZoomLevelChange` | `(zoomLevel: string) => void` | Zoom changed |
| `setZoomLevel` | `React.Dispatch<SetStateAction<string>>` | Deprecated, use `onZoomLevelChange` |
| `isNavbarVisible` | `boolean` | Controls navbar visibility. Optional, defaults to `true` |
| `onNavbarVisibleChange` | `(visible: boolean) => void` | Navbar shown or hidden |
| `setIsNavbarVisible` | `React.Dispatch<SetStateAction<boolean>>` | Deprecated, use `onNavbarVisibleChange` |
| `renderNavbar` | `() => JSX.Element` | Custom navbar renderer. Without it no navbar is rendered |
| `navbarContainer` | `HTMLElement \| null` | Element to render the navbar into. Omit for in-flow. `null` renders nothing until it attaches |
| `showNavbarToggle` | `boolean` | Show the toolbar's navbar collapse chevron. Default `true` |
| `renderThemeToggle` | `() => JSX.Element` | Custom theme toggle renderer |
| `showTOC` | `boolean` | Controls the outline |
| `onShowTOCChange` | `(show: boolean) => void` | Outline shown or hidden |
| `setShowTOC` | `React.Dispatch<SetStateAction<boolean>>` | Deprecated, use `onShowTOCChange` |
| `commentDrawerOpen` | `boolean` | Controls the comment drawer |
| `onCommentDrawerOpenChange` | `(open: boolean) => void` | Comment drawer opened or closed |
| `setCommentDrawerOpen` | `React.Dispatch<SetStateAction<boolean>>` | Deprecated, use `onCommentDrawerOpenChange` |
| `isPresentationMode` | `boolean` | Controls presentation mode |
| `onPresentationModeChange` | `(active: boolean) => void` | Presentation mode entered or left |
| `setIsPresentationMode` | `React.Dispatch<SetStateAction<boolean>>` | Deprecated, use `onPresentationModeChange` |
| `isSplitView` | `boolean` | Controls Split View |
| `onSplitViewChange` | `(active: boolean) => void` | Split View entered or left |
| `setIsSplitView` | `React.Dispatch<SetStateAction<boolean>>` | Deprecated, use `onSplitViewChange` |
| `isFocusMode` | `boolean` | Controls focus mode |
| `onFocusModeChange` | `(isFocusMode: boolean) => void` | Focus mode changed |
| `onFocusMode` | `(isFocusMode: boolean) => void` | Deprecated, use `onFocusModeChange` |
| `onStatsChange` | `(stats: { words; characters; selectedWords; pages: number \| null }) => void` | Active-tab stats; fires when any field changes. `pages` is `null` until measured |
| `setWordCount`, `setCharacterCount`, `setSelectedWordCount`, `setPageCount` | `React.Dispatch<SetStateAction<number>>` | Deprecated, use `onStatsChange` |
| `footerHeight` | `string` | Deprecated, no effect. Put the footer after the editor in normal flow |
| `sharedSlidesLink` | `string` | Link for shared presentation slides |
| `documentStyling` | `DocumentStyling` | Custom styling for document appearance |
| `fonts` | `FontDescriptor[]` | Consumer-provided font catalog (see Custom Fonts) |
## Document Styling
The `documentStyling` prop allows you to customize the visual appearance of your document with three distinct styling areas:
```typescript
interface ThemeVariantValue {
light: string;
dark: string;
}
interface DocumentStyling {
/**
* Background styling for the outer document area.
* Supports CSS background values including gradients.
* Example: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
*/
background?: string | ThemeVariantValue;
/**
* Background color for the editor canvas/content area.
* Should be a solid color value.
* Example: "#ffffff" or "rgb(255, 255, 255)"
*/
canvasBackground?: string | ThemeVariantValue;
/**
* Text color for the editor content.
* Example: "#333333" or "rgb(51, 51, 51)"
*/
textColor?: string | ThemeVariantValue;
/**
* Font family for the editor content.
* Example: "Inter, sans-serif" or "'Times New Roman', serif"
*/
fontFamily?: string;
}
```
### Usage Example
```tsx
<DdocEditor
documentStyling={{
background: {
light: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
dark: 'linear-gradient(135deg, #2a3145 0%, #3a2f59 100%)',
},
canvasBackground: { light: '#ffffff', dark: '#1e1f22' },
textColor: { light: '#333333', dark: '#e8ebec' },
fontFamily: 'Inter, sans-serif',
}}
// ... other props
/>
```
**Note:** Document styling works in both regular editor mode and presentation mode. In presentation mode, only `canvasBackground`, `textColor`, and `fontFamily` are applied to maintain clean slide appearance.
## Custom Fonts
The editor ships with a **system-font baseline only** (Arial, Calibri, Georgia, Times New Roman, etc.) and makes **no third-party font requests** — there is no Google Fonts `@import`. To offer additional fonts, pass a `fonts` catalog. Each font is self-hosted by your app and loaded **on demand** via the CSS Font Loading API: a font's `woff2` is fetched only when it's selected in the picker or when a document (including a remote collaborator's change) actually renders text in it.
```typescript
type FontDescriptor = {
/** Display name shown in the picker, e.g. "Poppins". */
name: string;
/** CSS font-family stack stored in the content, e.g. "Poppins, sans-serif". */
family: string;
/**
* woff2 source(s). Omit for a pure system font (no loading).
* - string: a single file covering all weights (e.g. a variable font).
* - Record<number, string>: a per-weight map, e.g. { 400: url400, 700: url700 }.
*/
url?: string | Record<number, string>;
/**
* Optional SVG preview rendered in the picker. Any React node that renders an
* <svg>. Falls back to the font name in the default font when absent.
*/
preview?: React.ReactNode;
};
```
### Usage Example
```tsx
import { DdocEditor, FontDescriptor } from '@fileverse-dev/ddoc';
// Self-host the woff2 files however your bundler prefers (e.g. @fontsource/*).
import poppins400 from '@fontsource/poppins/files/poppins-latin-400-normal.woff2';
import poppins700 from '@fontsource/poppins/files/poppins-latin-700-normal.woff2';
const fonts: FontDescriptor[] = [
{
name: 'Poppins',
family: 'Poppins, sans-serif',
url: { 400: poppins400, 700: poppins700 },
preview: <PoppinsPreview />, // optional SVG
},
];
<DdocEditor fonts={fonts} /* ...other props */ />;
```
**Notes:**
- The picker lists the system baseline and your catalog **together, sorted A–Z** (with **Default** pinned to the top).
- The CSS face name is derived from the first token of `family`, so `name` is purely cosmetic and can differ (e.g. `name: 'Poppins (Brand)'`, `family: 'Poppins, sans-serif'`).
- The catalog also drives PDF/print export, which emits `@font-face` rules for the registered fonts.
- Host your `woff2` files **same-origin** so they resolve in both the editor and the print iframe.
## Comments & Collaboration Props
| Property | Type | Description |
| ---------------------- | --------------------------------------- | ---------------------------------- |
| `initialComments` | `IComment[]` | Initial comments to display |
| `setInitialComments` | `(comments: IComment[]) => void` | Function to update comments |
| `onCommentReply` | `(id: string, reply: IComment) => void` | Callback for comment replies |
| `onNewComment` | `(comment: IComment) => void` | Callback for new comments |
| `commentDrawerOpen` | `boolean` | Controls comment drawer visibility |
| `onCommentDrawerOpenChange` | `(open: boolean) => void` | Comment drawer opened or closed |
| `setCommentDrawerOpen` | `(isOpen: boolean) => void` | Deprecated, use `onCommentDrawerOpenChange` |
| `onResolveComment` | `(commentId: string) => void` | Callback when resolving comments |
| `onUnresolveComment` | `(commentId: string) => void` | Callback when unresolving comments |
| `onDeleteComment` | `(commentId: string) => void` | Callback when deleting comments |
| `disableInlineComment` | `boolean` | Disables inline commenting feature |
## Authentication Props
| Property | Type | Description |
| -------------------- | ------------------------------------- | ----------------------------------- |
| `isConnected` | `boolean` | User connection status |
| `isLoading` | `boolean` | Authentication loading state |
| `connectViaUsername` | `(username: string) => Promise<void>` | Username-based authentication |
| `connectViaWallet` | `() => Promise<void>` | Wallet-based authentication |
| `isDDocOwner` | `boolean` | Indicates if user owns the document |
## Utility Props
| Property | Type | Description |
| ------------------- | ------------------------------------------------------------------------ | ----------------------------------------- |
| `setCharacterCount` | `React.Dispatch<SetStateAction<number>>` | Deprecated, use `onStatsChange`. Updates character count |
| `setWordCount` | `React.Dispatch<SetStateAction<number>>` | Deprecated, use `onStatsChange`. Updates word count |
| `setPageCount` | `React.Dispatch<SetStateAction<number>>` | Deprecated, use `onStatsChange`. Updates approx. export page count |
| `ensResolutionUrl` | `string` | URL for ENS name resolution |
| `ipfsImageUploadFn` | ` (file: File) => Promise<IpfsImageUploadResponse>` | function for secure image uploads |
| `ipfsImageFetchFn` | ` (_data: IpfsImageFetchPayload) => Promise<{ url: string;file: File;}>` | function for fetch secure image from IPFS |
| `onError` | `(error: string) => void` | General error handler |
| `onInlineComment` | `() => void` | Callback for inline comments |
| `onMarkdownExport` | `() => void` | Callback for markdown export |
| `onMarkdownImport` | `() => void` | Callback for markdown import |
| `onPdfExport` | `() => void` | Callback for pdf export |
| `onSlidesShare` | `() => void` | Callback for slides sharing |
| `onComment` | `() => void` | General comment callback |
## Steps to run this example locally
- `npm i`
- `npm run dev`
It will open up a vite server, that will have the Ddoc Editor.
⚠️ This repository is currently undergoing rapid development, with frequent updates and changes. We recommend not to use in production yet.