mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-02-24 04:00:59 +01:00
feat: add keep last line in view extension
This commit is contained in:
1
packages/editor/dist/es/extensions/keepinview/index.d.ts
vendored
Normal file
1
packages/editor/dist/es/extensions/keepinview/index.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./keep-in-view";
|
||||
1
packages/editor/dist/es/extensions/keepinview/index.js
vendored
Normal file
1
packages/editor/dist/es/extensions/keepinview/index.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./keep-in-view";
|
||||
3
packages/editor/dist/es/extensions/keepinview/keepinview.d.ts
vendored
Normal file
3
packages/editor/dist/es/extensions/keepinview/keepinview.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Editor, Extension } from "@tiptap/core";
|
||||
export declare const KeepInView: Extension<any, any>;
|
||||
export declare function keepLastLineInView(editor: Editor): void;
|
||||
46
packages/editor/dist/es/extensions/keepinview/keepinview.js
vendored
Normal file
46
packages/editor/dist/es/extensions/keepinview/keepinview.js
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Extension, posToDOMRect } from "@tiptap/core";
|
||||
export const KeepInView = Extension.create({
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
Enter: ({ editor }) => {
|
||||
setTimeout(() => {
|
||||
keepLastLineInView(editor);
|
||||
});
|
||||
return false;
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
export function keepLastLineInView(editor) {
|
||||
const THRESHOLD = 100;
|
||||
const node = editor.state.selection.$from;
|
||||
const { top } = posToDOMRect(editor.view, node.pos, node.pos + 1);
|
||||
const isBelowThreshold = window.innerHeight - top < THRESHOLD;
|
||||
if (isBelowThreshold) {
|
||||
let { node: domNode } = editor.view.domAtPos(node.pos);
|
||||
if (domNode.nodeType === Node.TEXT_NODE && domNode.parentNode)
|
||||
domNode = domNode.parentNode;
|
||||
if (domNode instanceof HTMLElement) {
|
||||
const container = findScrollContainer(domNode);
|
||||
if (container) {
|
||||
container.scrollBy({ top: THRESHOLD, behavior: "smooth" });
|
||||
}
|
||||
else
|
||||
domNode.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
}
|
||||
}
|
||||
}
|
||||
const findScrollContainer = (element) => {
|
||||
if (!element) {
|
||||
return undefined;
|
||||
}
|
||||
let parent = element.parentElement;
|
||||
while (parent) {
|
||||
const { overflow } = parent.style;
|
||||
if (overflow.split(" ").every((o) => o === "auto" || o === "scroll")) {
|
||||
return parent;
|
||||
}
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
return document.documentElement;
|
||||
};
|
||||
1
packages/editor/dist/es/extensions/scroll/index.d.ts
vendored
Normal file
1
packages/editor/dist/es/extensions/scroll/index.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./keep-in-view";
|
||||
1
packages/editor/dist/es/extensions/scroll/index.js
vendored
Normal file
1
packages/editor/dist/es/extensions/scroll/index.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./keep-in-view";
|
||||
3
packages/editor/dist/es/extensions/scroll/keepinview.d.ts
vendored
Normal file
3
packages/editor/dist/es/extensions/scroll/keepinview.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Editor, Extension } from "@tiptap/core";
|
||||
export declare const KeepInView: Extension<any, any>;
|
||||
export declare function keepLastLineInView(editor: Editor): void;
|
||||
46
packages/editor/dist/es/extensions/scroll/keepinview.js
vendored
Normal file
46
packages/editor/dist/es/extensions/scroll/keepinview.js
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Extension, posToDOMRect } from "@tiptap/core";
|
||||
export const KeepInView = Extension.create({
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
Enter: ({ editor }) => {
|
||||
setTimeout(() => {
|
||||
keepLastLineInView(editor);
|
||||
});
|
||||
return false;
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
export function keepLastLineInView(editor) {
|
||||
const THRESHOLD = 100;
|
||||
const node = editor.state.selection.$from;
|
||||
const { top } = posToDOMRect(editor.view, node.pos, node.pos + 1);
|
||||
const isBelowThreshold = window.innerHeight - top < THRESHOLD;
|
||||
if (isBelowThreshold) {
|
||||
let { node: domNode } = editor.view.domAtPos(node.pos);
|
||||
if (domNode.nodeType === Node.TEXT_NODE && domNode.parentNode)
|
||||
domNode = domNode.parentNode;
|
||||
if (domNode instanceof HTMLElement) {
|
||||
const container = findScrollContainer(domNode);
|
||||
if (container) {
|
||||
container.scrollBy({ top: THRESHOLD, behavior: "smooth" });
|
||||
}
|
||||
else
|
||||
domNode.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
}
|
||||
}
|
||||
}
|
||||
const findScrollContainer = (element) => {
|
||||
if (!element) {
|
||||
return undefined;
|
||||
}
|
||||
let parent = element.parentElement;
|
||||
while (parent) {
|
||||
const { overflow } = parent.style;
|
||||
if (overflow.split(" ").every((o) => o === "auto" || o === "scroll")) {
|
||||
return parent;
|
||||
}
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
return document.documentElement;
|
||||
};
|
||||
3
packages/editor/dist/es/extensions/scroll/scroll.d.ts
vendored
Normal file
3
packages/editor/dist/es/extensions/scroll/scroll.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Editor, Extension } from "@tiptap/core";
|
||||
export declare const Scroll: Extension<any, any>;
|
||||
export declare function keepLastLineInView(editor: Editor): void;
|
||||
46
packages/editor/dist/es/extensions/scroll/scroll.js
vendored
Normal file
46
packages/editor/dist/es/extensions/scroll/scroll.js
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Extension, posToDOMRect } from "@tiptap/core";
|
||||
export const Scroll = Extension.create({
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
Enter: ({ editor }) => {
|
||||
setTimeout(() => {
|
||||
keepLastLineInView(editor);
|
||||
});
|
||||
return false;
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
export function keepLastLineInView(editor) {
|
||||
const THRESHOLD = 100;
|
||||
const node = editor.state.selection.$from;
|
||||
const { top } = posToDOMRect(editor.view, node.pos, node.pos + 1);
|
||||
const isBelowThreshold = window.innerHeight - top < THRESHOLD;
|
||||
if (isBelowThreshold) {
|
||||
let { node: domNode } = editor.view.domAtPos(node.pos);
|
||||
if (domNode.nodeType === Node.TEXT_NODE && domNode.parentNode)
|
||||
domNode = domNode.parentNode;
|
||||
if (domNode instanceof HTMLElement) {
|
||||
const container = findScrollContainer(domNode);
|
||||
if (container) {
|
||||
container.scrollBy({ top: THRESHOLD, behavior: "smooth" });
|
||||
}
|
||||
else
|
||||
domNode.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
}
|
||||
}
|
||||
}
|
||||
const findScrollContainer = (element) => {
|
||||
if (!element) {
|
||||
return undefined;
|
||||
}
|
||||
let parent = element.parentElement;
|
||||
while (parent) {
|
||||
const { overflow } = parent.style;
|
||||
if (overflow.split(" ").every((o) => o === "auto" || o === "scroll")) {
|
||||
return parent;
|
||||
}
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
return document.documentElement;
|
||||
};
|
||||
1
packages/editor/dist/es/index.d.ts
vendored
1
packages/editor/dist/es/index.d.ts
vendored
@@ -1,4 +1,3 @@
|
||||
/// <reference types="react" />
|
||||
import "./extensions";
|
||||
import Toolbar from "./toolbar";
|
||||
import { Theme } from "@notesnook/theme";
|
||||
|
||||
2
packages/editor/dist/es/index.js
vendored
2
packages/editor/dist/es/index.js
vendored
@@ -46,6 +46,7 @@ import { MathInline, MathBlock } from "./extensions/math";
|
||||
import { NodeViewSelectionNotifier, usePortalProvider, } from "./extensions/react";
|
||||
import { OutlineList } from "./extensions/outline-list";
|
||||
import { OutlineListItem } from "./extensions/outline-list-item";
|
||||
import { KeepInView } from "./extensions/keep-in-view";
|
||||
import { Table } from "./extensions/table";
|
||||
import { useToolbarStore } from "./toolbar/stores/toolbar-store";
|
||||
import { useEditor } from "./hooks/use-editor";
|
||||
@@ -126,6 +127,7 @@ const useTiptap = (options = {}, deps = []) => {
|
||||
Codemark,
|
||||
MathInline,
|
||||
MathBlock,
|
||||
KeepInView,
|
||||
],
|
||||
onBeforeCreate: ({ editor }) => {
|
||||
if (theme) {
|
||||
|
||||
@@ -14,8 +14,8 @@ export declare type ToolButtonProps = ButtonProps & {
|
||||
};
|
||||
export declare const ToolButton: React.NamedExoticComponent<ButtonProps & {
|
||||
icon: IconNames;
|
||||
iconColor?: keyof import("@notesnook/theme/dist/theme/colorscheme/static").StaticColors | "primary" | "placeholder" | "background" | "bgTransparent" | "accent" | "bgSecondary" | "bgSecondaryText" | "bgSecondaryHover" | "border" | "hover" | "fontSecondary" | "fontTertiary" | "text" | "overlay" | "secondary" | "icon" | "disabled" | "checked" | "red" | "orange" | "yellow" | "green" | "blue" | "purple" | "gray" | undefined;
|
||||
iconSize?: number | "small" | "medium" | "big" | undefined;
|
||||
iconColor?: "background" | "border" | "text" | "blue" | "gray" | "green" | "orange" | "purple" | "red" | "yellow" | "checked" | "disabled" | "placeholder" | "icon" | "overlay" | "primary" | "bgSecondary" | keyof import("@notesnook/theme/dist/theme/colorscheme/static").StaticColors | "bgTransparent" | "accent" | "bgSecondaryText" | "bgSecondaryHover" | "hover" | "fontSecondary" | "fontTertiary" | "secondary" | undefined;
|
||||
iconSize?: number | "small" | "big" | "medium" | undefined;
|
||||
toggled: boolean;
|
||||
buttonRef?: React.MutableRefObject<HTMLButtonElement | null | undefined> | undefined;
|
||||
variant?: ToolButtonVariant | undefined;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ToolbarDefinition, ToolDefinition } from "./types";
|
||||
import { ToolId } from "./tools";
|
||||
export declare function getToolDefinition(id: ToolId): ToolDefinition;
|
||||
export declare function getAllTools(): Record<"bold" | "italic" | "underline" | "strikethrough" | "code" | "codeRemove" | "subscript" | "superscript" | "numberedList" | "bulletList" | "highlight" | "textColor" | "openLink" | "linkSettings" | "imageSettings" | "rowProperties" | "insertRowBelow" | "insertRowAbove" | "moveRowDown" | "moveRowUp" | "deleteRow" | "columnProperties" | "insertColumnRight" | "insertColumnLeft" | "moveColumnRight" | "moveColumnLeft" | "deleteColumn" | "cellProperties" | "cellBorderColor" | "deleteTable" | "mergeCells" | "splitCells" | "attachmentSettings" | "embedSettings" | "tableSettings" | "math" | "fontFamily" | "fontSize" | "indent" | "outdent" | "clearformatting" | "addLink" | "editLink" | "removeLink" | "insertBlock" | "headings" | "alignment" | "textDirection" | "imageAlignCenter" | "imageAlignLeft" | "imageAlignRight" | "imageProperties" | "embedAlignCenter" | "embedAlignLeft" | "embedAlignRight" | "embedProperties" | "downloadAttachment" | "removeAttachment" | "cellBackgroundColor" | "cellTextColor" | "cellBorderWidth", ToolDefinition>;
|
||||
export declare function getAllTools(): Record<"bulletList" | "fontSize" | "underline" | "bold" | "code" | "italic" | "subscript" | "superscript" | "textDirection" | "fontFamily" | "highlight" | "removeAttachment" | "downloadAttachment" | "deleteColumn" | "deleteRow" | "deleteTable" | "mergeCells" | "strikethrough" | "codeRemove" | "numberedList" | "textColor" | "openLink" | "linkSettings" | "imageSettings" | "rowProperties" | "insertRowBelow" | "insertRowAbove" | "moveRowDown" | "moveRowUp" | "columnProperties" | "insertColumnRight" | "insertColumnLeft" | "moveColumnRight" | "moveColumnLeft" | "cellProperties" | "cellBorderColor" | "splitCells" | "attachmentSettings" | "embedSettings" | "tableSettings" | "math" | "indent" | "outdent" | "clearformatting" | "addLink" | "editLink" | "removeLink" | "insertBlock" | "headings" | "alignment" | "imageAlignCenter" | "imageAlignLeft" | "imageAlignRight" | "imageProperties" | "embedAlignCenter" | "embedAlignLeft" | "embedAlignRight" | "embedProperties" | "cellBackgroundColor" | "cellTextColor" | "cellBorderWidth", ToolDefinition>;
|
||||
export declare function getDefaultPresets(): Record<"default" | "minimal", ToolbarDefinition>;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Editor } from "@tiptap/core";
|
||||
import { Node, Mark } from "prosemirror-model";
|
||||
import { Node as ProsemirrorNode, Mark } from "prosemirror-model";
|
||||
import { Selection } from "prosemirror-state";
|
||||
export declare type NodeWithOffset = {
|
||||
node: Node;
|
||||
node: ProsemirrorNode;
|
||||
from: number;
|
||||
to: number;
|
||||
};
|
||||
export declare function findSelectedDOMNode(editor: Editor, types: string[]): HTMLElement | null;
|
||||
export declare function findSelectedNode(editor: Editor, type: string): Node | null;
|
||||
export declare function findMark(node: Node, type: string): Mark | undefined;
|
||||
export declare function findSelectedNode(editor: Editor, type: string): ProsemirrorNode | null;
|
||||
export declare function findMark(node: ProsemirrorNode, type: string): Mark | undefined;
|
||||
export declare function selectionToOffset(selection: Selection): NodeWithOffset;
|
||||
export declare function findListItemType(editor: Editor): string | null;
|
||||
export declare function isListActive(editor: Editor): boolean;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { findParentNode, } from "@tiptap/core";
|
||||
import { findParentNode } from "@tiptap/core";
|
||||
export function findSelectedDOMNode(editor, types) {
|
||||
var _a;
|
||||
const { $anchor } = editor.state.selection;
|
||||
|
||||
1
packages/editor/src/extensions/keep-in-view/index.ts
Normal file
1
packages/editor/src/extensions/keep-in-view/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./keep-in-view";
|
||||
51
packages/editor/src/extensions/keep-in-view/keep-in-view.ts
Normal file
51
packages/editor/src/extensions/keep-in-view/keep-in-view.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Editor, Extension, posToDOMRect } from "@tiptap/core";
|
||||
|
||||
export const KeepInView = Extension.create({
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
Enter: ({ editor }) => {
|
||||
setTimeout(() => {
|
||||
keepLastLineInView(editor);
|
||||
});
|
||||
return false;
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export function keepLastLineInView(editor: Editor) {
|
||||
const THRESHOLD = 100;
|
||||
|
||||
const node = editor.state.selection.$from;
|
||||
const { top } = posToDOMRect(editor.view, node.pos, node.pos + 1);
|
||||
const isBelowThreshold = window.innerHeight - top < THRESHOLD;
|
||||
if (isBelowThreshold) {
|
||||
let { node: domNode } = editor.view.domAtPos(node.pos);
|
||||
if (domNode.nodeType === Node.TEXT_NODE && domNode.parentNode)
|
||||
domNode = domNode.parentNode;
|
||||
|
||||
if (domNode instanceof HTMLElement) {
|
||||
const container = findScrollContainer(domNode);
|
||||
if (container) {
|
||||
container.scrollBy({ top: THRESHOLD, behavior: "smooth" });
|
||||
} else domNode.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const findScrollContainer = (element: HTMLElement) => {
|
||||
if (!element) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let parent = element.parentElement;
|
||||
while (parent) {
|
||||
const { overflow } = parent.style;
|
||||
if (overflow.split(" ").every((o) => o === "auto" || o === "scroll")) {
|
||||
return parent;
|
||||
}
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
|
||||
return document.documentElement;
|
||||
};
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
} from "./extensions/react";
|
||||
import { OutlineList } from "./extensions/outline-list";
|
||||
import { OutlineListItem } from "./extensions/outline-list-item";
|
||||
import { KeepInView } from "./extensions/keep-in-view";
|
||||
import { Table } from "./extensions/table";
|
||||
import { useToolbarStore } from "./toolbar/stores/toolbar-store";
|
||||
import { useEditor } from "./hooks/use-editor";
|
||||
@@ -140,6 +141,7 @@ const useTiptap = (
|
||||
Codemark,
|
||||
MathInline,
|
||||
MathBlock,
|
||||
KeepInView,
|
||||
],
|
||||
onBeforeCreate: ({ editor }) => {
|
||||
if (theme) {
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import {
|
||||
Editor,
|
||||
findParentNode,
|
||||
findParentNodeClosestToPos,
|
||||
isNodeSelection,
|
||||
} from "@tiptap/core";
|
||||
import { Node, Mark } from "prosemirror-model";
|
||||
import { Selection } from "prosemirror-state";
|
||||
import { Editor, findParentNode, posToDOMRect } from "@tiptap/core";
|
||||
import { Node as ProsemirrorNode, Mark } from "prosemirror-model";
|
||||
import { Selection, Transaction } from "prosemirror-state";
|
||||
|
||||
export type NodeWithOffset = {
|
||||
node: Node;
|
||||
node: ProsemirrorNode;
|
||||
from: number;
|
||||
to: number;
|
||||
};
|
||||
@@ -30,7 +25,10 @@ export function findSelectedDOMNode(
|
||||
return (editor.view.nodeDOM(pos) as HTMLElement) || null;
|
||||
}
|
||||
|
||||
export function findSelectedNode(editor: Editor, type: string): Node | null {
|
||||
export function findSelectedNode(
|
||||
editor: Editor,
|
||||
type: string
|
||||
): ProsemirrorNode | null {
|
||||
const { $anchor } = editor.state.selection;
|
||||
|
||||
const selectedNode = editor.state.doc.nodeAt($anchor.pos);
|
||||
@@ -45,7 +43,10 @@ export function findSelectedNode(editor: Editor, type: string): Node | null {
|
||||
return editor.state.doc.nodeAt(pos);
|
||||
}
|
||||
|
||||
export function findMark(node: Node, type: string): Mark | undefined {
|
||||
export function findMark(
|
||||
node: ProsemirrorNode,
|
||||
type: string
|
||||
): Mark | undefined {
|
||||
const mark = node.marks.find((m) => m.type.name === type);
|
||||
return mark;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user