mirror of
https://gitlab.com/ArkHost/HelixNotes.git
synced 2026-07-24 07:45:57 +02:00
Scroll Past End (#116)
Adds editor scroll room after the final line so users can focus the active writing area without inserting blank lines. Ctrl+End moves to the document end and scrolls into that workspace; plain End keeps native line-end behavior. Presentation-only except cursor movement; does not mutate note content, mark dirty, or affect autosave. Reviewed-on: https://codeberg.org/ArkHost/HelixNotes/pulls/116
This commit is contained in:
@@ -86,6 +86,28 @@
|
|||||||
let alignDropdown = $state(false);
|
let alignDropdown = $state(false);
|
||||||
let insertDropdown = $state(false);
|
let insertDropdown = $state(false);
|
||||||
|
|
||||||
|
function scrollEditorBodyToBottom(source: HTMLElement | null | undefined = editorElement) {
|
||||||
|
const editorBody = source?.closest('.editor-body') as HTMLElement | null;
|
||||||
|
if (!editorBody) return;
|
||||||
|
editorBody.scrollTop = editorBody.scrollHeight;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
editorBody.scrollTop = editorBody.scrollHeight;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSourceCtrlEnd(event: KeyboardEvent) {
|
||||||
|
if (!event.ctrlKey || event.metaKey || event.altKey || event.shiftKey || event.key !== 'End') return false;
|
||||||
|
event.preventDefault();
|
||||||
|
const ta = sourceElement;
|
||||||
|
ta.focus({ preventScroll: true });
|
||||||
|
ta.setSelectionRange(sourceContent.length, sourceContent.length);
|
||||||
|
ta.scrollTop = ta.scrollHeight;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
ta.scrollTop = ta.scrollHeight;
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function closeAllDropdowns() {
|
function closeAllDropdowns() {
|
||||||
headingDropdown = false;
|
headingDropdown = false;
|
||||||
colorDropdown = false;
|
colorDropdown = false;
|
||||||
@@ -585,6 +607,30 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const CtrlEndScrollPastEnd = Extension.create({
|
||||||
|
name: 'ctrlEndScrollPastEnd',
|
||||||
|
addProseMirrorPlugins() {
|
||||||
|
return [
|
||||||
|
new Plugin({
|
||||||
|
key: new PluginKey('ctrlEndScrollPastEnd'),
|
||||||
|
props: {
|
||||||
|
handleDOMEvents: {
|
||||||
|
keydown(view, event) {
|
||||||
|
if (!event.ctrlKey || event.metaKey || event.altKey || event.shiftKey || event.key !== 'End') return false;
|
||||||
|
event.preventDefault();
|
||||||
|
const tr = view.state.tr.setSelection(TextSelection.atEnd(view.state.doc));
|
||||||
|
view.dispatch(tr);
|
||||||
|
(view.dom as HTMLElement).focus({ preventScroll: true });
|
||||||
|
scrollEditorBodyToBottom(view.dom as HTMLElement);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const PageBreak = TiptapNode.create({
|
const PageBreak = TiptapNode.create({
|
||||||
name: 'pageBreak',
|
name: 'pageBreak',
|
||||||
group: 'block',
|
group: 'block',
|
||||||
@@ -3115,6 +3161,7 @@
|
|||||||
TextAlign.configure({ types: ['heading', 'paragraph'] }).extend({
|
TextAlign.configure({ types: ['heading', 'paragraph'] }).extend({
|
||||||
addKeyboardShortcuts: () => ({}),
|
addKeyboardShortcuts: () => ({}),
|
||||||
}),
|
}),
|
||||||
|
CtrlEndScrollPastEnd,
|
||||||
HeadingShortcuts,
|
HeadingShortcuts,
|
||||||
SlashCommands,
|
SlashCommands,
|
||||||
MoveLineShortcuts,
|
MoveLineShortcuts,
|
||||||
@@ -4584,6 +4631,7 @@
|
|||||||
pushSourceHistoryDebounced();
|
pushSourceHistoryDebounced();
|
||||||
}}
|
}}
|
||||||
onkeydown={(e) => {
|
onkeydown={(e) => {
|
||||||
|
if (handleSourceCtrlEnd(e)) return;
|
||||||
const mod = e.ctrlKey || e.metaKey;
|
const mod = e.ctrlKey || e.metaKey;
|
||||||
if (e.key === 'Enter' && e.shiftKey && !mod) {
|
if (e.key === 'Enter' && e.shiftKey && !mod) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -4641,6 +4689,7 @@
|
|||||||
pushSourceHistoryDebounced();
|
pushSourceHistoryDebounced();
|
||||||
}}
|
}}
|
||||||
onkeydown={(e) => {
|
onkeydown={(e) => {
|
||||||
|
if (handleSourceCtrlEnd(e)) return;
|
||||||
const mod = e.ctrlKey || e.metaKey;
|
const mod = e.ctrlKey || e.metaKey;
|
||||||
// Shift+Enter: insert two trailing spaces + newline for markdown hard break
|
// Shift+Enter: insert two trailing spaces + newline for markdown hard break
|
||||||
if (e.key === 'Enter' && e.shiftKey && !mod) {
|
if (e.key === 'Enter' && e.shiftKey && !mod) {
|
||||||
@@ -6667,7 +6716,7 @@
|
|||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
resize: none;
|
resize: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
padding: 0;
|
padding: 0 0 var(--editor-scroll-past-end, 65vh);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
/* Wrap long lines instead of horizontal-scrolling (matches mobile). (issue #100) */
|
/* Wrap long lines instead of horizontal-scrolling (matches mobile). (issue #100) */
|
||||||
@@ -6725,6 +6774,11 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editor-container:not(.mobile) :global(.tiptap-wrapper .tiptap) {
|
||||||
|
min-height: calc(100% + var(--editor-scroll-past-end, 65vh));
|
||||||
|
padding-bottom: var(--editor-scroll-past-end, 65vh);
|
||||||
|
}
|
||||||
|
|
||||||
:global(.tiptap-wrapper .tiptap p) {
|
:global(.tiptap-wrapper .tiptap p) {
|
||||||
margin: 0 0 0.75em;
|
margin: 0 0 0.75em;
|
||||||
line-height: var(--editor-line-height, 1.65);
|
line-height: var(--editor-line-height, 1.65);
|
||||||
|
|||||||
Reference in New Issue
Block a user