Skip to content

Commit

Permalink
✏ Improved text edit
Browse files Browse the repository at this point in the history
- Reset caret on line changes
- Reset caret on shortcuts
- Paste on multiple lines
- Undo/redo
- Version update
  • Loading branch information
vassbo committed Jul 26, 2024
1 parent c58d021 commit 5830a93
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 32 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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!",
Expand Down
135 changes: 111 additions & 24 deletions src/frontend/components/edit/editbox/EditboxLines.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@
cutInTwo({ e, sel, lines, currentIndex, textPos, start })
}
storeCurrentCaretPos()
}
function cutInTwo({ e, sel, lines, currentIndex, textPos, start }) {
Expand Down Expand Up @@ -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
Expand All @@ -341,6 +347,8 @@
}
})
if (pasting) return newLines
if (updateHTML) {
// get caret pos
let sel = getSelectionRange()
Expand Down Expand Up @@ -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 }
}
}
Expand All @@ -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()
Expand All @@ -407,43 +444,92 @@
}
// 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(() => {
getStyle()
// set caret position back
setTimeout(() => {
setCaret(textElem, caret)
pasting = false
}, 10)
}, 10)
}
Expand Down Expand Up @@ -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}
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/components/edit/scripts/textStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 11 additions & 2 deletions src/frontend/components/edit/tools/BoxStyle.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
// -----
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/components/helpers/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[]) => {
Expand All @@ -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[]) => {
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/utils/shortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit 5830a93

Please sign in to comment.