feat: improve editor styling

pull/8104/head
NGPixel 2 weeks ago
parent 7b42568d8d
commit bf889ecfb2
No known key found for this signature in database

@ -284,16 +284,16 @@
<transition name="editor-markdown-preview"> <transition name="editor-markdown-preview">
<div class="editor-markdown-preview" v-if="state.previewShown"> <div class="editor-markdown-preview" v-if="state.previewShown">
<div class="editor-markdown-preview-toolbar"> <div class="editor-markdown-preview-toolbar">
<strong <strong class="editor-markdown-preview-label"
><em>{{ t('editor.renderPreview') }}</em></strong ><em>{{ t('editor.renderPreview') }}</em></strong
> >
<w-separator class="ml-4 mr-2" vertical inset /> <w-separator class="ml-4 mr-2" vertical inset dark />
<w-btn <w-btn
icon="mdi:arrow-vertical-lock" icon="mdi:arrow-vertical-lock"
padding="xs sm" padding="xs sm"
flat flat
@click="state.previewScrollSync = !state.previewScrollSync" @click="state.previewScrollSync = !state.previewScrollSync"
:color="state.previewScrollSync ? `primary` : null"> :color="state.previewScrollSync ? `secondary` : null">
<w-tooltip anchor="top middle" self="bottom middle">{{ <w-tooltip anchor="top middle" self="bottom middle">{{
t('editor.toggleScrollSync') t('editor.toggleScrollSync')
}}</w-tooltip> }}</w-tooltip>
@ -1920,6 +1920,7 @@ function notImplemented() {
<style lang="scss"> <style lang="scss">
@use 'sass:color'; @use 'sass:color';
@use '@/css/_bricks.scss' as *;
$editor-height: calc(100vh - 64px - 96px); $editor-height: calc(100vh - 64px - 96px);
$editor-preview-height: calc(100vh - 64px - 96px - 32px); $editor-preview-height: calc(100vh - 64px - 96px - 32px);
@ -1998,20 +1999,37 @@ $editor-height-mobile: calc(100vh - 112px - 16px);
&-leave-to { &-leave-to {
max-width: 0; max-width: 0;
} }
/*
Dark in either appearance, where this used to follow the theme. Two reasons, and the second is
the one that settled it: the bar labels the render rather than being part of it, so a dark strip
reads as a caption on the pane instead of as more page; and the brickwork at its right end needs
a colour with room above it -- `$grey-3` is 93% lightness, so a seam brick a shade lighter than
it is white, which is a hole rather than a highlight.
*/
&-toolbar { &-toolbar {
color: $grey-8;
height: 32px; height: 32px;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 0 1rem; padding: 0 1rem;
background-color: $dark-2;
color: $grey-6;
@at-root .body--light & { /*
background-color: $grey-3; -> The right end steps out of the bar's grey into the editor orange, as brickwork -- the same
} treatment the Visual editor's toolbar gets, and for the same reason: the actions rail is
@at-root .body--dark & { orange while an editor is open, and this bar runs up to it. `css/_bricks.scss` has the
background-color: $dark-2; whole of how and why.
color: $grey-6; */
} @include brick-transition($dark-2);
}
/*
-> A shade lighter than the bar's own text colour, which the two icon buttons beside it still
take: this is the one piece of writing on the strip and what names the pane, so it carries
slightly more of the reader's attention than the controls do.
*/
&-label {
color: $grey-5;
} }
&-content { &-content {
height: $editor-preview-height; height: $editor-preview-height;

@ -320,7 +320,7 @@
}}</w-tooltip> }}</w-tooltip>
</w-btn> </w-btn>
<w-space /> <w-separator class="mx-1" vertical inset dark />
<w-btn dense icon="mdi:undo-variant" padding="xs sm" flat @click="runUndo"> <w-btn dense icon="mdi:undo-variant" padding="xs sm" flat @click="runUndo">
<w-tooltip anchor="top middle" self="bottom middle">{{ <w-tooltip anchor="top middle" self="bottom middle">{{

@ -10,32 +10,50 @@
</w-chip> </w-chip>
</w-toolbar> </w-toolbar>
<div style="min-height: 450px"> <div style="min-height: 450px">
<!-- -> Square: this one spans the dialog edge to edge, so a radius would cut across its corners --> <!--
-> Square: this one spans the dialog edge to edge, so a radius would cut across its corners.
-> Dark in either appearance: what is typed here is the page's own code rather than a wiki
setting, and a dark field is both what an author reads code in and what tells their code
apart from the dialog around it.
-->
<util-code-editor <util-code-editor
ref="editor" ref="editor"
v-model="state.content" v-model="state.content"
:language="language" :language="language"
:min-height="450" :min-height="450"
:aria-label="languageLabel" :aria-label="languageLabel"
square /> square
dark />
</div> </div>
<w-card-actions class="card-actions"> <!--
Its own class rather than the shared `card-actions`: that one follows the app theme, and the
editor above is dark whichever theme is on -- a light bar under a dark pane reads as a different
component bolted to the bottom. Same treatment, and the same reasoning, as
`PageVersionSourceDialog`.
-->
<w-card-actions class="page-scripts-dialog-actions">
<w-space /> <w-space />
<!-- -> `grey-5`, not the `grey-7` a light bar takes: #757575 on this bar is barely there -->
<w-btn <w-btn
class="acrylic-btn" class="acrylic-btn"
icon="la:times" icon="la:times"
:label="t(`common.actions.discard`)" :label="t(`common.actions.discard`)"
color="grey-7" color="grey-5"
padding="xs md" padding="xs md"
flat flat
@click="$emit('close')" /> @click="$emit('close')" />
<!--
-> Apply, not Save: this writes the content into the page store and closes. Nothing reaches
the server until the page itself is saved, and a button saying otherwise invites an author
to close the editor believing their script is stored.
-->
<w-btn <w-btn
icon="la:check" icon="la:check"
:label="t(`common.actions.save`)" :label="t(`common.actions.apply`)"
unelevated unelevated
color="primary" color="primary"
padding="xs md" padding="xs md"
@click="saveAndClose" /> @click="applyAndClose" />
</w-card-actions> </w-card-actions>
</w-card> </w-card>
</template> </template>
@ -133,7 +151,7 @@ function persist() {
EXPRESSION, and oxfmt reformats a semicolon-separated pair onto separate lines without the EXPRESSION, and oxfmt reformats a semicolon-separated pair onto separate lines without the
semicolon, which stops being one. It broke the build twice while this file was being edited. semicolon, which stops being one. It broke the build twice while this file was being edited.
*/ */
function saveAndClose() { function applyAndClose() {
persist() persist()
emit('close') emit('close')
} }
@ -152,4 +170,13 @@ onMounted(() => {
}) })
</script> </script>
<style lang="scss"></style> <style lang="scss">
/* -> Colour only: WCardActions already lays the bar out */
.page-scripts-dialog-actions {
background-color: $dark-3;
background-image: radial-gradient(at top left, $dark-3, $dark-5);
border-top: 1px solid #000;
box-shadow: 0 -1px 0 0 rgba(#fff, 0.06);
color: #fff;
}
</style>

@ -11,7 +11,7 @@
--> -->
<div <div
class="util-code-editor" class="util-code-editor"
:class="{ 'util-code-editor--square': square }" :class="{ 'util-code-editor--square': square, 'util-code-editor--dark': dark }"
:style="{ height: `${minHeight}px`, '--util-code-editor-gutter': gutterWidth }"> :style="{ height: `${minHeight}px`, '--util-code-editor-gutter': gutterWidth }">
<pre class="util-code-editor-view" aria-hidden="true"><code v-html="highlighted" /></pre> <pre class="util-code-editor-view" aria-hidden="true"><code v-html="highlighted" /></pre>
<textarea <textarea
@ -88,6 +88,19 @@ const props = defineProps({
square: { square: {
type: Boolean, type: Boolean,
default: false default: false
},
/**
* Draw dark whatever appearance the rest of the interface is in.
*
* For a field whose CONTENT is code rather than a setting that happens to be typed -- the page's
* own scripts and styles -- where a dark field is what the author is used to reading code in and
* what separates their code from the wiki's chrome around it. The editor otherwise follows
* `body--dark` like everything else, so this only ever adds the dark palette; there is no light
* counterpart, because nothing wants a light field on a dark page.
*/
dark: {
type: Boolean,
default: false
} }
}) })
@ -435,22 +448,26 @@ defineExpose({
} }
} }
body.body--dark { /*
.util-code-editor { The dark appearance, as a mixin because two different things ask for it: the interface being in
border-color: rgb(255 255 255 / 0.3); dark mode, and the `dark` prop asking for a dark field regardless. Everything is written relative
background-color: $dark-5; to the root element -- the layers and the gutter numbers are its descendants -- so that one body
background-image: linear-gradient( serves both selectors below.
to right, */
$dark-4 0, @mixin util-code-editor-dark {
$dark-4 var(--util-code-editor-gutter), border-color: rgb(255 255 255 / 0.3);
rgb(255 255 255 / 0.12) var(--util-code-editor-gutter), background-color: $dark-5;
rgb(255 255 255 / 0.12) calc(var(--util-code-editor-gutter) + 1px), background-image: linear-gradient(
transparent calc(var(--util-code-editor-gutter) + 1px) to right,
); $dark-4 0,
$dark-4 var(--util-code-editor-gutter),
&:focus-within { rgb(255 255 255 / 0.12) var(--util-code-editor-gutter),
border-color: var(--color-primary); rgb(255 255 255 / 0.12) calc(var(--util-code-editor-gutter) + 1px),
} transparent calc(var(--util-code-editor-gutter) + 1px)
);
&:focus-within {
border-color: var(--color-primary);
} }
.util-code-editor-view { .util-code-editor-view {
@ -465,54 +482,65 @@ body.body--dark {
caret-color: #e6edf3; caret-color: #e6edf3;
} }
.util-code-editor { .hljs-comment,
.hljs-comment, .hljs-quote {
.hljs-quote { color: #8b949e;
color: #8b949e; }
} .hljs-keyword,
.hljs-keyword, .hljs-selector-tag,
.hljs-selector-tag, .hljs-literal,
.hljs-literal, .hljs-doctag,
.hljs-doctag, .hljs-formula {
.hljs-formula { color: #ff7b72;
color: #ff7b72; }
} .hljs-string,
.hljs-string, .hljs-regexp,
.hljs-regexp, .hljs-addition,
.hljs-addition, .hljs-selector-attr,
.hljs-selector-attr, .hljs-selector-pseudo {
.hljs-selector-pseudo { color: #a5d6ff;
color: #a5d6ff; }
} .hljs-number,
.hljs-number, .hljs-variable,
.hljs-variable, .hljs-template-variable,
.hljs-template-variable, .hljs-symbol,
.hljs-symbol, .hljs-bullet,
.hljs-bullet, .hljs-attr,
.hljs-attr, .hljs-meta {
.hljs-meta { color: #79c0ff;
color: #79c0ff; }
} .hljs-title,
.hljs-title, .hljs-section,
.hljs-section, .hljs-selector-id,
.hljs-selector-id, .hljs-selector-class {
.hljs-selector-class { color: #d2a8ff;
color: #d2a8ff;
}
.hljs-built_in,
.hljs-type,
.hljs-attribute,
.hljs-property,
.hljs-params {
color: #ffa657;
}
.hljs-name,
.hljs-tag {
color: #7ee787;
}
.hljs-deletion {
color: #ffa198;
}
} }
.hljs-built_in,
.hljs-type,
.hljs-attribute,
.hljs-property,
.hljs-params {
color: #ffa657;
}
.hljs-name,
.hljs-tag {
color: #7ee787;
}
.hljs-deletion {
color: #ffa198;
}
}
body.body--dark .util-code-editor {
@include util-code-editor-dark;
}
/*
-> Both classes, not just the modifier: the light palette above is `.util-code-editor .hljs-*`, two
classes, so a single-class modifier would tie with it and be settled by which rule the bundler
emitted last. Naming the root twice puts this one ahead on specificity rather than on luck.
*/
.util-code-editor.util-code-editor--dark {
@include util-code-editor-dark;
} }
</style> </style>

@ -0,0 +1,116 @@
/*
The brick transition: a toolbar whose right end stops being its own colour and becomes the editor's
orange, through a staircase of brickwork rather than a straight edge.
Used by the Visual editor's toolbar and the Markdown editor's preview toolbar, which is the whole
reason this is a partial rather than a rule in either of them.
*/
/*
The Sass variables, loaded explicitly. `vite.config.js` injects `theme` and `palette` into every SFC
through `additionalData`, but that reaches only the files Vite itself hands to Sass -- a partial
pulled in by `@use` arrives without them. Same note as `_visual-editor.scss` and `_page-contents.scss`.
*/
@use 'sass:color';
@use 'palette' as *;
/**
* Lay the right end of a bar as brickwork stepping from `$cold` into `$hot`.
*
* Read the bar as five courses of brick. Counting in from the right edge, the last column has all
* five of its bricks knocked out, the one before it four, then three, then two, then one -- so the
* hot colour behind shows through as a staircase climbing to the right, the cold one thinning course
* by course until the corner is hot all the way up.
*
* Where the two meet, the brick on either side of the step is laid in a lighter shade of its own
* colour: one lighter cold and one lighter hot per course, each a single brick, so the seam reads as
* two diagonals of highlighted brick running with the step rather than as a hard edge. In every
* course the hot colour reaches column N and the cold one starts at N+1, which is what makes each of
* those runs a clean diagonal.
*
* Fifteen background layers rather than a clip path or an SVG, in three groups. The last five are the
* staircase: each one step, a hot rectangle anchored to the bottom-right corner, differing only in
* `background-size` -- one brick wide by five courses tall, two by four, three by three, four by two,
* five by one -- overlapping into the shape rather than tiling it, which is why they need no
* coordinates of their own. The first ten are the seam, one brick each, and those DO need
* coordinates: they are placed by offset from the right and top edges, a brick further along both per
* course. Earlier layers paint over later ones, so the seam is listed first.
*
* Courses are percentages, so there are five of them whatever the bar's height turns out to be. A
* brick's WIDTH is not a share of anything, so it comes from `$height` -- and a brick is square, which
* makes the staircase exactly as wide as the bar is tall, its diagonal a true 45 degrees, and the
* seam's offsets one and the same measure in both directions.
*
* `$deep-orange-9` is the default hot colour because that is what the actions rail turns while an
* editor is open (`PageActionsCol`): the rail and the bar are on screen together, at opposite edges of
* the same editor, so a second orange between them would read as a mistake rather than as a pair.
*
* The cold seam is emitted as a custom property, not baked into the layers, so that a bar whose own
* colour changes with the theme can restate just that one line in its dark branch instead of the
* whole stack. `$cold` is still required: it is what makes the default right for the common case, and
* an unset `var()` here would take the entire `background-image` down with it.
*
* @param {Color} $cold The bar's own background colour, which the brickwork steps out of.
* @param {Color} $hot The colour it steps into.
* @param {Length} $height The bar's height. Five courses are cut from it, and a brick is one square.
*/
@mixin brick-transition($cold, $hot: $deep-orange-9, $height: 32px) {
--brick: calc(#{$height} / 5);
--seam-cold: #{color.adjust($cold, $lightness: 10%)};
--seam-hot: #{color.adjust($hot, $lightness: 10%)};
background-image:
/* -> The seam, cold side: the last cold brick of each course */
linear-gradient(var(--seam-cold), var(--seam-cold)),
linear-gradient(var(--seam-cold), var(--seam-cold)),
linear-gradient(var(--seam-cold), var(--seam-cold)),
linear-gradient(var(--seam-cold), var(--seam-cold)),
linear-gradient(var(--seam-cold), var(--seam-cold)),
/* -> The seam, hot side: the first hot brick of each course */
linear-gradient(var(--seam-hot), var(--seam-hot)),
linear-gradient(var(--seam-hot), var(--seam-hot)),
linear-gradient(var(--seam-hot), var(--seam-hot)),
linear-gradient(var(--seam-hot), var(--seam-hot)),
linear-gradient(var(--seam-hot), var(--seam-hot)),
/* -> The staircase */
linear-gradient($hot, $hot),
linear-gradient($hot, $hot),
linear-gradient($hot, $hot),
linear-gradient($hot, $hot),
linear-gradient($hot, $hot);
background-repeat: no-repeat;
background-position:
/* -> Cold side: course 1 sits one brick in from the right, and each course one further */
right calc(var(--brick) * 1) top calc(var(--brick) * 0),
right calc(var(--brick) * 2) top calc(var(--brick) * 1),
right calc(var(--brick) * 3) top calc(var(--brick) * 2),
right calc(var(--brick) * 4) top calc(var(--brick) * 3),
right calc(var(--brick) * 5) top calc(var(--brick) * 4),
/* -> Hot side: the same diagonal, one column further right */
right calc(var(--brick) * 0) top calc(var(--brick) * 0),
right calc(var(--brick) * 1) top calc(var(--brick) * 1),
right calc(var(--brick) * 2) top calc(var(--brick) * 2),
right calc(var(--brick) * 3) top calc(var(--brick) * 3),
right calc(var(--brick) * 4) top calc(var(--brick) * 4),
right bottom,
right bottom,
right bottom,
right bottom,
right bottom;
background-size:
var(--brick) var(--brick),
var(--brick) var(--brick),
var(--brick) var(--brick),
var(--brick) var(--brick),
var(--brick) var(--brick),
var(--brick) var(--brick),
var(--brick) var(--brick),
var(--brick) var(--brick),
var(--brick) var(--brick),
var(--brick) var(--brick),
var(--brick) 100%,
calc(var(--brick) * 2) 80%,
calc(var(--brick) * 3) 60%,
calc(var(--brick) * 4) 40%,
calc(var(--brick) * 5) 20%;
}

@ -18,6 +18,7 @@
@use 'sass:color'; @use 'sass:color';
@use 'palette' as *; @use 'palette' as *;
@use 'theme' as *; @use 'theme' as *;
@use 'bricks' as *;
/* /*
A flex child of the article column, filling whatever it is given rather than a height of its own. A flex child of the article column, filling whatever it is given rather than a height of its own.
@ -98,6 +99,12 @@
background-color: $primary; background-color: $primary;
color: #fff; color: #fff;
/*
-> The right end steps out of the bar's blue into the editor orange, as brickwork; the whole of
how and why is in `css/_bricks.scss`, which the Markdown editor's preview toolbar shares.
*/
@include brick-transition($primary);
/* /*
What a pressed button looks like here. The `primary` colour these used to take is the colour of What a pressed button looks like here. The `primary` colour these used to take is the colour of
the bar itself, so an active button was drawn in the background it sits on and was simply the bar itself, so an active button was drawn in the background it sits on and was simply

Loading…
Cancel
Save