markupeditor 0.9.27 → 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.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: 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,10 +34034,9 @@ 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:
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:
33799
34040
  * <lowercase name>" or "Language: none" — display-only, the underlying
33800
34041
  * `language` attribute value is untouched.
33801
34042
  *
@@ -33803,7 +34044,7 @@ function codeBlockAtSelection(state) {
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;
@@ -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,
@@ -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);
@@ -40579,78 +40818,37 @@ const tablePlugin = new Plugin({
40579
40818
  }
40580
40819
  });
40581
40820
 
40582
- 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
+ }
40583
40824
 
40584
- /**
40585
- * Convert an hljs-highlighted HTML string into a list of {from, to, class}
40586
- * ranges relative to the start of the original plain-text code, by building
40587
- * a detached element via innerHTML (not the global DOMParser class, whose
40588
- * separate parseFromString implementation is less consistently supported
40589
- * across environments than innerHTML) and walking its child nodes.
40590
- *
40591
- * @ignore
40592
- */
40593
- function highlightSpecs(code, language) {
40594
- let html;
40595
- try {
40596
- html = HighlightJS.highlight(code, {language, ignoreIllegals: true}).value;
40597
- } catch {
40598
- return []
40599
- }
40600
- const container = document.createElement('div');
40601
- container.innerHTML = html;
40602
- const specs = [];
40603
- let offset = 0;
40604
- function walk(parent) {
40605
- parent.childNodes.forEach((child) => {
40606
- if (child.nodeType === Node.TEXT_NODE) {
40607
- offset += child.textContent.length;
40608
- } else if (child.nodeType === Node.ELEMENT_NODE) {
40609
- const start = offset;
40610
- walk(child);
40611
- const cls = child.getAttribute('class');
40612
- if (cls) specs.push({from: start, to: offset, class: cls});
40613
- }
40614
- });
40615
- }
40616
- walk(container);
40617
- 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)}
40618
40831
  }
40619
40832
 
40620
- /**
40621
- * Walk doc for code_block nodes with a registered language and build a
40622
- * DecorationSet highlighting each one, using a WeakMap cache keyed by node
40623
- * identity so unchanged blocks (same node reference across transactions,
40624
- * per ProseMirror's persistent-tree structural sharing) are never re-run
40625
- * through hljs.highlight().
40626
- *
40627
- * @ignore
40628
- */
40629
- function computeCodeHighlightDecorations(doc) {
40833
+ function computeAllHighlights(doc) {
40834
+ const cache = new Map();
40630
40835
  const decorations = [];
40631
40836
  doc.descendants((node, pos) => {
40632
40837
  if (node.type.name !== 'code_block') return
40633
- const language = node.attrs.language;
40634
- if (!language || !HighlightJS.getLanguage(language)) return
40635
- let specs = codeHighlightCache.get(node);
40636
- if (!specs) {
40637
- specs = highlightSpecs(node.textContent, language);
40638
- codeHighlightCache.set(node, specs);
40639
- }
40640
- specs.forEach(({from, to, class: cls}) => {
40641
- decorations.push(Decoration.inline(pos + 1 + from, pos + 1 + to, {class: cls}));
40642
- });
40838
+ const entry = highlightEntryFor(node);
40839
+ if (entry) {
40840
+ cache.set(pos, entry);
40841
+ decorations.push(...decorationsForSpans(pos, entry.spans));
40842
+ }
40643
40843
  return false
40644
40844
  });
40645
- return DecorationSet.create(doc, decorations)
40845
+ return {cache, decorations: DecorationSet.create(doc, decorations)}
40646
40846
  }
40647
40847
 
40648
40848
  /**
40649
40849
  * Walk cur's children, comparing against old's, skipping any subtree that's
40650
- * the same node object as before (ProseMirror's persistent-tree structural
40651
- * sharing means an unchanged subtree is reference-identical). Only visits
40652
- * nodes in the changed region, so cost is bounded by how much of the doc
40653
- * 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.
40654
40852
  *
40655
40853
  * @ignore
40656
40854
  */
@@ -40670,149 +40868,73 @@ function changedDescendants(old, cur, offset, f) {
40670
40868
  }
40671
40869
  }
40672
40870
 
