pages/index.ts (123 lines of code) (raw):

import { EditorState } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { Schema, DOMParser } from "prosemirror-model"; import { marks, schema } from "prosemirror-schema-basic"; import { addListNodes } from "prosemirror-schema-list"; import { history } from "prosemirror-history"; import { toggleMark } from "prosemirror-commands"; import { exampleSetup, buildMenuItems } from "prosemirror-example-setup"; import applyDevTools from "prosemirror-dev-tools"; import "prosemirror-view/style/prosemirror.css"; import "prosemirror-menu/style/menu.css"; import "prosemirror-example-setup/style/style.css"; import "prosemirror-example-setup/style/style.css"; import "../src/css/index.scss"; import createTyperighterPlugin from "../src/ts/createTyperighterPlugin"; import { createOverlayView } from "../src/ts/components/createOverlayView"; import { createSidebarView } from "../src/ts/components/createSidebarView"; import { createBoundCommands } from "../src/ts/commands"; import TyperighterTelemetryAdapter from "../src/ts/services/TyperighterTelemetryAdapter"; import { UserTelemetryEventSender } from "@guardian/user-telemetry-client"; import { MatchType } from "../src/ts/utils/decoration"; import { filterByMatchState } from "../src/ts/utils/plugin"; import { findMarkPositions } from "../src/ts/utils/prosemirror"; import TyperighterChunkedAdapter from "../src/ts/services/adapters/TyperighterChunkedAdapter"; import { keymap } from "prosemirror-keymap"; const noteMark = { parseDOM: [{ tag: "note" }], toDOM() { return ['note', 0] as [string, number]; } } const mySchema = new Schema({ nodes: addListNodes(schema.spec.nodes as any, "paragraph block*", "block"), marks: { ...marks, note: noteMark } }); const contentElement = document.querySelector("#content") || document.createElement("content"); const doc = DOMParser.fromSchema(mySchema).parse(contentElement); if (contentElement && contentElement.parentElement) { contentElement.parentElement.removeChild(contentElement); } const historyPlugin = history(); const editorElement = document.querySelector("#editor"); const sidebarNode = document.querySelector("#sidebar"); const overlayNode = document.createElement("div"); document.body.append(overlayNode); const isElementPartOfTyperighterUI = (element: HTMLElement) => overlayNode.contains(element); const telemetryService = new UserTelemetryEventSender("https://example.com"); const typerighterTelemetryAdapter = new TyperighterTelemetryAdapter( telemetryService, "prosemirror-typerighter", "DEV" ); const { plugin: validatorPlugin, store, matcherService } = createTyperighterPlugin({ isElementPartOfTyperighterUI, filterOptions: { filterMatches: filterByMatchState, initialFilterState: [] as MatchType[] }, getIgnoredRanges: (node, from, to) => findMarkPositions(node, from, to, mySchema.marks.note), onMatchDecorationClicked: match => typerighterTelemetryAdapter.matchDecorationClicked(match, document.URL), requestMatchesOnDocModified: true, adapter: new TyperighterChunkedAdapter( "https://checker.typerighter.code.dev-gutools.co.uk" ), telemetryAdapter: typerighterTelemetryAdapter, typerighterEnabled: true }); const toggleNoteMark = toggleMark(mySchema.marks.note); if (editorElement && sidebarNode) { const view = new EditorView(editorElement, { state: EditorState.create({ doc, plugins: [ ...exampleSetup({ schema: mySchema, history: false, menuContent: buildMenuItems(mySchema).fullMenu, }), keymap({ "F10": toggleNoteMark, }), historyPlugin, validatorPlugin ] }) }); // When the user scrolls to matches, place the match in the middle of the editor. const menuHeight = 47; const getScrollOffset = () => editorElement.getBoundingClientRect().height / 2 - menuHeight; const commands = createBoundCommands(view, typerighterTelemetryAdapter); createSidebarView({ store, matcherService, commands, sidebarNode, contactHref: "mailto:example@typerighter.co.uk", feedbackHref: "http://a-form-for-example.com", editorScrollElement: editorElement, getScrollOffset, telemetryAdapter: typerighterTelemetryAdapter, enableDevMode: true }); createOverlayView({ view, store, commands, overlayNode, onMarkCorrect: match => console.info("Match ignored!", match), telemetryAdapter: typerighterTelemetryAdapter, }); // Handy debugging tools (window as any).editor = view; // We need this due to a transitive dependency that expects a global `process` object. // See https://github.com/d4rkr00t/prosemirror-dev-tools/issues/115 (window as any).process = {}; applyDevTools(view); }