New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Checkbox/TODO list item block #729
base: main
Are you sure you want to change the base?
Conversation
# Conflicts: # package-lock.json # tests/package.json
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
.../core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts
Outdated
Show resolved
Hide resolved
.../core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts
Outdated
Show resolved
Hide resolved
.../core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts
Show resolved
Hide resolved
.../core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts
Show resolved
Hide resolved
.../core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts
Show resolved
Hide resolved
@@ -184,6 +184,24 @@ NESTED BLOCKS | |||
gap: 1.2em; | |||
} | |||
|
|||
/* Checked */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes me think, would it even be possible to use a label for the inline content + checkbox input? That would be the most "semantic" / accessible way, but not sure if that only introduces more complexity in terms of keyboard handling etc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested this and while keyboard navigation is no problem, clicking the text (which is now a label) toggles the checkbox, which is default browser behaviour so I don't think there's an easy workaround (assuming the checkbox id
and label for
attributes match). Also not sure if the semantics are correct actually, since the contentDOM
is more like a second input field rather than a label.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now I'll leave the contentDOM
as a div
. We can also change it to a label
without issues, but we shouldn't set the for
attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@matthewlipski you can check how Tiptap handles that in their own extension: https://tiptap.dev/docs/editor/api/nodes/task-item
Clicking the text shouldn't trigger the checkbox, as this can become annoying for the user
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@matthewlipski Is there any way we can use the mantine checkbox with the indigo colorway. I think it looks a lot better than the one in the current preview. Would really appreciate this change. I am currently using a custom schema block that does the todo list with the mantine checkbox item. Here's what it looks like:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also happy to paste my custom code even though most if it is a crummy fix to the block not continuing on a new line.
'use client';
import { defaultProps, insertOrUpdateBlock } from '@blocknote/core';
import { createReactBlockSpec } from '@blocknote/react';
import { Box, Checkbox, Flex } from '@mantine/core';
import { IconCheck } from '@tabler/icons-react';
import { useEffect } from 'react';
import CategorySelect from './CategorySelect';
import './styles.css';
export const insertCheckBox = (editor) => ({
title: 'Todo',
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: 'checkbox',
});
},
aliases: ['checkbox', 'todo', 'list', 'bullets', 'to-do', 'tasks', 'taskboard', 'board'],
subtext: 'Plan out your tasks using a checkbox todo list',
icon: <IconCheck size="20" />,
});
export const CheckBoxBlock = createReactBlockSpec(
{
type: 'checkbox',
propSchema: {
textAlignment: defaultProps.textAlignment,
textColor: defaultProps.textColor,
type: {
default: false,
values: [true, false],
},
},
content: 'inline',
},
{
render: (props) => {
useEffect(() => {
const handleKeyDown = (event) => {
if (event.key == 'Tab') {
event.preventDefault();
return;
}
if (event.key === 'Enter') {
if (props?.editor?.getTextCursorPosition()?.prevBlock?.id != props.block.id) {
return;
}
//!OLD STUFF BELOW
var thisFunctionalComponentBlock = props.editor.getTextCursorPosition().prevBlock;
if (thisFunctionalComponentBlock.content.length == 0) {
props.editor.updateBlock(props.editor.getTextCursorPosition().prevBlock, {
type: 'paragraph',
});
props.editor.setTextCursorPosition(
props.editor.getTextCursorPosition().prevBlock,
'start'
);
if (props.editor.getTextCursorPosition().nextBlock.content.length == 0) {
props.editor.removeBlocks([props.editor.getTextCursorPosition().nextBlock]);
}
return;
}
props.editor.updateBlock(props.editor.getTextCursorPosition().block, {
type: 'checkbox',
});
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, []);
return (
<Flex
align={'center'}
style={{ textWrap: 'wrap', wordBreak: 'break-word' }}
gap="xs"
data-alert-type={props.block.props.type}
>
<div contentEditable={false}>
<Checkbox
checked={props.block.props.type}
// mt="1"
ml="2"
onChange={(event) =>
props.editor.updateBlock(props.block, {
type: 'checkbox',
props: { type: event.currentTarget.checked },
})
}
radius="1"
color="indigo"
/>
</div>
<Box
className={'inline-content'}
style={{ textDecoration: props.block.props.type ? 'line-through' : '' }}
ref={props.contentRef}
/>
</Flex>
);
},
}
);
Other todos: |
[] make sure we can't change checked status when doc is not editable |
# Conflicts: # docs/package.json # lerna.json # package-lock.json # packages/ariakit/package.json # packages/core/package.json # packages/core/src/api/testUtil/cases/defaultSchema.ts # packages/core/src/blocks/defaultBlocks.ts # packages/mantine/package.json # packages/react/package.json # packages/shadcn/package.json # playground/package.json # tests/package.json # tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-chromium-linux.png # tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-firefox-linux.png # tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-webkit-linux.png # tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-chromium-linux.png # tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-firefox-linux.png # tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-webkit-linux.png # tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-chromium-linux.png # tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-firefox-linux.png # tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-webkit-linux.png
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
awesome ❤️
@@ -232,6 +228,13 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ | |||
this.options.domAttributes?.inlineContent || {} | |||
); | |||
|
|||
if (typeof getPos !== "boolean") { | |||
const label = | |||
"label-" + this.editor.state.doc.resolve(getPos()).node().attrs.id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you comment (in code) or fix, why we can't just use node.attrs.id? (but need resolve + getpos)
if (typeof getPos === "boolean") { | ||
return; | ||
if (!editor.isEditable) { | ||
checkbox.checked = !checkbox.checked; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this necessary or can we also use preventDefault or return false to prevent the change?
if it's necessary, please comment in code why we need this (it's a typical piece of code that might raise questions / confuse ourselves later!)
@@ -17,6 +17,7 @@ const BulletListItemBlockContent = createStronglyTypedTiptapNode({ | |||
name: "bulletListItem", | |||
content: "inline*", | |||
group: "blockContent", | |||
priority: 90, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still needed?
return { | ||
checked: { | ||
default: false, | ||
// instead of "level" attributes, use "data-level" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix
.BNSplitBlock( | ||
state.selection.from, | ||
selectionAtBlockStart, | ||
selectionAtBlockStart |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why was this added? (the keepprops system)? (maybe comment as well?)
This PR adds a TODO list item style block to the editor. Also includes items for the block type dropdown and slash menu.
Closes #18
TODO: We should merge the
releases
branch into main firstTODO: Figure out what we should render to external HTML, since there aren't set standards and different platforms do it in different ways.