markupeditor 0.9.26 → 0.9.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  const sheet$4 = new CSSStyleSheet();sheet$4.replaceSync("/**\n The CSS here was adapted from the ProseMirror example.\n The adaptations mainly consisted of removing styling when it interfered with\n markup.css in some way. These changes were commented-out and moved to the end of\n this file. Top-level Prosemirror styling was brought to the top of the file.\n Styling from search.css in prosemirror-search were added and modified.\n */\n\n .ProseMirror {\n position: relative;\n padding: 4px 8px 4px 8px;\n line-height: 1.2;\n outline: none;\n word-wrap: break-word;\n white-space: pre-wrap;\n white-space: break-spaces;\n font-variant-ligatures: none;\n }\n \n .ProseMirror pre {\n white-space: pre-wrap;\n }\n \n .ProseMirror li {\n position: relative;\n }\n \n .ProseMirror-hideselection *::selection { background: transparent; }\n .ProseMirror-hideselection *::-moz-selection { background: transparent; }\n .ProseMirror-hideselection { caret-color: transparent; }\n \n /* See https://github.com/ProseMirror/prosemirror/issues/1421#issuecomment-1759320191 */\n .ProseMirror [draggable][contenteditable=false] { user-select: text }\n \n .ProseMirror-selectednode {\n outline: 2px solid #8cf;\n }\n \n /* Make sure li selections wrap around markers */\n \n li.ProseMirror-selectednode {\n outline: none;\n }\n \n li.ProseMirror-selectednode:after {\n content: \"\";\n position: absolute;\n left: -32px;\n right: -2px; top: -2px; bottom: -2px;\n border: 2px solid #8cf;\n pointer-events: none;\n }\n \n /* Protect against generic img rules */\n \n img.ProseMirror-separator {\n display: inline !important;\n border: none !important;\n margin: 0 !important;\n }\n .ProseMirror-textblock-dropdown {\n min-width: 3em;\n }\n \n .ProseMirror-icon svg {\n fill: currentColor;\n height: 1em;\n }\n \n .ProseMirror-icon span {\n vertical-align: text-top;\n }\n .ProseMirror-gapcursor {\n display: none;\n pointer-events: none;\n position: absolute;\n }\n \n .ProseMirror-gapcursor:after {\n content: \"\";\n display: block;\n position: absolute;\n top: -2px;\n width: 20px;\n border-top: 1px solid black;\n animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;\n }\n \n @keyframes ProseMirror-cursor-blink {\n to {\n visibility: hidden;\n }\n }\n \n .ProseMirror-focused .ProseMirror-gapcursor {\n display: block;\n }\n \n .ProseMirror-prompt {\n background: white;\n padding: 5px 10px 5px 15px;\n border: 1px solid silver;\n position: fixed;\n border-radius: 3px;\n z-index: 11;\n box-shadow: -.5px 2px 5px rgba(0, 0, 0, .2);\n }\n \n .ProseMirror-prompt h5 {\n margin: 0;\n font-weight: normal;\n font-size: 100%;\n color: #444;\n }\n \n .ProseMirror-prompt input[type=\"text\"],\n .ProseMirror-prompt textarea {\n background: #eee;\n border: none;\n outline: none;\n }\n \n .ProseMirror-prompt input[type=\"text\"] {\n padding: 0 4px;\n }\n \n .ProseMirror-prompt-close {\n position: absolute;\n left: 2px; top: 1px;\n color: #666;\n border: none; background: transparent; padding: 0;\n }\n \n .ProseMirror-prompt-close:after {\n content: \"✕\";\n font-size: 12px;\n }\n \n .ProseMirror-invalid {\n background: #ffc;\n border: 1px solid #cc7;\n border-radius: 4px;\n padding: 5px 10px;\n position: absolute;\n min-width: 10em;\n }\n \n .ProseMirror-prompt-buttons {\n margin-top: 5px;\n /* display: none; */\n }\n \n /*\n \n #editor, .editor {\n border-radius: 4px;\n border: 2px solid rgba(0, 0, 0, 0.2);\n }\n \n .ProseMirror-example-setup-style hr {\n padding: 2px 10px;\n border: none;\n margin: 1em 0;\n }\n \n .ProseMirror-example-setup-style hr:after {\n content: \"\";\n display: block;\n height: 1px;\n background-color: silver;\n line-height: 2px;\n }\n \n .ProseMirror ul, .ProseMirror ol {\n padding-left: 30px;\n }\n \n .ProseMirror blockquote {\n padding-left: 1em;\n border-left: 3px solid #eee;\n margin-left: 0; margin-right: 0;\n }\n \n .ProseMirror-example-setup-style img {\n cursor: default;\n }\n \n .ProseMirror p { margin-bottom: 1em }\n */\n ");
2
2
 
3
- const sheet$3 = new CSSStyleSheet();sheet$3.replaceSync("#editor, .editor {\n font-family: system-ui, sans-serif;\n background: white;\n color: black;\n background-clip: padding-box;\n margin: -8px; /* Set so that the .ProseMirror padding starts from edges */\n --padBottom: 0; /* MU.padBottom() sets this value in px based on fullHeight */\n padding-block: 0 var(--padBottom);\n overflow-x: clip;\n overflow-y: scroll;\n height: 100vh;\n padding-right: 12px;\n}\n@media (prefers-color-scheme: dark) {\n #editor, .editor {\n background: black;\n color: white;\n }\n}\n\nbody {\n font-family: system-ui, sans-serif;\n}\n\na {\n color: blue;\n text-decoration: none;\n}\n@media (prefers-color-scheme: dark) {\n a {\n color: #4183c4;\n }\n}\n\na:hover {\n text-decoration: underline;\n}\n\np, pre {\n font-size: 1.0rem;\n}\n\np, ul, ol, dl, table, pre {\n margin: 0 0 15px;\n}\n\nul, ol {\n padding-left: 30px;\n}\n\nh1 {\n font-size: 2.5rem;\n}\n\nh2 {\n font-size: 2.0rem;\n}\n\nh3 {\n font-size: 1.5rem;\n}\n\nh4 {\n font-size: 1.2rem;\n}\n\nh5 {\n font-size: 1.0rem;\n}\n\nh6 {\n font-size: .83rem;\n}\n\nh1, h2, h3, h4, h5, h6 {\n font-weight: bold;\n margin: 0 0 10px 0;\n}\n\nh1 + p, h2 + p, h3 + p {\n margin-top: 10px;\n}\n\ntable {\n table-layout: fixed;\n border-collapse: collapse;\n width: 100%;\n}\n\ntable th, table td {\n padding: 4px;\n}\n\ntable th {\n font-weight: normal; /* The default is bold, but we want to use paragraph styles and formatting */\n}\n\n/* Make the styling compact inside of a table */\ntable p, table h1, table h2, table h3, table h4, table h5, table h6 {\n margin: 0;\n}\n\n/* Table bordering options */\n.bordered-table-none {\n border: none;\n}\n\n.bordered-table-outer, .bordered-table-header, .bordered-table-cell {\n border: 1px solid #DDD;\n}\n\n.bordered-table-header th { /* border th not thead to refresh properly */\n border: 1px solid #DDD;\n}\n\n.bordered-table-cell th, .bordered-table-cell td {\n border: 1px solid #DDD;\n}\n\n/* Default table bordering is same as .bordered-table-cell but is only used when not specified */\ntable:not(.bordered-table-none, .bordered-table-outer, .bordered-table-header, .bordered-table-cell) {\n border: 1px solid #DDD;\n}\n\ntable:not(.bordered-table-none, .bordered-table-outer, .bordered-table-header, .bordered-table-cell) td {\n border: 1px solid #DDD;\n}\n\ntable:not(.bordered-table-none, .bordered-table-outer, .bordered-table-header, .bordered-table-cell) th {\n border: 1px solid #DDD;\n}\n\nli p {\n margin: 0px 0;\n}\n\nli > ul, li > ol {\n margin-bottom: 0;\n}\n\nli > h1, li > h2, li > h3, li > h4, li > h5, li > h6 {\n margin-bottom: 0;\n}\n\nblockquote {\n margin-right: 0px; /* Because nested blockquotes just keep getting narrower */\n}\n\ncode {\n overflow-x: scroll;\n display: block;\n background-color: #F8F8F8;\n border-radius: 3px;\n font-family: 'SF Mono', SFMono-Regular, ui-monospace, 'DejaVu Sans Mono', Menlo, Consolas, monospace;\n white-space: pre;\n}\n@media (prefers-color-scheme: dark) {\n code {\n background-color: #808080;\n }\n}\n\np code, h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {\n display: inline;\n}\n\n.resize-container {\n position: relative;\n display: inline-block;\n margin: 0;\n}\n\nimg {\n max-width: 100%;\n height: auto;\n}\n\n/* Provide a slightly darkened or lightened overlay while search is active */\n.searching {\n background-color: rgba(0, 0, 0, 0.10);\n}\n@media (prefers-color-scheme: dark) {\n .searching {\n background: rgba(255, 255, 255, 0.10);\n }\n}\n\n.resize-container img {\n display: block;\n outline: 1px black dashed;\n outline-offset: 4px;\n outline-width: 1px;\n}\n@media (prefers-color-scheme: dark) {\n .resize-container img {\n outline: 1px white dashed;\n }\n}\n\n.resize-handle-nw,\n.resize-handle-ne,\n.resize-handle-sw,\n.resize-handle-se {\n position: absolute;\n display: block;\n width: 6px;\n height: 6px;\n outline: 1px black solid;\n background: white;\n z-index: 999;\n}\n@media (prefers-color-scheme: dark) {\n .resize-handle-nw,\n .resize-handle-ne,\n .resize-handle-sw,\n .resize-handle-se {\n outline: 1px white solid;\n background: black;\n }\n}\n\n/* A transparent child for each resize-handle that expands the clickable area */\n.resize-handle-nw:after,\n.resize-handle-ne:after,\n.resize-handle-sw:after,\n.resize-handle-se:after {\n content: \"\";\n position: absolute;\n left: -5px;\n top: -5px;\n width: 16px;\n height: 16px;\n}\n\n/* The *-resize cursors do not work, at least in MacCatalyst.\n * Still specifying them below. They default to a pointer instead\n */\n.resize-handle-nw {\n cursor: nw-resize;\n top: -8px;\n left: -7px;\n}\n\n.resize-handle-ne {\n cursor: ne-resize;\n top: -8px;\n right: -7px;\n}\n\n.resize-handle-sw {\n cursor: sw-resize;\n bottom: -8px;\n left: -7px;\n}\n\n.resize-handle-se {\n cursor: se-resize;\n bottom: -8px;\n right: -7px;\n}\n\n.placeholder[placeholder]:before {\n content: attr(placeholder);\n position: absolute;\n color: #ccc;\n}\n\n/* Scoped to :has() rather than styling pre unconditionally, since a code_block's\n <pre> should only become a positioning context when it's actually carrying the\n language overlay (highlightCode on, block selected) — not for every code block. */\npre:has(.Markup-code-language-overlay) {\n position: relative;\n}\n\n.Markup-code-language-overlay {\n position: absolute;\n bottom: 100%;\n right: 0;\n font-size: 0.75rem;\n padding: 2px 6px;\n border: none;\n border-radius: 4px 4px 0 0;\n cursor: pointer;\n opacity: 0.6;\n color: white;\n background: var(--Markup-accent-color, blue);\n}\n\n.Markup-code-language-overlay:hover {\n opacity: 0.9;\n}\n\n/* Applied (setup/index.js, hasRoomAboveOverlay) when the code_block is too close to\n the top of the view for the label to fit above it — attach to the bottom instead. */\n.Markup-code-language-overlay-below {\n bottom: auto;\n top: 100%;\n border-radius: 0 0 4px 4px;\n}\n\n@media (prefers-color-scheme: dark) {\n .Markup-code-language-overlay {\n background: var(--Markup-accent-color, lightblue);\n color: black;\n }\n}\n\n/* Classes set by prosemirror-search module, modified for MarkupEditor */\n/* Note the dark and light mode are the same */\n\n.ProseMirror-search-match {\n background-color: yellow;\n color: black;\n}\n\n.ProseMirror-active-search-match {\n background-color: orange;\n color: black;\n outline: 1px orangered solid;\n z-index: 2;\n}");
3
+ const sheet$3 = new CSSStyleSheet();sheet$3.replaceSync("#editor, .editor {\n font-family: system-ui, sans-serif;\n background: white;\n color: black;\n background-clip: padding-box;\n margin: -8px; /* Set so that the .ProseMirror padding starts from edges */\n --padBottom: 0; /* MU.padBottom() sets this value in px based on fullHeight */\n padding-block: 0 var(--padBottom);\n overflow-x: clip;\n overflow-y: scroll;\n height: 100vh;\n padding-right: 12px;\n}\n@media (prefers-color-scheme: dark) {\n #editor, .editor {\n background: black;\n color: white;\n }\n}\n\nbody {\n font-family: system-ui, sans-serif;\n}\n\na {\n color: blue;\n text-decoration: none;\n}\n@media (prefers-color-scheme: dark) {\n a {\n color: #4183c4;\n }\n}\n\na:hover {\n text-decoration: underline;\n}\n\np, pre {\n font-size: 1.0rem;\n}\n\np, ul, ol, dl, table, pre {\n margin: 0 0 15px;\n}\n\nul, ol {\n padding-left: 30px;\n}\n\nh1 {\n font-size: 2.5rem;\n}\n\nh2 {\n font-size: 2.0rem;\n}\n\nh3 {\n font-size: 1.5rem;\n}\n\nh4 {\n font-size: 1.2rem;\n}\n\nh5 {\n font-size: 1.0rem;\n}\n\nh6 {\n font-size: .83rem;\n}\n\nh1, h2, h3, h4, h5, h6 {\n font-weight: bold;\n margin: 0 0 10px 0;\n}\n\nh1 + p, h2 + p, h3 + p {\n margin-top: 10px;\n}\n\ntable {\n table-layout: fixed;\n border-collapse: collapse;\n width: 100%;\n}\n\ntable th, table td {\n padding: 4px;\n}\n\ntable th {\n font-weight: normal; /* The default is bold, but we want to use paragraph styles and formatting */\n}\n\n/* Make the styling compact inside of a table */\ntable p, table h1, table h2, table h3, table h4, table h5, table h6 {\n margin: 0;\n}\n\n/* Table bordering options */\n.bordered-table-none {\n border: none;\n}\n\n.bordered-table-outer, .bordered-table-header, .bordered-table-cell {\n border: 1px solid #DDD;\n}\n\n.bordered-table-header th { /* border th not thead to refresh properly */\n border: 1px solid #DDD;\n}\n\n.bordered-table-cell th, .bordered-table-cell td {\n border: 1px solid #DDD;\n}\n\n/* Default table bordering is same as .bordered-table-cell but is only used when not specified */\ntable:not(.bordered-table-none, .bordered-table-outer, .bordered-table-header, .bordered-table-cell) {\n border: 1px solid #DDD;\n}\n\ntable:not(.bordered-table-none, .bordered-table-outer, .bordered-table-header, .bordered-table-cell) td {\n border: 1px solid #DDD;\n}\n\ntable:not(.bordered-table-none, .bordered-table-outer, .bordered-table-header, .bordered-table-cell) th {\n border: 1px solid #DDD;\n}\n\nli p {\n margin: 0px 0;\n}\n\nli > ul, li > ol {\n margin-bottom: 0;\n}\n\nli > h1, li > h2, li > h3, li > h4, li > h5, li > h6 {\n margin-bottom: 0;\n}\n\nblockquote {\n margin-right: 0px; /* Because nested blockquotes just keep getting narrower */\n}\n\ncode {\n overflow-x: scroll;\n display: block;\n background-color: #F8F8F8;\n border-radius: 3px;\n font-family: 'SF Mono', SFMono-Regular, ui-monospace, 'DejaVu Sans Mono', Menlo, Consolas, monospace;\n white-space: pre;\n}\n@media (prefers-color-scheme: dark) {\n code {\n background-color: #808080;\n }\n}\n\np code, h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {\n display: inline;\n}\n\n.resize-container {\n position: relative;\n display: inline-block;\n margin: 0;\n}\n\nimg {\n max-width: 100%;\n height: auto;\n}\n\n/* Provide a slightly darkened or lightened overlay while search is active */\n.searching {\n background-color: rgba(0, 0, 0, 0.10);\n}\n@media (prefers-color-scheme: dark) {\n .searching {\n background: rgba(255, 255, 255, 0.10);\n }\n}\n\n.resize-container img {\n display: block;\n outline: 1px black dashed;\n outline-offset: 4px;\n outline-width: 1px;\n}\n@media (prefers-color-scheme: dark) {\n .resize-container img {\n outline: 1px white dashed;\n }\n}\n\n.resize-handle-nw,\n.resize-handle-ne,\n.resize-handle-sw,\n.resize-handle-se {\n position: absolute;\n display: block;\n width: 6px;\n height: 6px;\n outline: 1px black solid;\n background: white;\n z-index: 999;\n}\n@media (prefers-color-scheme: dark) {\n .resize-handle-nw,\n .resize-handle-ne,\n .resize-handle-sw,\n .resize-handle-se {\n outline: 1px white solid;\n background: black;\n }\n}\n\n/* A transparent child for each resize-handle that expands the clickable area */\n.resize-handle-nw:after,\n.resize-handle-ne:after,\n.resize-handle-sw:after,\n.resize-handle-se:after {\n content: \"\";\n position: absolute;\n left: -5px;\n top: -5px;\n width: 16px;\n height: 16px;\n}\n\n/* The *-resize cursors do not work, at least in MacCatalyst.\n * Still specifying them below. They default to a pointer instead\n */\n.resize-handle-nw {\n cursor: nw-resize;\n top: -8px;\n left: -7px;\n}\n\n.resize-handle-ne {\n cursor: ne-resize;\n top: -8px;\n right: -7px;\n}\n\n.resize-handle-sw {\n cursor: sw-resize;\n bottom: -8px;\n left: -7px;\n}\n\n.resize-handle-se {\n cursor: se-resize;\n bottom: -8px;\n right: -7px;\n}\n\n.placeholder[placeholder]:before {\n content: attr(placeholder);\n position: absolute;\n color: #ccc;\n}\n\n/* Scoped to :has() rather than styling pre unconditionally, since a code_block's\n <pre> should only become a positioning context when it's actually carrying the\n language tab (highlightCode on, block selected) — not for every code block. */\npre:has(.Markup-code-language-tab) {\n position: relative;\n}\n\n.Markup-code-language-tab {\n position: absolute;\n bottom: 100%;\n right: 0;\n font-size: 0.75rem;\n padding: 2px 6px;\n border: none;\n border-radius: 4px 4px 0 0;\n cursor: pointer;\n opacity: 0.6;\n color: white;\n background: var(--Markup-accent-color, blue);\n}\n\n.Markup-code-language-tab:hover {\n opacity: 0.9;\n}\n\n/* Applied (setup/index.js, CodeView.hasRoomAbove) when the code_block is too close to\n the top of the view for the tab to fit above it — attach to the bottom instead. */\n.Markup-code-language-tab-below {\n bottom: auto;\n top: 100%;\n border-radius: 0 0 4px 4px;\n}\n\n/* Matches .Markup-menuitem-active's selected-button color scheme. */\n@media (prefers-color-scheme: dark) {\n .Markup-code-language-tab {\n background: var(--Markup-accent-color, lightblue);\n color: black;\n }\n}\n\n/* Classes set by prosemirror-search module, modified for MarkupEditor */\n/* Note the dark and light mode are the same */\n\n.ProseMirror-search-match {\n background-color: yellow;\n color: black;\n}\n\n.ProseMirror-active-search-match {\n background-color: orange;\n color: black;\n outline: 1px orangered solid;\n z-index: 2;\n}");
4
4
 