40673
- /**
40674
- * The codeHighlightPlugin applies syntax-highlighting decorations to
40675
- * code_block nodes whose language is registered in highlighting.js's hljs
40676
- * instance. Only registered via markupSetup() when the highlightCode
40677
- * behavior setting is on.
40678
- *
40679
- * @ignore
40680
- */
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.
40681
40899
  const codeHighlightPlugin = new Plugin({
40682
40900
  state: {
40683
40901
  init(_, {doc}) {
40684
- return computeCodeHighlightDecorations(doc)
40902
+ return computeAllHighlights(doc)
40685
40903
  },
40686
- apply(tr, set) {
40687
- if (!tr.docChanged) return set
40688
- let touchedCodeBlock = false;
40689
- const checkCodeBlock = (node) => {
40690
- if (node.type.name === 'code_block') touchedCodeBlock = true;
40691
- };
40692
- // Check both directions: a code_block added/changed in the new doc, and one
40693
- // removed (e.g. by undoing a paragraph->code_block conversion) from the old
40694
- // doc. changedDescendants(old, cur, ...) only ever visits cur's children, so
40695
- // catching removal requires calling it again with old/new swapped.
40696
- changedDescendants(tr.before, tr.doc, 0, checkCodeBlock);
40697
- if (!touchedCodeBlock) changedDescendants(tr.doc, tr.before, 0, checkCodeBlock);
40698
- if (!touchedCodeBlock) return set.map(tr.mapping, tr.doc)
40699
- return computeCodeHighlightDecorations(tr.doc)
40904
+ apply(tr, value) {
40905
+ return tr.docChanged ? updateHighlights(tr, value) : value
40700
40906
  }
40701
40907
  },
40702
40908
  props: {
40703
- decorations(state) { return codeHighlightPlugin.getState(state) }
40909
+ decorations(state) { return codeHighlightPlugin.getState(state).decorations }
40704
40910
  }
40705
40911
  });
40706
40912
 
40707
40913
  /**
40708
- * Approximate rendered height of the language overlay label (font-size 0.75rem +
40709
- * padding 2px 6px, styles/markup.css). Only used for the room-above check below —
40710
- * doesn't need to be pixel-exact, just enough to decide which side to attach to.
40711
- *
40712
- * @ignore
40713
- */
40714
- const CODE_LANGUAGE_OVERLAY_HEIGHT = 24;
40715
-
40716
- /**
40717
- * Whether there's room above `preDOM` (the code_block's own <pre> element) to show
40718
- * the language overlay label there without it being pushed above the toolbar or
40719
- * off-screen — if not, it should attach below instead. `view.nodeDOM` is used
40720
- * read-only here (getBoundingClientRect only); mutating its result is what caused
40721
- * the CPU-loop regression fixed earlier, so this must never write to preDOM.
40722
- *
40723
- * @ignore
40724
- */
40725
- function hasRoomAboveOverlay(view, preDOM) {
40726
- if (!preDOM) return true
40727
- const preRect = preDOM.getBoundingClientRect();
40728
- const toolbarRect = getToolbar(view)?.getBoundingClientRect();
40729
- const minTop = (toolbarRect?.bottom ?? 0) + CODE_LANGUAGE_OVERLAY_HEIGHT;
40730
- return preRect.top >= minTop
40731
- }
40732
-
40733
- /**
40734
- * Compute the semi-transparent "Language: <name>" widget Decoration for the
40735
- * selected code_block, if any. Recomputed on every transaction — this is only
40736
- * a selection lookup plus a string, not a doc walk, so unlike
40737
- * computeCodeHighlightDecorations there's no need to cache or gate on
40738
- * tr.docChanged.
40739
- *
40740
- * @ignore
40741
- */
40742
- function computeCodeLanguageOverlayDecorations(state, languageDialog) {
40743
- const info = codeLanguageOverlayInfo(state);
40744
- if (!info) return DecorationSet.empty
40745
- // An empty code_block's only content would become this widget's contentEditable=false
40746
- // button — the exact "non-editable content alone in a textblock" case prosemirror-view
40747
- // itself flags as fragile in Safari/Chrome (addTextblockHacks, working around Safari
40748
- // bug #1165 and Chrome bug #1152). Skip the overlay until there's actual code; there's
40749
- // nothing useful to show a language badge over yet anyway.
40750
- const found = codeBlockAtSelection(state);
40751
- if (!found || found.node.content.size === 0) return DecorationSet.empty
40752
- const widget = Decoration.widget(info.pos, (view) => {
40753
- const button = document.createElement('button');
40754
- button.type = 'button';
40755
- button.className = prefix + '-code-language-overlay';
40756
- if (!hasRoomAboveOverlay(view, view.nodeDOM(found.pos))) {
40757
- button.classList.add(prefix + '-code-language-overlay-below');
40758
- }
40759
- button.textContent = info.label;
40760
- // Without this, the button is ambiguous to the browser's native cursor placement
40761
- // as part of the code_block's editable text flow.
40762
- button.contentEditable = 'false';
40763
- button.addEventListener('mousedown', (e) => {
40764
- e.preventDefault();
40765
- const found = codeBlockAtSelection(view.state);
40766
- languageDialog.open(view, found?.node.attrs.language ?? '', (entered) => {
40767
- setCodeLanguageCommand(entered ? entered : null)(view.state, view.dispatch, view);
40768
- });
40769
- });
40770
- return button
40771
- }, {
40772
- side: 1,
40773
- // By default the cursor at the widget's position is strictly kept on the
40774
- // `side` indicated above, and per prosemirror-view's own docs "keyboard
40775
- // cursor motion will not, without further custom handling, visit both
40776
- // sides of the widget" — which is what broke placing/moving the cursor to
40777
- // a code_block's start. relaxedSide lets the DOM selection land on either
40778
- // side instead of being force-pinned to one.
40779
- relaxedSide: true,
40780
- // Keyed so ProseMirror reuses the existing button DOM node (and its click listener)
40781
- // across transactions unrelated to this block, instead of destroying and rebuilding it
40782
- // on every single transaction while a code block is selected (WidgetType.eq() only
40783
- // short-circuits reuse on a spec.key match). The key includes pos and label — not just
40784
- // a static string — so a genuinely different block or language change still gets a
40785
- // fresh toDOM call rather than silently reusing stale button text.
40786
- key: `code-language-overlay-${info.pos}-${info.label}`
40787
- });
40788
- return DecorationSet.create(state.doc, [widget])
40789
- }
40790
-
40791
- /**
40792
- * Build the plugin that shows the selected code_block's language overlay.
40793
- * A factory (not a module-level singleton like codeHighlightPlugin) because it
40794
- * owns a LanguageDialogItem bound to this editor instance's `config` — sharing
40795
- * one across multiple `<markup-editor>` instances on the same page would let
40796
- * 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).
40797
40916
  *
40798
40917
  * @ignore
40799
40918
  */
40800
- function codeLanguageOverlayPlugin(config) {
40801
- const languageDialog = new LanguageDialogItem(config);
40802
- const thePlugin = new Plugin({
40803
- state: {
40804
- init() {
40805
- return DecorationSet.empty
40806
- },
40807
- apply(tr, set, oldState, newState) {
40808
- return computeCodeLanguageOverlayDecorations(newState, languageDialog)
40809
- }
40810
- },
40811
- props: {
40812
- 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 }
40813
40936
  }
40814
- });
40815
- return thePlugin
40937
+ })
40816
40938
  }
