diff --git a/src/routes/(app)/pad/+page.svelte b/src/routes/(app)/pad/+page.svelte
index 7b62769..84394f4 100644
--- a/src/routes/(app)/pad/+page.svelte
+++ b/src/routes/(app)/pad/+page.svelte
@@ -5,20 +5,216 @@
import '@friendofsvelte/tipex/styles/Controls.css';
import '@friendofsvelte/tipex/styles/EditLink.css';
import '@friendofsvelte/tipex/styles/CodeBlock.css';
- const INITIAL_HTML_CONTENT = `
Simple editor
What's written here will be saved to your local storage, and won't be sent to the server.
`;
- let body = localStorage.getItem('tipex') || INITIAL_HTML_CONTENT;
- let editor: TipexEditor | undefined = $state();
- function handleUpdate() {
+ const INITIAL_HTML_CONTENT = `Simple editor
What's written here will be saved to your local storage. Highlight a word to see suggested rhymes.
`;
+ let body = $state(localStorage.getItem('tipex') || INITIAL_HTML_CONTENT);
+ let editor = $state();
+ let suggestions = $state>([]);
+ let rhymes = $state>([]);
+ let currentWord = $state('');
+ let cursorPosition = $state({ x: 0, y: 0 });
+ let debounceTimer = $state();
+
+ type Suggestion = {
+ word: string;
+ score: number;
+ };
+
+ function debounce any>(
+ fn: T,
+ delay: number
+ ): (...args: Parameters) => void {
+ return (...args: Parameters) => {
+ clearTimeout(debounceTimer);
+ debounceTimer = setTimeout(() => fn(...args), delay);
+ };
+ }
+
+ const datamuseApi = {
+ async getSuggestions(word: string): Promise {
+ if (!word || word.length < 2) return [];
+ const response = await fetch(`https://api.datamuse.com/sug?s=${encodeURIComponent(word)}`);
+ return response.json();
+ },
+
+ async getRhymes(word: string): Promise {
+ if (!word) return [];
+ const response = await fetch(
+ `https://api.datamuse.com/words?rel_rhy=${encodeURIComponent(word)}`
+ );
+ return response.json();
+ }
+ };
+
+ const debouncedFetchSuggestions = debounce(async (word: string) => {
+ suggestions = await datamuseApi.getSuggestions(word);
+ }, 300);
+
+ function updateCursorPosition() {
+ const selection = window.getSelection();
+ if (!selection?.rangeCount) return;
+
+ const range = selection.getRangeAt(0);
+ const rect = range.getBoundingClientRect();
+
+ cursorPosition = {
+ x: rect.left + window.scrollX,
+ y: rect.bottom + window.scrollY + 10
+ };
+ }
+
+ async function handleUpdate() {
const currentHtml = editor?.getHTML();
localStorage.setItem('tipex', currentHtml || '');
+
+ const { state } = editor?.view || {};
+ const { doc, selection } = state || {};
+
+ if (!doc || !selection) return;
+
+ const pos = selection.from;
+ const currentChar = doc.textBetween(Math.max(0, pos - 1), pos);
+
+ if (/[\s\.,!?;:]/.test(currentChar)) {
+ suggestions = [];
+ currentWord = '';
+ return;
+ }
+
+ const textBefore = doc.textBetween(Math.max(0, pos - 100), pos);
+
+ const lastSpace = textBefore.search(/\s\w+$/);
+ const currentWordMatch =
+ lastSpace >= 0 ? textBefore.slice(lastSpace).match(/\w+$/) : textBefore.match(/\w+$/);
+
+ const word = currentWordMatch ? currentWordMatch[0] : '';
+
+ if (word && word !== currentWord) {
+ currentWord = word;
+ debouncedFetchSuggestions(currentWord);
+ } else if (!word) {
+ suggestions = [];
+ currentWord = '';
+ }
+ updateCursorPosition();
+ }
+
+ async function handleMouseUp() {
+ const selection = editor?.view.state.selection;
+ if (selection && !selection.empty) {
+ const selectedText = editor?.view.state.doc.textBetween(selection.from, selection.to, ' ');
+
+ if (selectedText) {
+ rhymes = await datamuseApi.getRhymes(selectedText);
+ }
+ } else {
+ rhymes = [];
+ }
+ updateCursorPosition();
+ }
+
+ async function handleMouseDown() {
+ suggestions = [];
+ currentWord = '';
+ rhymes = [];
+ }
+
+ function applySuggestion(suggestion: string) {
+ if (!editor?.view) return;
+
+ const view = editor.view;
+ const { state } = view;
+ const { schema, selection, tr } = state;
+
+ view.focus();
+
+ if (rhymes.length > 0 && !selection.empty) {
+ const newTr = tr.replaceSelectionWith(schema.text(suggestion));
+ view.dispatch(newTr);
+ rhymes = [];
+ } else {
+ const pos = selection.from;
+ const wordBefore = state.doc.textBetween(Math.max(0, pos - 100), pos);
+ const match = wordBefore.match(/\S+$/);
+
+ if (match) {
+ const wordStart = pos - match[0].length;
+ const newTr = tr.replaceWith(wordStart, pos, schema.text(suggestion));
+ view.dispatch(newTr);
+ }
+ }
+
+ suggestions = [];
+ currentWord = '';
+
+ setTimeout(() => {
+ view.focus();
+ }, 0);
}
silentsilas - Pad
+
-
+
+
+ {#if suggestions.length > 0 || rhymes.length > 0}
+
+ {#if suggestions.length > 0}
+
+
Suggestions
+ {#each suggestions.slice(0, 10) as { word }}
+
+ {/each}
+
+ {/if}
+
+ {#if rhymes.length > 0}
+
+
Rhymes
+ {#each rhymes.slice(0, 10) as { word }}
+
+ {/each}
+
+ {/if}
+
+ {/if}
+
+