5
- const sheet$2 = new CSSStyleSheet();sheet$2.replaceSync("/* Spacing at the top */\n/* Note that toolbar-overlay below is also set to these values */\n\n.Markup-searchbar-showing {\n height: 38px;\n top: 76px;\n}\n\n/* The wrapper lets us automatically embed a toolbar and scroll under it */\n.Markup-toolbar-wrapper {\n margin-right: -12px; /* Prevent side scroll even when menu fits */\n height: inherit;\n overflow-y: scroll;\n position: relative; \n}\n\n/* We add Markup-prompt-showing to the wrapper to prevent scroll, and remove it when done */\n.Markup-prompt-showing {\n overflow-y: hidden;\n}\n\n/* Z-indexes in one place */\n\n.body-content { \n z-index: 1; \n}\n\n.Markup-prompt-overlay { \n z-index: 2; \n}\n\n.Markup-prompt, .Markup-selection {\n z-index: 3;\n}\n\n.Markup-searchbar {\n z-index: 4;\n}\n\n.Markup-toolbar, .Markup-toolbar-more {\n z-index: 5;\n}\n\n.Markup-toolbar-overlay { \n z-index: 6; \n}\n\n.Markup-menu-dropdown-menu {\n z-index: 7;\n}\n\n/* Toolbar, menu item, and dropdown styling */\n\n.Markup-toolbar {\n display: inline-flex;\n position: sticky;\n overflow: visible;\n width: 100%;\n font-size: var(--Markup-button-font-size, 24px);\n vertical-align: middle;\n border-top-left-radius: inherit;\n border-top-right-radius: inherit;\n top: 0;\n max-height: calc(var(--Markup-button-size, 28px) + 10px);\n padding: 2px 8px;\n color: var(--Markup-accent-color, blue);\n background: var(--Markup-toolbar-bg, rgba(250, 249, 246, 0.95));\n border-bottom: 1px solid var(--Markup-border-color, lightgray);\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-toolbar {\n color: var(--Markup-accent-color, lightblue);\n background: var(--Markup-toolbar-bg, rgba(40, 40, 43, 0.9));\n border-bottom: 1px solid var(--Markup-border-color, gray);\n }\n}\n\n.Markup-toolbar-hidden {\n display: none;\n}\n\n.Markup-menuitem {\n display: inline-block;\n flex-shrink: 0; /* Otherwise, the drop-downs with icons shrink */\n overflow-y: visible;\n vertical-align: middle;\n min-width: var(--Markup-button-size, 28px);\n height: var(--Markup-button-size, 28px);\n cursor: pointer;\n margin-right: 4px;\n border-radius: 4px;\n color: var(--Markup-accent-color, blue);\n border: 1px solid var(--Markup-accent-color, blue);\n background: var(--Markup-button-bg, white);\n fill: var(--Markup-accent-color, blue);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menuitem {\n color: var(--Markup-accent-color, lightblue);\n border: 1px solid var(--Markup-accent-color, lightblue);\n /* border: 1px solid #4183c4; */\n background: var(--Markup-button-bg, black);\n fill: var(--Markup-accent-color, lightblue);\n }\n}\n\n.Markup-menuitem-active {\n fill: white;\n color: white;\n background: var(--Markup-accent-color, blue);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menuitem-active {\n fill: black;\n color: black;\n background: var(--Markup-accent-color, lightblue);\n }\n}\n\n.Markup-menuitem-disabled {\n cursor: default;\n opacity: .3;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menuitem-disabled {\n opacity: .6;\n }\n}\n\n.Markup-icon {\n display: inline-block;\n width: var(--Markup-button-size, 28px);\n height: var(--Markup-button-size, 28px);\n border: 0;\n}\n\n.Markup-icon span {\n vertical-align: middle;\n}\n\n.Markup-icon svg {\n width: var(--Markup-button-font-size, 24px);\n height: var(--Markup-button-font-size, 24px);\n padding: 2px;\n border: 0;\n fill: inherit;\n}\n\n.Markup-menuseparator {\n display: inline-block;\n vertical-align: middle;\n height: var(--Markup-button-size, 28px);\n border-right: 1px solid var(--Markup-border-color, lightgray);\n margin-right: 4px;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menuseparator {\n border-right: 1px solid var(--Markup-border-color, gray);\n }\n}\n\n.Markup-menu-disabled .Markup-icon {\n cursor: default;\n}\n\n.Markup-menu-dropdown, .Markup-menu-dropdown-menu {\n background: inherit;\n text-align: left;\n white-space: nowrap;\n}\n\n.Markup-menu-dropdown span {\n vertical-align: middle;\n}\n\n.Markup-menu-dropdown-icon span {\n vertical-align: middle;\n}\n\n.Markup-menu-dropdown-wrap {\n display: flex;\n justify-content: space-between;\n padding: 0 4px;\n height: inherit;\n border-radius: 4px;\n background: inherit;\n}\n\n.Markup-menu-dropdown-icon-wrap {\n display: flex;\n justify-content: space-between;\n padding-right: 4px;\n width: 1.8em;\n height: inherit;\n border-radius: 4px;\n background: inherit;\n}\n\n.Markup-menu-dropdown-icon-wrap-noindicator {\n display: inline-flex;\n padding-right: 4px;\n width: 1em;\n height: inherit;\n border-radius: 4px;\n background: inherit;\n}\n\n/* For the paragraph style dropdown with a label, make the font smaller */\n.Markup-menu-dropdown {\n display: flex;\n justify-content: space-between;\n align-items: center;\n box-sizing: border-box;\n font-size: 0.8em;\n width: inherit;\n height: inherit;\n background: inherit;\n position: absolute;\n}\n\n/* But for the indicator, bump size up to match the icon-style dropdown */\n.Markup-menu-dropdown .Markup-menu-dropdown-indicator {\n font-size: 1.25em;\n}\n\n.Markup-menu-dropdown-icon {\n display: inline-flex;\n justify-content: space-between;\n font-size: inherit;\n width: inherit;\n cursor: pointer;\n position: absolute;\n}\n\n.Markup-menu-dropdown-indicator {\n opacity: .6;\n}\n\n.Markup-tooltip .Markup-menu {\n width: -webkit-fit-content;\n width: fit-content;\n white-space: pre;\n}\n\n.Markup-menu-dropdown-menu, .Markup-menu-submenu {\n position: absolute;\n background: inherit;\n padding: 2px;\n background: inherit;\n border: 1px solid var(--Markup-border-color, gray);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menu-dropdown-menu, .Markup-menu-submenu {\n border: 1px solid var(--Markup-border-color, lightgray);\n }\n}\n\n.Markup-menu-dropdown-menu {\n margin-top: calc(1em + 6px);\n min-width: 4em;\n}\n\n.Markup-menu-dropdown-item {\n cursor: pointer;\n padding: 2px 8px 2px 4px;\n}\n\n/* Used to overlfow and ellipsis-truncate text in Dropdown menu items */\n.Markup-menuitem-clipped {\n white-space: nowrap; /* Required for text-overflow */\n overflow-x: hidden; /* Required for text-overflow */\n max-width: 30em;\n text-overflow: ellipsis;\n}\n\n.Markup-menu-dropdown-item:hover, .Markup-menu-submenu-wrap:hover {\n background: var(--Markup-hover-bg, #f2f2f2);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menu-dropdown-item:hover, .Markup-menu-submenu-wrap:hover {\n background: var(--Markup-hover-bg, rgb(80, 80, 80));\n }\n}\n\n.Markup-menu-dropdown-item p, \n.Markup-menu-dropdown-item h1, \n.Markup-menu-dropdown-item h2, \n.Markup-menu-dropdown-item h3, \n.Markup-menu-dropdown-item h4, \n.Markup-menu-dropdown-item h5, \n.Markup-menu-dropdown-item h6, \n.Markup-menu-dropdown-item pre {\n padding: 0;\n margin: 0;\n min-height: 24px;\n}\n\n.Markup-stylelabel {\n display: flex;\n justify-content: space-between;\n align-items: center;\n white-space: pre;\n width: 100%;\n}\n\n.Markup-stylelabel-keymap {\n display: flex;\n justify-content: flex-end;\n font-size: 1.0rem;\n font-weight: normal;\n color: gray;\n}\n\n.Markup-menu-submenu-wrap:hover .Markup-menu-submenu, .Markup-menu-submenu-wrap-active .Markup-menu-submenu {\n display: block;\n}\n\n.Markup-menu-submenu-wrap {\n position: relative;\n margin-right: -4px;\n background: inherit;\n}\n\n.Markup-menu-submenu {\n display: none;\n min-width: 4em;\n left: 100%;\n top: -5px;\n max-height: 80vh;\n overflow-y: auto;\n /* background cannot be inherited because of hover highlighting in dropdown */\n background: var(--Markup-button-bg, white);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menu-submenu {\n /* background cannot be inherited because of hover highlighting in dropdown */\n background: var(--Markup-button-bg, black);\n }\n}\n\n.Markup-menu-submenu-label:after {\n content: \"\";\n border-top: 4px solid transparent;\n border-bottom: 4px solid transparent;\n border-left: 4px solid currentColor;\n opacity: .6;\n position: absolute;\n right: 4px;\n top: calc(50% - 4px);\n}\n\n.Markup-selection {\n position: absolute;\n border: 1px solid var(--Markup-accent-color, blue);\n background: transparent;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-selection {\n border: 1px solid var(--Markup-accent-color, lightblue);\n }\n}\n\n/* Toolbar-more styling */\n\n.Markup-toolbar-more {\n display: inline-flex;\n position: sticky;\n overflow: visible;\n width: 100%;\n font-size: var(--Markup-button-font-size, 24px);\n vertical-align: middle;\n border-top-left-radius: inherit;\n border-top-right-radius: inherit;\n top: calc(var(--Markup-button-size, 28px) + 7px);\n max-height: calc(var(--Markup-button-size, 28px) + 10px);\n padding: 2px 8px;\n color: var(--Markup-accent-color, blue);\n background: var(--Markup-toolbar-bg, rgba(250, 249, 246, 0.95));\n border-bottom: 1px solid var(--Markup-border-color, lightgray);\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-toolbar-more {\n color: var(--Markup-accent-color, lightblue);\n background: var(--Markup-toolbar-bg, rgba(40, 40, 43, 0.9));\n border-bottom: 1px solid var(--Markup-border-color, gray);\n }\n}\n\n/* Searchbar styling */\n\n.Markup-searchbar {\n display: inline-block;\n font-size: var(--Markup-button-font-size, 24px);\n vertical-align: middle;\n position: sticky;\n top: calc(var(--Markup-button-size, 28px) + 7px);\n width: 100%;\n line-height: 30px;\n height: 37px;\n color: var(--Markup-accent-color, blue);\n background: var(--Markup-toolbar-bg, rgba(250, 249, 246, 0.95));\n border-bottom: 1px solid var(--Markup-border-color, lightgray);\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n overflow: hidden;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-searchbar {\n color: var(--Markup-accent-color, lightblue);\n background: var(--Markup-toolbar-bg, rgba(40, 40, 43, 0.9));\n border-bottom: 1px solid var(--Markup-border-color, gray);\n }\n}\n\n.Markup-searchbar input {\n appearance: none; /* Otherwise, in Safari/WebKit, the height is small and fixed */\n position: relative;\n font-size: 18px;\n line-height: 30px;\n height: 30px;\n width: calc(100% - 128px);\n margin: 0 8px;\n padding: 0 4px;\n outline: none;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n border: 1px solid lightgray;\n border-radius: 6px;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-searchbar input {\n color: rgb(250, 249, 246);\n background: rgb(40, 40, 43);\n border: 1px solid gray;\n }\n}\n\n.Markup-searchbar .Markup-searchbar-status {\n position: absolute;\n display: flex;\n justify-content: flex-end;\n align-items: center;\n white-space: nowrap;\n font-size: 12px;\n overflow-x: visible;\n height: 28px;\n top: 5px;\n left: calc(100% - 13em);\n width: 1px;\n color: gray;\n user-select: none;\n}\n\n/* Dialog/prompt styling for insert/edit actions */\n\n.Markup-prompt-overlay {\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n\tbackground-color: rgba(0, 0, 0, 0.2);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-prompt-overlay {\n background-color: rgba(255, 255, 255, 0.2);\n }\n}\n\n.Markup-toolbar-overlay {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\tbackground-color: rgba(0, 0, 0, 0.1);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-toolbar-overlay {\n background-color: rgba(255, 255, 255, 0.1);\n }\n}\n\n.Markup-toolbar-overlay.Markup-searchbar-hidden {\n height: 38px;\n}\n\n.Markup-toolbar-overlay.Markup-searchbar-showing {\n height: 76px;\n}\n\n.Markup-prompt {\n margin: 0;\n font-size: 18px;\n background: white;\n padding: 8px;\n border: 1px solid silver;\n position: absolute;\n border-radius: 3px;\n box-shadow: -.5px 2px 5px rgba(0, 0, 0, 0.2);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-prompt {\n background: black;\n box-shadow: -.5px 2px 5px rgba(255, 255, 255, 0.8);\n }\n}\n\n.Markup-prompt-link {\n height: 96px;\n width: 300px;\n}\n\n.Markup-prompt-image {\n height: 130px;\n width: 300px;\n}\n\n.Markup-prompt-language {\n height: 96px;\n width: 300px;\n}\n\n.Markup-prompt img {\n display: inline-block;\n vertical-align: middle;\n min-width: 28px;\n height: 28px;\n margin-right: 4px;\n}\n\n.Markup-prompt p {\n margin: 0 0 4px 0;\n font-weight: normal;\n color: black;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-prompt p {\n color: white;\n }\n}\n\n.Markup-prompt input[type=\"text\"] {\n font-size: 100%;\n line-height: 28px;\n margin-top: 2px;\n padding: 1px 4px;\n width: 100%;\n appearance: none;\n background: #eee;\n outline: none;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n border: 1px solid lightgray;\n border-radius: 6px;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-prompt input[type=\"text\"] {\n color: rgb(250, 249, 246);\n background: rgb(40, 40, 43);\n border: 1px solid gray;\n }\n}\n\n.Markup-prompt input.invalid {\n color: red;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-prompt input.invalid {\n color: pink;\n }\n}\n\n.Markup-prompt-buttons {\n display: flex;\n vertical-align: middle;\n justify-content: space-between;\n justify-items: center;\n line-height: 28px;\n height: 28px;\n margin-top: 8px;\n margin-bottom: 8px;\n}\n\n.Markup-prompt-buttongroup {\n flex-shrink: 0;\n}\n\n.Markup-prompt-buttons img {\n aspect-ratio: auto;\n height: auto;\n object-fit: contain;\n}\n\n.Markup-prompt-buttons .Markup-menuitem {\n display: inline-flex;\n padding-right: 4px;\n padding-left: 4px;\n}\n\n/* The buttongroup contains OK and Cancel menuitems on the right */\n.Markup-prompt-buttongroup .Markup-menuitem {\n margin-right: 0;\n margin-left: 4px;\n}\n\n/* .Markup-menu-tablesizer is a specialized submenu */\n.Markup-menu-tablesizer {\n display: none;\n grid-template-columns: repeat(4, 1fr);\n grid-auto-rows: 18px;\n row-gap: 1px;\n column-gap: 1px;\n padding: 0;\n left: 100%;\n top: -5px;\n /* background cannot be inherited because of hover highlighting in dropdown */\n background: var(--Markup-button-bg, white);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menu-tablesizer {\n /* background cannot be inherited because of hover highlighting in dropdown */\n background: var(--Markup-button-bg, black);\n }\n}\n\n.Markup-menu-submenu-wrap:hover .Markup-menu-tablesizer, .Markup-menu-submenu-wrap-active .Markup-menu-tablesizer {\n display: grid;\n}\n\n.Markup-menu-dropdown-menu, .Markup-menu-tablesizer {\n position: absolute;\n background: inherit;\n padding: 2px;\n background: inherit;\n border: 1px solid var(--Markup-border-color, gray);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menu-dropdown-menu, .Markup-menu-submenu {\n border: 1px solid var(--Markup-border-color, lightgray);\n }\n}\n\n.Markup-menu-tablesizer .Markup-menu-dropdown-item {\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n border: 1px solid var(--Markup-accent-color, blue);\n width: 18px;\n height: 18px;\n padding: 0;\n font-size: 13px;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menu-tablesizer .Markup-menu-dropdown-item {\n border: 1px solid var(--Markup-accent-color, lightblue);\n }\n}\n\n.Markup-menu-tablesizer .Markup-menuitem-active {\n height: 13px;\n padding-top: 3px;\n fill: black;\n color: black;\n background: var(--Markup-accent-color, blue);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menu-tablesizer .Markup-menuitem-active {\n fill: black;\n color: black;\n background: var(--Markup-accent-color, lightblue);\n }\n}");
5
+ const sheet$2 = new CSSStyleSheet();sheet$2.replaceSync("/* Spacing at the top */\n/* Note that toolbar-overlay below is also set to these values */\n\n.Markup-searchbar-showing {\n height: 38px;\n top: 76px;\n}\n\n/* The wrapper lets us automatically embed a toolbar and scroll under it */\n.Markup-toolbar-wrapper {\n margin-right: -12px; /* Prevent side scroll even when menu fits */\n height: inherit;\n overflow-y: scroll;\n position: relative; \n}\n\n/* We add Markup-prompt-showing to the wrapper to prevent scroll, and remove it when done */\n.Markup-prompt-showing {\n overflow-y: hidden;\n}\n\n/* Z-indexes in one place */\n\n.body-content { \n z-index: 1; \n}\n\n.Markup-prompt-overlay { \n z-index: 2; \n}\n\n.Markup-prompt, .Markup-selection {\n z-index: 3;\n}\n\n.Markup-searchbar {\n z-index: 4;\n}\n\n.Markup-toolbar, .Markup-toolbar-more {\n z-index: 5;\n}\n\n.Markup-toolbar-overlay { \n z-index: 6; \n}\n\n.Markup-menu-dropdown-menu {\n z-index: 7;\n}\n\n/* Toolbar, menu item, and dropdown styling */\n\n.Markup-toolbar {\n display: inline-flex;\n position: sticky;\n overflow: visible;\n width: 100%;\n font-size: var(--Markup-button-font-size, 24px);\n vertical-align: middle;\n border-top-left-radius: inherit;\n border-top-right-radius: inherit;\n top: 0;\n max-height: calc(var(--Markup-button-size, 28px) + 10px);\n padding: 2px 8px;\n color: var(--Markup-accent-color, blue);\n background: var(--Markup-toolbar-bg, rgba(250, 249, 246, 0.95));\n border-bottom: 1px solid var(--Markup-border-color, lightgray);\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-toolbar {\n color: var(--Markup-accent-color, lightblue);\n background: var(--Markup-toolbar-bg, rgba(40, 40, 43, 0.9));\n border-bottom: 1px solid var(--Markup-border-color, gray);\n }\n}\n\n.Markup-toolbar-hidden {\n display: none;\n}\n\n.Markup-menuitem {\n display: inline-block;\n flex-shrink: 0; /* Otherwise, the drop-downs with icons shrink */\n overflow-y: visible;\n vertical-align: middle;\n min-width: var(--Markup-button-size, 28px);\n height: var(--Markup-button-size, 28px);\n cursor: pointer;\n margin-right: 4px;\n border-radius: 4px;\n color: var(--Markup-accent-color, blue);\n border: 1px solid var(--Markup-accent-color, blue);\n background: var(--Markup-button-bg, white);\n fill: var(--Markup-accent-color, blue);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menuitem {\n color: var(--Markup-accent-color, lightblue);\n border: 1px solid var(--Markup-accent-color, lightblue);\n /* border: 1px solid #4183c4; */\n background: var(--Markup-button-bg, black);\n fill: var(--Markup-accent-color, lightblue);\n }\n}\n\n.Markup-menuitem-active {\n fill: white;\n color: white;\n background: var(--Markup-accent-color, blue);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menuitem-active {\n fill: black;\n color: black;\n background: var(--Markup-accent-color, lightblue);\n }\n}\n\n.Markup-menuitem-disabled {\n cursor: default;\n opacity: .3;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menuitem-disabled {\n opacity: .6;\n }\n}\n\n.Markup-icon {\n display: inline-block;\n width: var(--Markup-button-size, 28px);\n height: var(--Markup-button-size, 28px);\n border: 0;\n}\n\n.Markup-icon span {\n vertical-align: middle;\n}\n\n.Markup-icon svg {\n width: var(--Markup-button-font-size, 24px);\n height: var(--Markup-button-font-size, 24px);\n padding: 2px;\n border: 0;\n fill: inherit;\n}\n\n.Markup-menuseparator {\n display: inline-block;\n vertical-align: middle;\n height: var(--Markup-button-size, 28px);\n border-right: 1px solid var(--Markup-border-color, lightgray);\n margin-right: 4px;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menuseparator {\n border-right: 1px solid var(--Markup-border-color, gray);\n }\n}\n\n.Markup-menu-disabled .Markup-icon {\n cursor: default;\n}\n\n.Markup-menu-dropdown, .Markup-menu-dropdown-menu {\n background: inherit;\n text-align: left;\n white-space: nowrap;\n}\n\n.Markup-menu-dropdown span {\n vertical-align: middle;\n}\n\n.Markup-menu-dropdown-icon span {\n vertical-align: middle;\n}\n\n.Markup-menu-dropdown-wrap {\n display: flex;\n justify-content: space-between;\n padding: 0 4px;\n height: inherit;\n border-radius: 4px;\n background: inherit;\n}\n\n.Markup-menu-dropdown-icon-wrap {\n display: flex;\n justify-content: space-between;\n padding-right: 4px;\n width: 1.8em;\n height: inherit;\n border-radius: 4px;\n background: inherit;\n}\n\n.Markup-menu-dropdown-icon-wrap-noindicator {\n display: inline-flex;\n padding-right: 4px;\n width: 1em;\n height: inherit;\n border-radius: 4px;\n background: inherit;\n}\n\n/* For the paragraph style dropdown with a label, make the font smaller */\n.Markup-menu-dropdown {\n display: flex;\n justify-content: space-between;\n align-items: center;\n box-sizing: border-box;\n font-size: 0.8em;\n width: inherit;\n height: inherit;\n background: inherit;\n position: absolute;\n}\n\n/* But for the indicator, bump size up to match the icon-style dropdown */\n.Markup-menu-dropdown .Markup-menu-dropdown-indicator {\n font-size: 1.25em;\n}\n\n.Markup-menu-dropdown-icon {\n display: inline-flex;\n justify-content: space-between;\n font-size: inherit;\n width: inherit;\n cursor: pointer;\n position: absolute;\n}\n\n.Markup-menu-dropdown-indicator {\n opacity: .6;\n}\n\n.Markup-tooltip .Markup-menu {\n width: -webkit-fit-content;\n width: fit-content;\n white-space: pre;\n}\n\n.Markup-menu-dropdown-menu, .Markup-menu-submenu {\n position: absolute;\n background: inherit;\n padding: 2px;\n background: inherit;\n border: 1px solid var(--Markup-border-color, gray);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menu-dropdown-menu, .Markup-menu-submenu {\n border: 1px solid var(--Markup-border-color, lightgray);\n }\n}\n\n.Markup-menu-dropdown-menu {\n margin-top: calc(1em + 6px);\n min-width: 4em;\n}\n\n.Markup-menu-dropdown-item {\n cursor: pointer;\n padding: 2px 8px 2px 4px;\n}\n\n.Markup-menu-dropdown-separator {\n border-top: 1px solid var(--Markup-border-color, lightgray);\n margin: 4px 0;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menu-dropdown-separator {\n border-top: 1px solid var(--Markup-border-color, gray);\n }\n}\n\n/* Used to overlfow and ellipsis-truncate text in Dropdown menu items */\n.Markup-menuitem-clipped {\n white-space: nowrap; /* Required for text-overflow */\n overflow-x: hidden; /* Required for text-overflow */\n max-width: 30em;\n text-overflow: ellipsis;\n}\n\n.Markup-menu-dropdown-item:hover, .Markup-menu-submenu-wrap:hover {\n background: var(--Markup-hover-bg, #f2f2f2);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menu-dropdown-item:hover, .Markup-menu-submenu-wrap:hover {\n background: var(--Markup-hover-bg, rgb(80, 80, 80));\n }\n}\n\n.Markup-menu-dropdown-item p, \n.Markup-menu-dropdown-item h1, \n.Markup-menu-dropdown-item h2, \n.Markup-menu-dropdown-item h3, \n.Markup-menu-dropdown-item h4, \n.Markup-menu-dropdown-item h5, \n.Markup-menu-dropdown-item h6, \n.Markup-menu-dropdown-item pre {\n padding: 0;\n margin: 0;\n min-height: 24px;\n}\n\n.Markup-stylelabel {\n display: flex;\n justify-content: space-between;\n align-items: center;\n white-space: pre;\n width: 100%;\n}\n\n.Markup-stylelabel-keymap {\n display: flex;\n justify-content: flex-end;\n font-size: 1.0rem;\n font-weight: normal;\n color: gray;\n}\n\n.Markup-menu-submenu-wrap:hover .Markup-menu-submenu, .Markup-menu-submenu-wrap-active .Markup-menu-submenu {\n display: block;\n}\n\n.Markup-menu-submenu-wrap {\n position: relative;\n margin-right: -4px;\n background: inherit;\n}\n\n.Markup-menu-submenu {\n display: none;\n min-width: 4em;\n left: 100%;\n top: -5px;\n max-height: 80vh;\n overflow-y: auto;\n /* background cannot be inherited because of hover highlighting in dropdown */\n background: var(--Markup-button-bg, white);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menu-submenu {\n /* background cannot be inherited because of hover highlighting in dropdown */\n background: var(--Markup-button-bg, black);\n }\n}\n\n.Markup-menu-submenu-label:after {\n content: \"\";\n border-top: 4px solid transparent;\n border-bottom: 4px solid transparent;\n border-left: 4px solid currentColor;\n opacity: .6;\n position: absolute;\n right: 4px;\n top: calc(50% - 4px);\n}\n\n.Markup-selection {\n position: absolute;\n border: 1px solid var(--Markup-accent-color, blue);\n background: transparent;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-selection {\n border: 1px solid var(--Markup-accent-color, lightblue);\n }\n}\n\n/* Toolbar-more styling */\n\n.Markup-toolbar-more {\n display: inline-flex;\n position: sticky;\n overflow: visible;\n width: 100%;\n font-size: var(--Markup-button-font-size, 24px);\n vertical-align: middle;\n border-top-left-radius: inherit;\n border-top-right-radius: inherit;\n top: calc(var(--Markup-button-size, 28px) + 7px);\n max-height: calc(var(--Markup-button-size, 28px) + 10px);\n padding: 2px 8px;\n color: var(--Markup-accent-color, blue);\n background: var(--Markup-toolbar-bg, rgba(250, 249, 246, 0.95));\n border-bottom: 1px solid var(--Markup-border-color, lightgray);\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-toolbar-more {\n color: var(--Markup-accent-color, lightblue);\n background: var(--Markup-toolbar-bg, rgba(40, 40, 43, 0.9));\n border-bottom: 1px solid var(--Markup-border-color, gray);\n }\n}\n\n/* Searchbar styling */\n\n.Markup-searchbar {\n display: inline-block;\n font-size: var(--Markup-button-font-size, 24px);\n vertical-align: middle;\n position: sticky;\n top: calc(var(--Markup-button-size, 28px) + 7px);\n width: 100%;\n line-height: 30px;\n height: 37px;\n color: var(--Markup-accent-color, blue);\n background: var(--Markup-toolbar-bg, rgba(250, 249, 246, 0.95));\n border-bottom: 1px solid var(--Markup-border-color, lightgray);\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n overflow: hidden;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-searchbar {\n color: var(--Markup-accent-color, lightblue);\n background: var(--Markup-toolbar-bg, rgba(40, 40, 43, 0.9));\n border-bottom: 1px solid var(--Markup-border-color, gray);\n }\n}\n\n.Markup-searchbar input {\n appearance: none; /* Otherwise, in Safari/WebKit, the height is small and fixed */\n position: relative;\n font-size: 18px;\n line-height: 30px;\n height: 30px;\n width: calc(100% - 128px);\n margin: 0 8px;\n padding: 0 4px;\n outline: none;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n border: 1px solid lightgray;\n border-radius: 6px;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-searchbar input {\n color: rgb(250, 249, 246);\n background: rgb(40, 40, 43);\n border: 1px solid gray;\n }\n}\n\n.Markup-searchbar .Markup-searchbar-status {\n position: absolute;\n display: flex;\n justify-content: flex-end;\n align-items: center;\n white-space: nowrap;\n font-size: 12px;\n overflow-x: visible;\n height: 28px;\n top: 5px;\n left: calc(100% - 13em);\n width: 1px;\n color: gray;\n user-select: none;\n}\n\n/* Dialog/prompt styling for insert/edit actions */\n\n.Markup-prompt-overlay {\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n\tbackground-color: rgba(0, 0, 0, 0.2);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-prompt-overlay {\n background-color: rgba(255, 255, 255, 0.2);\n }\n}\n\n.Markup-toolbar-overlay {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\tbackground-color: rgba(0, 0, 0, 0.1);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-toolbar-overlay {\n background-color: rgba(255, 255, 255, 0.1);\n }\n}\n\n.Markup-toolbar-overlay.Markup-searchbar-hidden {\n height: 38px;\n}\n\n.Markup-toolbar-overlay.Markup-searchbar-showing {\n height: 76px;\n}\n\n.Markup-prompt {\n margin: 0;\n font-size: 18px;\n background: white;\n padding: 8px;\n border: 1px solid silver;\n position: absolute;\n border-radius: 3px;\n box-shadow: -.5px 2px 5px rgba(0, 0, 0, 0.2);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-prompt {\n background: black;\n box-shadow: -.5px 2px 5px rgba(255, 255, 255, 0.8);\n }\n}\n\n.Markup-prompt-link {\n height: calc(68px + var(--Markup-button-size, 28px)); /* 96 px by default */\n width: 300px;\n}\n\n.Markup-prompt-image {\n height: calc(102px + var(--Markup-button-size, 28px)); /* 130 px by default */\n width: 300px;\n}\n\n.Markup-prompt-language {\n height: calc(68px + var(--Markup-button-size, 28px)); /* 96 px by default */\n width: 300px;\n}\n\n.Markup-prompt img {\n display: inline-block;\n vertical-align: middle;\n min-width: var(--Markup-button-size, 28px);\n height: var(--Markup-button-size, 28px);\n margin-right: 4px;\n}\n\n.Markup-prompt p {\n margin: 0 0 4px 0;\n font-weight: normal;\n color: black;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-prompt p {\n color: white;\n }\n}\n\n.Markup-prompt input[type=\"text\"] {\n font-size: 100%;\n line-height: 28px;\n margin-top: 2px;\n padding: 1px 4px;\n width: 100%;\n appearance: none;\n background: #eee;\n outline: none;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n border: 1px solid lightgray;\n border-radius: 6px;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-prompt input[type=\"text\"] {\n color: rgb(250, 249, 246);\n background: rgb(40, 40, 43);\n border: 1px solid gray;\n }\n}\n\n.Markup-prompt input.invalid {\n color: red;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-prompt input.invalid {\n color: pink;\n }\n}\n\n.Markup-prompt-buttons {\n display: flex;\n vertical-align: middle;\n justify-content: space-between;\n justify-items: center;\n line-height: var(--Markup-button-size, 28px);\n height: var(--Markup-button-size, 28px);\n margin-top: 8px;\n margin-bottom: 8px;\n}\n\n.Markup-prompt-buttongroup {\n flex-shrink: 0;\n}\n\n.Markup-prompt-buttons img {\n aspect-ratio: auto;\n height: auto;\n object-fit: contain;\n}\n\n.Markup-prompt-buttons .Markup-menuitem {\n display: inline-flex;\n padding-right: 4px;\n padding-left: 4px;\n}\n\n/* The buttongroup contains OK and Cancel menuitems on the right */\n.Markup-prompt-buttongroup .Markup-menuitem {\n margin-right: 0;\n margin-left: 4px;\n}\n\n/* .Markup-menu-tablesizer is a specialized submenu */\n.Markup-menu-tablesizer {\n display: none;\n grid-template-columns: repeat(4, 1fr);\n grid-auto-rows: 18px;\n row-gap: 1px;\n column-gap: 1px;\n padding: 0;\n left: 100%;\n top: -5px;\n /* background cannot be inherited because of hover highlighting in dropdown */\n background: var(--Markup-button-bg, white);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menu-tablesizer {\n /* background cannot be inherited because of hover highlighting in dropdown */\n background: var(--Markup-button-bg, black);\n }\n}\n\n.Markup-menu-submenu-wrap:hover .Markup-menu-tablesizer, .Markup-menu-submenu-wrap-active .Markup-menu-tablesizer {\n display: grid;\n}\n\n.Markup-menu-dropdown-menu, .Markup-menu-tablesizer {\n position: absolute;\n background: inherit;\n padding: 2px;\n background: inherit;\n border: 1px solid var(--Markup-border-color, gray);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menu-dropdown-menu, .Markup-menu-submenu {\n border: 1px solid var(--Markup-border-color, lightgray);\n }\n}\n\n.Markup-menu-tablesizer .Markup-menu-dropdown-item {\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n border: 1px solid var(--Markup-accent-color, blue);\n width: 18px;\n height: 18px;\n padding: 0;\n font-size: 13px;\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menu-tablesizer .Markup-menu-dropdown-item {\n border: 1px solid var(--Markup-accent-color, lightblue);\n }\n}\n\n.Markup-menu-tablesizer .Markup-menuitem-active {\n height: 13px;\n padding-top: 3px;\n fill: black;\n color: black;\n background: var(--Markup-accent-color, blue);\n}\n@media (prefers-color-scheme: dark) {\n .Markup-menu-tablesizer .Markup-menuitem-active {\n fill: black;\n color: black;\n background: var(--Markup-accent-color, lightblue);\n }\n}");
6
6
 
