fix: various markdown editor fixes

scarlett
NGPixel 1 month ago
parent 5021b31a0a
commit 7f53b60dfb
No known key found for this signature in database

@ -5,7 +5,7 @@
never waits on (or depends on) the icon service. Regenerate with `npm run icons` after adding or
removing an icon; `check-icons.mjs` fails the build if this drifts.
259 icons.
260 icons.
*/
export const BUNDLED_ICONS = {
"la:angle-double-right": {"body":"<path fill=\"currentColor\" d=\"M9.094 4.781L7.688 6.22l9.78 9.78l-9.78 9.781l1.406 1.438L20.313 16zm7 0L14.687 6.22L24.47 16l-9.782 9.781l1.407 1.438L27.312 16z\"/>","width":32,"height":32},
@ -266,5 +266,6 @@ export const BUNDLED_ICONS = {
"mdi:transfer-down": {"body":"<path fill=\"currentColor\" d=\"M16 3v2H8V3zm0 4v2H8V7zm0 4v2H8v-2zM5 15h14l-7 7z\"/>","width":24,"height":24},
"mdi:transfer-up": {"body":"<path fill=\"currentColor\" d=\"M8 21v-2h8v2zm0-4v-2h8v2zm0-4v-2h8v2zm11-4H5l7-7z\"/>","width":24,"height":24},
"mdi:undo-variant": {"body":"<path fill=\"currentColor\" d=\"M13.5 7a6.5 6.5 0 0 1 6.5 6.5a6.5 6.5 0 0 1-6.5 6.5H10v-2h3.5c2.5 0 4.5-2 4.5-4.5S16 9 13.5 9H7.83l3.08 3.09L9.5 13.5L4 8l5.5-5.5l1.42 1.41L7.83 7zM6 18h2v2H6z\"/>","width":24,"height":24},
"mdi:view-split-vertical": {"body":"<path fill=\"currentColor\" d=\"M13 5h8v14h-8zM3 5h8v2H3zm0 6V9h8v2zm0 8v-2h8v2zm0-4v-2h8v2z\"/>","width":24,"height":24},
"mdi:web": {"body":"<path fill=\"currentColor\" d=\"M16.36 14c.08-.66.14-1.32.14-2s-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2m-5.15 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95a8.03 8.03 0 0 1-4.33 3.56M14.34 14H9.66c-.1-.66-.16-1.32-.16-2s.06-1.35.16-2h4.68c.09.65.16 1.32.16 2s-.07 1.34-.16 2M12 19.96c-.83-1.2-1.5-2.53-1.91-3.96h3.82c-.41 1.43-1.08 2.76-1.91 3.96M8 8H5.08A7.92 7.92 0 0 1 9.4 4.44C8.8 5.55 8.35 6.75 8 8m-2.92 8H8c.35 1.25.8 2.45 1.4 3.56A8 8 0 0 1 5.08 16m-.82-2C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2s.06 1.34.14 2M12 4.03c.83 1.2 1.5 2.54 1.91 3.97h-3.82c.41-1.43 1.08-2.77 1.91-3.97M18.92 8h-2.95a15.7 15.7 0 0 0-1.38-3.56c1.84.63 3.37 1.9 4.33 3.56M12 2C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2\"/>","width":24,"height":24}
}

@ -233,6 +233,19 @@
t('editor.markup.keyboardKey')
}}</w-tooltip>
</w-btn>
<!-- -> The only way back once the preview is closed: its own toggle goes with it -->
<template v-if="!state.previewShown">
<w-space />
<w-btn
icon="mdi:view-split-vertical"
padding="xs sm"
flat
@click="state.previewShown = true">
<w-tooltip anchor="top middle" self="bottom middle">{{
t('editor.togglePreviewPane')
}}</w-tooltip>
</w-btn>
</template>
</div>
<!-- ------------------------------------------------------- -->
<!-- MONACO EDITOR -->
@ -857,10 +870,6 @@ function processContent(newContent) {
})
}
function openEditorSettings() {
siteStore.$patch({ overlay: 'EditorMarkdownConfig' })
}
/**
* Take files the author brought in pasted or dropped and write markdown for them at the cursor.
*
@ -1203,7 +1212,6 @@ onMounted(async () => {
EVENT_BUS.on('insertAsset', insertAssetClb)
EVENT_BUS.on('insertTable', insertTableClb)
EVENT_BUS.on('insertBlock', insertBlockClb)
EVENT_BUS.on('openEditorSettings', openEditorSettings)
EVENT_BUS.on('reloadEditorContent', reloadEditorContent)
// this.$root.$on('editorInsert', opts => {
@ -1243,7 +1251,6 @@ onBeforeUnmount(() => {
EVENT_BUS.off('insertAsset', insertAssetClb)
EVENT_BUS.off('insertTable', insertTableClb)
EVENT_BUS.off('insertBlock', insertBlockClb)
EVENT_BUS.off('openEditorSettings', openEditorSettings)
EVENT_BUS.off('reloadEditorContent', reloadEditorContent)
pasteCaptureNode?.removeEventListener('paste', onEditorPaste, true)
monacoRef.value?.removeEventListener('dragover', onEditorDragOver)
@ -1283,6 +1290,14 @@ $editor-height-mobile: calc(100vh - 112px - 16px);
height: $editor-height;
position: relative;
border-right: 5px solid $primary;
/*
Monaco writes its measured width in pixels onto its own elements, so this item's automatic
min-width -- min-content, i.e. whatever Monaco last laid itself out at -- pins it to the full
width it took while the preview was closed. Bringing the preview back then leaves it the few
pixels the flex line has left over, and Monaco never re-measures because its container never
shrinks. Zero lets the basis decide instead.
*/
min-width: 0;
}
&-editor {
display: block;
@ -1318,16 +1333,21 @@ $editor-height-mobile: calc(100vh - 112px - 16px);
// @include until($tablet) {
// display: none;
// }
/*
`-enter-from` is the Vue 3 name; as `-enter` it matched nothing, so the pane animated shut but
snapped open. The inner selector was stale in the same way -- the content class is
`-preview-content` -- which left the render reflowing for the length of the transition.
*/
&-enter-active,
&-leave-active {
transition: max-width 0.5s ease;
max-width: 50vw;
.editor-code-preview-content {
.editor-markdown-preview-content {
width: 50vw;
overflow: hidden;
}
}
&-enter,
&-enter-from,
&-leave-to {
max-width: 0;
}
@ -1445,6 +1465,9 @@ $editor-height-mobile: calc(100vh - 112px - 16px);
border-left: 60px solid color.adjust($primary, $lightness: -5%);
color: #fff;
height: 32px;
// -> Flex so the preview toggle can be pushed to the far right by `w-space`
display: flex;
align-items: center;
}
&-sidebar {
background-color: $dark-4;

@ -197,15 +197,6 @@
type="a">
<w-tooltip>{{ t(`common.actions.viewDocs`) }}</w-tooltip>
</w-btn>
<w-btn
class="ml-2 acrylic-btn"
icon="la:cog"
flat
color="grey"
:aria-label="t(`editor.settings`)"
@click="openEditorSettings">
<w-tooltip>{{ t(`editor.settings`) }}</w-tooltip>
</w-btn>
</template>
<!--
Not `v-else-if` on the block below: changes made from the page properties panel put the header
@ -462,10 +453,6 @@ function onEditableBlur(field, event) {
syncEditable(event.target, tidied)
}
function openEditorSettings() {
EVENT_BUS.emit('openEditorSettings')
}
async function discardChanges() {
// From create mode
if (editorStore.mode === 'create') {

@ -76,6 +76,11 @@
--content-mark: #fdf1a0;
/* The box behind the tick of a done task-list item; the tick itself is white in both themes */
--content-tick: #5b616b;
/* -> And the empty box of an item still to do, white whatever surface the content sits on */
--content-tick-empty: #fff;
/* Admonition hues: the bar and the wash behind it, per severity */
--content-info: #1867c0;
--content-info-wash: rgba(24, 103, 192, 0.08);
@ -153,6 +158,14 @@
--content-mark: #6b5d13;
/*
Lighter than the light theme's, because the box has to be seen against a dark page -- but held
well below the white of the tick on it, which is what makes the tick readable.
*/
--content-tick: rgba(255, 255, 255, 0.38);
/* -> A white box would be a lit square on a dark page; the empty one is just its outline */
--content-tick-empty: transparent;
--content-info: #6ab0ff;
--content-info-wash: rgba(106, 176, 255, 0.12);
--content-success: #4ecf9a;
@ -588,16 +601,53 @@
/*
Laid out inline rather than as a flex row: a task item can contain a nested list, and as a flex
child that list would sit BESIDE the text instead of under it.
Drawn here rather than left to the browser, and NOT with `accent-color`: the renderer marks these
inputs disabled, and a disabled checkbox ignores `accent-color` entirely -- Chrome paints it its
own washed-out grey, which is the pale tick this replaces. `appearance: none` is what gets the
box back under this stylesheet's control; the tick then has to be drawn too, since the one the
browser supplies goes with the appearance.
*/
.task-list-item-checkbox {
/*
The prefixed form is for Safari before 15.4, which is the last engine that will not take the
unprefixed one -- and which without it draws its own rounded box UNDER these rules, so the
border and the fill land on a control that is still the platform's.
*/
-webkit-appearance: none;
appearance: none;
/* -> So the box measures 1em in every engine rather than 1em plus whatever the UA pads it by */
box-sizing: border-box;
width: 1em;
height: 1em;
/* -> Firefox gives a checkbox 3px of its own margin, Chrome 3px 3px 3px 4px */
margin: 0 0.5em 0 0;
accent-color: var(--color-primary);
padding: 0;
border: 1px solid var(--content-rule-strong);
border-radius: 3px;
/* -> White rather than the surface behind it, so the box reads as empty and not as unfilled */
background-color: var(--content-tick-empty);
/* -> Disabled by the renderer, so it must not read as something to try to click */
cursor: default;
/* -> Safari and Firefox both dim a disabled control; here it is a mark, not a dead input */
opacity: 1;
vertical-align: -0.1em;
/*
A done item: a filled box with the tick laid over it. White in both themes -- the dark theme
lightens the box rather than the tick, so this one mark works on either.
*/
&:checked {
border-color: transparent;
background-color: var(--content-tick);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23fff' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round' d='M5 12.5l4.5 4.5L19 7'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
background-size: 0.8em 0.8em;
/* -> Print drops background fills, and a done item that prints as an empty box says the opposite */
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
}
}
/*
@ -1091,6 +1141,18 @@
vertical-align: middle;
}
/*
An image in a list item stays in the sentence. Tailwind's preflight declares `img { display:
block }`, so without this the image takes a line of its own and the item's own words go under it,
which reads as a broken item rather than as an illustrated one.
`align-left` / `align-right` / `align-center` still win on specificity, so an author who asks for
one of those in a list still gets it.
*/
li img {
display: inline-block;
}
/* The alignment classes an author can attach through `markdown-it-attrs` */
img,
figure {

@ -169,6 +169,36 @@ export class MarkdownRenderer {
return inlineSpan(state, silent)
})
/*
MDC's inline props, `{.class}`, and `markdown-it-attrs` both claim `{`, and MDC gets there first
it runs while the inline is being parsed, `markdown-it-attrs` in a core rule afterwards, so
whatever MDC takes is already gone by the time the braces would have become attributes.
That is what made `{.is-warning}` on the line under a blockquote do nothing at all: the braces
were eaten and the class never reached the element. The same collision crashed the render
outright `Cannot read properties of undefined (reading 'tag')` out of MDC's own renderer
when the braces opened an inline, since the props it parsed then had no node to attach to. In
the editor that reads as the preview freezing on the last good render, and a save then storing
that stale HTML.
The two are told apart by what comes before the brace, which is also what each one means by it:
MDC's props decorate the thing they are stuck to (`[text]{.cls}`, `![img](…){.cls}`), while a
brace opening a line, or standing off behind a space, is `markdown-it-attrs` addressing the
block as a whole. So MDC keeps every brace that abuts a preceding character and lets the rest
fall through to the core rule.
*/
const propsRule = this.md.inline.ruler.__rules__.find(
(rule) => rule.name === 'mdc_inline_props'
)
const inlineProps = propsRule.fn
this.md.inline.ruler.at('mdc_inline_props', (state, silent) => {
const preceding = state.src[state.pos - 1]
if (preceding === undefined || /\s/.test(preceding)) {
return false
}
return inlineProps(state, silent)
})
if (config.underline) {
this.md.use(mdUnderline)
}

Loading…
Cancel
Save