mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
fca10221b9
* chore: promote no-explicit-any from warn to error and resolve violations Upgrades the oxlint rule severity and removes all 40 existing `no-explicit-any` warnings across the codebase. Most call sites gained proper types (SharedEditor refs, JSONNode/JSONMark for ProseMirror JSON walking, DocumentsStore, dd-trace `Span` parameter inference, prosemirror Fragment public API in place of internal `(fragment as any).content`). A few load-bearing `any` uses were preserved with scoped disable comments where changing the type would cascade widely (Sequelize JSONB columns on `Event`, the `withTracing` higher-order function generic, `Extension.options` consumed by many subclasses, dd-trace's `req` patching). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
110 lines
2.7 KiB
TypeScript
110 lines
2.7 KiB
TypeScript
import { action, observable } from "mobx";
|
|
import type { EditorState, Selection } from "prosemirror-state";
|
|
import { NodeSelection, Plugin, TextSelection } from "prosemirror-state";
|
|
import type { EditorView } from "prosemirror-view";
|
|
import type { WidgetProps } from "@shared/editor/lib/Extension";
|
|
import Extension from "@shared/editor/lib/Extension";
|
|
import { isInNotice } from "@shared/editor/queries/isInNotice";
|
|
import { isMarkActive } from "@shared/editor/queries/isMarkActive";
|
|
import { isNodeActive } from "@shared/editor/queries/isNodeActive";
|
|
import { SelectionToolbar } from "../components/SelectionToolbar";
|
|
|
|
export default class SelectionToolbarExtension extends Extension {
|
|
get name() {
|
|
return "selection-toolbar";
|
|
}
|
|
|
|
get allowInReadOnly() {
|
|
return true;
|
|
}
|
|
|
|
get plugins(): Plugin[] {
|
|
return [
|
|
new Plugin({
|
|
view: () => ({
|
|
update: this.handleUpdate,
|
|
}),
|
|
}),
|
|
];
|
|
}
|
|
|
|
@observable
|
|
state: Selection | boolean = false;
|
|
|
|
private handleUpdate = action((view: EditorView) => {
|
|
const { state } = view;
|
|
this.state = this.calculateState(state);
|
|
});
|
|
|
|
private calculateState(state: EditorState): Selection | boolean {
|
|
const { selection, doc, schema } = state;
|
|
|
|
if (isMarkActive(schema.marks.link)(state)) {
|
|
return selection;
|
|
}
|
|
|
|
if (
|
|
isNodeActive(schema.nodes.code_block)(state) ||
|
|
isNodeActive(schema.nodes.code_fence)(state)
|
|
) {
|
|
return selection;
|
|
}
|
|
|
|
if (isInNotice(state) && selection.from > 0) {
|
|
return selection;
|
|
}
|
|
|
|
if (!selection || selection.empty) {
|
|
return false;
|
|
}
|
|
|
|
if (
|
|
selection instanceof NodeSelection &&
|
|
selection.node.type.name === "hr"
|
|
) {
|
|
return selection;
|
|
}
|
|
|
|
if (
|
|
selection instanceof NodeSelection &&
|
|
["image", "attachment", "embed"].includes(selection.node.type.name)
|
|
) {
|
|
return selection;
|
|
}
|
|
|
|
if (selection instanceof NodeSelection) {
|
|
return false;
|
|
}
|
|
|
|
const selectionText = doc.cut(selection.from, selection.to).textContent;
|
|
if (selection instanceof TextSelection && !selectionText) {
|
|
return false;
|
|
}
|
|
|
|
const fragment = selection.content().content;
|
|
|
|
for (let i = 0; i < fragment.childCount; i++) {
|
|
if (fragment.child(i).content.size) {
|
|
return selection;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
widget = (props: WidgetProps) => {
|
|
const editorProps = this.editor.props;
|
|
|
|
return (
|
|
<SelectionToolbar
|
|
{...props}
|
|
isActive={!!this.state}
|
|
selection={this.state ? (this.state as Selection) : undefined}
|
|
canUpdate={editorProps.canUpdate}
|
|
canComment={editorProps.canComment}
|
|
isTemplate={editorProps.template === true}
|
|
/>
|
|
);
|
|
};
|
|
}
|