7
7
  const sheet$1 = new CSSStyleSheet();sheet$1.replaceSync("pre code.hljs {\n display: block;\n overflow-x: auto;\n padding: 1em\n}\ncode.hljs {\n padding: 3px 5px\n}\n/*!\n Theme: GitHub\n Description: Light theme as seen on github.com\n Author: github.com\n Maintainer: @Hirse\n Updated: 2021-05-15\n\n Outdated base version: https://github.com/primer/github-syntax-light\n Current colors taken from GitHub's CSS\n*/\n.hljs {\n color: #24292e;\n background: #ffffff\n}\n.hljs-doctag,\n.hljs-keyword,\n.hljs-meta .hljs-keyword,\n.hljs-template-tag,\n.hljs-template-variable,\n.hljs-type,\n.hljs-variable.language_ {\n /* prettylights-syntax-keyword */\n color: #d73a49\n}\n.hljs-title,\n.hljs-title.class_,\n.hljs-title.class_.inherited__,\n.hljs-title.function_ {\n /* prettylights-syntax-entity */\n color: #6f42c1\n}\n.hljs-attr,\n.hljs-attribute,\n.hljs-literal,\n.hljs-meta,\n.hljs-number,\n.hljs-operator,\n.hljs-variable,\n.hljs-selector-attr,\n.hljs-selector-class,\n.hljs-selector-id {\n /* prettylights-syntax-constant */\n color: #005cc5\n}\n.hljs-regexp,\n.hljs-string,\n.hljs-meta .hljs-string {\n /* prettylights-syntax-string */\n color: #032f62\n}\n.hljs-built_in,\n.hljs-symbol {\n /* prettylights-syntax-variable */\n color: #e36209\n}\n.hljs-comment,\n.hljs-code,\n.hljs-formula {\n /* prettylights-syntax-comment */\n color: #6a737d\n}\n.hljs-name,\n.hljs-quote,\n.hljs-selector-tag,\n.hljs-selector-pseudo {\n /* prettylights-syntax-entity-tag */\n color: #22863a\n}\n.hljs-subst {\n /* prettylights-syntax-storage-modifier-import */\n color: #24292e\n}\n.hljs-section {\n /* prettylights-syntax-markup-heading */\n color: #005cc5;\n font-weight: bold\n}\n.hljs-bullet {\n /* prettylights-syntax-markup-list */\n color: #735c0f\n}\n.hljs-emphasis {\n /* prettylights-syntax-markup-italic */\n color: #24292e;\n font-style: italic\n}\n.hljs-strong {\n /* prettylights-syntax-markup-bold */\n color: #24292e;\n font-weight: bold\n}\n.hljs-addition {\n /* prettylights-syntax-markup-inserted */\n color: #22863a;\n background-color: #f0fff4\n}\n.hljs-deletion {\n /* prettylights-syntax-markup-deleted */\n color: #b31d28;\n background-color: #ffeef0\n}\n.hljs-char.escape_,\n.hljs-link,\n.hljs-params,\n.hljs-property,\n.hljs-punctuation,\n.hljs-tag {\n /* purposely ignored */\n \n}");
