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 StateCore from "markdown-it/lib/rules_core/state_core.mjs";
|
|
import type Token from "markdown-it/lib/token.mjs";
|
|
import { v4 as uuidv4 } from "uuid";
|
|
import parseMentionUrl from "@shared/utils/parseMentionUrl";
|
|
|
|
/**
|
|
* Check whether a URL is a valid mention:// href.
|
|
*
|
|
* @param href the URL string to test.
|
|
* @returns true when the href is a recognised mention URL.
|
|
*/
|
|
function isMentionHref(href: string) {
|
|
const { mentionType, modelId } = parseMentionUrl(href);
|
|
return mentionType !== undefined && modelId !== undefined;
|
|
}
|
|
|
|
/**
|
|
* Parse a mention:// href into the id, type and modelId needed by the editor.
|
|
* For 2-segment URLs (no instance id) a fresh UUID is generated.
|
|
*
|
|
* @param href the mention URL to parse.
|
|
* @returns the parsed components.
|
|
* @throws when the href is not a valid mention URL.
|
|
*/
|
|
function parseMentionHref(href: string): {
|
|
id: string;
|
|
type: string;
|
|
modelId: string;
|
|
} {
|
|
const { id, mentionType, modelId } = parseMentionUrl(href);
|
|
|
|
if (!mentionType || !modelId) {
|
|
throw new Error(`Invalid mention href: ${href}`);
|
|
}
|
|
|
|
return { id: id ?? uuidv4(), type: mentionType, modelId };
|
|
}
|
|
|
|
function renderMention(tokens: Token[], idx: number) {
|
|
const id = tokens[idx].attrGet("id");
|
|
const mType = tokens[idx].attrGet("type");
|
|
const mId = tokens[idx].attrGet("modelId");
|
|
const label = tokens[idx].content;
|
|
|
|
return `<span id="${id}" class="mention" data-type="${mType}" data-id="${mId}">${label}</span>`;
|
|
}
|
|
|
|
function parseMentions(state: StateCore) {
|
|
for (let i = 0; i < state.tokens.length; i++) {
|
|
const tok = state.tokens[i];
|
|
if (!(tok.type === "inline" && tok.children)) {
|
|
continue;
|
|
}
|
|
|
|
const canChunkComposeMentionToken = (chunk: Token[]) => {
|
|
// no group of tokens of size less than 4 can compose a mention token
|
|
if (chunk.length < 4) {
|
|
return false;
|
|
}
|
|
|
|
const [precToken, openToken, textToken, closeToken] = chunk;
|
|
|
|
// check for the valid order of tokens required to compose a mention token
|
|
if (
|
|
!(
|
|
precToken.type === "text" &&
|
|
precToken.content &&
|
|
precToken.content.endsWith("@") &&
|
|
openToken.type === "link_open" &&
|
|
textToken.content &&
|
|
closeToken.type === "link_close"
|
|
)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
// "link_open" token should have valid href
|
|
const attr = openToken.attrs?.[0];
|
|
if (!(attr && attr[0] === "href" && isMentionHref(attr[1]))) {
|
|
return false;
|
|
}
|
|
|
|
// can probably compose a mention token if arrived here
|
|
return true;
|
|
};
|
|
|
|
const chunkWithMentionToken = (chunk: Token[]) => {
|
|
const [precToken, openToken, textToken] = chunk;
|
|
|
|
// remove "@" from preceding token
|
|
precToken.content = precToken.content.slice(0, -1);
|
|
|
|
// href must be present, otherwise the isMentionHref test would've failed
|
|
// oxlint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
const href = openToken.attrs![0][1];
|
|
const { id, type: mType, modelId: mId } = parseMentionHref(href);
|
|
|
|
const mentionToken = new state.Token("mention", "", 0);
|
|
mentionToken.attrSet("id", id);
|
|
mentionToken.attrSet("type", mType);
|
|
mentionToken.attrSet("modelId", mId);
|
|
mentionToken.content = textToken.content;
|
|
|
|
// "link_open", followed by "text" and "link_close" tokens are coalesced
|
|
// into "mention" token, hence removed
|
|
return [precToken, mentionToken];
|
|
};
|
|
|
|
let newChildren: Token[] = [];
|
|
let j = 0;
|
|
while (j < tok.children.length) {
|
|
// attempt to grab next four tokens that could potentially construct a mention token
|
|
const chunk = tok.children.slice(j, j + 4);
|
|
if (canChunkComposeMentionToken(chunk)) {
|
|
newChildren = newChildren.concat(chunkWithMentionToken(chunk));
|
|
// skip by 4 since mention token for this group of tokens has been composed
|
|
// and the group cannot compose mention tokens any further
|
|
j += 4;
|
|
} else {
|
|
// push the tokens which do not participate in composing a mention token as it is
|
|
newChildren.push(tok.children[j]);
|
|
j++;
|
|
}
|
|
}
|
|
|
|
state.tokens[i].children = newChildren;
|
|
}
|
|
}
|
|
|
|
export default function mention(md: MarkdownIt) {
|
|
md.renderer.rules.mention = renderMention;
|
|
md.core.ruler.after("inline", "mention", parseMentions);
|
|
}
|