40817
40939
 
40818
40940
  const searchModePlugin = new Plugin({
@@ -40961,10 +41083,10 @@ function markupSetup(config, schema) {
40961
41083
  plugins.push(tablePlugin);
40962
41084
 
40963
41085
  // Add the plugins that highlight code blocks and show the selected block's
40964
- // language overlay, if enabled in behavior config
41086
+ // language tab, if enabled in behavior config
40965
41087
  if (config.behavior.highlightCode) {
40966
41088
  plugins.push(codeHighlightPlugin);
40967
- plugins.push(codeLanguageOverlayPlugin(config));
41089
+ plugins.push(codeLanguageTabPlugin());
40968
41090
  }
40969
41091
 
40970
41092
  // Add the plugin that handles placeholder display for an empty document, as passed in config
@@ -41599,6 +41721,102 @@ class DivView {
41599
41721
 
41600
41722
  }
41601
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
+
41602
41820
  /**
41603
41821
  * The MessageHandler receives `postMessage` from the MarkupEditor as the document state changes.
41604
41822
  *
@@ -42041,10 +42259,19 @@ class MarkupEditor {
42041
42259
  this.config.delegate = getDelegate(delegate);
42042
42260
  }
42043
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
+
42044
42271
  // Create the EditorView for this MarkupEditor
42045
42272
  this.view = new EditorView(this.element, {
42046
42273
  state: EditorState.create({
42047
- // For the MarkupEditor, we can just use the editor element.
42274
+ // For the MarkupEditor, we can just use the editor element.
42048
42275
  // There is no need to use a separate content element.
42049
42276
  doc: DOMParser.fromSchema(schema).parse(this.element),
42050
42277
  plugins: markupSetup(this.config, schema)
@@ -42053,6 +42280,7 @@ class MarkupEditor {
42053
42280
  link(node, view, getPos) { return new LinkView(node, view, getPos) },
42054
42281
  image(node, view, getPos) { return new ImageView(node, view, getPos) },
42055
42282
  div(node, view, getPos) { return new DivView(node, view, getPos) },
42283
+ code_block(node, view, getPos) { return new CodeView(node, view, getPos, languageDialog) },
42056
42284
  },
42057
42285
  // All text input makes callbacks to indicate the document state has changed.
42058
42286
  // For history, used handleTextInput, but that fires *before* input happens.
@@ -42194,6 +42422,7 @@ const MU = {
42194
42422
  cancelSearch,
42195
42423
  canUndo,
42196
42424
  canRedo,
42425
+ CodeView,
42197
42426
  consoleLog,
42198
42427
  cutImage,
42199
42428
  deactivateSearch,
@@ -42230,6 +42459,7 @@ const MU = {
42230
42459
  removeAllDivs,
42231
42460
  removeButton,
42232
42461
  removeDiv,
42462
+ reportError,
42233
42463
  resetSelection,
42234
42464
  savedDataImage,
42235
42465
  searchFor,
@@ -42240,7 +42470,7 @@ const MU = {
42240
42470
  setToolbarVisible,
42241
42471
  setTopLevelAttributes,
42242
42472
  testBlockquoteEnter,
42243
- testCodeLanguageOverlayInfo,
42473
+ testCodeLanguageTabInfo,
42244
42474
  testExtractContents,
42245
42475
  testIsRecognizedLanguage,
42246
42476
  testListEnter,
@@ -42273,8 +42503,9 @@ const MU = {
42273
42503
  registerDelegate,
42274
42504
  registerMessageHandler,
42275
42505
  registerPlugin,
42276
- getPluginManifest,
42277
- invokePlugin,
42506
+ unregisterPlugin,
42507
+ getPlugin,
42508
+ getPlugins,
42278
42509
  };
42279
42510
 
42280
42511
  sheet.media.mediaText = '(prefers-color-scheme: dark)';
@@ -42291,22 +42522,20 @@ sheet.media.mediaText = '(prefers-color-scheme: dark)';
42291
42522
  *
42292
42523
  * @param {string[]} pluginPaths Resolved paths to plugin modules.
42293
42524
  * @param {object|null} delegate A MarkupDelegate instance (may be null/undefined).
42294
- * @param {function} [importFn] Optional import function; defaults to the native dynamic
42295
- * import. Provided for testing.
42296
42525
  * @returns {Promise<void>}
42297
42526
  */
42298
- async function loadPlugins(pluginPaths, delegate, importFn = (path) => import(path)) {
42527
+ async function loadPlugins(pluginPaths, delegate) {
42299
42528
  if (!pluginPaths || pluginPaths.length === 0) return
42300
- const before = new Set(MU.getPluginManifest().map(m => m.name));
42529
+ const before = new Set(MU.getPlugins().map(m => m.name));
42301
42530
  await Promise.all(
42302
42531
  pluginPaths.map(path =>
42303
- importFn(path).catch(err => {
42532
+ import(path).catch(err => {
42304
42533
  console.error('Plugin load failed:', path, err);
42305
42534
  return null
42306
42535
  })
42307
42536
  )
42308
42537
  );
42309
- const after = MU.getPluginManifest();
42538
+ const after = MU.getPlugins();
42310
42539
  const newManifests = after.filter(m => !before.has(m.name));
42311
42540
  delegate?.markupPluginsDidLoad && delegate.markupPluginsDidLoad(newManifests);
42312
42541
  }
@@ -42367,7 +42596,7 @@ class MarkupEditorElement extends HTMLElement {
42367
42596
  const pluginPaths = pluginsAttr ? JSON.parse(pluginsAttr) : [];
42368
42597
  const delegate = this.editor.config?.delegate;
42369
42598
  await loadPlugins(pluginPaths, delegate);
42370
- const manifests = MU.getPluginManifest();
42599
+ const manifests = MU.getPlugins();
42371
42600
  if (manifests.length > 0) {
42372
42601
  this.editor.messageHandler.postMessage(
42373
42602
  JSON.stringify({ messageType: 'markupPluginsDidLoad', plugins: manifests })
@@ -42544,4 +42773,4 @@ class MarkupEditorElement extends HTMLElement {
42544
42773
  // Let the browser know about the custom element
42545
42774
  customElements.define('markup-editor', MarkupEditorElement);
42546
42775
 
42547
- 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markupeditor",
3
- "version": "0.9.27",
3
+ "version": "0.9.28",
4
4
  "description": "A web component and API for WYSIWYG HTML editing.",
5
5
  "keywords": [
6
6
  "wysiwyg",
package/styles/markup.css CHANGED
@@ -267,12 +267,12 @@ img {
267
267
 
268
268
  /* Scoped to :has() rather than styling pre unconditionally, since a code_block's
269
269
  <pre> should only become a positioning context when it's actually carrying the
270
- language overlay (highlightCode on, block selected) — not for every code block. */
271
- pre:has(.Markup-code-language-overlay) {
270
+ language tab (highlightCode on, block selected) — not for every code block. */
271
+ pre:has(.Markup-code-language-tab) {
272
272
  position: relative;
273
273
  }
274
274
 
275
- .Markup-code-language-overlay {
275
+ .Markup-code-language-tab {
276
276
  position: absolute;
277
277
  bottom: 100%;
278
278
  right: 0;
@@ -286,20 +286,21 @@ pre:has(.Markup-code-language-overlay) {
286
286
  background: var(--Markup-accent-color, blue);
287
287
  }
288
288
 
289
- .Markup-code-language-overlay:hover {
289
+ .Markup-code-language-tab:hover {
290
290
  opacity: 0.9;
291
291
  }
292
292
 
293
- /* Applied (setup/index.js, hasRoomAboveOverlay) when the code_block is too close to
294
- the top of the view for the label to fit above it — attach to the bottom instead. */
295
- .Markup-code-language-overlay-below {
293
+ /* Applied (setup/index.js, CodeView.hasRoomAbove) when the code_block is too close to
294
+ the top of the view for the tab to fit above it — attach to the bottom instead. */
295
+ .Markup-code-language-tab-below {
296
296
  bottom: auto;
297
297
  top: 100%;
298
298
  border-radius: 0 0 4px 4px;
299
299
  }
300
300
 
301
+ /* Matches .Markup-menuitem-active's selected-button color scheme. */
301
302
  @media (prefers-color-scheme: dark) {
302
- .Markup-code-language-overlay {
303
+ .Markup-code-language-tab {
303
304
  background: var(--Markup-accent-color, lightblue);
304
305
  color: black;
305
306
  }
@@ -520,25 +520,25 @@
520
520
  }
521
521
 
522
522
  .Markup-prompt-link {
523
- height: 96px;
523
+ height: calc(68px + var(--Markup-button-size, 28px)); /* 96 px by default */
524
524
  width: 300px;
525
525
  }
526
526
 
527
527
  .Markup-prompt-image {
528
- height: 130px;
528
+ height: calc(102px + var(--Markup-button-size, 28px)); /* 130 px by default */
529
529
  width: 300px;
530
530
  }
531
531
 
532
532
  .Markup-prompt-language {
533
- height: 96px;
533
+ height: calc(68px + var(--Markup-button-size, 28px)); /* 96 px by default */
534
534
  width: 300px;
535
535
  }
536
536
 
537
537
  .Markup-prompt img {
538
538
  display: inline-block;
539
539
  vertical-align: middle;
540
- min-width: 28px;
541
- height: 28px;
540
+ min-width: var(--Markup-button-size, 28px);
541
+ height: var(--Markup-button-size, 28px);
542
542
  margin-right: 4px;
543
543
  }
544
544
 
@@ -589,8 +589,8 @@
589
589
  vertical-align: middle;
590
590
  justify-content: space-between;
591
591
  justify-items: center;
592
- line-height: 28px;
593
- height: 28px;
592
+ line-height: var(--Markup-button-size, 28px);
593
+ height: var(--Markup-button-size, 28px);
594
594
  margin-top: 8px;
595
595
  margin-bottom: 8px;
596
596
  }