Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions apps/docs/app/(diffs)/_edit/DecorationDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use client';

import type { EditorOptions } from '@pierre/diffs/edit';
import { File } from '@pierre/diffs/react';
import type { PreloadedFileResult } from '@pierre/diffs/ssr';
import { useMemo } from 'react';

import {
type CursorDecorationMetadata,
DECORATION_DEMO_DECORATIONS,
} from './constants';

interface DecorationDemoProps {
// Server-preloaded, highlighted File; hydrating from it avoids a highlight flash on load.
prerenderedFile: PreloadedFileResult<undefined>;
}

// Decorations render inside the editor's shadow DOM, so the collaborator
// cursors use inline styles rather than page-level Tailwind classes.
export function DecorationDemo({ prerenderedFile }: DecorationDemoProps) {
const editorOptions = useMemo<
EditorOptions<undefined, CursorDecorationMetadata>
>(
() => ({
renderDecoration({ metadata }) {
const cursor = document.createElement('span');
cursor.ariaLabel = `${metadata.name}'s cursor`;
cursor.style.cssText = `position:relative;display:block;width:2px;height:1lh;background-color:${metadata.color};pointer-events:none;`;

const label = document.createElement('span');
label.ariaHidden = 'true';
label.textContent = metadata.name;
label.style.cssText = `position:absolute;bottom:100%;left:0;padding:1px 5px;border-radius:4px 4px 4px 0;background-color:${metadata.color};color:#fff;font:500 11px/16px system-ui,sans-serif;white-space:nowrap;`;

cursor.append(label);
return cursor;
},
onAttach(editor) {
editor.setDecorations(DECORATION_DEMO_DECORATIONS);
},
}),
[]
);

return (
<div className="not-prose">
<File<undefined, CursorDecorationMetadata>
{...prerenderedFile}
className="diff-container"
edit
editorOptions={editorOptions}
/>
</div>
);
}
21 changes: 21 additions & 0 deletions apps/docs/app/(diffs)/_edit/EditPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {

import { WorkerPoolContext } from '../_components/WorkerPoolContext';
import { LiveEditing } from '../_examples/LiveEditing/LiveEditing';
import { DecorationDemo } from './DecorationDemo';
import { EditHero } from './EditHero';
import { EditReference } from './EditReference';
import { FindDemo } from './FindDemo';
Expand All @@ -26,6 +27,7 @@ interface EditPageProps {
historyFile: PreloadedFileResult<undefined>;
keymapFile: PreloadedFileResult<undefined>;
selectionFile: PreloadedFileResult<undefined>;
decorationFile: PreloadedFileResult<undefined>;
}

export function EditPage({
Expand All @@ -36,6 +38,7 @@ export function EditPage({
historyFile,
keymapFile,
selectionFile,
decorationFile,
}: EditPageProps) {
return (
<WorkerPoolContext>
Expand Down Expand Up @@ -68,6 +71,24 @@ export function EditPage({
<SelectionDemo prerenderedFile={selectionFile} />
</div>

<div className="space-y-5">
<FeatureHeader
id="decorations"
title="Decorations"
description={
<>
Use <code>editor.setDecorations()</code> to anchor arbitrary
UI to document positions. Supply typed metadata and map it to
DOM with <code>renderDecoration()</code>—here, custom
decorations render collaborators' cursors. Type or press{' '}
<code>Enter</code> before either cursor to see it follow the
surrounding code.
</>
}
/>
<DecorationDemo prerenderedFile={decorationFile} />
</div>

<div className="space-y-5">
<FeatureHeader
id="markers"
Expand Down
5 changes: 4 additions & 1 deletion apps/docs/app/(diffs)/_edit/KeyboardShortcuts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type {
EditorCommand,
EditorKeymap,
EditorShortcut,
KeyboardModifier,
} from '@pierre/diffs/edit';
import { File } from '@pierre/diffs/react';
import type { PreloadedFileResult } from '@pierre/diffs/ssr';
Expand All @@ -21,6 +20,10 @@ interface KeyboardShortcutsProps {
}

type EditorPlatform = NonNullable<EditorKeymap[number]['platform']>;
type ShortcutModifier<T> = T extends `${infer Modifier}+${string}`
? Modifier
: never;
type KeyboardModifier = ShortcutModifier<EditorShortcut>;

interface ShortcutRow {
shortcut: EditorShortcut;
Expand Down
42 changes: 41 additions & 1 deletion apps/docs/app/(diffs)/_edit/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { DEFAULT_THEMES, type FileContents } from '@pierre/diffs';
import {
DEFAULT_THEMES,
type EditorDecoration,
type FileContents,
} from '@pierre/diffs';
import type { EditorCommand, EditorKeymap } from '@pierre/diffs/edit';
import type { FileOptions } from '@pierre/diffs/react';
import type { PreloadFileOptions } from '@pierre/diffs/ssr';
Expand All @@ -12,6 +16,37 @@ const EDITABLE_FILE_OPTIONS: FileOptions<undefined> = {
useTokenTransformer: true,
};

export interface CursorDecorationMetadata {
name: string;
color: string;
}

export const DECORATION_DEMO_FILE: FileContents = {
name: 'review.ts',
contents: `type Review = {
author: string
approved: boolean
}

export function summarize(review: Review) {
const status = review.approved ? 'approved' : 'needs review'
return \`\${review.author}: \${status}\`
}
`,
};

export const DECORATION_DEMO_DECORATIONS: EditorDecoration<CursorDecorationMetadata>[] =
[
{
position: { line: 5, character: 26 },
metadata: { name: 'Amadeus', color: '#7c3aed' },
},
{
position: { line: 7, character: 18 },
metadata: { name: 'Mark', color: '#c2410c' },
},
];

// Lint-marker demo source. Marker positions below are tied to these exact
// lines, so keep the two in sync if the contents change.
export const MARKER_DEMO_FILE: FileContents = {
Expand Down Expand Up @@ -333,6 +368,11 @@ export const MARKER_DEMO_FILE_EXAMPLE: PreloadFileOptions<undefined> = {
options: EDITABLE_FILE_OPTIONS,
};

export const DECORATION_DEMO_FILE_EXAMPLE: PreloadFileOptions<undefined> = {
file: DECORATION_DEMO_FILE,
options: EDITABLE_FILE_OPTIONS,
};

export const FIND_DEMO_FILE_EXAMPLE: PreloadFileOptions<undefined> = {
file: FIND_DEMO_FILE,
options: EDITABLE_FILE_OPTIONS,
Expand Down
8 changes: 6 additions & 2 deletions apps/docs/app/(diffs)/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { preloadFile, preloadFileDiff } from '@pierre/diffs/ssr';
import type { Metadata } from 'next';

import {
DECORATION_DEMO_FILE_EXAMPLE,
DEFAULT_KEYMAP_FILE_EXAMPLE,
FIND_DEMO_FILE_EXAMPLE,
HISTORY_DEMO_FILE_EXAMPLE,
Expand All @@ -16,7 +17,7 @@ import {

const editTitle = 'Pierre Diffs — now with edit';
const editDescription =
'A lightweight, SSR, mobile-friendly editable file and diff layer for @pierre/diffs. Edit files and diffs in place with selection management, multiple cursors, undo history, find/replace, and lint markers.';
'A lightweight, SSR, mobile-friendly editable file and diff layer for @pierre/diffs. Edit files and diffs in place with selection management, multiple cursors, virtual cursors, undo history, find/replace, and lint markers.';

export const metadata: Metadata = {
title: editTitle,
Expand All @@ -34,7 +35,7 @@ export const metadata: Metadata = {

// Server-renders every edit demo so they all paint highlighted on first load
// and hydrate cleanly (no flash): the "Live editing" File surface, and the
// lint-marker, find-in-file, undo-history, shortcuts, and selection files.
// lint-marker, find-in-file, undo-history, shortcuts, selection, and decoration files.
export default async function EditRoute() {
const [
liveEditingFile,
Expand All @@ -44,6 +45,7 @@ export default async function EditRoute() {
historyFile,
keymapFile,
selectionFile,
decorationFile,
] = await Promise.all([
preloadFile(LIVE_EDITING_FILE_EXAMPLE),
preloadFileDiff(LIVE_EDITING_FILE_DIFF_EXAMPLE),
Expand All @@ -52,6 +54,7 @@ export default async function EditRoute() {
preloadFile(HISTORY_DEMO_FILE_EXAMPLE),
preloadFile(DEFAULT_KEYMAP_FILE_EXAMPLE),
preloadFile(SELECTION_DEMO_FILE_EXAMPLE),
preloadFile(DECORATION_DEMO_FILE_EXAMPLE),
]);

return (
Expand All @@ -63,6 +66,7 @@ export default async function EditRoute() {
historyFile={historyFile}
keymapFile={keymapFile}
selectionFile={selectionFile}
decorationFile={decorationFile}
/>
);
}
6 changes: 3 additions & 3 deletions apps/docs/components/AppEditProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { Editor, type EditorOptions } from '@pierre/diffs/edit';
import { EditProvider } from '@pierre/diffs/react';
import type { ReactNode } from 'react';

function createEditor<LAnnotation>(
options: EditorOptions<LAnnotation>
): Editor<LAnnotation> {
function createEditor<LAnnotation, LDecoration>(
options: EditorOptions<LAnnotation, LDecoration>
): Editor<LAnnotation, LDecoration> {
return new Editor(options);
}

Expand Down
8 changes: 5 additions & 3 deletions packages/diffs/src/edit/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ export type {
EditorCommand,
EditorKeymap,
EditorShortcut,
KeyboardKey,
KeyboardModifier,
} from '../editor/command';
Comment thread
ije marked this conversation as resolved.
export * from '../editor/editor';
export type {
IStateStorage,
PersistStateStorage,
} from '../editor/stateStorage';
export * from '../editor/textDocument';
export type { EditorChange, EditorChangeEvent } from '../types';
export type {
EditorChange,
EditorChangeEvent,
EditorDecoration,
} from '../types';
3 changes: 2 additions & 1 deletion packages/diffs/src/editor/editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@
[data-selection-range],
[data-match-range],
[data-bracket-match-range],
[data-marker-range] {
[data-marker-range],
[data-editor-decoration] {
position: absolute;
top: 0;
left: 0;
Expand Down
Loading