Add markdown support for script/category names

Add markdown rendering for script and category titles to improve the
presentation of textual content.

- Introduce reusable `MarkdownText` for markdown rendering.
- Incorporate markdown styling into dedicated SCSS file for clarity.
- Define explicit font sizes for consistent visual experience.
- Apply `MarkdownText` usage across UI for unified markdown rendering.
- Streamline related styles and layout for improved maintainability
- Set font sizes explicitly for better consistency and to avoid
  unexpected inheritence.

This enhancement enables richer text formatting and improves the user
interface's flexibility in displaying content.
This commit is contained in:
undergroundwires
2024-01-30 16:36:55 +01:00
parent 6ab6dacd1b
commit a5ffed4cd6
10 changed files with 259 additions and 183 deletions

View File

@@ -1,17 +1,13 @@
<template>
<!-- eslint-disable vue/no-v-html -->
<div
class="documentation-text"
@click.stop
v-html="renderedText"
/>
<MarkdownText :text="renderedMarkdown" class="documentation-text" />
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from 'vue';
import { createMarkdownRenderer } from './MarkdownRenderer';
import MarkdownText from '../Markdown/MarkdownText.vue';
export default defineComponent({
components: { MarkdownText },
props: {
docs: {
type: Array as PropType<ReadonlyArray<string>>,
@@ -19,30 +15,27 @@ export default defineComponent({
},
},
setup(props) {
const renderedText = computed<string>(() => renderText(props.docs));
const renderedMarkdown = computed<string>(() => buildMarkdownText(props.docs));
return {
renderedText,
renderedMarkdown,
};
},
});
const renderer = createMarkdownRenderer();
function renderText(docs: readonly string[] | undefined): string {
function buildMarkdownText(docs: readonly string[] | undefined): string {
if (!docs || docs.length === 0) {
return '';
}
if (docs.length === 1) {
return renderer.render(docs[0]);
return docs[0];
}
const bulletpoints = docs
.map((doc) => renderAsMarkdownListItem(doc))
const bulletpointsMarkdown = docs
.map((doc) => formatAsMarkdownListItem(doc))
.join('\n');
return renderer.render(bulletpoints);
return bulletpointsMarkdown;
}
function renderAsMarkdownListItem(content: string): string {
function formatAsMarkdownListItem(content: string): string {
if (content.length === 0) {
throw new Error('missing content');
}
@@ -53,154 +46,16 @@ function renderAsMarkdownListItem(content: string): string {
}
</script>
<style lang="scss"> /* Not scoped due to element styling such as "a". */
<style scoped lang="scss">
@use "@/presentation/assets/styles/main" as *;
@use 'sass:math';
$text-color: $color-on-primary;
$text-size: 0.75em; // Lower looks bad on Firefox
$base-spacing: $text-size;
@mixin code-block() {
pre {
@content; // :has(> code) { @content; } would be better, but Firefox doesn't support it https://caniuse.com/css-has
}
}
@mixin inline-code() {
:not(pre) > code {
@content;
}
}
@mixin code() {
code {
@content;
}
}
.documentation-text {
color: $text-color;
font-size: $text-size;
display: flex;
flex-direction: column;
flex: 1; // Expands the container to fill available horizontal space, enabling alignment of child items.
max-width: 100%; // Prevents horizontal expansion of inner content (e.g., when a code block is shown)
font-size: calc(24px * 75/100);
font-family: $font-main;
@include code {
font-family: $font-normal;
font-weight: 600;
}
@include inline-code {
word-break: break-all; // Enables inline code to wrap with the text, even for long single words (like registry paths), thus preventing overflow.
}
@include code-block {
padding: $base-spacing;
background: $color-primary-darker;
overflow: auto; // Prevents horizontal expansion of inner content (e.g., when a code block is shown)
}
a {
&[href] {
word-break: break-word; // Enables long URLs to wrap within the container, preventing horizontal overflow.
}
&[href^="http"]{
&:after {
/*
Use mask element instead of content/background-image etc.
This way we can apply current font color to it to match the theme
*/
mask: url(@/presentation/assets/icons/external-link.svg) no-repeat 50% 50%;
mask-size: cover;
content: '';
display: inline-block;
width: $text-size;
height: $text-size;
background-color: $text-color;
margin-left: math.div($text-size, 4);
}
/*
Match color of global hover behavior. We need to do it manually because global hover sets
`color` property but here we need to modify `background-color` property because color only
works if SVG is embedded as HTML element (as `<svg/>`) not as `url(..)` as we do. Then the
only option is to use `mask` and `background-color` properties.
*/
@include hover-or-touch {
&::after{
background-color: $color-highlight;
}
}
}
}
@mixin no-margin($selectors) {
#{$selectors} {
margin: 0;
}
}
@mixin no-padding($selectors) {
#{$selectors} {
padding: 0;
}
}
@mixin left-padding($selectors, $horizontal-spacing) {
#{$selectors} {
padding-inline-start: $horizontal-spacing;
}
}
@mixin bottom-margin($selectors, $vertical-spacing) {
#{$selectors} {
&:not(:last-child) {
margin-bottom: $vertical-spacing;
}
}
}
@mixin apply-uniform-vertical-spacing($base-vertical-spacing) {
/* Reset default top/bottom margins added by browser. */
@include no-margin('p');
@include no-margin('h1, h2, h3, h4, h5, h6');
@include no-margin('blockquote');
@include no-margin('pre');
/* Add spacing between elements using `margin-bottom` only (bottom-up instead of top-down strategy). */
$small-vertical-spacing: math.div($base-vertical-spacing, 2);
@include bottom-margin('p', $base-vertical-spacing);
@include bottom-margin('h1, h2, h3, h4, h5, h6', $base-vertical-spacing);
@include bottom-margin('ul, ol', $base-vertical-spacing);
@include bottom-margin('li', $small-vertical-spacing);
@include bottom-margin('table', $base-vertical-spacing);
@include bottom-margin('blockquote', $base-vertical-spacing);
@include bottom-margin('pre', $base-vertical-spacing);
}
@mixin apply-uniform-horizontal-spacing($base-horizontal-spacing) {
/* Reset default left/right paddings added by browser. */
@include no-padding('ul, ol');
/* Add spacing for list items. */
$large-horizontal-spacing: $base-horizontal-spacing * 2;
@include left-padding('ul, ol', $large-horizontal-spacing);
}
@include apply-uniform-vertical-spacing($base-spacing);
@include apply-uniform-horizontal-spacing($base-spacing);
ul {
/*
Set list style explicitly, because otherwise it changes based on parent <ul>s in tree view.
We reset the style from here.
*/
list-style: square;
}
blockquote {
padding: 0 $base-spacing;
border-left: .25em solid $color-primary;
}
}
</style>

View File

@@ -49,6 +49,8 @@ export default defineComponent({
.documentation-button {
vertical-align: middle;
color: $color-primary;
font-size: 24px;
:deep() { // This override leads to inconsistent highlight color, it should be re-styled.
@include hover-or-touch {
color: $color-primary-darker;

View File

@@ -0,0 +1,50 @@
<template>
<!-- eslint-disable vue/no-v-html -->
<div
class="markdown-text"
@click.stop
v-html="htmlOutput"
/>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { createMarkdownRenderer } from './MarkdownRenderer';
export default defineComponent({
props: {
text: {
type: String,
required: true,
},
},
setup(props) {
const htmlOutput = computed<string>(() => convertMarkdownToHtml(props.text));
return {
htmlOutput,
};
},
});
const renderer = createMarkdownRenderer();
function convertMarkdownToHtml(markdownText: string): string {
return renderer.render(markdownText);
}
</script>
<style lang="scss"> /* Not scoped due to element styling such as "a". */
@use "@/presentation/assets/styles/main" as *;
@import './markdown-styles.scss';
$text-color: $color-on-primary;
$text-size: 0.75em; // Lower looks bad on Firefox
.markdown-text {
color: $text-color;
font-size: $text-size;
font-family: $font-main;
@include markdown-text-styles($text-size: $text-size);
}
</style>

View File

@@ -0,0 +1,143 @@
@use "@/presentation/assets/styles/main" as *;
@use 'sass:math';
@mixin code-block() {
pre {
@content; // :has(> code) { @content; } would be better, but Firefox doesn't support it https://caniuse.com/css-has
}
}
@mixin inline-code() {
:not(pre) > code {
@content;
}
}
@mixin code() {
code {
@content;
}
}
@mixin no-margin($selectors) {
#{$selectors} {
margin: 0;
}
}
@mixin no-padding($selectors) {
#{$selectors} {
padding: 0;
}
}
@mixin left-padding($selectors, $horizontal-spacing) {
#{$selectors} {
padding-inline-start: $horizontal-spacing;
}
}
@mixin bottom-margin($selectors, $vertical-spacing) {
#{$selectors} {
&:not(:last-child) {
margin-bottom: $vertical-spacing;
}
}
}
@mixin apply-uniform-vertical-spacing($base-vertical-spacing) {
/* Reset default top/bottom margins added by browser. */
@include no-margin('p');
@include no-margin('h1, h2, h3, h4, h5, h6');
@include no-margin('blockquote');
@include no-margin('pre');
/* Add spacing between elements using `margin-bottom` only (bottom-up instead of top-down strategy). */
$small-vertical-spacing: math.div($base-vertical-spacing, 2);
@include bottom-margin('p', $base-vertical-spacing);
@include bottom-margin('h1, h2, h3, h4, h5, h6', $base-vertical-spacing);
@include bottom-margin('ul, ol', $base-vertical-spacing);
@include bottom-margin('li', $small-vertical-spacing);
@include bottom-margin('table', $base-vertical-spacing);
@include bottom-margin('blockquote', $base-vertical-spacing);
@include bottom-margin('pre', $base-vertical-spacing);
}
@mixin apply-uniform-horizontal-spacing($base-horizontal-spacing) {
/* Reset default left/right paddings added by browser. */
@include no-padding('ul, ol');
/* Add spacing for list items. */
$large-horizontal-spacing: $base-horizontal-spacing * 2;
@include left-padding('ul, ol', $large-horizontal-spacing);
}
@mixin markdown-text-styles($text-size) {
$base-spacing: $text-size;
a {
&[href] {
word-break: break-word; // Enables long URLs to wrap within the container, preventing horizontal overflow.
}
&[href^="http"]{
&:after {
/*
Use mask element instead of content/background-image etc.
This way we can apply current font color to it to match the theme
*/
mask: url(@/presentation/assets/icons/external-link.svg) no-repeat 50% 50%;
mask-size: cover;
content: '';
display: inline-block;
width: $text-size;
height: $text-size;
background-color: $text-color;
margin-left: math.div($text-size, 4);
}
/*
Match color of global hover behavior. We need to do it manually because global hover sets
`color` property but here we need to modify `background-color` property because color only
works if SVG is embedded as HTML element (as `<svg/>`) not as `url(..)` as we do. Then the
only option is to use `mask` and `background-color` properties.
*/
@include hover-or-touch {
&::after{
background-color: $color-highlight;
}
}
}
}
@include code {
font-family: $font-normal;
font-weight: 600;
}
@include inline-code {
word-break: break-all; // Enables inline code to wrap with the text, even for long single words (like registry paths), thus preventing overflow.
}
@include code-block {
padding: $base-spacing;
background: $color-primary-darker;
overflow: auto; // Prevents horizontal expansion of inner content (e.g., when a code block is shown)
}
@include apply-uniform-vertical-spacing($base-spacing);
@include apply-uniform-horizontal-spacing($base-spacing);
ul {
/*
Set list style explicitly, because otherwise it changes based on parent <ul>s in tree view.
We reset the style from here.
*/
list-style: square;
}
blockquote {
padding: 0 $base-spacing;
border-left: .25em solid $color-primary;
}
}

View File

@@ -1,8 +1,8 @@
<template>
<DocumentableNode :docs="nodeMetadata.docs">
<div id="node">
<div class="item text">
{{ nodeMetadata.text }}
<div class="item">
<NodeTitle :title="nodeMetadata.text" />
</div>
<RevertToggle
v-if="nodeMetadata.isReversible"
@@ -18,11 +18,13 @@ import { defineComponent, PropType } from 'vue';
import { NodeMetadata } from './NodeMetadata';
import RevertToggle from './RevertToggle.vue';
import DocumentableNode from './Documentation/DocumentableNode.vue';
import NodeTitle from './NodeTitle.vue';
export default defineComponent({
components: {
RevertToggle,
DocumentableNode,
NodeTitle,
},
props: {
nodeMetadata: {
@@ -41,10 +43,6 @@ export default defineComponent({
flex-direction: row;
flex-wrap: wrap;
.text {
display: flex;
align-items: center;
}
.item:not(:first-child) {
margin-left: 5px;
}

View File

@@ -0,0 +1,40 @@
<template>
<MarkdownText :text="title" class="node-title" />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import MarkdownText from './Markdown/MarkdownText.vue';
export default defineComponent({
components: {
MarkdownText,
},
props: {
title: {
type: String,
required: true,
},
},
});
</script>
<style scoped lang="scss">
@use "@/presentation/assets/styles/main" as *;
.node-title {
font-family: $font-main;
font-size: 1.5em;
line-height: 24px;
/*
Following is a workaround fixing overflow-y caused by line height being smaller than font.
It should be removed once a proper line-height matching the font-size (not smaller than) is used.
*/
$line-height-compensation: calc((1.5em - 24px) / 4);
padding-top: $line-height-compensation;
padding-bottom: $line-height-compensation;
margin-top: calc(-1 * $line-height-compensation);
margin-bottom: calc(-1 * $line-height-compensation);
overflow-y: none;
}
</style>

View File

@@ -90,18 +90,6 @@ export default defineComponent({
padding-right: 6px;
text-decoration: none;
user-select: none;
font-size: 1.5em;
line-height: 24px;
/*
Following is a workaround fixing overflow-y caused by line height being smaller than font.
It should be removed once a proper line-height matching the font-size (not smaller than) is used.
*/
$line-height-compensation: calc((1.5em - 24px) / 4);
padding-top: $line-height-compensation;
padding-bottom: $line-height-compensation;
margin-top: calc(-1 * $line-height-compensation);
margin-bottom: calc(-1 * $line-height-compensation);
}
}
</style>