Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
134 changes: 78 additions & 56 deletions pwa/src/components/Composer.svelte
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
<script lang="typescript">
import { tick } from "svelte";
import type { Reference, Memo } from "../conversation";
import type { Block, InputEvent, Range } from "../writing";
import type { Block, InputEvent, Range, Point } from "../writing";
import * as Writing from "../writing";
import BlockComponent from "./Block.svelte";
import * as Icons from "../icons";

export let previous: Memo[];
export let blocks: Block[];
export let position: number;
export let selected: Range | null;

let composer: HTMLElement;
let composition: { updated: Block[]; cursor: Point } | null = null;

if (!Writing.isBeforeInputEventAvailable()) {
alert(
"beforeInput event not supported on this browser, sorry the editor will not work."
);
}

// TODO move to editor
export function addAnnotation(reference: Reference) {
let lastBlock = blocks[blocks.length - 1];
let before: Block[];
Expand All @@ -42,58 +44,39 @@
}

function handleInput(event: InputEvent) {
const domRange = event.getTargetRanges()[0];
if (event.isComposing === false) {
const domRange = event.getTargetRanges()[0];
if (domRange === undefined) {
throw "Should have a target range";
}

// console.log(event);
// alert(
// event.inputType +
// " with the following composing " +
// isComposing +
// " data: " +
// JSON.stringify(event.data)
// );
const result = Writing.rangeFromDom(domRange);
if (result === null) {
throw "There should always be a range for a domRange";
}

const range = result[0];
const [updated, cursor] = Writing.handleInput(blocks, range, event);

let range: Range;
if (domRange !== undefined) {
blocks = updated;

tick().then(() => {
Writing.placeCursor(composer, updated, cursor);
});
} else if (event.isComposing) {
const domRange = window.getSelection()?.getRangeAt(0);
if (domRange === undefined) {
throw "Should have a current selection";
}
const result = Writing.rangeFromDom(domRange);

if (result === null) {
throw "There should always be a range";
}
range = result[0];
} else {
// domRange SHOULD NOT be undefined however on chrome for android this often seems to be the case.
// This fix doesn't tackle moving the range for collapsed delete events
if (selected === null) {
// We still to this point on chrome on android when we press Space or new line
if (isComposing) {
return null;
}
throw "How did we get input";
} else {
range = selected;
throw "There should always be a range for a domRange";
}
const [range] = result;
const [updated, cursor] = Writing.handleInput(blocks, range, event);
composition = { updated, cursor };
}
const [updated, cursor] = Writing.handleInput(blocks, range, event);

blocks = updated;
tick().then(function () {
let paragraph = Writing.nodeFromPath(composer, cursor.path);
let line = Writing.getLine(updated, cursor.path);
let [spanIndex, offset] = Writing.spanFromOffset(line, cursor.offset);
let span = paragraph.children[spanIndex] as HTMLElement;
let textNode = span.childNodes[0] as Node;

// This is why slate has it's weak Map

let selection = window.getSelection();
const domRange = selection?.getRangeAt(0);
if (selection && domRange) {
domRange.setStart(textNode, offset);
domRange.setEnd(textNode, offset);
selection.addRange(domRange);
}
});
}

let dragging = false;
Expand Down Expand Up @@ -141,24 +124,54 @@
class="outline-none overflow-y-auto"
style="max-height: 60vh; caret-color: #6ab869;"
contenteditable
on:input={() => {
on:input={(event) => {
// This shouldn't be firing, it might on android
// alert(
// "Input event fired but it should not have been, this seems to be an issue affecting Chrome on Android"

// );
// Prevent default on before input stops this mostly
console.log("input!!!!", event, event.getTargetRanges());

return false;
// Disabled doesn't seem to do anything on content editable
}}
disabled
data-memo-position={position}
on:compositionstart={() => {
on:compositionstart={(event) => {
console.log("composition start", window.getSelection().getRangeAt(0));

isComposing = true;
}}
on:compositionend={() => {
on:compositionupdate|preventDefault={(event) => {
// console.log(
// "composition update",
// event
// // window.getSelection().getRangeAt(0)
// );

blocks = blocks;
isComposing = false;
}}
on:compositionend={(event) => {
console.log(event.preventDefault());

console.log(
"composition end",
event.data
// window.getSelection().getRangeAt(0),
// false
);
if (composition) {
blocks = composition.updated;
let { updated, cursor } = composition;
composition = null;
tick().then(() => {
blocks = blocks;
Writing.placeCursor(composer, updated, cursor);
});
}
}}
on:beforeinput|preventDefault={handleInput}
>
{#each blocks as block, index}
Expand Down Expand Up @@ -188,12 +201,21 @@
/>
</div>
{:else}
<BlockComponent
block={{ type: "paragraph", spans: [] }}
index={0}
peers={previous}
placeholder={"message"}
/>
<div class="flex">
<div
class="ml-1 md:ml-7 w-5 pt-2 text-gray-100 hover:text-gray-500 cursor-pointer "
contenteditable="false"
>
<Icons.Drag />
</div>

<BlockComponent
block={{ type: "paragraph", spans: [] }}
index={0}
peers={previous}
placeholder={"message"}
/>
</div>
{/each}
</div>
{#if dragging}
Expand Down
5 changes: 1 addition & 4 deletions pwa/src/routes/Contact.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,7 @@
<Composer
previous={response.data.memos}
bind:this={composer}
selected={composerRange}
blocks={[
{ type: "paragraph", spans: [{ type: "text", text: "" }] },
]}
blocks={[]}
position={response.data.memos.length + 1}
let:blocks
>
Expand Down
23 changes: 2 additions & 21 deletions pwa/src/routes/Profile.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,7 @@
if (!("me" in state) || state.me === undefined) {
throw "This should be an idified page only";
}

let composerRange: Range | null = null;

function handleSelectionChange() {
let selection: Selection = (Writing as any).getSelection();
let result = Writing.rangeFromDom(selection.getRangeAt(0));

if (result && result[1] == 1) {
const [range] = result;
composerRange = range;
} else {
composerRange = null;
}
}
let blocks = state.me.greeting || [];

async function saveGreeting(blocks: Block[]) {
// return id somehow, separate public profile from identifier
Expand Down Expand Up @@ -68,13 +55,7 @@
<article
class="my-4 py-6 pr-6 md:pr-12 bg-white rounded-lg sticky bottom-0 border shadow-top"
>
<Composer
previous={[]}
blocks={state.me.greeting || [{ type: "paragraph", spans: [] }]}
position={1}
selected={composerRange}
let:blocks
>
<Composer previous={[]} {blocks} position={1} let:blocks>
<!-- {JSON.stringify(state.me)}
<br /> -->
<div class="mt-2 pl-6 md:pl-12 flex items-center">
Expand Down
13 changes: 13 additions & 0 deletions pwa/src/writing/editor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ export function handleInput(blocks: Block[], range: Range, event: Event): [Block
return insertParagraph(blocks, range)
} else if (inputType === "insertLineBreak") {
return insertParagraph(blocks, range)
} else if (inputType === "insertCompositionText") {
let text = data || ""
let newLine = false
if (text.slice(-1) === "\n") {
newLine = true
text = text.slice(0, -1)
}
let [updated, cursor] = insertText(blocks, range, text)
if (newLine) {
return insertParagraph(updated, { anchor: cursor, focus: cursor })
} else {
return [updated, cursor]
}
} else if (inputType === "deleteContent") {
return insertText(blocks, range, "")
} else if (inputType === "deleteContentBackward") {
Expand Down
2 changes: 1 addition & 1 deletion pwa/src/writing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ export { parse } from "./parser"
export { toString } from "./serializer"

export type { InputEvent } from "./view"
export { rangeFromDom, nodeFromPath, getSelection, isBeforeInputEventAvailable } from "./view"
export { rangeFromDom, nodeFromPath, getSelection, isBeforeInputEventAvailable, placeCursor } from "./view"
export { handleInput } from "./editor"
4 changes: 2 additions & 2 deletions pwa/src/writing/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,9 @@ function splitBlocks(blocks: Block[], point: Point): [Block[], Block[]] {


const [pre, block, post] = arrayPopIndex(blocks, index);
// Point must be after blocks
if (!block) {

throw "invalid point"
return [pre, []]
}

if ('blocks' in block) {
Expand Down
25 changes: 23 additions & 2 deletions pwa/src/writing/view/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { Block } from "../elements"
import type { Path } from "../path"
import type { Point } from "../point"
import type { Range as ModelRange } from "../range"
import { getLine, spanFromOffset } from "../tree"

export function isBeforeInputEventAvailable() {
return window.InputEvent && typeof (InputEvent.prototype as any).getTargetRanges === "function";
Expand All @@ -11,7 +13,8 @@ export type InputEvent = {
getTargetRanges: () => StaticRange[]
inputType: string,
data: string | null,
dataTransfer: DataTransfer | null
dataTransfer: DataTransfer | null,
isComposing: boolean
}

export function getSelection(): Selection {
Expand Down Expand Up @@ -113,4 +116,22 @@ export function nodeFromPath(root: HTMLElement, path: number[]) {
// Search needs to be breadth first
// return element.querySelector(`[data-sveditor-index="${idx}"]`)
}, root)
}
}

export function placeCursor(root: HTMLElement, updated: Block[], cursor: Point) {
let paragraph = nodeFromPath(root, cursor.path);
let line = getLine(updated, cursor.path);
let [spanIndex, offset] = spanFromOffset(line, cursor.offset);
let span = paragraph.children[spanIndex] as HTMLElement;
let textNode = span.childNodes[0] as Node;

// This is why slate has it's weak Map

let selection = window.getSelection();
const domRange = selection?.getRangeAt(0);
if (selection && domRange) {
domRange.setStart(textNode, offset);
domRange.setEnd(textNode, offset);
selection.addRange(domRange);
}
}