Files
outline/shared/editor/nodes/Notice.tsx
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

183 lines
5.4 KiB
TypeScript

import type Token from "markdown-it/lib/token.mjs";
import { WarningIcon, InfoIcon, StarredIcon, DoneIcon } from "outline-icons";
import { wrappingInputRule } from "prosemirror-inputrules";
import type {
NodeSpec,
Node as ProsemirrorNode,
NodeType,
} from "prosemirror-model";
import type { Command, EditorState, Transaction } from "prosemirror-state";
import * as React from "react";
import ReactDOM from "react-dom";
import type { Primitive } from "utility-types";
import toggleWrap from "../commands/toggleWrap";
import type { MarkdownSerializerState } from "../lib/markdown/serializer";
import noticesRule from "../rules/notices";
import Node from "./Node";
export enum NoticeTypes {
Info = "info",
Success = "success",
Tip = "tip",
Warning = "warning",
}
export default class Notice extends Node {
get name() {
return "container_notice";
}
get rulePlugins() {
return [noticesRule];
}
get schema(): NodeSpec {
return {
attrs: {
style: {
default: NoticeTypes.Info,
},
},
content:
"(list | blockquote | hr | paragraph | heading | code_block | code_fence | attachment)+",
group: "block",
defining: true,
draggable: true,
parseDOM: [
{
tag: "div.notice-block",
preserveWhitespace: "full",
contentElement: (node: HTMLDivElement) =>
node.querySelector("div.content") || node,
getAttrs: (dom: HTMLDivElement) => ({
style: dom.className.includes(NoticeTypes.Tip)
? NoticeTypes.Tip
: dom.className.includes(NoticeTypes.Warning)
? NoticeTypes.Warning
: dom.className.includes(NoticeTypes.Success)
? NoticeTypes.Success
: undefined,
}),
},
// Quill editor parsing
{
tag: "div.ql-hint",
preserveWhitespace: "full",
getAttrs: (dom: HTMLDivElement) => ({
style: dom.dataset.hint,
}),
},
// GitBook parsing
{
tag: "div.alert.theme-admonition",
preserveWhitespace: "full",
getAttrs: (dom: HTMLDivElement) => ({
style: dom.className.includes(NoticeTypes.Warning)
? NoticeTypes.Warning
: dom.className.includes(NoticeTypes.Success)
? NoticeTypes.Success
: undefined,
}),
},
// Confluence parsing
{
tag: "div.confluence-information-macro",
preserveWhitespace: "full",
getAttrs: (dom: HTMLDivElement) => ({
style: dom.className.includes("confluence-information-macro-tip")
? NoticeTypes.Success
: dom.className.includes("confluence-information-macro-note")
? NoticeTypes.Tip
: dom.className.includes("confluence-information-macro-warning")
? NoticeTypes.Warning
: undefined,
}),
},
],
toDOM: (node) => {
let icon;
if (typeof document !== "undefined") {
let component;
if (node.attrs.style === NoticeTypes.Tip) {
component = <StarredIcon />;
} else if (node.attrs.style === NoticeTypes.Warning) {
component = <WarningIcon />;
} else if (node.attrs.style === NoticeTypes.Success) {
component = <DoneIcon />;
} else {
component = <InfoIcon />;
}
icon = document.createElement("div");
icon.className = "icon";
ReactDOM.render(component, icon);
}
return [
"div",
{ class: `notice-block ${node.attrs.style}` },
...(icon ? [icon] : []),
["div", { class: "content" }, 0],
];
},
};
}
commands({ type }: { type: NodeType }) {
return {
container_notice: (attrs: Record<string, Primitive>) =>
toggleWrap(type, attrs),
info: (): Command => (state, dispatch) =>
this.handleStyleChange(state, dispatch, NoticeTypes.Info),
warning: (): Command => (state, dispatch) =>
this.handleStyleChange(state, dispatch, NoticeTypes.Warning),
success: (): Command => (state, dispatch) =>
this.handleStyleChange(state, dispatch, NoticeTypes.Success),
tip: (): Command => (state, dispatch) =>
this.handleStyleChange(state, dispatch, NoticeTypes.Tip),
};
}
handleStyleChange = (
state: EditorState,
dispatch: ((tr: Transaction) => void) | undefined,
style: NoticeTypes
): boolean => {
const { tr, selection } = state;
const { $from } = selection;
const node = $from.node(-1);
if (node?.type.name === this.name) {
if (dispatch) {
const transaction = tr.setNodeMarkup($from.before(-1), undefined, {
...node.attrs,
style,
});
dispatch(transaction);
}
return true;
}
return false;
};
inputRules({ type }: { type: NodeType }) {
return [wrappingInputRule(/^:::$/, type)];
}
toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) {
state.write("\n:::" + (node.attrs.style || "info") + "\n");
state.renderContent(node);
state.ensureNewLine();
state.write(":::");
state.closeBlock(node);
}
parseMarkdown() {
return {
block: "container_notice",
getAttrs: (tok: Token) => ({ style: tok.info }),
};
}
}