Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
e2c60845
Commit
e2c60845
authored
Apr 26, 2026
by
boojack
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix(markdown): split mixed task and bullet lists
parent
c268551a
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
113 additions
and
1 deletion
+113
-1
index.tsx
web/src/components/MemoContent/index.tsx
+11
-1
remark-split-mixed-task-lists.ts
...src/utils/remark-plugins/remark-split-mixed-task-lists.ts
+57
-0
memo-content-list.test.tsx
web/tests/memo-content-list.test.tsx
+45
-0
No files found.
web/src/components/MemoContent/index.tsx
View file @
e2c60845
...
...
@@ -14,6 +14,7 @@ import { rehypeHeadingId } from "@/utils/rehype-plugins/rehype-heading-id";
import
{
remarkDisableSetext
}
from
"@/utils/remark-plugins/remark-disable-setext"
;
import
{
extractMentionUsernames
,
remarkMention
}
from
"@/utils/remark-plugins/remark-mention"
;
import
{
remarkPreserveType
}
from
"@/utils/remark-plugins/remark-preserve-type"
;
import
{
remarkSplitMixedTaskLists
}
from
"@/utils/remark-plugins/remark-split-mixed-task-lists"
;
import
{
remarkTag
}
from
"@/utils/remark-plugins/remark-tag"
;
import
{
CodeBlock
}
from
"./CodeBlock"
;
import
{
isMentionNode
,
isTagNode
,
isTaskListItemNode
}
from
"./ConditionalComponent"
;
...
...
@@ -79,7 +80,16 @@ const MemoContent = (props: MemoContentProps) => {
onDoubleClick=
{
onDoubleClick
}
>
<
ReactMarkdown
remarkPlugins=
{
[
remarkDisableSetext
,
remarkMath
,
remarkGfm
,
remarkBreaks
,
remarkMention
,
remarkTag
,
remarkPreserveType
]
}
remarkPlugins=
{
[
remarkDisableSetext
,
remarkMath
,
remarkGfm
,
remarkSplitMixedTaskLists
,
remarkBreaks
,
remarkMention
,
remarkTag
,
remarkPreserveType
,
]
}
rehypePlugins=
{
[
rehypeRaw
,
[
rehypeSanitize
,
SANITIZE_SCHEMA
],
...
...
web/src/utils/remark-plugins/remark-split-mixed-task-lists.ts
0 → 100644
View file @
e2c60845
import
type
{
List
,
ListItem
,
Root
}
from
"mdast"
;
import
type
{
Parent
}
from
"unist"
;
const
isTaskListItem
=
(
item
:
ListItem
):
boolean
=>
typeof
item
.
checked
===
"boolean"
;
const
splitMixedList
=
(
list
:
List
):
List
[]
=>
{
const
hasTaskItem
=
list
.
children
.
some
(
isTaskListItem
);
const
hasRegularItem
=
list
.
children
.
some
((
item
)
=>
!
isTaskListItem
(
item
));
if
(
!
hasTaskItem
||
!
hasRegularItem
)
{
return
[
list
];
}
const
groups
:
Array
<
{
isTaskGroup
:
boolean
;
items
:
ListItem
[]
}
>
=
[];
for
(
const
item
of
list
.
children
)
{
const
isTaskGroup
=
isTaskListItem
(
item
);
const
previousGroup
=
groups
.
at
(
-
1
);
if
(
previousGroup
&&
previousGroup
.
isTaskGroup
===
isTaskGroup
)
{
previousGroup
.
items
.
push
(
item
);
}
else
{
groups
.
push
({
isTaskGroup
,
items
:
[
item
]
});
}
}
return
groups
.
map
(({
isTaskGroup
,
items
})
=>
({
...
list
,
children
:
isTaskGroup
?
items
:
items
.
map
((
item
)
=>
({
...
item
,
spread
:
false
})),
spread
:
isTaskGroup
?
list
.
spread
:
false
,
}));
};
const
splitMixedTaskListsInParent
=
(
parent
:
Parent
):
void
=>
{
for
(
let
index
=
0
;
index
<
parent
.
children
.
length
;
index
++
)
{
const
child
=
parent
.
children
[
index
];
if
(
"children"
in
child
&&
Array
.
isArray
(
child
.
children
))
{
splitMixedTaskListsInParent
(
child
as
Parent
);
}
if
(
child
.
type
!==
"list"
)
{
continue
;
}
const
splitLists
=
splitMixedList
(
child
as
List
);
if
(
splitLists
.
length
>
1
)
{
parent
.
children
.
splice
(
index
,
1
,
...
splitLists
);
index
+=
splitLists
.
length
-
1
;
}
}
};
export
const
remarkSplitMixedTaskLists
=
()
=>
{
return
(
tree
:
Root
)
=>
{
splitMixedTaskListsInParent
(
tree
);
};
};
web/tests/memo-content-list.test.tsx
0 → 100644
View file @
e2c60845
import
{
renderToStaticMarkup
}
from
"react-dom/server"
;
import
ReactMarkdown
from
"react-markdown"
;
import
remarkGfm
from
"remark-gfm"
;
import
{
List
,
ListItem
}
from
"@/components/MemoContent/markdown"
;
import
{
TASK_LIST_CLASS
,
TASK_LIST_ITEM_CLASS
}
from
"@/components/MemoContent/constants"
;
import
{
remarkSplitMixedTaskLists
}
from
"@/utils/remark-plugins/remark-split-mixed-task-lists"
;
import
{
describe
,
expect
,
it
}
from
"vitest"
;
const
renderListContent
=
(
content
:
string
):
string
=>
renderToStaticMarkup
(
<
ReactMarkdown
remarkPlugins=
{
[
remarkGfm
,
remarkSplitMixedTaskLists
]
}
components=
{
{
ul
:
({
children
,
...
props
})
=>
<
List
{
...
props
}
>
{
children
}
</
List
>,
li
:
({
children
,
...
props
})
=>
<
ListItem
{
...
props
}
>
{
children
}
</
ListItem
>,
}
}
>
{
content
}
</
ReactMarkdown
>,
);
describe
(
"memo content lists"
,
()
=>
{
it
(
"keeps bullets on regular items in mixed task and bullet lists"
,
()
=>
{
const
html
=
renderListContent
(
"- [ ] pickup package
\n
- [ ] library returns
\n\n
- milk
\n
- eggs
\n
- bread"
);
const
listOpenTags
=
html
.
match
(
/<ul class="
[^
"
]
*"/g
)
??
[];
expect
(
listOpenTags
).
toHaveLength
(
2
);
expect
(
listOpenTags
[
0
]).
toContain
(
TASK_LIST_CLASS
);
expect
(
listOpenTags
[
0
]).
toContain
(
"list-none"
);
expect
(
listOpenTags
[
0
]).
not
.
toContain
(
"pl-6"
);
expect
(
listOpenTags
[
1
]).
not
.
toContain
(
TASK_LIST_CLASS
);
expect
(
listOpenTags
[
1
]).
toContain
(
"pl-6"
);
expect
(
listOpenTags
[
1
]).
toContain
(
"list-disc"
);
expect
(
html
).
toContain
(
'<li class="mt-0.5 leading-6">milk</li>'
);
expect
(
html
).
not
.
toContain
(
'<li class="mt-0.5 leading-6">
\
n<p>milk</p>'
);
expect
(
html
).
toContain
(
TASK_LIST_ITEM_CLASS
);
});
it
(
"keeps compact styling for pure task lists"
,
()
=>
{
const
html
=
renderListContent
(
"- [ ] pickup package
\n
- [ ] library returns"
);
expect
(
html
).
toMatch
(
/<ul class="
[^
"
]
*
\b
list-none
\b[^
"
]
*"/
);
expect
(
html
).
not
.
toMatch
(
/<ul class="
[^
"
]
*
\b
list-disc
\b[^
"
]
*"/
);
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment