From 393bbd9a64d1b2248ce3e17dbde6f3485140f777 Mon Sep 17 00:00:00 2001 From: Cédric <42071178+BOTkirial@users.noreply.github.com> Date: Sun, 2 Nov 2025 22:48:38 +0100 Subject: feat: Support inline toggling for todos. fixes #1931 (#1933) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [1931] Can now chain the creation of todos from the quick add form * [1931] Can now toggle todos from the masonry view + added a custom renderer for inputs of type checkbox (required to remove the readonly default attribute) * handle nested lists and case --------- Co-authored-by: Cédric Co-authored-by: Mohamed Bassem --- .../bookmarks/BookmarkMarkdownComponent.tsx | 5 ++- .../components/dashboard/bookmarks/EditorCard.tsx | 35 ++++++++++++++++ .../components/ui/markdown/markdown-readonly.tsx | 46 ++++++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) (limited to 'apps/web/components') diff --git a/apps/web/components/dashboard/bookmarks/BookmarkMarkdownComponent.tsx b/apps/web/components/dashboard/bookmarks/BookmarkMarkdownComponent.tsx index 82e483a9..e7fea2c3 100644 --- a/apps/web/components/dashboard/bookmarks/BookmarkMarkdownComponent.tsx +++ b/apps/web/components/dashboard/bookmarks/BookmarkMarkdownComponent.tsx @@ -33,10 +33,13 @@ export function BookmarkMarkdownComponent({ text, }); }; + return (
{readOnly ? ( - {bookmark.content.text} + + {bookmark.content.text} + ) : ( {bookmark.content.text} diff --git a/apps/web/components/dashboard/bookmarks/EditorCard.tsx b/apps/web/components/dashboard/bookmarks/EditorCard.tsx index 7ac1cade..b80cd889 100644 --- a/apps/web/components/dashboard/bookmarks/EditorCard.tsx +++ b/apps/web/components/dashboard/bookmarks/EditorCard.tsx @@ -172,6 +172,35 @@ export default function EditorCard({ className }: { className?: string }) { } }; + /** + * Methods that triggers when "enter" is pressed (without ctrl) + * It checks if the current line is a todo + * if it is it automatically appends a todo a the start of the new line + */ + const handleNewTodo = (e: React.KeyboardEvent) => { + const todoMarkup = "- [ ] "; + const textarea = inputRef.current; + if (!textarea) return; + const start = textarea.selectionStart; + const end = textarea.selectionEnd; + const textBefore = textarea.value.slice(0, start); + const lines = textBefore.split("\n"); + const currentLine = lines[lines.length - 1]; + const currentLineIsTodo = currentLine.startsWith(todoMarkup); + if (!currentLineIsTodo) return; + e.preventDefault(); + const newValue = + textarea.value.slice(0, start) + + "\n" + + todoMarkup + + textarea.value.slice(end); + form.setValue("text", newValue, { shouldDirty: true, shouldTouch: true }); + textarea.value = newValue; + textarea.selectionStart = start + todoMarkup.length + 1; + textarea.selectionEnd = start + todoMarkup.length + 1; + textarea.dispatchEvent(new Event("input", { bubbles: true })); + }; + const OS = getOS(); return ( @@ -205,6 +234,12 @@ export default function EditorCard({ className }: { className?: string }) { if (demoMode) { return; } + if ( + e.key === "Enter" && + !(e.metaKey || e.ctrlKey || e.shiftKey) + ) { + handleNewTodo(e); + } if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { form.handleSubmit(onSubmit, onError)(); } diff --git a/apps/web/components/ui/markdown/markdown-readonly.tsx b/apps/web/components/ui/markdown/markdown-readonly.tsx index 3c6daf31..5436e961 100644 --- a/apps/web/components/ui/markdown/markdown-readonly.tsx +++ b/apps/web/components/ui/markdown/markdown-readonly.tsx @@ -25,15 +25,61 @@ function PreWithCopyBtn({ className, ...props }: React.ComponentProps<"pre">) { export function MarkdownReadonly({ children: markdown, className, + onSave, }: { children: string; className?: string; + onSave?: (markdown: string) => void; }) { + /** + * This method is triggered when a checkbox is toggled from the masonry view + * It finds the index of the clicked checkbox inside of the note + * It then finds the corresponding markdown and changes it accordingly + */ + const handleTodoClick = (e: React.ChangeEvent) => { + e.preventDefault(); + const parent = e.target.closest(".prose"); + if (!parent) return; + const allCheckboxes = parent.querySelectorAll(".todo-checkbox"); + let checkboxIndex = 0; + allCheckboxes.forEach((cb, i) => { + if (cb === e.target) checkboxIndex = i; + }); + let i = 0; + const todoPattern = /^(\s*[-*+]\s*\[)( |x|X)(\])/gm; + const newMarkdown = markdown.replace( + todoPattern, + (match, prefix: string, state: string, suffix: string) => { + const currentIndex = i++; + if (currentIndex !== checkboxIndex) { + return match; + } + const isDone = state.toLowerCase() === "x"; + const nextState = isDone ? " " : "x"; + return `${prefix}${nextState}${suffix}`; + }, + ); + if (onSave) { + onSave(newMarkdown); + } + }; + return ( + props.type === "checkbox" ? ( + + ) : ( + + ), pre({ ...props }) { return ; }, -- cgit v1.2.3-70-g09d2