-
-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Revert "chore(json-crdt-peritext-ui): 🤖 remove ui surface"
This reverts commit a5553af.
- Loading branch information
Showing
41 changed files
with
1,780 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
# Peritext UI | ||
|
||
## Controller Events | ||
|
||
### Editing | ||
|
||
- Events | ||
- [ ] `insert` event | ||
- [ ] Insert a character. | ||
- [ ] Insert a string. | ||
- [ ] `delete` event | ||
- [ ] Delete one character backward. | ||
- [ ] Delete one character forward. | ||
- [ ] `mark` event | ||
- [ ] Annotate range. | ||
- [ ] Insert block split. | ||
- [ ] `mark-update` event | ||
- [ ] Change annotation metadata. | ||
- [ ] Change annotation range. | ||
- [ ] Change block metadata. | ||
- [ ] `mark-delete` event | ||
- [ ] Delete block split. | ||
- [ ] Delete annotation. | ||
- Imperative | ||
- [ ] Delete range. | ||
- [ ] Delete all annotations in range. | ||
- [ ] Find block by position. | ||
- [ ] Find annotations by position. | ||
- [ ] Get block range. | ||
|
||
### Selection | ||
|
||
- Events | ||
- `select` event | ||
- [ ] Set caret. | ||
- [ ] Set selection. | ||
- `select-all` | ||
- [ ] Select all. | ||
- `select-object` | ||
- [ ] Select block. | ||
- [ ] Select word. | ||
- `update-selection` | ||
- [ ] Change position of left or right end of selection. | ||
- Imperative | ||
|
||
### Navigation | ||
|
||
- [ ] Move one character left. | ||
- [ ] Move one character right. | ||
- [ ] Move one word left. | ||
- [ ] Move one word right. | ||
- [ ] Move left until end of line. | ||
- [ ] Move right until end of line. | ||
- [ ] Move up one line. | ||
- [ ] Move down one line. | ||
|
||
### Clipboard | ||
|
||
- [ ] Export range. | ||
- [ ] Import range. | ||
- [ ] Insert plain text. | ||
- [ ] Insert HTML. | ||
|
||
### History | ||
|
||
- [ ] Undo. | ||
- [ ] Redo. | ||
|
||
### Overlays | ||
|
||
- [ ] Insert an overlay. | ||
- [ ] Update overlay metadata. | ||
- [ ] Delete an overlay. | ||
- [ ] Change overlay range. | ||
- [ ] Assign a set of overlays. | ||
- [ ] Remove an overlay set. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import * as React from 'react'; | ||
import {Provider, GlobalCss} from 'nano-theme'; | ||
import {ModelWithExt, ext} from '../../../json-crdt-extensions'; | ||
import {PeritextView} from '../../react'; | ||
import {renderers} from '../../renderers/default'; | ||
import {renderers as debugRenderers} from '../../renderers/debug'; | ||
|
||
export const App: React.FC = ({}) => { | ||
const [[model, peritext]] = React.useState(() => { | ||
const model = ModelWithExt.create(ext.peritext.new('Hello world!')); | ||
const peritext = model.s.toExt().txt; | ||
peritext.refresh(); | ||
return [model, peritext] as const; | ||
}); | ||
|
||
return ( | ||
<Provider theme={'light'}> | ||
<GlobalCss /> | ||
<div style={{maxWidth: '640px', fontSize: '21px', margin: '32px auto'}}> | ||
<PeritextView peritext={peritext} renderers={[debugRenderers({enabled: true}), renderers]} /> | ||
</div> | ||
</Provider> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import {createRoot} from 'react-dom/client'; | ||
import * as React from 'react'; | ||
import {App} from './components/App'; | ||
|
||
const div = document.createElement('div'); | ||
document.body.appendChild(div); | ||
|
||
const root = createRoot(div); | ||
root.render( | ||
<React.StrictMode> | ||
<App /> | ||
</React.StrictMode>, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"extends": "../../../tsconfig.json", | ||
"include": ["./"], | ||
"compilerOptions": {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
const path = require('path'); | ||
const HtmlWebpackPlugin = require('html-webpack-plugin'); | ||
|
||
module.exports = { | ||
mode: 'development', | ||
devtool: 'inline-source-map', | ||
entry: __dirname + '/main.tsx', | ||
plugins: [ | ||
new HtmlWebpackPlugin({ | ||
title: 'Development', | ||
}), | ||
], | ||
module: { | ||
rules: [ | ||
{ | ||
test: /\.tsx?$/, | ||
exclude: /node_modules/, | ||
loader: 'ts-loader', | ||
}, | ||
], | ||
}, | ||
resolve: { | ||
extensions: ['.tsx', '.ts', '.js'], | ||
}, | ||
output: { | ||
filename: 'bundle.js', | ||
path: path.resolve('../../..', 'dist'), | ||
}, | ||
devServer: { | ||
port: 9876, | ||
hot: false, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export const enum Char { | ||
ZeroLengthSpace = '\uFEFF', | ||
} | ||
|
||
export const enum ElementAttr { | ||
InlineOffset = '__jsonjoy.com', | ||
} | ||
|
||
export enum CssClass { | ||
Editor = 'jsonjoy-peritext-editor', | ||
Inline = 'jsonjoy-peritext-inline', | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
import type {Peritext} from '../../json-crdt-extensions/peritext'; | ||
import type {PeritextEventTarget} from '../events/PeritextEventTarget'; | ||
import type {TypedEventTarget} from '../events/TypedEventTarget'; | ||
import type {UiLifeCycles} from './types'; | ||
|
||
export interface InputControllerEventSourceMap { | ||
beforeinput: HTMLElementEventMap['beforeinput']; | ||
keydown: HTMLElementEventMap['keydown']; | ||
} | ||
|
||
export type InputControllerEventSource = TypedEventTarget<InputControllerEventSourceMap>; | ||
|
||
const unit = (event: KeyboardEvent): '' | 'word' | 'line' => | ||
event.metaKey ? 'line' : event.altKey || event.ctrlKey ? 'word' : ''; | ||
|
||
export interface InputControllerOpts { | ||
source: InputControllerEventSource; | ||
txt: Peritext; | ||
et: PeritextEventTarget; | ||
} | ||
|
||
/** | ||
* Processes incoming DOM "input" events (such as "beforeinput", "input", | ||
* "keydown", etc.) and translates them into Peritext events. | ||
*/ | ||
export class InputController implements UiLifeCycles { | ||
protected readonly source: InputControllerEventSource; | ||
protected readonly txt: Peritext; | ||
public readonly et: PeritextEventTarget; | ||
|
||
public constructor(options: InputControllerOpts) { | ||
this.source = options.source; | ||
this.txt = options.txt; | ||
this.et = options.et; | ||
} | ||
|
||
public start(): void { | ||
this.source.addEventListener('beforeinput', this.onBeforeInput); | ||
this.source.addEventListener('keydown', this.onKeyDown); | ||
} | ||
|
||
public stop(): void { | ||
this.source.removeEventListener('beforeinput', this.onBeforeInput); | ||
this.source.removeEventListener('keydown', this.onKeyDown); | ||
} | ||
|
||
private onBeforeInput = (event: InputEvent): void => { | ||
// TODO: prevent default more selectively? | ||
event.preventDefault(); | ||
const editor = this.txt.editor; | ||
const et = this.et; | ||
const inputType = event.inputType; | ||
switch (inputType) { | ||
case 'insertParagraph': { | ||
// editor.saved.insMarker('p'); | ||
// editor.cursor.move(1); | ||
// this.et.change(event); | ||
break; | ||
} | ||
case 'insertFromComposition': | ||
case 'insertFromDrop': | ||
case 'insertFromPaste': | ||
case 'insertFromYank': | ||
case 'insertReplacementText': // insert or replace existing text by means of a spell checker, auto-correct, writing suggestions or similar | ||
case 'insertText': { | ||
// insert typed plain | ||
if (typeof event.data === 'string') { | ||
et.insert(event.data); | ||
} else { | ||
const item = event.dataTransfer ? event.dataTransfer.items[0] : null; | ||
if (item) { | ||
item.getAsString((text) => { | ||
et.insert(text); | ||
}); | ||
} | ||
} | ||
break; | ||
} | ||
case 'deleteContentBackward': // delete the content directly before the caret position and this intention is not covered by another inputType or delete the selection with the selection collapsing to its start after the deletion | ||
case 'deleteContent': { | ||
// delete the selection without specifying the direction of the deletion and this intention is not covered by another inputType | ||
et.delete(-1, 'char'); | ||
break; | ||
} | ||
case 'deleteContentForward': { | ||
// delete the content directly after the caret position and this intention is not covered by another inputType or delete the selection with the selection collapsing to its end after the deletion | ||
et.delete(1, 'char'); | ||
break; | ||
} | ||
case 'deleteWordBackward': { | ||
// delete a word directly before the caret position | ||
et.delete(-1, 'word'); | ||
break; | ||
} | ||
case 'deleteWordForward': { | ||
// delete a word directly after the caret position | ||
et.delete(1, 'word'); | ||
break; | ||
} | ||
case 'deleteSoftLineBackward': { | ||
// delete from the caret to the nearest visual line break before the caret position | ||
et.delete(-1, 'line'); | ||
break; | ||
} | ||
case 'deleteSoftLineForward': { | ||
// delete from the caret to the nearest visual line break after the caret position | ||
et.delete(1, 'line'); | ||
break; | ||
} | ||
case 'deleteEntireSoftLine': // delete from the nearest visual line break before the caret position to the nearest visual line break after the caret position | ||
case 'deleteHardLineBackward': // delete from the caret to the nearest beginning of a block element or br element before the caret position | ||
case 'deleteHardLineForward': { | ||
// delete from the caret to the nearest end of a block element or br element after the caret position | ||
et.delete(-1, 'word'); | ||
break; | ||
} | ||
// case 'insertLineBreak': { // insert a line break | ||
// } | ||
// case 'insertParagraph': { // insert a paragraph break | ||
// } | ||
// case 'insertOrderedList': { // insert a numbered list | ||
// } | ||
// case 'insertUnorderedList': { // insert a bulleted list | ||
// } | ||
// case 'insertHorizontalRule': { // insert a horizontal rule | ||
// } | ||
// case 'insertFromYank': { // replace the current selection with content stored in a kill buffer | ||
// } | ||
// case 'insertFromDrop': { // insert content by means of drop | ||
// } | ||
// case 'insertFromPaste': { // paste content from clipboard or paste image from client provided image library | ||
// } | ||
// case 'insertFromPasteAsQuotation': { // paste content from the clipboard as a quotation | ||
// } | ||
// case 'insertTranspose': { // transpose the last two grapheme cluster. that were entered | ||
// } | ||
// case 'insertCompositionText': { // replace the current composition string | ||
// } | ||
// case 'insertLink': { // insert a link | ||
// } | ||
// case 'deleteByDrag': { // remove content from the DOM by means of drag | ||
// } | ||
// case 'deleteByCut': { // remove the current selection as part of a cut | ||
// } | ||
// case 'historyUndo': { // undo the last editing action | ||
// } | ||
// case 'historyRedo': { // to redo the last undone editing action | ||
// } | ||
// case 'formatBold': { // initiate bold text | ||
// } | ||
// case 'formatItalic': { // initiate italic text | ||
// } | ||
// case 'formatUnderline': { // initiate underline text | ||
// } | ||
// case 'formatStrikeThrough': { // initiate stricken through text | ||
// } | ||
// case 'formatSuperscript': { // initiate superscript text | ||
// } | ||
// case 'formatSubscript': { // initiate subscript text | ||
// } | ||
// case 'formatJustifyFull': { // make the current selection fully justified | ||
// } | ||
// case 'formatJustifyCenter': { // center align the current selection | ||
// } | ||
// case 'formatJustifyRight': { // right align the current selection | ||
// } | ||
// case 'formatJustifyLeft': { // left align the current selection | ||
// } | ||
// case 'formatIndent': { // indent the current selection | ||
// } | ||
// case 'formatOutdent': { // outdent the current selection | ||
// } | ||
// case 'formatRemove': { // remove all formatting from the current selection | ||
// } | ||
// case 'formatSetBlockTextDirection': { // set the text block direction | ||
// } | ||
// case 'formatSetInlineTextDirection': { // set the text inline direction | ||
// } | ||
// case 'formatBackColor': { // change the background color | ||
// } | ||
// case 'formatFontColor': { // change the font color | ||
// } | ||
// case 'formatFontName': { // change the font name | ||
// } | ||
} | ||
}; | ||
|
||
private onKeyDown = (event: KeyboardEvent): void => { | ||
const key = event.key; | ||
const et = this.et; | ||
switch (key) { | ||
case 'ArrowLeft': | ||
case 'ArrowRight': { | ||
const direction = key === 'ArrowLeft' ? -1 : 1; | ||
event.preventDefault(); | ||
if (event.shiftKey) et.move(direction, unit(event) || 'char', 'focus'); | ||
else if (event.metaKey) et.move(direction, 'line'); | ||
else if (event.altKey || event.ctrlKey) et.move(direction, 'word'); | ||
else et.move(direction); | ||
break; | ||
} | ||
case 'Backspace': | ||
case 'Delete': { | ||
const direction = key === 'Delete' ? 1 : -1; | ||
const deleteUnit = unit(event); | ||
if (deleteUnit) { | ||
event.preventDefault(); | ||
et.delete(direction, deleteUnit); | ||
} | ||
break; | ||
} | ||
case 'Home': | ||
case 'End': | ||
event.preventDefault(); | ||
const direction = key === 'End' ? 1 : -1; | ||
const edge = event.shiftKey ? 'focus' : 'both'; | ||
return this.et.move(direction, 'line', edge); | ||
case 'a': | ||
if (event.metaKey || event.ctrlKey) { | ||
event.preventDefault(); | ||
this.et.cursor({unit: 'all'}); | ||
return; | ||
} | ||
} | ||
}; | ||
} |
Oops, something went wrong.