mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-02-24 04:00:59 +01:00
editor: add support for simple checklists
This commit is contained in:
2
packages/core/package-lock.json
generated
2
packages/core/package-lock.json
generated
@@ -2214,6 +2214,8 @@
|
||||
},
|
||||
"node_modules/@streetwriters/showdown": {
|
||||
"version": "3.0.5-alpha",
|
||||
"resolved": "https://registry.npmjs.org/@streetwriters/showdown/-/showdown-3.0.5-alpha.tgz",
|
||||
"integrity": "sha512-jD9JFhxLDx6XeyZOLVB0zWtwGduwNiFpxn5rxu6ThyKyWGnu1O+L1w04WLC1L56pyEhypr3Tsk24dzo2Se/50g==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"showdown": "bin/showdown.js"
|
||||
|
||||
@@ -59,11 +59,12 @@ export class Tiptap {
|
||||
preserveNewlines: true,
|
||||
selectors: [
|
||||
{ selector: "table", format: "dataTable" },
|
||||
{ selector: "ul.checklist", format: "taskList" },
|
||||
{ selector: "ul.checklist", format: "checkList" },
|
||||
{ selector: "ul.simple-checklist", format: "checkList" },
|
||||
{ selector: "p", format: "paragraph" }
|
||||
],
|
||||
formatters: {
|
||||
taskList: (elem, walk, builder, formatOptions) => {
|
||||
checkList: (elem, walk, builder, formatOptions) => {
|
||||
return formatList(elem, walk, builder, formatOptions, (elem) => {
|
||||
return elem.attribs.class && elem.attribs.class.includes("checked")
|
||||
? " ✅ "
|
||||
|
||||
@@ -28,7 +28,6 @@ export interface CheckListItemOptions {
|
||||
onReadOnlyChecked?: (node: ProseMirrorNode, checked: boolean) => boolean;
|
||||
nested: boolean;
|
||||
HTMLAttributes: Record<string, any>;
|
||||
checkListTypeName: string;
|
||||
}
|
||||
|
||||
export const inputRegex = /^\s*(\[([( |x])?\])\s$/;
|
||||
@@ -39,8 +38,7 @@ export const CheckListItem = Node.create<CheckListItemOptions>({
|
||||
addOptions() {
|
||||
return {
|
||||
nested: false,
|
||||
HTMLAttributes: {},
|
||||
checkListTypeName: "checkList"
|
||||
HTMLAttributes: {}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -55,9 +53,9 @@ export const CheckListItem = Node.create<CheckListItemOptions>({
|
||||
checked: {
|
||||
default: false,
|
||||
keepOnSplit: false,
|
||||
parseHTML: (element) => element.getAttribute("data-checked") === "true",
|
||||
parseHTML: (element) => element.classList.contains("checked"),
|
||||
renderHTML: (attributes) => ({
|
||||
"data-checked": attributes.checked
|
||||
class: attributes.checked ? "checked" : ""
|
||||
})
|
||||
}
|
||||
};
|
||||
@@ -66,30 +64,19 @@ export const CheckListItem = Node.create<CheckListItemOptions>({
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: `li[data-type="${this.name}"]`,
|
||||
tag: `li.simple-checklist--item`,
|
||||
priority: 51
|
||||
}
|
||||
];
|
||||
},
|
||||
|
||||
renderHTML({ node, HTMLAttributes }) {
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return [
|
||||
"li",
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
||||
"data-type": this.name
|
||||
class: "simple-checklist--item"
|
||||
}),
|
||||
[
|
||||
"label",
|
||||
[
|
||||
"input",
|
||||
{
|
||||
type: "checkbox",
|
||||
checked: node.attrs.checked ? "checked" : null
|
||||
}
|
||||
],
|
||||
["span"]
|
||||
],
|
||||
["div", 0]
|
||||
0
|
||||
];
|
||||
},
|
||||
|
||||
@@ -112,16 +99,15 @@ export const CheckListItem = Node.create<CheckListItemOptions>({
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return ({ node, HTMLAttributes, getPos, editor }) => {
|
||||
return ({ node, getPos, editor }) => {
|
||||
const listItem = document.createElement("li");
|
||||
const checkboxWrapper = document.createElement("label");
|
||||
const checkboxStyler = document.createElement("span");
|
||||
const checkbox = document.createElement("input");
|
||||
const content = document.createElement("div");
|
||||
|
||||
checkboxWrapper.contentEditable = "false";
|
||||
checkbox.contentEditable = "false";
|
||||
checkbox.type = "checkbox";
|
||||
checkbox.addEventListener("change", (event) => {
|
||||
event.preventDefault();
|
||||
// if the editor isn’t editable and we don't have a handler for
|
||||
// readonly checks we have to undo the latest change
|
||||
if (!editor.isEditable && !this.options.onReadOnlyChecked) {
|
||||
@@ -135,7 +121,6 @@ export const CheckListItem = Node.create<CheckListItemOptions>({
|
||||
if (editor.isEditable && typeof getPos === "function") {
|
||||
editor
|
||||
.chain()
|
||||
.focus(undefined, { scrollIntoView: false })
|
||||
.command(({ tr }) => {
|
||||
const position = getPos();
|
||||
const currentNode = tr.doc.nodeAt(position);
|
||||
@@ -157,21 +142,11 @@ export const CheckListItem = Node.create<CheckListItemOptions>({
|
||||
}
|
||||
});
|
||||
|
||||
Object.entries(this.options.HTMLAttributes).forEach(([key, value]) => {
|
||||
listItem.setAttribute(key, value);
|
||||
});
|
||||
|
||||
listItem.dataset.checked = node.attrs.checked;
|
||||
if (node.attrs.checked) {
|
||||
checkbox.setAttribute("checked", "checked");
|
||||
}
|
||||
|
||||
checkboxWrapper.append(checkbox, checkboxStyler);
|
||||
listItem.append(checkboxWrapper, content);
|
||||
|
||||
Object.entries(HTMLAttributes).forEach(([key, value]) => {
|
||||
listItem.setAttribute(key, value);
|
||||
});
|
||||
listItem.append(checkbox, content);
|
||||
|
||||
return {
|
||||
dom: listItem,
|
||||
|
||||
@@ -53,7 +53,7 @@ export const CheckList = Node.create<CheckListOptions>({
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: `ul[data-type="${this.name}"]`,
|
||||
tag: `ul.simple-checklist`,
|
||||
priority: 51
|
||||
}
|
||||
];
|
||||
@@ -63,7 +63,7 @@ export const CheckList = Node.create<CheckListOptions>({
|
||||
return [
|
||||
"ul",
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
||||
"data-type": this.name
|
||||
class: "simple-checklist"
|
||||
}),
|
||||
0
|
||||
];
|
||||
|
||||
@@ -55,16 +55,7 @@ export const TaskItemNode = TaskItem.extend({
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: "li",
|
||||
getAttrs: (node: any) => {
|
||||
if (node instanceof Node && node instanceof HTMLElement) {
|
||||
return node.classList.contains("checklist--item") ||
|
||||
node.parentElement?.classList.contains("checklist")
|
||||
? null
|
||||
: false;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
tag: "li.checklist--item",
|
||||
priority: 51
|
||||
}
|
||||
];
|
||||
|
||||
@@ -89,13 +89,7 @@ export const TaskListNode = TaskList.extend({
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: "ul",
|
||||
getAttrs: (node) => {
|
||||
if (node instanceof Node && node instanceof HTMLElement) {
|
||||
return node.classList.contains("checklist") && null;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
tag: "ul.checklist",
|
||||
priority: 51
|
||||
}
|
||||
];
|
||||
|
||||
@@ -284,7 +284,9 @@ const useTiptap = (
|
||||
KeyMap,
|
||||
WebClipNode,
|
||||
CheckList,
|
||||
CheckListItem,
|
||||
CheckListItem.configure({
|
||||
nested: true
|
||||
}),
|
||||
|
||||
// Quirks handlers
|
||||
Quirks.configure({
|
||||
|
||||
@@ -105,8 +105,8 @@ const tools: Record<ToolId, ToolDefinition> = {
|
||||
title: "Numbered list"
|
||||
},
|
||||
checkList: {
|
||||
icon: "checkList",
|
||||
title: "Numbered list"
|
||||
icon: "checklist",
|
||||
title: "Checklist"
|
||||
},
|
||||
fontFamily: {
|
||||
icon: "fontFamily",
|
||||
|
||||
@@ -43,6 +43,8 @@ import { TaskItemNode } from "../extensions/task-item";
|
||||
import { TaskListNode } from "../extensions/task-list";
|
||||
import { LIST_NODE_TYPES } from "./node-types";
|
||||
import TextStyle from "@tiptap/extension-text-style";
|
||||
import CheckList from "../extensions/check-list";
|
||||
import CheckListItem from "../extensions/check-list-item";
|
||||
|
||||
export type NodeWithOffset = {
|
||||
node?: ProsemirrorNode;
|
||||
@@ -61,6 +63,7 @@ export function hasSameAttributes(prev: Attrs, next: Attrs) {
|
||||
|
||||
export function findListItemType(editor: Editor): string | null {
|
||||
const isTaskList = editor.isActive(TaskListNode.name);
|
||||
const isCheckList = editor.isActive(CheckList.name);
|
||||
const isOutlineList = editor.isActive(OutlineList.name);
|
||||
const isList =
|
||||
editor.isActive(BulletList.name) || editor.isActive(OrderedList.name);
|
||||
@@ -71,6 +74,8 @@ export function findListItemType(editor: Editor): string | null {
|
||||
? OutlineListItem.name
|
||||
: isTaskList
|
||||
? TaskItemNode.name
|
||||
: isCheckList
|
||||
? CheckListItem.name
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
@@ -621,34 +621,23 @@ p > *::selection {
|
||||
[dir="rtl"] .taskItemTools { right: unset; left: 0 }
|
||||
|
||||
/* Check list */
|
||||
.ProseMirror ul[data-type="checkList"] {
|
||||
.ProseMirror ul.simple-checklist {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
.ProseMirror ul.simple-checklist > li {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
.ProseMirror ul.simple-checklist > li > input {
|
||||
flex: 0 0 auto;
|
||||
margin-right: 0.5rem;
|
||||
user-select: none;
|
||||
height: 18px;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
|
||||
> label {
|
||||
flex: 0 0 auto;
|
||||
margin-right: 0.5rem;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
> div {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
ul li,
|
||||
ol li {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
ul[data-type="checkList"] > li {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
.ProseMirror ul.simple-checklist > li > div {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
Reference in New Issue
Block a user