mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
fc01deeefd
* 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>
135 lines
4.3 KiB
TypeScript
135 lines
4.3 KiB
TypeScript
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;
|
||
});
|
||
}
|