8
8
 
@@ -236,7 +236,7 @@ class Registry {
236
236
  /**
237
237
  * Add `plugin` to the registry, keyed by `name`.
238
238
  *
239
- * @param {object} plugin Plugin object with { name, extension, export, import }.
239
+ * @param {object} plugin Plugin object with string values for { name, type, filename }.
240
240
  * @param {string} name The key used to retrieve and invoke the plugin.
241
241
  */
242
242
  registerPlugin(plugin, name) {
@@ -263,33 +263,19 @@ class Registry {
263
263
  }
264
264
 
265
265
  /**
266
- * Return the public manifest for all registered plugins.
267
- * Each entry contains only { id, name, extension } — function references are excluded.
266
+ * Return an array of registered plugins that match type, all if not specified.
267
+ * Each entry contains {name, type, filename}
268
268
  *
269
- * @returns {Array<{id: string, name: string, extension: string}>}
269
+ * @returns {Array<{name: string, type: string, filename: string}>}
270
270
  */
271
- getPluginManifest() {
272
- const manifest = [];
273
- for (const plugin of this._plugins.values()) {
274
- manifest.push({ name: plugin.name, extension: plugin.extension });
271
+ getPlugins(type) {
272
+ if (type) {
273
+ return Array.from(this._plugins.values()).filter(plugin => plugin.type === type)
274
+ } else {
275
+ return Array.from(this._plugins.values())
275
276
  }
276
- return manifest
277
277
  }
278
278
 
279
- /**
280
- * Invoke `action` on the named plugin, passing `...args`.
281
- * Returns null if the plugin is not found or does not define the action.
282
- *
283
- * @param {string} name The key used to identify the plugin.
284
- * @param {string} action The action to invoke (e.g. 'export', 'import').
285
- * @param {...*} args Arguments forwarded to the plugin action.
286
- * @returns {*|null}
287
- */
288
- invokePlugin(name, action, ...args) {
289
- const plugin = this._plugins.get(name);
290
- if (!plugin || typeof plugin[action] !== 'function') return null
291
- return plugin[action](...args)
292
- }
293
279
  }
294
280
 
295
281
  /**
@@ -369,29 +355,174 @@ function registerDelegate(delegate, name) {_registry.registerDelegate.bind(_regi
369
355
  */
370
356
  function registerMessageHandler(handler, name) {_registry.registerMessageHandler.bind(_registry)(handler, name);}
371
357
 
358
+ /**
359
+ * Add the `editor` to the registry.
360
+ *
361
+ * When we register an editor, it becomes the active editor.
362
+ *
363
+ * @param {MarkupEditor} editor The MarkupEditor to be added
364
+ */
372
365
  const registerEditor = _registry.registerEditor.bind(_registry);
366
+
367
+ /**
368
+ * Remove the `editor` from the registry.
369
+ *
370
+ * @param {MarkupEditor} editor The MarkupEditor to be removed
371
+ */
373
372
  const unregisterEditor = _registry.unregisterEditor.bind(_registry);
373
+
374
+ /**
375
+ * Remove the `delegate` from the registry.
376
+ *
377
+ * @param {MarkupDelegate | object} delegate A MarkupDelegate or appropriate object.
378
+ * @param {string} name A name used to retrieve the `delegate`.
379
+ */
374
380
  _registry.unregisterDelegate.bind(_registry);
381
+
382
+ /**
383
+ * Return the `delegate` with `name`.
384
+ *
385
+ * @param {string} name A name used to retrieve the `delegate`.
386
+ * @returns {MarkupDelegate | object | undefined}
387
+ */
375
388
  const getDelegate = _registry.getDelegate.bind(_registry);
389
+
390
+ /**
391
+ * Remove the `config` from the registry.
392
+ *
393
+ * @param {ToolbarConfig | KeymapConfig | BehaviorConfig | object} config One of the "configs" from the MarkupEditor API or appropriate object.
394
+ * @param {string} name A name used to retrieve the `config`.
395
+ */
376
396
  _registry.unregisterConfig.bind(_registry);
397
+
398
+ /**
399
+ * Return the `config` with `name`.
400
+ *
401
+ * @param {string} name A name used to retrieve the `config`.
402
+ * @returns {ToolbarConfig | KeymapConfig | BehaviorConfig | object | undefined}
403
+ */
377
404
  const getConfig = _registry.getConfig.bind(_registry);
405
+
406
+ /**
407
+ * Remove the `handler` from the registry.
408
+ *
409
+ * @param {MessageHandler | object} handler An instance of MessageHandler or appropriate object.
410
+ * @param {string} name A name used to retrieve the `handler`.
411
+ */
378
412
  _registry.unregisterMessageHandler.bind(_registry);
413
+
414
+ /**
415
+ * Return the `handler` with `name`.
416
+ *
417
+ * @param {string} name A name used to retrieve the `handler`.
418
+ * @returns {MessageHandler | object | undefined}
419
+ */
379
420
  const getMessageHandler = _registry.getMessageHandler.bind(_registry);
421
+
422
+ /**
423
+ * Remove the `toolbar` augmentation from the registry.
424
+ *
425
+ * @param {object} toolbar A toolbar that holds `cmdItems`.
426
+ * @param {string} name A name used to retrieve the `toolbar`.
427
+ */
380
428
  _registry.unregisterAugmentation.bind(_registry);
429
+
430
+ /**
431
+ * Return the `toolbar` whose `menuItems` will be
432
+ * either prepended or appended to the normal MarkupEditor toolbar.
433
+ *
434
+ * @param {string} name A name used to retrieve the `toolbar`.
435
+ * @returns {object | undefined}
436
+ */
381
437
  const getAugmentation = _registry.getAugmentation.bind(_registry);
438
+
439
+ /**
440
+ * Return the active editor with `muId` of the registry's `activeMuId`.
441
+ *
442
+ * @returns {MarkupEditor | undefined}
443
+ */
382
444
  _registry.activeEditor.bind(_registry);
445
+
446
+ /**
447
+ * Return the active editor's `document` (could be the shadow root).
448
+ *
449
+ * @returns {Document | ShadowRoot | undefined}
450
+ */
383
451
  const activeDocument = _registry.activeDocument.bind(_registry);
452
+
453
+ /**
454
+ * Set `activeMuId` based on `document` (could be a shadow root).
455
+ *
456
+ * @param {Document | ShadowRoot} document
457
+ */
384
458
  const setActiveDocument = _registry.setActiveDocument.bind(_registry);
459
+
460
+ /**
461
+ * Return the active editor's element (typically with focus).
462
+ *
463
+ * @returns {Element | undefined}
464
+ */
385
465
  const activeEditorElement = _registry.activeEditorElement.bind(_registry);
466
+
467
+ /**
468
+ * Return the active editor's `messageHandler`.
469
+ *
470
+ * @returns {MessageHandler | object | undefined}
471
+ */
386
472
  _registry.activeMessageHandler.bind(_registry);
473
+
474
+ /**
475
+ * Return the active editor's instance of Searcher, `searcher`.
476
+ *
477
+ * @returns {object | undefined}
478
+ */
387
479
  const activeSearcher = _registry.activeSearcher.bind(_registry);
480
+
481
+ /**
482
+ * Return the cached ID of the selected contentEditable element.
483
+ *
484
+ * @returns {string | undefined}
485
+ */
388
486
  const selectedID = _registry.selectedID.bind(_registry);
487
+
488
+ /**
489
+ * Set/cache the ID of the selected contentEditable element to `string`.
490
+ *
491
+ * @param {string} string
492
+ */
389
493
  const setSelectedID = _registry.setSelectedID.bind(_registry);
494
+
495
+ /**
496
+ * Add `plugin` to the registry, keyed by `name`.
497
+ *
498
+ * @param {object} plugin Plugin object with string values for { name, type, filename }.
499
+ * @param {string} name The key used to retrieve and invoke the plugin.
500
+ */
390
501
  const registerPlugin = _registry.registerPlugin.bind(_registry);
391
- _registry.unregisterPlugin.bind(_registry);
392
- _registry.getPlugin.bind(_registry);
393
- const getPluginManifest = _registry.getPluginManifest.bind(_registry);
394
- const invokePlugin = _registry.invokePlugin.bind(_registry);
502
+
503
+ /**
504
+ * Remove the plugin with `name` from the registry.
505
+ *
506
+ * @param {string} name The key used to identify the plugin.
507
+ */
508
+ const unregisterPlugin = _registry.unregisterPlugin.bind(_registry);
509
+
510
+ /**
511
+ * Return the plugin object with `name`, or undefined if not found.
512
+ *
513
+ * @param {string} name The key used to identify the plugin.
514
+ * @returns {object|undefined}
515
+ */
516
+ const getPlugin = _registry.getPlugin.bind(_registry);
517
+
518
+ /**
519
+ * Return an array of registered plugins that match `type`, all if not specified.
520
+ * Each entry contains {name, type, filename}
521
+ *
522
+ * @param {string} [type] The plugin type to filter by; all plugins are returned if omitted.
523
+ * @returns {Array<{name: string, type: string, filename: string}>}
524
+ */
525
+ const getPlugins = _registry.getPlugins.bind(_registry);
395
526
 
396
527
  /**
397
528
  * MUError captures internal errors and makes it easy to communicate them externally.
@@ -12417,6 +12548,15 @@ function isSurrogatePair(str) {
12417
12548
  let a = str.charCodeAt(0), b = str.charCodeAt(1);
12418
12549
  return a >= 0xDC00 && a <= 0xDFFF && b >= 0xD800 && b <= 0xDBFF;
12419
12550
  }
12551
+
12552
+ /**
12553
+ @internal
12554
+ */
12555
+ const __parseFromClipboard = parseFromClipboard;
12556
+ /**
12557
+ @internal
12558
+ */
12559
+ const __endComposition = endComposition;
12420
12560
  /**
12421
12561
  An editor view manages the DOM structure that represents an
12422
12562
  editable document. Its state and behavior are determined by its
@@ -32597,11 +32737,18 @@ var HighlightJS = /*@__PURE__*/getDefaultExportFromCjs(commonExports);
32597
32737
  * Delegates to hljs.getLanguage, which lowercases internally and checks
32598
32738
  * both registered names and aliases, so this is case-insensitive and
32599
32739
  * alias-aware.
32740
+ *
32741
+ * Plugins of type 'codeview' are also recognized languages in the sense
32742
+ * that UI that depends on the return value here needs to know they will
32743
+ * be rendered by the plugin.
32600
32744
  *
32601
32745
  * @param {string} name
32602
32746
  * @returns {boolean}
32603
32747
  */
32604
32748
  function isRecognizedLanguage(name) {
32749
+ let codeviews = getPlugins('codeview');
32750
+ let isCodeViewLanguage = codeviews.filter(plugin => plugin.name === name).length > 0;
32751
+ if (isCodeViewLanguage) { return true }
32605
32752
  return !!HighlightJS.getLanguage((name ?? '').trim())
32606
32753
  }
32607
32754
 
@@ -32629,6 +32776,61 @@ function presentCodeLanguages(doc) {
32629
32776
  return Array.from(languages).sort()
32630
32777
  }
32631
32778
 
32779
+ // hljs never has classPrefix reconfigured elsewhere in this codebase, so this
32780
+ // matches its own default. No public getter exists to read it back instead.
32781
+ const HLJS_CLASS_PREFIX = 'hljs-';
32782
+
32783
+ // Mirrors hljs's internal (unexported) scopeToCSSClass so class strings here
32784
+ // match what hljs's own HTML renderer would produce for the same scope name.
32785
+ function scopeToClass(name, prefix) {
32786
+ if (name.startsWith('language:')) return name.replace('language:', 'language-')
32787
+ if (name.includes('.')) {
32788
+ const pieces = name.split('.');
32789
+ return [`${prefix}${pieces.shift()}`, ...pieces.map((piece, i) => `${piece}${'_'.repeat(i + 1)}`)].join(' ')
32790
+ }
32791
+ return `${prefix}${name}`
32792
+ }
32793
+
32794
+ // Implements hljs's documented Renderer contract (addText/openNode/closeNode)
32795
+ // for TokenTree.walk, tracking span positions instead of building HTML.
32796
+ class SpanWalker {
32797
+ constructor(prefix) {
32798
+ this.prefix = prefix;
32799
+ this.position = 0;
32800
+ this.stack = [];
32801
+ this.spans = [];
32802
+ }
32803
+
32804
+ addText(text) {
32805
+ this.position += text.length;
32806
+ }
32807
+
32808
+ openNode(node) {
32809
+ if (node.scope) this.stack.push({ scope: node.scope, from: this.position });
32810
+ }
32811
+
32812
+ closeNode(node) {
32813
+ if (!node.scope) return
32814
+ const { scope, from } = this.stack.pop();
32815
+ this.spans.push({ from, to: this.position, class: scopeToClass(scope, this.prefix) });
32816
+ }
32817
+ }
32818
+
32819
+ /**
32820
+ * {from, to, class} spans for `code` highlighted as `language`, via hljs's
32821
+ * token tree (result._emitter.walk).
32822
+ */
32823
+ function highlightSpans(code, language) {
32824
+ try {
32825
+ const result = HighlightJS.highlight(code, { language, ignoreIllegals: true });
32826
+ const walker = new SpanWalker(HLJS_CLASS_PREFIX);
32827
+ result._emitter.walk(walker);
32828
+ return walker.spans
32829
+ } catch {
32830
+ return []
32831
+ }
32832
+ }
32833
+
32632
32834
  /**
32633
32835
  * Define various arrays of tags used to represent MarkupEditor-specific concepts.
32634
32836
  *
@@ -32962,21 +33164,61 @@ function pasteCode(text) {
32962
33164
 
32963
33165
  /**
32964
33166
  * Do a custom paste operation of "text only", which we will extract from the html
32965
- * ourselves. First we get a node that conforms to the schema, which by definition
33167
+ * ourselves. First we get a node that conforms to the schema, which by definition
32966
33168
  * only includes elements in a form we recognize, no spans, styles, etc.
32967
33169
  * The trick here is that we want to use the same code to paste text as we do for
32968
33170
  * HTML, but we want to paste something that is the MarkupEditor-equivalent of
32969
33171
  * unformatted text.
32970
- *
33172
+ *
32971
33173
  * @param {string} html The HTML to be pasted
32972
33174
  * @param {ClipboardEvent | null} event A mocked ClipboardEvent for testing
32973
33175
  */
32974
33176
  function pasteText(html, event) {
32975
- const node = _nodeFromHTML(html);
33177
+ const node = _nodeFromHTML(_preprocessPasteText(html));
32976
33178
  const htmlFragment = fragmentFromNode(node);
32977
33179
  const minimalHTML = _minimalHTML(htmlFragment); // Reduce to MarkupEditor-equivalent of "plain" text
32978
33180
  pasteHTML(minimalHTML, event);
32979
33181
  }
33182
+ /**
33183
+ * Preprocess raw text before it's parsed as HTML by _nodeFromHTML, converting
33184
+ * meaningful line structure into real markup so it survives.
33185
+ *
33186
+ * Only applied when there's an actual newline AND no real markup at all: that
33187
+ * combination means genuinely plain text (e.g., from a clipboard with no text/html
33188
+ * flavor available, or a plain-text-only source app) whose line structure is
33189
+ * meaningful — not HTML, where a run of whitespace (including newlines) collapses to
33190
+ * nothing meaningful once parsed. Gating on '\n' presence too, not just the absence
33191
+ * of '<', matters: some callers intentionally pass single-line text containing
33192
+ * literal HTML entities (e.g. "Hello &lt;b&gt;bold&lt;/b&gt; world", meant to render
33193
+ * as that literal text, not a tag) with no '<' character anywhere in it either —
33194
+ * without the '\n' check, that would be misidentified as bare plain text needing
33195
+ * escaping and get double-escaped (&lt; -> &amp;lt;). No newline means there's
33196
+ * nothing this preprocessing could improve regardless, so it only ever applies to
33197
+ * input _nodeFromHTML couldn't already have handled correctly as HTML on its own.
33198
+ * @ignore
33199
+ */
33200
+ function _preprocessPasteText(html) {
33201
+ return (html.includes('\n') && !html.includes('<')) ? _linebreaksToHTML(html) : html;
33202
+ }
33203
+ /**
33204
+ * Convert bare plain text (no HTML markup at all) into minimal paragraph/br-structured
33205
+ * HTML: a blank line (a run of two or more newlines) starts a new paragraph; a single
33206
+ * newline within a paragraph becomes a <br>. Text is HTML-escaped first via a detached
33207
+ * element's textContent/innerHTML round-trip, the standard safe way to escape arbitrary
33208
+ * text for insertion into an HTML string, so literal &, <, > in the source can't be
33209
+ * misinterpreted once this result is parsed as HTML by the caller.
33210
+ * @ignore
33211
+ */
33212
+ function _linebreaksToHTML(text) {
33213
+ return text
33214
+ .split(/\n[ \t]*\n+/)
33215
+ .map(paragraph => {
33216
+ const div = document.createElement('div');
33217
+ div.textContent = paragraph;
33218
+ return `<p>${div.innerHTML.split('\n').join('<br>')}</p>`;
33219
+ })
33220
+ .join('');
33221
+ }
32980
33222
  /**
32981
33223
  * Return a minimal "unformatted equivalent" version of the HTML that is in fragment.
32982
33224
  *
@@ -33792,22 +34034,21 @@ function codeBlockAtSelection(state) {
33792
34034
  }
33793
34035
 
33794
34036
  /**
33795
- * Return `{pos, label}` describing the language overlay for the code_block at
33796
- * `state`'s selection, or null if the selection isn't in a code_block. `pos`
33797
- * is the position just inside the code_block's content, where a widget
33798
- * Decoration for the overlay should be anchored. `label` is "Language:
33799
- * <Capitalized name>" or "Language: None" — display-only, the underlying
34037
+ * Return `{pos, label}` for the language tab of the code_block at `state`'s
34038
+ * selection, or null if the selection isn't in a code_block. `pos` is the
34039
+ * position just inside the code_block's content. `label` is "Language:
34040
+ * <lowercase name>" or "Language: none" — display-only, the underlying
33800
34041
  * `language` attribute value is untouched.
33801
34042
  *
33802
34043
  * @ignore
33803
34044
  * @param {EditorState} state
33804
34045
  * @returns {{pos: number, label: string} | null}
33805
34046
  */
33806
- function codeLanguageOverlayInfo(state) {
34047
+ function codeLanguageTabInfo(state) {
33807
34048
  const found = codeBlockAtSelection(state);
33808
34049
  if (!found) return null
33809
34050
  const language = found.node.attrs.language;
33810
- const label = language ? `Language: ${language.charAt(0).toUpperCase()}${language.slice(1)}` : 'Language: None';
34051
+ const label = language ? `Language: ${language.toLowerCase()}` : 'Language: none';
33811
34052
  return { pos: found.pos + 1, label }
33812
34053
  }
33813
34054
 
@@ -34833,7 +35074,7 @@ function _loadedUserFiles(target) {
34833
35074
  * @param {MUError} error
34834
35075
  */
34835
35076
  function _callbackError(error) {
34836
- _callback(error.messageDict());
35077
+ _callback(JSON.stringify(error.messageDict()));
34837
35078
  }
34838
35079
 
34839
35080
  /**
@@ -35112,7 +35353,7 @@ function testPasteHTMLPreprocessing(html) {
35112
35353
  * @param {string} html The HTML to paste
35113
35354
  */
35114
35355
  function testPasteTextPreprocessing(html) {
35115
- const node = _nodeFromHTML(html);
35356
+ const node = _nodeFromHTML(_preprocessPasteText(html));
35116
35357
  const fragment = fragmentFromNode(node);
35117
35358
  const minimalHTML = _minimalHTML(fragment);
35118
35359
  return minimalHTML;
@@ -35133,12 +35374,12 @@ function testPresentCodeLanguages() {
35133
35374
  return presentCodeLanguages(view.state.doc);
35134
35375
  }
35135
35376
  /**
35136
- * For testing purposes, invoke codeLanguageOverlayInfo on the active document's
35377
+ * For testing purposes, invoke codeLanguageTabInfo on the active document's
35137
35378
  * current selection.
35138
35379
  */
35139
- function testCodeLanguageOverlayInfo() {
35380
+ function testCodeLanguageTabInfo() {
35140
35381
  const view = activeView();
35141
- return codeLanguageOverlayInfo(view.state);
35382
+ return codeLanguageTabInfo(view.state);
35142
35383
  }
35143
35384
  /********************************************************************************
35144
35385
  * Links
@@ -36085,6 +36326,20 @@ function consoleLog(string) {
36085
36326
  };
36086
36327
  _callback(JSON.stringify(messageDict));
36087
36328
  }
36329
+ /**
36330
+ * Report an error to the delegate's markupError method, the same path internal
36331
+ * MUErrors use. For external plugins that have their own error conditions
36332
+ * (e.g., a failed render) but no access to the internal MUError/_callbackError
36333
+ * machinery directly.
36334
+ *
36335
+ * @param {string} code A short identifier for the error.
36336
+ * @param {string} message The error message.
36337
+ * @param {string} [info] Optional additional detail.
36338
+ * @param {boolean} [alert] Whether the host app should treat this as alert-worthy. Defaults to true.
36339
+ */
36340
+ function reportError(code, message, info, alert=true) {
36341
+ _callbackError(new MUError(code, message, info, alert));
36342
+ }
36088
36343
 
36089
36344
  var visibility = {
36090
36345
  toolbar: true,
@@ -37683,7 +37938,7 @@ class Dropdown {
37683
37938
  */
37684
37939
  render(view) {
37685
37940
  let options = this.options;
37686
- let content = renderDropdownItems(this.content, view);
37941
+ let content = renderDropdownItems([this.content], view);
37687
37942
  let win = view.dom.ownerDocument.defaultView || window;
37688
37943
  let indicator = crelt("span", "\u25BE");
37689
37944
  setClass(indicator, this.prefix + "-dropdown-indicator", true);
@@ -37853,7 +38108,7 @@ class DropdownSubmenu {
37853
38108
  */
37854
38109
  render(view) {
37855
38110
  let options = this.options;
37856
- let items = renderDropdownItems(this.content, view);
38111
+ let items = renderDropdownItems([this.content], view);
37857
38112
  let win = view.dom.ownerDocument.defaultView || window;
37858
38113
  let label = crelt("div", { class: this.prefix + "-submenu-label" }, translate(view, this.options.label || ""));
37859
38114
  let submenu = crelt("div", { class: this.prefix + "-submenu" }, items.dom);
@@ -38491,15 +38746,11 @@ class LanguageDialogItem extends DialogItem {
38491
38746
  this.languageArea.addEventListener('keydown', e => { // Use keydown because 'input' isn't triggered for Enter
38492
38747
  if (e.key === 'Enter') {
38493
38748
  e.preventDefault();
38494
- // Enter activates whichever button is currently the default (matches LinkItem's
38495
- // isValid()-gated Enter handling) — Cancel when empty/unrecognized, OK once a
38496
- // recognized language is entered. An unsupported language can still be submitted
38497
- // explicitly via the OK button, which stays enabled regardless.
38498
- if (this.hasRecognizedLanguage()) {
38499
- this.submit();
38500
- } else {
38501
- this.closeDialog();
38502
- }
38749
+ // OK is always the default action, even for an empty or unrecognized
38750
+ // language — entering something not yet in the recognized set (e.g. a
38751
+ // new fenced-code language) must work on Enter alone, without first
38752
+ // having to click OK to override Cancel as the default.
38753
+ this.submit();
38503
38754
  } else if (e.key === 'Tab') {
38504
38755
  e.preventDefault();
38505
38756
  } else if (e.key === 'Escape') {
@@ -38511,25 +38762,12 @@ class LanguageDialogItem extends DialogItem {
38511
38762
 
38512
38763
  /**
38513
38764
  * Whether the input currently has any language name entered, recognized or
38514
- * not. An empty field means "no language" — never colored invalid — but it's
38515
- * also not something recognized, so it alone doesn't make OK the default
38516
- * button; see hasRecognizedLanguage().
38765
+ * not. An empty field means "no language" — never colored invalid.
38517
38766
  */
38518
38767
  hasContent() {
38519
38768
  return this.languageArea.value.trim().length > 0
38520
38769
  }
38521
38770
 
38522
- /**
38523
- * Whether the input currently holds a recognized language name. Drives which
38524
- * of OK/Cancel looks like the default button — Cancel is default when the
38525
- * field is empty OR holds an unsupported name, OK only once a supported
38526
- * language is entered. Submission itself is never blocked either way (OK
38527
- * stays enabled always); this only changes which button LOOKS default.
38528
- */
38529
- hasRecognizedLanguage() {
38530
- return this.hasContent() && isRecognizedLanguage(this.languageArea.value)
38531
- }
38532
-
38533
38771
  /**
38534
38772
  * Color the input text red (the same .invalid convention ImageItem's srcArea
38535
38773
  * uses) when it has an unsupported language entered. Called on every keystroke;
@@ -38544,10 +38782,11 @@ class LanguageDialogItem extends DialogItem {
38544
38782
  /**
38545
38783
  * Create and append the OK/Cancel buttons, positioned and styled like LinkItem's:
38546
38784
  * a left-side spacer so buttonsDiv's space-between pushes the OK/Cancel group to
38547
- * the right, and Cancel as the default (active-styled) button until the field has
38548
- * a language entered, at which point OK becomes the default. OK is always enabled
38549
- * regardless — an unrecognized language is a valid, deliberate choice, not an
38550
- * error to block on — only which button LOOKS like the default one changes.
38785
+ * the right, and OK always the default (active-styled) button, regardless of
38786
+ * whether the field is empty or holds an unrecognized language — an unrecognized
38787
+ * language is a valid, deliberate choice (e.g. a new fenced-code language), not
38788
+ * an error to block submission on. Both buttons stay enabled always; only OK's
38789
+ * active styling is a fixed constant, not state-dependent.
38551
38790
  *
38552
38791
  * @param {EditorView} view
38553
38792
  */
@@ -38562,7 +38801,7 @@ class LanguageDialogItem extends DialogItem {
38562
38801
  let okItem = cmdItem(() => this.submit(), {
38563
38802
  class: prefix + '-menuitem',
38564
38803
  title: 'OK',
38565
- active: () => { return this.hasRecognizedLanguage() },
38804
+ active: () => { return true },
38566
38805
  enable: () => { return true }
38567
38806
  });
38568
38807
  let {dom: okDom, update: okUpdate} = okItem.render(view);
@@ -38573,7 +38812,7 @@ class LanguageDialogItem extends DialogItem {
38573
38812
  let cancelItem = cmdItem(() => this.closeDialog(), {
38574
38813
  class: prefix + '-menuitem',
38575
38814
  title: 'Cancel',
38576
- active: () => { return !this.hasRecognizedLanguage() },
38815
+ active: () => { return false },
38577
38816
  enable: () => { return true }
38578
38817
  });
38579
38818
  let {dom: cancelDom, update: cancelUpdate} = cancelItem.render(view);
@@ -38597,18 +38836,6 @@ class LanguageDialogItem extends DialogItem {
38597
38836
 
38598
38837
  }
38599
38838
 
38600
- /**
38601
- * Capitalize just the first character of `text`, leaving the rest as-is
38602
- * (e.g. "html" -> "Html", "weirdLanguage" -> "WeirdLanguage"). Display-only —
38603
- * the underlying language value stays exactly as entered/stored.
38604
- *
38605
- * @param {string} text
38606
- * @returns {string}
38607
- */
38608
- function capitalized(text) {
38609
- return text ? text.charAt(0).toUpperCase() + text.slice(1) : text
38610
- }
38611
-
38612
38839
  /**
38613
38840
  * Find the code_block containing `state`'s selection and return its `language`
38614
38841
  * attribute, or null if the selection isn't in a code_block or the block has
@@ -38657,15 +38884,24 @@ class CodeLanguageSubmenu {
38657
38884
  }
38658
38885
 
38659
38886
  buildItems(view) {
38660
- let items = [];
38661
- items.push(cmdItem(() => { this.setLanguage(view, null); return true }, {
38662
- label: 'Language: None',
38887
+ let noneItem = cmdItem(() => { this.setLanguage(view, null); return true }, {
38888
+ label: 'Language: none',
38663
38889
  enable: () => true,
38664
38890
  active: (state) => currentCodeLanguage(state) == null
38665
- }));
38891
+ });
38892
+ let dialogItem = cmdItem(() => {
38893
+ this.languageDialog.open(view, currentCodeLanguage(view.state) ?? '', (entered) => {
38894
+ this.setLanguage(view, entered ? entered : null);
38895
+ });
38896
+ return true
38897
+ }, {
38898
+ label: 'Language...',
38899
+ enable: () => true
38900
+ });
38901
+ let languageItems = [];
38666
38902
  for (let language of presentCodeLanguages(view.state.doc)) {
38667
- items.push(cmdItem(() => { this.setLanguage(view, language); return true }, {
38668
- label: `Language: ${capitalized(language)}`,
38903
+ languageItems.push(cmdItem(() => { this.setLanguage(view, language); return true }, {
38904
+ label: language.toLowerCase(),
38669
38905
  enable: () => true,
38670
38906
  // presentCodeLanguages lowercases its results for dedup, but the selected block's
38671
38907
  // own attribute value keeps whatever case was originally typed — compare case-
@@ -38673,16 +38909,9 @@ class CodeLanguageSubmenu {
38673
38909
  active: (state) => (currentCodeLanguage(state) ?? '').toLowerCase() === language
38674
38910
  }));
38675
38911
  }
38676
- items.push(cmdItem(() => {
38677
- this.languageDialog.open(view, currentCodeLanguage(view.state) ?? '', (entered) => {
38678
- this.setLanguage(view, entered ? entered : null);
38679
- });
38680
- return true
38681
- }, {
38682
- label: 'Language...',
38683
- enable: () => true
38684
- }));
38685
- return items
38912
+ // Grouped so renderDropdownItems provides separators. Safe to pass languageItems
38913
+ // even when empty — renderDropdownItems drops structurally empty groups.
38914
+ return [[noneItem, dialogItem], languageItems]
38686
38915
  }
38687
38916
 
38688
38917
  render(view) {
@@ -39143,7 +39372,7 @@ class TableCreateSubmenu {
39143
39372
  render(view) {
39144
39373
  let resetSize = this.resetSize.bind(this);
39145
39374
  let options = this.options;
39146
- let items = renderDropdownItems(this.content, view);
39375
+ let items = renderDropdownItems([this.content], view);
39147
39376
  this.itemsUpdate = items.update; // Track the update method so we can update as the mouse is over items
39148
39377
  let win = view.dom.ownerDocument.defaultView || window;
39149
39378
  let label = crelt("div", { class: this.prefix + "-submenu-label" }, translate(view, this.options.label || ""));
@@ -39500,6 +39729,17 @@ function separator() {
39500
39729
  return crelt("span", { class: prefix + "-menuseparator" });
39501
39730
  }
39502
39731
 
39732
+ /**
39733
+ * Return a div for a separator between groups of MenuItems in a dropdown-item list
39734
+ * (vertical layout) — distinct from `separator()`, which is styled for the toolbar
39735
+ * bar's horizontal layout.
39736
+ *
39737
+ * @private
39738
+ */
39739
+ function dropdownSeparator() {
39740
+ return crelt("div", { class: prefix + "-menu-dropdown-separator" });
39741
+ }
39742
+
39503
39743
  /**
39504
39744
  * Return whether the selection in state is within a mark of type `markType`.
39505
39745
  * @private
@@ -39641,19 +39881,55 @@ function renderGroupedFit(view, content, wrapAtIndex) {
39641
39881
  }
39642
39882
 
39643
39883
  /**
39644
- * Render the items in a Dropdown in the view.
39645
- *
39646
- * @param {Array<MenuItem>} items The content to be rendered in a Dropdown
39647
- * @param {EditorView} view The EditorView to render the items in
39648
- * @returns {object} An object containing the DOM for the Dropdown and an update function to display it
39884
+ * Render the items in a Dropdown in the view, as a list of dropdown-item rows.
39885
+ *
39886
+ * `groups` is an array of arrays: each sub-array is a group of MenuItems, and a
39887
+ * separator row is inserted between adjacent groups that both have content for
39888
+ * the current state (auto-hidden otherwise). Mirrors `renderGrouped`'s toolbar-bar
39889
+ * behavior, but keeps the dropdown-item row wrapper/CSS instead of switching to
39890
+ * the toolbar's menuitem span.
39891
+ *
39892
+ * @param {Array<Array<MenuItem>>} groups The groups of MenuItems to be rendered in a Dropdown
39893
+ * @param {EditorView} view The EditorView to render the items in
39894
+ * @returns {object} An object containing the DOM (items and separators, in display order) for the Dropdown and an update function to display it
39649
39895
  */
39650
- function renderDropdownItems(items, view) {
39651
- let rendered = [], updates = [];
39652
- for (let i = 0; i < items.length; i++) {
39653
- let { dom, update } = items[i].render(view);
39654
- rendered.push(crelt("div", { class: prefix + "-menu-dropdown-item" }, dom));
39655
- updates.push(update);
39656
- } return { dom: rendered, update: combineUpdates(updates, rendered) };
39896
+ function renderDropdownItems(groups, view) {
39897
+ // Drop structurally empty groups (e.g. no present languages) up front, so a
39898
+ // separator is never placed next to a group that was never going to render
39899
+ // anything — update() below only knows how to auto-hide a separator when the
39900
+ // adjacent group rendered items that are merely inactive/disabled for the
39901
+ // current state, not when the group had zero items to begin with.
39902
+ let nonEmptyGroups = groups.filter(items => items.length > 0);
39903
+ let rendered = [], updates = [], separators = [];
39904
+ for (let i = 0; i < nonEmptyGroups.length; i++) {
39905
+ let items = nonEmptyGroups[i], localUpdates = [], localNodes = [];
39906
+ for (let j = 0; j < items.length; j++) {
39907
+ let { dom, update } = items[j].render(view);
39908
+ let div = crelt("div", { class: prefix + "-menu-dropdown-item" }, dom);
39909
+ rendered.push(div);
39910
+ localNodes.push(div);
39911
+ localUpdates.push(update);
39912
+ }
39913
+ updates.push(combineUpdates(localUpdates, localNodes));
39914
+ if (i < nonEmptyGroups.length - 1) {
39915
+ let sep = dropdownSeparator();
39916
+ rendered.push(sep);
39917
+ separators.push(sep);
39918
+ }
39919
+ }
39920
+ function update(state) {
39921
+ let something = false, needSep = false;
39922
+ for (let i = 0; i < updates.length; i++) {
39923
+ let hasContent = updates[i](state);
39924
+ if (i)
39925
+ separators[i - 1].style.display = needSep && hasContent ? "" : "none";
39926
+ needSep = hasContent;
39927
+ if (hasContent)
39928
+ something = true;
39929
+ }
39930
+ return something;
39931
+ }
39932
+ return { dom: rendered, update };
39657
39933
  }
39658
39934
 
39659
39935
  function combineUpdates(updates, nodes) {
@@ -40542,78 +40818,37 @@ const tablePlugin = new Plugin({
40542
40818
  }
40543
40819
  });
40544
40820
 
40545
- const codeHighlightCache = new WeakMap();
40821
+ function decorationsForSpans(pos, spans) {
40822
+ return spans.map(({from, to, class: cls}) => Decoration.inline(pos + 1 + from, pos + 1 + to, {class: cls}))
40823
+ }
40546
40824
 
40547
- /**
40548
- * Convert an hljs-highlighted HTML string into a list of {from, to, class}
40549
- * ranges relative to the start of the original plain-text code, by building
40550
- * a detached element via innerHTML (not the global DOMParser class, whose
40551
- * separate parseFromString implementation is less consistently supported
40552
- * across environments than innerHTML) and walking its child nodes.
40553
- *
40554
- * @ignore
40555
- */
40556
- function highlightSpecs(code, language) {
40557
- let html;
40558
- try {
40559
- html = HighlightJS.highlight(code, {language, ignoreIllegals: true}).value;
40560
- } catch {
40561
- return []
40562
- }
40563
- const container = document.createElement('div');
40564
- container.innerHTML = html;
40565
- const specs = [];
40566
- let offset = 0;
40567
- function walk(parent) {
40568
- parent.childNodes.forEach((child) => {
40569
- if (child.nodeType === Node.TEXT_NODE) {
40570
- offset += child.textContent.length;
40571
- } else if (child.nodeType === Node.ELEMENT_NODE) {
40572
- const start = offset;
40573
- walk(child);
40574
- const cls = child.getAttribute('class');
40575
- if (cls) specs.push({from: start, to: offset, class: cls});
40576
- }
40577
- });
40578
- }
40579
- walk(container);
40580
- return specs
40825
+ // null for anything that isn't a highlightable code_block, so callers can
40826
+ // skip it (no cache entry, no decorations) with a single check.
40827
+ function highlightEntryFor(node) {
40828
+ const language = node.attrs.language;
40829
+ if (!language || !isRecognizedLanguage(language)) return null
40830
+ return {node, spans: highlightSpans(node.textContent, language)}
40581
40831
  }
40582
40832
 
40583
- /**
40584
- * Walk doc for code_block nodes with a registered language and build a
40585
- * DecorationSet highlighting each one, using a WeakMap cache keyed by node
40586
- * identity so unchanged blocks (same node reference across transactions,
40587
- * per ProseMirror's persistent-tree structural sharing) are never re-run
40588
- * through hljs.highlight().
40589
- *
40590
- * @ignore
40591
- */
40592
- function computeCodeHighlightDecorations(doc) {
40833
+ function computeAllHighlights(doc) {
40834
+ const cache = new Map();
40593
40835
  const decorations = [];
40594
40836
  doc.descendants((node, pos) => {
40595
40837
  if (node.type.name !== 'code_block') return
40596
- const language = node.attrs.language;
40597
- if (!language || !HighlightJS.getLanguage(language)) return
40598
- let specs = codeHighlightCache.get(node);
40599
- if (!specs) {
40600
- specs = highlightSpecs(node.textContent, language);
40601
- codeHighlightCache.set(node, specs);
40602
- }
40603
- specs.forEach(({from, to, class: cls}) => {
40604
- decorations.push(Decoration.inline(pos + 1 + from, pos + 1 + to, {class: cls}));
40605
- });
40838
+ const entry = highlightEntryFor(node);
40839
+ if (entry) {
40840
+ cache.set(pos, entry);
40841
+ decorations.push(...decorationsForSpans(pos, entry.spans));
40842
+ }
40606
40843
  return false
40607
40844
  });
40608
- return DecorationSet.create(doc, decorations)
40845
+ return {cache, decorations: DecorationSet.create(doc, decorations)}
40609
40846
  }
40610
40847
 
40611
40848
  /**
40612
40849
  * Walk cur's children, comparing against old's, skipping any subtree that's
40613
- * the same node object as before (ProseMirror's persistent-tree structural
40614
- * sharing means an unchanged subtree is reference-identical). Only visits
40615
- * nodes in the changed region, so cost is bounded by how much of the doc
40616
- * actually changed rather than the whole document.
40850
+ * the same node object as before. Bounds cost to the changed region rather
40851
+ * than the whole document.
40617
40852
  *
40618
40853
  * @ignore
40619
40854
  */
@@ -40633,149 +40868,73 @@ function changedDescendants(old, cur, offset, f) {
40633
40868
  }
40634
40869
  }
40635
40870
 
40636
- /**
40637
- * The codeHighlightPlugin applies syntax-highlighting decorations to
40638
- * code_block nodes whose language is registered in highlighting.js's hljs
40639
- * instance. Only registered via markupSetup() when the highlightCode
40640
- * behavior setting is on.
40641
- *
40642
- * @ignore
40643
- */
40871
+ // Carries cache entries forward by position + Node.eq, not object identity;
40872
+ // changedDescendants then fills in anything new or actually changed.
40873
+ function updateHighlights(tr, {cache}) {
40874
+ const nextCache = new Map();
40875
+ const decorations = [];
40876
+ const carryForward = (pos, entry) => {
40877
+ nextCache.set(pos, entry);
40878
+ decorations.push(...decorationsForSpans(pos, entry.spans));
40879
+ };
40880
+
40881
+ cache.forEach(({node, spans}, pos) => {
40882
+ const mapped = tr.mapping.mapResult(pos);
40883
+ if (mapped.deleted) return
40884
+ const newNode = tr.doc.nodeAt(mapped.pos);
40885
+ if (newNode && newNode.eq(node)) carryForward(mapped.pos, {node: newNode, spans});
40886
+ });
40887
+
40888
+ changedDescendants(tr.before, tr.doc, 0, (node, pos) => {
40889
+ if (node.type.name !== 'code_block' || nextCache.has(pos)) return
40890
+ const entry = highlightEntryFor(node);
40891
+ if (entry) carryForward(pos, entry);
40892
+ });
40893
+
40894
+ return {cache: nextCache, decorations: DecorationSet.create(tr.doc, decorations)}
40895
+ }
40896
+
40897
+ // Syntax-highlighting decorations for code_block nodes with a registered
40898
+ // language. Only registered via markupSetup() when highlightCode is on.
40644
40899
  const codeHighlightPlugin = new Plugin({
40645
40900
  state: {
40646
40901
  init(_, {doc}) {
40647
- return computeCodeHighlightDecorations(doc)
40902
+ return computeAllHighlights(doc)
40648
40903
  },
40649
- apply(tr, set) {
40650
- if (!tr.docChanged) return set
40651
- let touchedCodeBlock = false;
40652
- const checkCodeBlock = (node) => {
40653
- if (node.type.name === 'code_block') touchedCodeBlock = true;
40654
- };
40655
- // Check both directions: a code_block added/changed in the new doc, and one
40656
- // removed (e.g. by undoing a paragraph->code_block conversion) from the old
40657
- // doc. changedDescendants(old, cur, ...) only ever visits cur's children, so
40658
- // catching removal requires calling it again with old/new swapped.
40659
- changedDescendants(tr.before, tr.doc, 0, checkCodeBlock);
40660
- if (!touchedCodeBlock) changedDescendants(tr.doc, tr.before, 0, checkCodeBlock);
40661
- if (!touchedCodeBlock) return set.map(tr.mapping, tr.doc)
40662
- return computeCodeHighlightDecorations(tr.doc)
40904
+ apply(tr, value) {
40905
+ return tr.docChanged ? updateHighlights(tr, value) : value
40663
40906
  }
40664
40907
  },
40665
40908
  props: {
40666
- decorations(state) { return codeHighlightPlugin.getState(state) }
40909
+ decorations(state) { return codeHighlightPlugin.getState(state).decorations }
40667
40910
  }
40668
40911
  });
40669
40912
 
40670
40913
  /**
40671
- * Approximate rendered height of the language overlay label (font-size 0.75rem +
40672
- * padding 2px 6px, styles/markup.css). Only used for the room-above check below —
40673
- * doesn't need to be pixel-exact, just enough to decide which side to attach to.
40674
- *
40675
- * @ignore
40676
- */
40677
- const CODE_LANGUAGE_OVERLAY_HEIGHT = 24;
40678
-
40679
- /**
40680
- * Whether there's room above `preDOM` (the code_block's own <pre> element) to show
40681
- * the language overlay label there without it being pushed above the toolbar or
40682
- * off-screen — if not, it should attach below instead. `view.nodeDOM` is used
40683
- * read-only here (getBoundingClientRect only); mutating its result is what caused
40684
- * the CPU-loop regression fixed earlier, so this must never write to preDOM.
40685
- *
40686
- * @ignore
40687
- */
40688
- function hasRoomAboveOverlay(view, preDOM) {
40689
- if (!preDOM) return true
40690
- const preRect = preDOM.getBoundingClientRect();
40691
- const toolbarRect = getToolbar(view)?.getBoundingClientRect();
40692
- const minTop = (toolbarRect?.bottom ?? 0) + CODE_LANGUAGE_OVERLAY_HEIGHT;
40693
- return preRect.top >= minTop
40694
- }
40695
-
40696
- /**
40697
- * Compute the semi-transparent "Language: <name>" widget Decoration for the
40698
- * selected code_block, if any. Recomputed on every transaction — this is only
40699
- * a selection lookup plus a string, not a doc walk, so unlike
40700
- * computeCodeHighlightDecorations there's no need to cache or gate on
40701
- * tr.docChanged.
40702
- *
40703
- * @ignore
40704
- */
40705
- function computeCodeLanguageOverlayDecorations(state, languageDialog) {
40706
- const info = codeLanguageOverlayInfo(state);
40707
- if (!info) return DecorationSet.empty
40708
- // An empty code_block's only content would become this widget's contentEditable=false
40709
- // button — the exact "non-editable content alone in a textblock" case prosemirror-view
40710
- // itself flags as fragile in Safari/Chrome (addTextblockHacks, working around Safari
40711
- // bug #1165 and Chrome bug #1152). Skip the overlay until there's actual code; there's
40712
- // nothing useful to show a language badge over yet anyway.
40713
- const found = codeBlockAtSelection(state);
40714
- if (!found || found.node.content.size === 0) return DecorationSet.empty
40715
- const widget = Decoration.widget(info.pos, (view) => {
40716
- const button = document.createElement('button');
40717
- button.type = 'button';
40718
- button.className = prefix + '-code-language-overlay';
40719
- if (!hasRoomAboveOverlay(view, view.nodeDOM(found.pos))) {
40720
- button.classList.add(prefix + '-code-language-overlay-below');
40721
- }
40722
- button.textContent = info.label;
40723
- // Without this, the button is ambiguous to the browser's native cursor placement
40724
- // as part of the code_block's editable text flow.
40725
- button.contentEditable = 'false';
40726
- button.addEventListener('mousedown', (e) => {
40727
- e.preventDefault();
40728
- const found = codeBlockAtSelection(view.state);
40729
- languageDialog.open(view, found?.node.attrs.language ?? '', (entered) => {
40730
- setCodeLanguageCommand(entered ? entered : null)(view.state, view.dispatch, view);
40731
- });
40732
- });
40733
- return button
40734
- }, {
40735
- side: 1,
40736
- // By default the cursor at the widget's position is strictly kept on the
40737
- // `side` indicated above, and per prosemirror-view's own docs "keyboard
40738
- // cursor motion will not, without further custom handling, visit both
40739
- // sides of the widget" — which is what broke placing/moving the cursor to
40740
- // a code_block's start. relaxedSide lets the DOM selection land on either
40741
- // side instead of being force-pinned to one.
40742
- relaxedSide: true,
40743
- // Keyed so ProseMirror reuses the existing button DOM node (and its click listener)
40744
- // across transactions unrelated to this block, instead of destroying and rebuilding it
40745
- // on every single transaction while a code block is selected (WidgetType.eq() only
40746
- // short-circuits reuse on a spec.key match). The key includes pos and label — not just
40747
- // a static string — so a genuinely different block or language change still gets a
40748
- // fresh toDOM call rather than silently reusing stale button text.
40749
- key: `code-language-overlay-${info.pos}-${info.label}`
40750
- });
40751
- return DecorationSet.create(state.doc, [widget])
40752
- }
40753
-
40754
- /**
40755
- * Build the plugin that shows the selected code_block's language overlay.
40756
- * A factory (not a module-level singleton like codeHighlightPlugin) because it
40757
- * owns a LanguageDialogItem bound to this editor instance's `config` — sharing
40758
- * one across multiple `<markup-editor>` instances on the same page would let
40759
- * one instance's dialog state stomp on another's.
40914
+ * Return the plugin that shows the selected code_block's language tab (owned
40915
+ * by its CodeView NodeView, ../nodeview/codeview.js).
40760
40916
  *
40761
40917
  * @ignore
40762
40918
  */
40763
- function codeLanguageOverlayPlugin(config) {
40764
- const languageDialog = new LanguageDialogItem(config);
40765
- const thePlugin = new Plugin({
40766
- state: {
40767
- init() {
40768
- return DecorationSet.empty
40769
- },
40770
- apply(tr, set, oldState, newState) {
40771
- return computeCodeLanguageOverlayDecorations(newState, languageDialog)
40772
- }
40773
- },
40774
- props: {
40775
- decorations(state) { return thePlugin.getState(state) }
40919
+ function codeLanguageTabPlugin() {
40920
+ return new Plugin({
40921
+ view(editorView) {
40922
+ let activeCodeView = null;
40923
+ // A pure selection-only transaction never calls a NodeView's own
40924
+ // update(), so this reaches the CodeView directly via view.nodeDOM(pos)
40925
+ // instead.
40926
+ const sync = (view) => {
40927
+ const found = codeBlockAtSelection(view.state);
40928
+ const nextCodeView = found ? view.nodeDOM(found.pos)?.codeView : null;
40929
+ if (nextCodeView === activeCodeView) return
40930
+ activeCodeView?.setActive(false);
40931
+ nextCodeView?.setActive(true);
40932
+ activeCodeView = nextCodeView;
40933
+ };
40934
+ sync(editorView);
40935
+ return { update: sync }
40776
40936
  }
40777
- });
40778
- return thePlugin
40937
+ })
40779
40938
  }
40780
40939
 
40781
40940
  const searchModePlugin = new Plugin({
@@ -40924,10 +41083,10 @@ function markupSetup(config, schema) {
40924
41083
  plugins.push(tablePlugin);
40925
41084
 
40926
41085
  // Add the plugins that highlight code blocks and show the selected block's
40927
- // language overlay, if enabled in behavior config
41086
+ // language tab, if enabled in behavior config
40928
41087
  if (config.behavior.highlightCode) {
40929
41088
  plugins.push(codeHighlightPlugin);
40930
- plugins.push(codeLanguageOverlayPlugin(config));
41089
+ plugins.push(codeLanguageTabPlugin());
40931
41090
  }
40932
41091
 
40933
41092
  // Add the plugin that handles placeholder display for an empty document, as passed in config
@@ -41562,6 +41721,102 @@ class DivView {
41562
41721
 
41563
41722
  }
41564
41723
 
41724
+ // Approximate rendered height of the language tab (font-size 0.75rem +
41725
+ // padding 2px 6px, styles/markup.css) — only used for the room-above check below,
41726
+ // doesn't need to be pixel-exact.
41727
+ const CODE_LANGUAGE_TAB_HEIGHT = 24;
41728
+
41729
+ /**
41730
+ * NodeView for code_block. The language tab is a DOM sibling of contentDOM,
41731
+ * appended/removed by setActive. No selectNode/deselectNode — code_block
41732
+ * selection is a TextSelection inside its content, never a NodeSelection.
41733
+ *
41734
+ * Stores `view` directly rather than this codebase's activeView() lookup
41735
+ * (imageview.js): the tab's mousedown only ever fires from this view's own
41736
+ * DOM, so there's no cross-instance ambiguity to resolve.
41737
+ *
41738
+ * node.attrs.language only ever affects the tab's label and contentDOM's
41739
+ * class — never which code_block this applies to or how it's structured.
41740
+ */
41741
+ class CodeView {
41742
+ constructor(node, view, getPos, languageDialog) {
41743
+ this.view = view;
41744
+ this.languageDialog = languageDialog;
41745
+ this.dom = document.createElement('pre');
41746
+ this.contentDOM = document.createElement('code');
41747
+ this.dom.appendChild(this.contentDOM);
41748
+ // codeLanguageTabPlugin (setup/index.js) reads this via
41749
+ // view.nodeDOM(pos) to call setActive on the right instance.
41750
+ this.dom.codeView = this;
41751
+ this.syncLanguageClass(node);
41752
+ this.tab = this.buildTab();
41753
+ this.setTabLabel(node);
41754
+ }
41755
+
41756
+ update(node) {
41757
+ if (node.type.name !== 'code_block') return false
41758
+ this.syncLanguageClass(node);
41759
+ this.setTabLabel(node);
41760
+ return true
41761
+ }
41762
+
41763
+ // ProseMirror's default ignoreMutation is `!contentDOM && mutation.type !=
41764
+ // "selection"`. Override here to avoid losing tab during mutation.
41765
+ ignoreMutation(mutation) {
41766
+ return mutation.type !== 'selection' && !this.contentDOM.contains(mutation.target)
41767
+ }
41768
+
41769
+ // Whether there's room above this.dom to show the language tab
41770
+ // without it being pushed above the toolbar or off-screen.
41771
+ hasRoomAbove() {
41772
+ if (!this.dom) return true
41773
+ const preRect = this.dom.getBoundingClientRect();
41774
+ const toolbarRect = getToolbar(this.view)?.getBoundingClientRect();
41775
+ const minTop = (toolbarRect?.bottom ?? 0) + CODE_LANGUAGE_TAB_HEIGHT;
41776
+ return preRect.top >= minTop
41777
+ }
41778
+
41779
+ // The tab sits outside contentDOM, similar to the way imageView works
41780
+ // for handles.
41781
+ setActive(isActive) {
41782
+ if (isActive && !this.tab.isConnected) {
41783
+ this.tab.classList.toggle(prefix + '-code-language-tab-below', !this.hasRoomAbove());
41784
+ this.dom.appendChild(this.tab);
41785
+ } else if (!isActive && this.tab.isConnected) {
41786
+ this.dom.removeChild(this.tab);
41787
+ }
41788
+ }
41789
+
41790
+ destroy() {
41791
+ this.dom.codeView = null;
41792
+ }
41793
+
41794
+ syncLanguageClass(node) {
41795
+ const language = node.attrs.language;
41796
+ this.contentDOM.className = language ? `language-${language}` : '';
41797
+ }
41798
+
41799
+ setTabLabel(node) {
41800
+ const language = node.attrs.language;
41801
+ this.tab.textContent = language ? `Language: ${language.toLowerCase()}` : 'Language: none';
41802
+ }
41803
+
41804
+ buildTab() {
41805
+ const button = document.createElement('button');
41806
+ button.type = 'button';
41807
+ button.className = prefix + '-code-language-tab';
41808
+ button.contentEditable = 'false';
41809
+ button.addEventListener('mousedown', (e) => {
41810
+ e.preventDefault();
41811
+ const found = codeBlockAtSelection(this.view.state);
41812
+ this.languageDialog.open(this.view, found?.node.attrs.language ?? '', (entered) => {
41813
+ setCodeLanguageCommand(entered ? entered : null)(this.view.state, this.view.dispatch, this.view);
41814
+ });
41815
+ });
41816
+ return button
41817
+ }
41818
+ }
41819
+
41565
41820
  /**
41566
41821
  * The MessageHandler receives `postMessage` from the MarkupEditor as the document state changes.
41567
41822
  *
@@ -42004,10 +42259,19 @@ class MarkupEditor {
42004
42259
  this.config.delegate = getDelegate(delegate);
42005
42260
  }
42006
42261
 
42262
+ // One dialog per editor instance, not per code_block — sharing one across
42263
+ // multiple <markup-editor> instances on the same page would let one
42264
+ // instance's dialog state stomp on another's. In contrast to the other
42265
+ // dialogs, like LinkDialog, the languageDialog can be initiated from
42266
+ // the tab that shows when it is selected, not just the menu item. This
42267
+ // necessitates creating it here so we can pass it to the CodeView.
42268
+ const languageDialog = new LanguageDialogItem(this.config);
42269
+ MU.languageDialog = languageDialog;
42270
+
42007
42271
  // Create the EditorView for this MarkupEditor
42008
42272
  this.view = new EditorView(this.element, {
42009
42273
  state: EditorState.create({
42010
- // For the MarkupEditor, we can just use the editor element.
42274
+ // For the MarkupEditor, we can just use the editor element.
42011
42275
  // There is no need to use a separate content element.
42012
42276
  doc: DOMParser.fromSchema(schema).parse(this.element),
42013
42277
  plugins: markupSetup(this.config, schema)
@@ -42016,6 +42280,7 @@ class MarkupEditor {
42016
42280
  link(node, view, getPos) { return new LinkView(node, view, getPos) },
42017
42281
  image(node, view, getPos) { return new ImageView(node, view, getPos) },
42018
42282
  div(node, view, getPos) { return new DivView(node, view, getPos) },
42283
+ code_block(node, view, getPos) { return new CodeView(node, view, getPos, languageDialog) },
42019
42284
  },
42020
42285
  // All text input makes callbacks to indicate the document state has changed.
42021
42286
  // For history, used handleTextInput, but that fires *before* input happens.
@@ -42157,6 +42422,7 @@ const MU = {
42157
42422
  cancelSearch,
42158
42423
  canUndo,
42159
42424
  canRedo,
42425
+ CodeView,
42160
42426
  consoleLog,
42161
42427
  cutImage,
42162
42428
  deactivateSearch,
@@ -42193,6 +42459,7 @@ const MU = {
42193
42459
  removeAllDivs,
42194
42460
  removeButton,
42195
42461
  removeDiv,
42462
+ reportError,
42196
42463
  resetSelection,
42197
42464
  savedDataImage,
42198
42465
  searchFor,
@@ -42203,7 +42470,7 @@ const MU = {
42203
42470
  setToolbarVisible,
42204
42471
  setTopLevelAttributes,
42205
42472
  testBlockquoteEnter,
42206
- testCodeLanguageOverlayInfo,
42473
+ testCodeLanguageTabInfo,
42207
42474
  testExtractContents,
42208
42475
  testIsRecognizedLanguage,
42209
42476
  testListEnter,
@@ -42236,8 +42503,9 @@ const MU = {
42236
42503
  registerDelegate,
42237
42504
  registerMessageHandler,
42238
42505
  registerPlugin,
42239
- getPluginManifest,
42240
- invokePlugin,
42506
+ unregisterPlugin,
42507
+ getPlugin,
42508
+ getPlugins,
42241
42509
  };
42242
42510
 
42243
42511
  sheet.media.mediaText = '(prefers-color-scheme: dark)';
@@ -42254,22 +42522,20 @@ sheet.media.mediaText = '(prefers-color-scheme: dark)';
42254
42522
  *
42255
42523
  * @param {string[]} pluginPaths Resolved paths to plugin modules.
42256
42524
  * @param {object|null} delegate A MarkupDelegate instance (may be null/undefined).
42257
- * @param {function} [importFn] Optional import function; defaults to the native dynamic
42258
- * import. Provided for testing.
42259
42525
  * @returns {Promise<void>}
42260
42526
  */
42261
- async function loadPlugins(pluginPaths, delegate, importFn = (path) => import(path)) {
42527
+ async function loadPlugins(pluginPaths, delegate) {
42262
42528
  if (!pluginPaths || pluginPaths.length === 0) return
42263
- const before = new Set(MU.getPluginManifest().map(m => m.name));
42529
+ const before = new Set(MU.getPlugins().map(m => m.name));
42264
42530
  await Promise.all(
42265
42531
  pluginPaths.map(path =>
42266
- importFn(path).catch(err => {
42532
+ import(path).catch(err => {
42267
42533
  console.error('Plugin load failed:', path, err);
42268
42534
  return null
42269
42535
  })
42270
42536
  )
42271
42537
  );
42272
- const after = MU.getPluginManifest();
42538
+ const after = MU.getPlugins();
42273
42539
  const newManifests = after.filter(m => !before.has(m.name));
42274
42540
  delegate?.markupPluginsDidLoad && delegate.markupPluginsDidLoad(newManifests);
42275
42541
  }
@@ -42330,7 +42596,7 @@ class MarkupEditorElement extends HTMLElement {
42330
42596
  const pluginPaths = pluginsAttr ? JSON.parse(pluginsAttr) : [];
42331
42597
  const delegate = this.editor.config?.delegate;
42332
42598
  await loadPlugins(pluginPaths, delegate);
42333
- const manifests = MU.getPluginManifest();
42599
+ const manifests = MU.getPlugins();
42334
42600
  if (manifests.length > 0) {
42335
42601
  this.editor.messageHandler.postMessage(
42336
42602
  JSON.stringify({ messageType: 'markupPluginsDidLoad', plugins: manifests })
@@ -42507,4 +42773,4 @@ class MarkupEditorElement extends HTMLElement {
42507
42773
  // Let the browser know about the custom element
42508
42774
  customElements.define('markup-editor', MarkupEditorElement);
42509
42775
 
42510
- export { ContentMatch, DOMParser, DOMSerializer, Fragment, MU, Mark, MarkType, Node$1 as Node, NodeRange, NodeType$1 as NodeType, ReplaceError, ResolvedPos, Schema, Slice, loadPlugins };
42776
+ export { AllSelection, ContentMatch, DOMParser, DOMSerializer, Decoration, DecorationSet, EditorState, EditorView, Fragment, MU, Mark, MarkType, Node$1 as Node, NodeRange, NodeSelection, NodeType$1 as NodeType, Plugin, PluginKey, ReplaceError, ResolvedPos, Schema, Selection, SelectionRange, Slice, TextSelection, Transaction, __endComposition, __parseFromClipboard, loadPlugins };