Files
outline/shared/editor/rules/checkboxes.ts
T
dependabot[bot] fc01deeefd chore(deps-dev): bump oxlint-tsgolint from 0.14.2 to 0.22.1 (#12320)
* chore(deps-dev): bump oxlint-tsgolint from 0.14.2 to 0.22.1

Bumps [oxlint-tsgolint](https://github.com/oxc-project/tsgolint) from 0.14.2 to 0.22.1.
- [Release notes](https://github.com/oxc-project/tsgolint/releases)
- [Commits](https://github.com/oxc-project/tsgolint/compare/v0.14.2...v0.22.1)

---
updated-dependencies:
- dependency-name: oxlint-tsgolint
  dependency-version: 0.22.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: Switch tsconfig to bundler resolution for tsgolint 0.22.1

oxlint-tsgolint 0.22.1 removed support for moduleResolution=node10
(the alias for "node"). Switch to "bundler" with resolvePackageJsonExports
disabled so packages whose exports field omits a types condition still
resolve. Update markdown-it type imports to sub-paths since the package's
.d.mts entry only re-exports a subset of named types.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: Resolve type-aware lint errors caught by tsgolint 0.22.1

oxlint-tsgolint 0.22.1 catches several await-thenable, no-floating-promises,
and no-meaningless-void-operator cases the prior 0.14.2 missed:

- Drop redundant inner `await` from Promise.all([await x, await y]) call sites
  so the array entries are real Promises rather than already-resolved values.
- Replace Promise.all wrappers around synchronous presenters (presentEvent,
  presentTemplate, presentPublicTeam) with plain map / direct calls.
- Wrap non-promise branches of ternaries inside Promise.all with
  Promise.resolve so the array remains thenable across both arms.
- Add `void` to the unawaited provider.connect() in the auth-failed retry
  chain, and remove `void` from the disconnect() call which returns void.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 07:59:13 -04:00

135 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type MarkdownIt from "markdown-it";
import type Token from "markdown-it/lib/token.mjs";
const CHECKBOX_REGEX = /\[(X|\s|_|-)\]\s(.*)?/i;
function matches(token: Token) {
return token && token.content.match(CHECKBOX_REGEX);
}
function isInline(token: Token): boolean {
return !!token && token.type === "inline";
}
function isParagraph(token: Token): boolean {
return !!token && token.type === "paragraph_open";
}
function isListItem(token: Token): boolean {
// Only match list_item_open, not checkbox_item_open - items that are already
// checkbox_item_open have been processed (e.g., by the tables rule for
// checkboxes in table cells) and should not be processed again.
return !!token && token.type === "list_item_open";
}
function looksLikeChecklist(tokens: Token[], index: number) {
return (
isInline(tokens[index]) &&
isListItem(tokens[index - 2]) &&
isParagraph(tokens[index - 1]) &&
matches(tokens[index])
);
}
export default function markdownItCheckbox(md: MarkdownIt): void {
function render(tokens: Token[], idx: number) {
const token = tokens[idx];
const checked = !!token.attrGet("checked");
if (token.nesting === 1) {
// opening tag
return `<li class="checkbox-list-item"><span class="checkbox ${
checked ? "checked" : ""
}">${checked ? "[x]" : "[ ]"}</span>`;
} else {
// closing tag
return "</li>\n";
}
}
md.renderer.rules.checkbox_item_open = render;
md.renderer.rules.checkbox_item_close = render;
// insert a new rule after the "inline" rules are parsed
md.core.ruler.after("inline", "checkboxes", (state) => {
const tokens = state.tokens;
// work backwards through the tokens and find text that looks like a checkbox
for (let i = tokens.length - 1; i > 0; i--) {
const matchesChecklist = looksLikeChecklist(tokens, i);
if (matchesChecklist) {
const value = matchesChecklist[1];
const checked = value.toLowerCase() === "x";
// convert surrounding list tokens
if (tokens[i - 3].type === "bullet_list_open") {
tokens[i - 3].type = "checkbox_list_open";
}
if (tokens[i + 3].type === "bullet_list_close") {
tokens[i + 3].type = "checkbox_list_close";
}
// remove [ ] [x] from list item label must use the content from the
// child for escaped characters to be unescaped correctly.
const tokenChildren = tokens[i].children;
if (tokenChildren && tokenChildren[0].type === "text") {
const contentMatches = tokenChildren[0].content.match(CHECKBOX_REGEX);
if (contentMatches) {
const label = contentMatches[2];
tokens[i].content = label;
tokenChildren[0].content = label;
}
}
// open list item and ensure checked state is transferred
tokens[i - 2].type = "checkbox_item_open";
if (checked === true) {
tokens[i - 2].attrs = [["checked", "true"]];
}
// close the list item
let j = i;
while (
tokens[j] &&
tokens[j].type !== "list_item_close" &&
tokens[j].type !== "checkbox_item_close"
) {
j++;
}
if (tokens[j]) {
tokens[j].type = "checkbox_item_close";
}
}
}
// Second pass: convert any remaining direct child list_item tokens inside
// a checkbox_list to checkbox_item so they aren't silently dropped by the
// Prosemirror schema which requires checkbox_item+ children.
const checkboxListOpenLevels: number[] = [];
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
if (token.type === "checkbox_list_open") {
checkboxListOpenLevels.push(token.level);
} else if (token.type === "checkbox_list_close") {
checkboxListOpenLevels.pop();
} else if (checkboxListOpenLevels.length > 0) {
const checkboxListOpenLevel =
checkboxListOpenLevels[checkboxListOpenLevels.length - 1];
const isDirectChild = token.level === checkboxListOpenLevel + 1;
if (isDirectChild && token.type === "list_item_open") {
token.type = "checkbox_item_open";
} else if (isDirectChild && token.type === "list_item_close") {
token.type = "checkbox_item_close";
}
}
}
return false;
});
}