From 5830a9364c20027f8dba30109009c109e24dfd88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristoffer=20Vassb=C3=B8?= Date: Fri, 26 Jul 2024 13:02:07 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=8F=20Improved=20text=20edit=20-=20Reset?= =?UTF-8?q?=20caret=20on=20line=20changes=20-=20Reset=20caret=20on=20short?= =?UTF-8?q?cuts=20-=20Paste=20on=20multiple=20lines=20-=20Undo/redo=20-=20?= =?UTF-8?q?Version=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- .../edit/editbox/EditboxLines.svelte | 135 ++++++++++++++---- .../components/edit/scripts/textStyle.ts | 2 +- .../components/edit/tools/BoxStyle.svelte | 13 +- src/frontend/components/helpers/history.ts | 4 +- src/frontend/utils/shortcuts.ts | 4 +- 6 files changed, 128 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index feab7cd5..c9b1ad0b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freeshow", - "version": "1.2.0", + "version": "1.2.1", "private": true, "main": "build/electron/index.js", "description": "Show song lyrics and more for free!", diff --git a/src/frontend/components/edit/editbox/EditboxLines.svelte b/src/frontend/components/edit/editbox/EditboxLines.svelte index 539a9723..bf8ce0db 100644 --- a/src/frontend/components/edit/editbox/EditboxLines.svelte +++ b/src/frontend/components/edit/editbox/EditboxLines.svelte @@ -125,6 +125,8 @@ cutInTwo({ e, sel, lines, currentIndex, textPos, start }) } + + storeCurrentCaretPos() } function cutInTwo({ e, sel, lines, currentIndex, textPos, start }) { @@ -316,7 +318,11 @@ // TODO: pressing enter / backspace will remove any following style in list view // if (plain && !style && i > 0) style = item.lines![i - 1]?.text[j]?.style - newLines[pos].text.push({ style, value: child.innerText }) + let lineText = child.innerText + // empty line + if (lineText === "\n") lineText = "" + newLines[pos].text.push({ style, value: lineText }) + currentStyle += style // GET CHORDS @@ -341,6 +347,8 @@ } }) + if (pasting) return newLines + if (updateHTML) { // get caret pos let sel = getSelectionRange() @@ -374,10 +382,14 @@ } }) - // set to end (if backspace) - if (!caret && (item.lines || []).length > newLines.length) { - // WIP if line in middle is deleted, the caret is still moved to the last line... (get the last used line here) - caret = { line: newLines.length - 1, pos: getLineText(newLines[newLines.length - 1]).length } + // set to last caret pos (if backspace) + let sel = getSelectionRange() + let currentLine = sel.findIndex((a) => a?.start !== undefined) + let deleteKey = currentLine === lastCaretPos.line + if (!caret && (item.lines || []).length > newLines.length && !deleteKey) { + let newLine = lastCaretPos.line > -1 ? lastCaretPos.line - 1 : newLines.length - 1 + let newPos = lastCaretPos.pos > -1 ? getLineText(newLines[lastCaretPos.line - 1]).length - lastCaretPos.lineLength : getLineText(newLines[newLines.length - 1]).length + caret = { line: newLine, pos: newPos } } } @@ -392,11 +404,36 @@ setCaret(textElem, caret) }, 10) }, 10) + + lastCaretPos = caret + } else { + storeCurrentCaretPos() } return newLines } + let lastCaretPos: { line: number; pos: number; lineLength: number } = { line: -1, pos: -1, lineLength: 0 } + function storeCurrentCaretPos() { + let sel = getSelectionRange() + let caretLineIndex = sel.findIndex((line) => line.start !== undefined) + if (caretLineIndex > -1) lastCaretPos = { line: caretLineIndex, pos: sel[caretLineIndex]?.start ?? -1, lineLength: getHTMLLineText(caretLineIndex).length } + } + + function getHTMLLineText(lineIndex: number) { + if (!textElem || !item) return "" + + let text = "" + + let lineElem = textElem.children[lineIndex] + new Array(...lineElem.childNodes).forEach((child: any) => { + if (child.nodeName === "#text") text += child.textContent + else text += child.innerText + }) + + return text.trim() + } + function textElemKeydown(e: any) { if (e.key === "v" && (e.ctrlKey || e.metaKey)) { e.preventDefault() @@ -407,36 +444,84 @@ } // paste + let pasting: boolean = false function paste(e: any, clipboardText: string = "") { let clipboard: string = clipboardText || e.clipboardData?.getData("text/plain") || "" if (!clipboard) return + pasting = true + let sel = getSelectionRange() + let caret = { line: 0, pos: 0 } + let emptySelection: boolean = !sel.filter((a) => Object.keys(a).length).length + let lines: Line[] = getNewLines() + let newLines: any[] = [] + let pastingIndex: number = -1 + sel.forEach((lineSel, lineIndex) => { + if (lineSel.start === undefined && (!emptySelection || lineIndex < sel.length - 1)) { + newLines.push(lines[lineIndex]) + return + } - let emptySelection: boolean = !sel.filter((a) => Object.keys(a).length).length + if (pastingIndex < 0) { + pastingIndex = lineIndex + caret = { line: pastingIndex, pos: lineSel.start + clipboard.length } + } - // get caret pos - let caret = { line: 0, pos: 0 } - sel.forEach((lineSel, i) => { - if (lineSel.start !== undefined || (emptySelection && i >= sel.length - 1)) { - let pos = 0 - let pasted = false - lines[i].text.forEach(({ value }, j) => { - if (!pasted && (pos + value.length >= lineSel.start || (emptySelection && j >= lines[i].text.length - 1))) { - let caretPos = lineSel.start - pos - caret = { line: i, pos: lineSel.start + clipboard.length } - let removeText = lineSel.end - lineSel.start - removeText = removeText > 0 ? removeText : 0 - - lines[i].text[j].value = value.slice(0, caretPos) + clipboard + value.slice(caretPos + removeText, value.length) - pasted = true - } - pos += value.length - }) + let lineText: any[] = [] + let linePos = 0 + let pasteOverflow = 0 + // move multi line select to one line + lines[lineIndex].text.forEach((text) => { + let value = text.value + let newLinePos = linePos + value.length + if (newLinePos < lineSel.start || linePos > lineSel.end) { + lineText.push(text) + linePos = newLinePos + return + } + + // selected more text (different styles) on one line + if (pasteOverflow > 0) { + let newValue = value.slice(pasteOverflow) + pasteOverflow = pasteOverflow - value.length + if (!newValue.length) return + + text.value = newValue + lineText.push(text) + return + } + + let caretPos = lineSel.start - linePos + let removeText = lineSel.end - lineSel.start + removeText = removeText > 0 ? removeText : 0 + pasteOverflow = caretPos + removeText - value.length + + let newValue = value.slice(0, caretPos) + (pastingIndex === lineIndex ? clipboard : "") + value.slice(caretPos + removeText) + if (!newValue.length) return + + text.value = newValue + lineText.push(text) + + linePos = newLinePos + }) + + if (pastingIndex < 0) { + newLines.push(lines[lineIndex]) + return + } + + if (!newLines[pastingIndex]?.text) { + newLines[pastingIndex] = clone(lines[lineIndex]) + newLines[pastingIndex].text = lineText + } else { + newLines[pastingIndex].text.push(...lineText) } }) + lines = newLines + lines = EditboxHelper.splitAllCrlf(lines) updateLines(lines) setTimeout(() => { @@ -444,6 +529,7 @@ // set caret position back setTimeout(() => { setCaret(textElem, caret) + pasting = false }, 10) }, 10) } @@ -475,6 +561,7 @@ let newLines = chordMove(e, { textElem, item }) if (newLines) item.lines = newLines }} + on:mouseup={() => storeCurrentCaretPos()} class="edit" class:hidden={chordsMode} class:autoSize={item.auto && autoSize} diff --git a/src/frontend/components/edit/scripts/textStyle.ts b/src/frontend/components/edit/scripts/textStyle.ts index e84b4fe3..83257e04 100644 --- a/src/frontend/components/edit/scripts/textStyle.ts +++ b/src/frontend/components/edit/scripts/textStyle.ts @@ -234,7 +234,7 @@ export function getItemText(item: Item): string { export function getLineText(line: any): string { let text: string = "" - line.text?.forEach((content: any) => { + line?.text?.forEach((content: any) => { text += content.value }) return text diff --git a/src/frontend/components/edit/tools/BoxStyle.svelte b/src/frontend/components/edit/tools/BoxStyle.svelte index a2f28727..3e0cf3bb 100644 --- a/src/frontend/components/edit/tools/BoxStyle.svelte +++ b/src/frontend/components/edit/tools/BoxStyle.svelte @@ -9,7 +9,7 @@ import { getListOfShows, getStageList } from "../../helpers/show" import { _show } from "../../helpers/shows" import { getStyles } from "../../helpers/style" - import { addFilterString, addStyle, addStyleString, getItemStyleAtPos, getItemText, getLastLineAlign, getLineText, getSelectionRange } from "../scripts/textStyle" + import { addFilterString, addStyle, addStyleString, getItemStyleAtPos, getItemText, getLastLineAlign, getLineText, getSelectionRange, setCaret } from "../scripts/textStyle" import { boxes } from "../values/boxes" import EditValues from "./EditValues.svelte" import { uid } from "uid" @@ -58,7 +58,16 @@ updateValue({ detail: value }) - // WIP reset selection (caret is at start, but it remembers the selection) + // reset caret position (styles can be changed without this also) + setTimeout(() => { + if (!selection) return + + let editElem = document.querySelector(".editArea")?.querySelectorAll(".editItem")?.[$activeEdit.items[0]]?.querySelector(".edit") + if (!editElem) return + + let selectedLine = selection.findIndex((a) => a.start !== undefined) + if (selectedLine > -1) setCaret(editElem, { line: selectedLine, pos: selection[selectedLine].end }) + }, 10) } // ----- diff --git a/src/frontend/components/helpers/history.ts b/src/frontend/components/helpers/history.ts index da44f5dd..a6203e09 100644 --- a/src/frontend/components/helpers/history.ts +++ b/src/frontend/components/helpers/history.ts @@ -285,7 +285,7 @@ export function history(obj: History, undo: null | boolean = null) { export const undo = () => { if (!get(undoHistory).length) return - if (document.activeElement?.classList?.contains("edit")) return + if (document.activeElement?.classList?.contains("edit") && !document.activeElement?.closest(".editItem")) return let lastUndo: History undoHistory.update((uh: History[]) => { @@ -304,7 +304,7 @@ export const undo = () => { export const redo = () => { if (!get(redoHistory).length) return - if (document.activeElement?.classList?.contains("edit")) return + if (document.activeElement?.classList?.contains("edit") && !document.activeElement?.closest(".editItem")) return let lastRedo: History redoHistory.update((rh: History[]) => { diff --git a/src/frontend/utils/shortcuts.ts b/src/frontend/utils/shortcuts.ts index 7d3f2085..17813495 100644 --- a/src/frontend/utils/shortcuts.ts +++ b/src/frontend/utils/shortcuts.ts @@ -74,8 +74,8 @@ export function keydown(e: any) { return } - // use default input shortcuts on supported devices (this includes working undo/redo) - const exeption = ["e", "i", "n", "o", "s", "a"] + // use default input shortcuts on supported devices + const exeption = ["e", "i", "n", "o", "s", "a", "z", "Z", "y"] if ((e.key === "i" && document.activeElement?.closest(".editItem")) || (document.activeElement?.classList?.contains("edit") && !exeption.includes(e.key) && get(os).platform !== "darwin")) { return }