mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-29 00:20:04 +01:00
feat: add support for single spaced paragraphs
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import { Extension, TextSerializer } from "@tiptap/core";
|
||||
import { Plugin, PluginKey } from "prosemirror-state";
|
||||
import { Node as ProseMirrorNode, Schema, Slice } from "prosemirror-model";
|
||||
|
||||
export const ClipboardTextSerializer = Extension.create({
|
||||
name: "clipboardTextSerializer",
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
new Plugin({
|
||||
key: new PluginKey("clipboardTextSerializer"),
|
||||
props: {
|
||||
clipboardTextSerializer: (content) => {
|
||||
const {
|
||||
editor: { schema },
|
||||
} = this;
|
||||
return getTextBetween(content, schema);
|
||||
},
|
||||
},
|
||||
}),
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
function getTextBetween(slice: Slice, schema: Schema): string {
|
||||
const range = { from: 0, to: slice.size };
|
||||
const { from, to } = range;
|
||||
const separator = (node: ProseMirrorNode) =>
|
||||
node.attrs.spacing === "single" ? "\n" : "\n\n";
|
||||
let text = "";
|
||||
let separated = true;
|
||||
|
||||
slice.content.nodesBetween(0, slice.size, (node, pos, parent, index) => {
|
||||
const textSerializer = schema.nodes[node.type.name]?.spec
|
||||
.toText as TextSerializer;
|
||||
|
||||
if (textSerializer) {
|
||||
if (node.isBlock && !separated) {
|
||||
text += separator(node);
|
||||
separated = true;
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
text += textSerializer({
|
||||
node,
|
||||
pos,
|
||||
parent,
|
||||
index,
|
||||
range,
|
||||
});
|
||||
}
|
||||
} else if (node.isText) {
|
||||
text += node?.text?.slice(Math.max(from, pos) - pos, to - pos); // eslint-disable-line
|
||||
separated = false;
|
||||
} else if (node.isBlock && !separated) {
|
||||
text += separator(node);
|
||||
separated = true;
|
||||
}
|
||||
});
|
||||
|
||||
return text;
|
||||
}
|
||||
1
packages/editor/src/extensions/paragraph/index.ts
Normal file
1
packages/editor/src/extensions/paragraph/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./paragraph";
|
||||
109
packages/editor/src/extensions/paragraph/paragraph.ts
Normal file
109
packages/editor/src/extensions/paragraph/paragraph.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { mergeAttributes, Node } from "@tiptap/core";
|
||||
import { HardBreak } from "@tiptap/extension-hard-break";
|
||||
|
||||
export interface ParagraphOptions {
|
||||
HTMLAttributes: Record<string, any>;
|
||||
doubleSpaced: boolean;
|
||||
}
|
||||
|
||||
declare module "@tiptap/core" {
|
||||
interface Commands<ReturnType> {
|
||||
paragraph: {
|
||||
/**
|
||||
* Toggle a paragraph
|
||||
*/
|
||||
setParagraph: () => ReturnType;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const Paragraph = Node.create<ParagraphOptions>({
|
||||
name: "paragraph",
|
||||
|
||||
priority: 1000,
|
||||
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
doubleSpaced: true,
|
||||
};
|
||||
},
|
||||
|
||||
group: "block",
|
||||
|
||||
content: "inline*",
|
||||
|
||||
// addAttributes() {
|
||||
// return {
|
||||
// spacing: {
|
||||
// keepOnSplit: false,
|
||||
// default: getSpacing(this.options.doubleSpaced),
|
||||
// parseHTML: (element) => element.dataset.spacing,
|
||||
// renderHTML: (attributes) => {
|
||||
// if (!attributes.spacing) return;
|
||||
|
||||
// return {
|
||||
// "data-spacing": attributes.spacing,
|
||||
// };
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
// },
|
||||
|
||||
parseHTML() {
|
||||
return [{ tag: "p" }];
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return [
|
||||
"p",
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
||||
0,
|
||||
];
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
return {
|
||||
setParagraph:
|
||||
() =>
|
||||
({ commands }) => {
|
||||
return commands.setNode(this.name);
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
Enter: ({ editor }) => {
|
||||
if (this.options.doubleSpaced) return false;
|
||||
|
||||
// if (this.options.doubleSpaced) return false;
|
||||
|
||||
const { state } = editor;
|
||||
const { selection } = state;
|
||||
const { $from, empty } = selection;
|
||||
|
||||
if (!empty || $from.parent.type !== this.type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const endsWithNewline = $from.nodeBefore?.type.name === HardBreak.name;
|
||||
|
||||
if (endsWithNewline) {
|
||||
return this.editor.commands.command(({ tr }) => {
|
||||
const { from } = tr.selection;
|
||||
tr.delete(from - 1, from);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
return this.editor.commands.setHardBreak();
|
||||
},
|
||||
"Mod-Alt-0": () => this.editor.commands.setParagraph(),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
function getSpacing(doubleSpaced: boolean): "single" | "double" {
|
||||
return doubleSpaced ? "double" : "single";
|
||||
}
|
||||
@@ -43,9 +43,19 @@ import { SelectionPersist } from "./extensions/selection-persist";
|
||||
import { Table } from "./extensions/table";
|
||||
import { useToolbarStore } from "./toolbar/stores/toolbar-store";
|
||||
import { useEditor } from "./hooks/use-editor";
|
||||
import { EditorOptions } from "@tiptap/core";
|
||||
import {
|
||||
EditorOptions,
|
||||
extensions as TiptapCoreExtensions,
|
||||
} from "@tiptap/core";
|
||||
import { usePermissionHandler } from "./hooks/use-permission-handler";
|
||||
import { Highlight } from "./extensions/highlight";
|
||||
import { Paragraph } from "./extensions/paragraph";
|
||||
import { ClipboardTextSerializer } from "./extensions/clipboard-text-serializer";
|
||||
|
||||
const CoreExtensions = Object.entries(TiptapCoreExtensions)
|
||||
// we will implement our own customized clipboard serializer
|
||||
.filter(([name]) => name !== "ClipboardTextSerializer")
|
||||
.map(([, extension]) => extension);
|
||||
|
||||
EditorView.prototype.updateState = function updateState(state) {
|
||||
if (!(this as any).docView) return; // This prevents the matchesNode error on hot reloads
|
||||
@@ -57,6 +67,7 @@ type TiptapOptions = EditorOptions &
|
||||
theme: Theme;
|
||||
isMobile?: boolean;
|
||||
isKeyboardOpen?: boolean;
|
||||
doubleSpacedLines?: boolean;
|
||||
};
|
||||
|
||||
const useTiptap = (
|
||||
@@ -65,6 +76,7 @@ const useTiptap = (
|
||||
) => {
|
||||
const {
|
||||
theme,
|
||||
doubleSpacedLines = true,
|
||||
isMobile,
|
||||
isKeyboardOpen,
|
||||
onDownloadAttachment,
|
||||
@@ -85,16 +97,26 @@ const useTiptap = (
|
||||
|
||||
const defaultOptions = useMemo<Partial<EditorOptions>>(
|
||||
() => ({
|
||||
enableCoreExtensions: false,
|
||||
extensions: [
|
||||
...CoreExtensions,
|
||||
ClipboardTextSerializer,
|
||||
NodeViewSelectionNotifier,
|
||||
SearchReplace,
|
||||
TextStyle,
|
||||
Paragraph.configure({
|
||||
doubleSpaced: doubleSpacedLines,
|
||||
}),
|
||||
StarterKit.configure({
|
||||
dropcursor: false,
|
||||
codeBlock: false,
|
||||
listItem: false,
|
||||
orderedList: false,
|
||||
bulletList: false,
|
||||
paragraph: false,
|
||||
hardBreak: {
|
||||
keepMarks: true,
|
||||
},
|
||||
history: {
|
||||
depth: 200,
|
||||
newGroupDelay: 1000,
|
||||
|
||||
Reference in New Issue
Block a user