Render bracket references as superscript text

This commit improves markdown rendering to convert reference labels
(e.g., `[1]`) to superscripts, improving document readability without
cluttering the text. This improvement applies documentation of all
scripts and categories.

Changes:

- Implement superscript conversion for reference labels within markdown
  content, ensuring a cleaner presentation of textual references.
- Enable HTML content within markdown, necessary for inserting `<sup>`
  elements due to limitations in `markdown-it`, see
  markdown-it/markdown-it#999 for details.
- Refactor markdown rendering process for improved testability and
  adherence to the Single Responsibility Principle.
- Create `_typography.scss` with font size definitions, facilitating
  better control over text presentation.
- Adjust external URL indicator icon sizing for consistency, aligning
  images with the top of the text to maintain a uniform appearence.
- Use normal font-size explicitly for documentation text to ensure
  consistency.
- Remove text size specification in `markdown-styles` mixin, using `1em`
  for spacing to simplify styling.
- Rename font sizing variables for clarity, distinguishing between
  absolute and relative units.
- Change `font-size-relative-smaller` to be `80%`, browser default for
  `font-size: smaller;` CSS style and use it with `<sup>` elements.
- Improve the logic for converting plain URLs to hyperlinks, removing
  trailing whitespace for cleaner link generation.
- Fix plain URL to hyperlink (autolinking) logic removing trailing
  whitespace from the original markdown content. This was revealed by
  tests after separating its logic.
- Increase test coverage with more tests.
- Add types for `markdown-it` through `@types/markdown-it` package for
  better editor support and maintainability.
- Simplify implementation of adding custom anchor attributes in
  `markdown-it` using latest documentation.
This commit is contained in:
undergroundwires
2024-02-09 16:25:05 +01:00
parent 311fcb1813
commit b9c89b701f
42 changed files with 1036 additions and 378 deletions

View File

@@ -33,12 +33,3 @@
$font-normal : 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
$font-artistic : 'Yesteryear', cursive;
$font-main : 'Slabo 27px';
$font-size-smaller : 14px;
$font-size-small : 16px;
$font-size-normal : 18px;
$font-size-large : 22px;
$font-size-larger : 26px;
$font-size-largest : 40px;
$font-size-relative-smaller: 85%;

View File

@@ -6,6 +6,7 @@
@use "@/presentation/assets/styles/fonts" as *;
@use "@/presentation/assets/styles/mixins" as *;
@use "@/presentation/assets/styles/vite-path" as *;
@use "@/presentation/assets/styles/typography" as *;
* {
box-sizing: border-box;
@@ -20,5 +21,5 @@ a {
body {
background: $color-background;
font-family: $font-main;
font-size: $font-size-normal;
font-size: $font-size-absolute-normal;
}

View File

@@ -1,5 +1,6 @@
@use "@/presentation/assets/styles/colors" as *;
@use "@/presentation/assets/styles/fonts" as *;
@use "@/presentation/assets/styles/typography" as *;
@mixin hover-or-touch($selector-suffix: '', $selector-prefix: '&') {
@media (hover: hover) {
@@ -69,6 +70,11 @@
list-style: none;
}
@mixin reset-sup {
vertical-align: unset;
font-size: unset;
}
@mixin reset-button {
margin: 0;
padding-block: 0;
@@ -93,7 +99,7 @@
@mixin flat-button($disabled: false) {
@include reset-button;
$font-size: $font-size-normal;
$font-size: $font-size-absolute-normal;
@if $disabled {
color: $color-primary-light;

View File

@@ -0,0 +1,19 @@
/*
This naming convention for font sizes adheres to CSS standards, distinguishing between absolute and relative sizes.
We prefix each variable with its type (absolute or relative) for clear identification and context.
*/
// Absolute sizes use the <absolute-size> CSS data type, representing specific, fixed sizes unaffected by the parent element's size.
// See: https://archive.today/2024.02.02-005228/https://developer.mozilla.org/en-US/docs/Web/CSS/absolute-size.
$font-size-absolute-x-small : 14px;
$font-size-absolute-small : 16px;
$font-size-absolute-normal : 18px;
$font-size-absolute-large : 22px;
$font-size-absolute-x-large : 26px;
$font-size-absolute-xx-large : 40px;
// Relative sizes employ the <relative-size> CSS data type, allowing font size adjustments based on the parent element's size.
// See: https://archive.today/2024.02.02-010054/https://developer.mozilla.org/en-US/docs/Web/CSS/relative-size.
$font-size-relative-smallest : 80%; // Common browser standard for `font-size: smaller;`
$font-size-relative-smaller : 85%; // Common browser standard for `font-size: smaller;`

View File

@@ -1,6 +1,7 @@
/* This class is not supposed to more than forwarding other styles */
@forward "./fonts";
@forward "./typography";
@forward "./media";
@forward "./colors";
@forward "./globals";

View File

@@ -81,7 +81,7 @@ export default defineComponent({
border-radius: 4px;
.button__icon {
font-size: $font-size-larger;
font-size: $font-size-absolute-x-large;
}
@include clickable;
@@ -99,7 +99,7 @@ export default defineComponent({
.button__text {
display: none;
font-family: $font-artistic;
font-size: $font-size-large;
font-size: $font-size-absolute-large;
color: $color-primary;
font-weight: 500;
@include hover-or-touch {

View File

@@ -63,14 +63,14 @@ export default defineComponent({
padding: 0.2rem;
.dollar {
margin-right: 0.5rem;
font-size: $font-size-smaller;
font-size: $font-size-absolute-x-small;
user-select: none;
}
.copy-action-container {
margin-left: 1rem;
}
code {
font-size: $font-size-small;
font-size: $font-size-absolute-small;
}
}
</style>

View File

@@ -203,7 +203,7 @@ function getDefaultCode(language: ScriptingLanguage): string {
width: 100%;
height: 100%;
overflow: auto;
font-size: $font-size-small;
font-size: $font-size-absolute-small;
&__highlight {
background-color: $color-secondary-light;
position: absolute;

View File

@@ -142,7 +142,7 @@ function isClickable(element: Element) {
.error {
width: 100%;
text-align: center;
font-size: $font-size-largest;
font-size: $font-size-absolute-xx-large;
font-family: $font-normal;
}

View File

@@ -168,7 +168,7 @@ $card-horizontal-gap : $card-gap;
flex-direction: column;
flex: 1;
justify-content: center;
font-size: $font-size-large;
font-size: $font-size-absolute-large;
}
.card__inner__selection_indicator {
height: $card-inner-padding;
@@ -181,7 +181,7 @@ $card-horizontal-gap : $card-gap;
width: 100%;
margin-top: .25em;
vertical-align: middle;
font-size: $font-size-normal;
font-size: $font-size-absolute-normal;
}
}
.card__expander {
@@ -203,7 +203,7 @@ $card-horizontal-gap : $card-gap;
}
.card__expander__close-button {
font-size: $font-size-large;
font-size: $font-size-absolute-large;
align-self: flex-end;
margin-right: 0.25em;
@include clickable;

View File

@@ -57,6 +57,6 @@ export default defineComponent({
<style scoped lang="scss">
@use "@/presentation/assets/styles/main" as *;
.icon {
font-size: $font-size-normal;
font-size: $font-size-absolute-normal;
}
</style>

View File

@@ -151,7 +151,7 @@ $margin-inner: 4px;
margin-top: 1em;
color: $color-primary-light;
.search__query__close-button {
font-size: $font-size-large;
font-size: $font-size-absolute-large;
margin-left: 0.25rem;
}
}
@@ -160,7 +160,7 @@ $margin-inner: 4px;
flex-direction: column;
word-break:break-word;
color: $color-on-primary;
font-size: $font-size-large;
font-size: $font-size-absolute-large;
padding:10px;
text-align:center;
> div {

View File

@@ -59,7 +59,7 @@ function formatAsMarkdownListItem(content: string): string {
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: $font-size-normal;
font-size: $font-size-absolute-normal;
font-family: $font-main;
}
</style>

View File

@@ -49,7 +49,7 @@ export default defineComponent({
.documentation-button {
vertical-align: middle;
color: $color-primary;
font-size: $font-size-large;
font-size: $font-size-absolute-large;
: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,28 @@
import { InlineReferenceLabelsToSuperscriptConverter } from './Renderers/InlineReferenceLabelsToSuperscriptConverter';
import { MarkdownItHtmlRenderer } from './Renderers/MarkdownItHtmlRenderer';
import { PlainTextUrlsToHyperlinksConverter } from './Renderers/PlainTextUrlsToHyperlinksConverter';
import type { MarkdownRenderer } from './MarkdownRenderer';
export class CompositeMarkdownRenderer implements MarkdownRenderer {
constructor(
private readonly renderers: readonly MarkdownRenderer[] = StandardMarkdownRenderers,
) {
if (!renderers.length) {
throw new Error('missing renderers');
}
}
public render(markdownContent: string): string {
let renderedContent = markdownContent;
for (const renderer of this.renderers) {
renderedContent = renderer.render(renderedContent);
}
return renderedContent;
}
}
const StandardMarkdownRenderers: readonly MarkdownRenderer[] = [
new PlainTextUrlsToHyperlinksConverter(),
new InlineReferenceLabelsToSuperscriptConverter(),
new MarkdownItHtmlRenderer(),
] as const;

View File

@@ -1,196 +1,3 @@
import MarkdownIt from 'markdown-it';
import Renderer from 'markdown-it/lib/renderer';
import Token from 'markdown-it/lib/token';
export function createMarkdownRenderer(): MarkdownRenderer {
const markdownParser = new MarkdownIt({
linkify: false, // Disables auto-linking; handled manually for custom formatting.
breaks: false, // Disables conversion of single newlines (`\n`) to HTML breaks (`<br>`).
});
configureLinksToOpenInNewTab(markdownParser);
return {
render: (markdownContent: string) => {
markdownContent = beautifyAutoLinkedUrls(markdownContent);
return markdownParser.render(markdownContent);
},
};
}
interface MarkdownRenderer {
export interface MarkdownRenderer {
render(markdownContent: string): string;
}
const PlainTextUrlInMarkdownRegex = /(?<!\]\(|\[\d+\]:\s+|https?\S+|`)((?:https?):\/\/[^\s\])]*)(?:[\])](?!\()|$|\s)/gm;
function beautifyAutoLinkedUrls(content: string): string {
if (!content) {
return content;
}
return content.replaceAll(PlainTextUrlInMarkdownRegex, (_fullMatch, url) => {
return formatReadableLink(url);
});
}
function formatReadableLink(url: string): string {
const urlParts = new URL(url);
let displayText = formatHostName(urlParts.hostname);
const pageLabel = extractPageLabel(urlParts);
if (pageLabel) {
displayText += ` - ${truncateTextFromEnd(capitalizeEachWord(pageLabel), 50)}`;
}
if (displayText.includes('Msdn.microsoft')) {
console.log(`[${displayText}](${urlParts.href})`);
}
return buildMarkdownLink(displayText, urlParts.href);
}
function formatHostName(hostname: string): string {
const withoutWww = hostname.replace(/^(www\.)/, '');
const truncatedHostName = truncateTextFromStart(withoutWww, 30);
return truncatedHostName;
}
function extractPageLabel(urlParts: URL): string | undefined {
const readablePath = makePathReadable(urlParts.pathname);
if (readablePath) {
return readablePath;
}
return formatQueryParameters(urlParts.searchParams);
}
function buildMarkdownLink(label: string, url: string): string {
return `[${label}](${url})`;
}
function formatQueryParameters(queryParameters: URLSearchParams): string | undefined {
const queryValues = [...queryParameters.values()];
if (queryValues.length === 0) {
return undefined;
}
return findMostDescriptiveName(queryValues);
}
function truncateTextFromStart(text: string, maxLength: number): string {
return text.length > maxLength ? `${text.substring(text.length - maxLength)}` : text;
}
function truncateTextFromEnd(text: string, maxLength: number): string {
return text.length > maxLength ? `${text.substring(0, maxLength)}` : text;
}
function isNumeric(value: string): boolean {
return /^\d+$/.test(value);
}
function makePathReadable(path: string): string | undefined {
const decodedPath = decodeURI(path); // Decode URI components, e.g., '%20' to space
const pathParts = decodedPath.split('/');
const decodedPathParts = pathParts // Split then decode to correctly handle '%2F' as '/'
.map((pathPart) => decodeURIComponent(pathPart));
const descriptivePart = findMostDescriptiveName(decodedPathParts);
if (!descriptivePart) {
return undefined;
}
const withoutExtension = removeFileExtension(descriptivePart);
const tokenizedText = tokenizeTextForReadability(withoutExtension);
return tokenizedText;
}
function tokenizeTextForReadability(text: string): string {
return text
.replaceAll(/[-_+]/g, ' ') // Replace hyphens, underscores, and plus signs with spaces
.replaceAll(/\s+/g, ' '); // Collapse multiple consecutive spaces into a single space
}
function removeFileExtension(value: string): string {
const parts = value.split('.');
if (parts.length === 1) {
return value;
}
const extension = parts[parts.length - 1];
if (extension.length > 9) {
return value; // Heuristically web file extension is no longer than 9 chars (e.g. "html")
}
return parts.slice(0, -1).join('.');
}
function capitalizeEachWord(text: string): string {
return text
.split(' ')
.map((word) => capitalizeFirstLetter(word))
.join(' ');
}
function capitalizeFirstLetter(text: string): string {
return text.charAt(0).toUpperCase() + text.slice(1);
}
function findMostDescriptiveName(segments: readonly string[]): string | undefined {
const meaningfulSegments = segments.filter(isMeaningfulPathSegment);
if (meaningfulSegments.length === 0) {
return undefined;
}
const longestGoodSegment = meaningfulSegments.reduce((a, b) => (a.length > b.length ? a : b));
return longestGoodSegment;
}
function isMeaningfulPathSegment(segment: string): boolean {
return segment.length > 2 // This is often non-human categories like T5 etc.
&& !isNumeric(segment) // E.g. article numbers, issue numbers
&& !/^index(?:\.\S{0,10}$|$)/.test(segment) // E.g. index.html
&& !/^[A-Za-z]{2,4}([_-][A-Za-z]{4})?([_-]([A-Za-z]{2}|[0-9]{3}))$/.test(segment) // Locale string e.g. fr-FR
&& !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(segment) // GUID
&& !/^[0-9a-f]{40}$/.test(segment); // Git SHA (e.g. GitHub links)
}
const AnchorAttributesForExternalLinks: Record<string, string> = {
target: '_blank',
rel: 'noopener noreferrer',
};
function configureLinksToOpenInNewTab(markdownParser: MarkdownIt): void {
// https://github.com/markdown-it/markdown-it/blob/12.2.0/docs/architecture.md#renderer
const defaultLinkRenderer = getDefaultRenderer(markdownParser, 'link_open');
markdownParser.renderer.rules.link_open = (tokens, index, options, env, self) => {
const currentToken = tokens[index];
Object.entries(AnchorAttributesForExternalLinks).forEach(([attribute, value]) => {
const existingValue = getTokenAttribute(currentToken, attribute);
if (!existingValue) {
addAttributeToToken(currentToken, attribute, value);
} else if (existingValue !== value) {
updateTokenAttribute(currentToken, attribute, value);
}
});
return defaultLinkRenderer(tokens, index, options, env, self);
};
}
function getDefaultRenderer(md: MarkdownIt, ruleName: string): Renderer.RenderRule {
const ruleRenderer = md.renderer.rules[ruleName];
return ruleRenderer || renderTokenAsDefault;
function renderTokenAsDefault(tokens, idx, options, _env, self) {
return self.renderToken(tokens, idx, options);
}
}
function getTokenAttribute(token: Token, attributeName: string): string | undefined {
const attributeIndex = token.attrIndex(attributeName);
if (attributeIndex < 0) {
return undefined;
}
const value = token.attrs[attributeIndex][1];
return value;
}
function addAttributeToToken(token: Token, attributeName: string, value: string): void {
token.attrPush([attributeName, value]);
}
function updateTokenAttribute(token: Token, attributeName: string, newValue: string): void {
const attributeIndex = token.attrIndex(attributeName);
if (attributeIndex < 0) {
throw new Error(`Attribute "${attributeName}" not found in token.`);
}
token.attrs[attributeIndex][1] = newValue;
}

View File

@@ -8,7 +8,7 @@
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { createMarkdownRenderer } from './MarkdownRenderer';
import { CompositeMarkdownRenderer } from './CompositeMarkdownRenderer';
export default defineComponent({
props: {
@@ -26,9 +26,8 @@ export default defineComponent({
},
});
const renderer = createMarkdownRenderer();
function convertMarkdownToHtml(markdownText: string): string {
const renderer = new CompositeMarkdownRenderer();
return renderer.render(markdownText);
}
</script>
@@ -38,12 +37,11 @@ function convertMarkdownToHtml(markdownText: string): string {
@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-size: $font-size-absolute-normal;
font-family: $font-main;
@include markdown-text-styles($text-size: $text-size);
@include markdown-text-styles;
}
</style>

View File

@@ -0,0 +1,36 @@
import type { MarkdownRenderer } from '../MarkdownRenderer';
export class InlineReferenceLabelsToSuperscriptConverter implements MarkdownRenderer {
public render(markdownContent: string): string {
return convertInlineReferenceLabelsToSuperscript(markdownContent);
}
}
function convertInlineReferenceLabelsToSuperscript(content: string): string {
if (!content) {
return content;
}
return content.replaceAll(TextInsideBracketsPattern, (_fullMatch, label, offset) => {
if (!isInlineReferenceLabel(label, content, offset)) {
return `[${label}]`;
}
return `<sup>[${label}]</sup>`;
});
}
function isInlineReferenceLabel(
referenceLabel: string,
markdownText: string,
openingBracketPosition: number,
): boolean {
const referenceLabelDefinitionIndex = markdownText.indexOf(`\n[${referenceLabel}]: `);
if (openingBracketPosition - 1 /* -1 for newline */ === referenceLabelDefinitionIndex) {
return false; // It is a reference definition, not a label.
}
if (referenceLabelDefinitionIndex === -1) {
return false; // The reference definition is missing.
}
return true;
}
const TextInsideBracketsPattern = /\[(.*?)\]/gm;

View File

@@ -0,0 +1,40 @@
import MarkdownIt from 'markdown-it';
import type { MarkdownRenderer } from '../MarkdownRenderer';
import type { RenderRule } from 'markdown-it/lib/renderer';
export class MarkdownItHtmlRenderer implements MarkdownRenderer {
public render(markdownContent: string): string {
const markdownParser = new MarkdownIt({
html: true, // Enable HTML tags in source to allow other custom rendering logic.
linkify: false, // Disables auto-linking; handled manually for custom formatting.
breaks: false, // Disables conversion of single newlines (`\n`) to HTML breaks (`<br>`).
});
configureLinksToOpenInNewTab(markdownParser);
return markdownParser.render(markdownContent);
}
}
function configureLinksToOpenInNewTab(markdownParser: MarkdownIt): void {
// https://github.com/markdown-it/markdown-it/blob/14.0.0/docs/architecture.md#renderer
const defaultLinkRenderer = getDefaultRenderer(markdownParser, 'link_open');
markdownParser.renderer.rules.link_open = (tokens, index, options, env, self) => {
const currentToken = tokens[index];
Object.entries(AnchorAttributesForExternalLinks).forEach(([attribute, value]) => {
currentToken.attrSet(attribute, value);
});
return defaultLinkRenderer(tokens, index, options, env, self);
};
}
function getDefaultRenderer(md: MarkdownIt, ruleName: string): RenderRule {
const ruleRenderer = md.renderer.rules[ruleName];
const renderTokenAsDefault: RenderRule = (tokens, idx, options, _env, self) => {
return self.renderToken(tokens, idx, options);
};
return ruleRenderer || renderTokenAsDefault;
}
const AnchorAttributesForExternalLinks: Record<string, string> = {
target: '_blank',
rel: 'noopener noreferrer',
} as const;

View File

@@ -0,0 +1,127 @@
import type { MarkdownRenderer } from '../MarkdownRenderer';
export class PlainTextUrlsToHyperlinksConverter implements MarkdownRenderer {
public render(markdownContent: string): string {
return autoLinkPlainUrls(markdownContent);
}
}
const PlainTextUrlInMarkdownRegex = /(?<!\]\(|\[\d+\]:\s+|https?\S+|`)((?:https?):\/\/[^\s\])]*)(?:[\])](?!\()|$|\s)/gm;
function autoLinkPlainUrls(content: string): string {
if (!content) {
return content;
}
return content.replaceAll(PlainTextUrlInMarkdownRegex, (fullMatch, url) => {
return fullMatch.replace(url, formatReadableLink(url));
});
}
function formatReadableLink(url: string): string {
const urlParts = new URL(url);
let displayText = formatHostName(urlParts.hostname);
const pageLabel = extractPageLabel(urlParts);
if (pageLabel) {
displayText += ` - ${truncateTextFromEnd(capitalizeEachWord(pageLabel), 50)}`;
}
return buildMarkdownLink(displayText, url);
}
function formatHostName(hostname: string): string {
const withoutWww = hostname.replace(/^(www\.)/, '');
const truncatedHostName = truncateTextFromStart(withoutWww, 30);
return truncatedHostName;
}
function extractPageLabel(urlParts: URL): string | undefined {
const readablePath = makePathReadable(urlParts.pathname);
if (readablePath) {
return readablePath;
}
return formatQueryParameters(urlParts.searchParams);
}
function buildMarkdownLink(label: string, url: string): string {
return `[${label}](${url})`;
}
function formatQueryParameters(queryParameters: URLSearchParams): string | undefined {
const queryValues = [...queryParameters.values()];
if (queryValues.length === 0) {
return undefined;
}
return findMostDescriptiveName(queryValues);
}
function truncateTextFromStart(text: string, maxLength: number): string {
return text.length > maxLength ? `${text.substring(text.length - maxLength)}` : text;
}
function truncateTextFromEnd(text: string, maxLength: number): string {
return text.length > maxLength ? `${text.substring(0, maxLength)}` : text;
}
function isNumeric(value: string): boolean {
return /^\d+$/.test(value);
}
function makePathReadable(path: string): string | undefined {
const decodedPath = decodeURI(path); // Decode URI components, e.g., '%20' to space
const pathParts = decodedPath.split('/');
const decodedPathParts = pathParts // Split then decode to correctly handle '%2F' as '/'
.map((pathPart) => decodeURIComponent(pathPart));
const descriptivePart = findMostDescriptiveName(decodedPathParts);
if (!descriptivePart) {
return undefined;
}
const withoutExtension = removeFileExtension(descriptivePart);
const tokenizedText = tokenizeTextForReadability(withoutExtension);
return tokenizedText;
}
function tokenizeTextForReadability(text: string): string {
return text
.replaceAll(/[-_+]/g, ' ') // Replace hyphens, underscores, and plus signs with spaces
.replaceAll(/\s+/g, ' '); // Collapse multiple consecutive spaces into a single space
}
function removeFileExtension(value: string): string {
const parts = value.split('.');
if (parts.length === 1) {
return value;
}
const extension = parts[parts.length - 1];
if (extension.length > 9) {
return value; // Heuristically web file extension is no longer than 9 chars (e.g. "html")
}
return parts.slice(0, -1).join('.');
}
function capitalizeEachWord(text: string): string {
return text
.split(' ')
.map((word) => capitalizeFirstLetter(word))
.join(' ');
}
function capitalizeFirstLetter(text: string): string {
return text.charAt(0).toUpperCase() + text.slice(1);
}
function findMostDescriptiveName(segments: readonly string[]): string | undefined {
const meaningfulSegments = segments.filter(isMeaningfulPathSegment);
if (meaningfulSegments.length === 0) {
return undefined;
}
const longestGoodSegment = meaningfulSegments.reduce((a, b) => (a.length > b.length ? a : b));
return longestGoodSegment;
}
function isMeaningfulPathSegment(segment: string): boolean {
return segment.length > 2 // This is often non-human categories like T5 etc.
&& !isNumeric(segment) // E.g. article numbers, issue numbers
&& !/^index(?:\.\S{0,10}$|$)/.test(segment) // E.g. index.html
&& !/^[A-Za-z]{2,4}([_-][A-Za-z]{4})?([_-]([A-Za-z]{2}|[0-9]{3}))$/.test(segment) // Locale string e.g. fr-FR
&& !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(segment) // GUID
&& !/^[0-9a-f]{40}$/.test(segment); // Git SHA (e.g. GitHub links)
}

View File

@@ -55,8 +55,8 @@
@include left-padding('ul, ol', $large-horizontal-spacing);
}
@mixin markdown-text-styles($text-size) {
$base-spacing: $text-size;
@mixin markdown-text-styles {
$base-spacing: 1em;
a {
&[href] {
@@ -73,11 +73,19 @@
content: '';
display: inline-block;
width: $text-size;
height: $text-size;
/*
Use absolute sizing instead of relative. Relative sizing looks bad and inconsistent if there are external elements
inside small text (such as inside `<sup>`) and bigger elements like in bigger text. Making them always have same size
make the text read and flow better.
*/
width: $font-size-absolute-x-small;
height: $font-size-absolute-x-small;
vertical-align: text-top;
background-color: $text-color;
margin-left: math.div($text-size, 4);
margin-left: math.div(1em, 4);
}
/*
Match color of global hover behavior. We need to do it manually because global hover sets
@@ -113,4 +121,11 @@
$code-block-padding: $base-spacing,
$color-background: $color-primary-darker,
);
sup {
@include reset-sup;
vertical-align: super;
font-size: $font-size-relative-smallest;
}
}

View File

@@ -24,6 +24,6 @@ export default defineComponent({
.node-title {
font-family: $font-main;
font-size: $font-size-large;
font-size: $font-size-absolute-large;
}
</style>

View File

@@ -75,7 +75,7 @@ export default defineComponent({
@use 'sass:math';
@use "@/presentation/assets/styles/main" as *;
$font-size : $font-size-small;
$font-size : $font-size-absolute-small;
$color-toggle-unchecked : $color-primary-darker;
$color-toggle-checked : $color-on-secondary;

View File

@@ -53,7 +53,7 @@ export default defineComponent({
@use "@/presentation/assets/styles/main" as *;
@use "./../tree-colors" as *;
$side-size-in-px: $font-size-larger;
$side-size-in-px: $font-size-absolute-x-large;
.checkbox {
position: relative;

View File

@@ -74,7 +74,7 @@ export default defineComponent({
.dialog__close-button {
color: $color-primary-dark;
width: auto;
font-size: $font-size-large;
font-size: $font-size-absolute-large;
margin-right: 0.25em;
align-self: flex-start;
}

View File

@@ -225,7 +225,7 @@ $color-tooltip-background: $color-primary-darkest;
color: $color-on-primary;
border-radius: 16px;
padding: 5px 10px 4px;
font-size: $font-size-normal;
font-size: $font-size-absolute-normal;
/*
This margin creates a visual buffer between the tooltip and the edges of the document.

View File

@@ -78,7 +78,7 @@ export default defineComponent({
&__url {
&:not(:first-child)::before {
content: "|";
font-size: $font-size-smaller;
font-size: $font-size-absolute-x-small;
padding: 0 5px;
}
}

View File

@@ -65,7 +65,7 @@ function hasDesktopVersion(os: OperatingSystem): boolean {
@use "@/presentation/assets/styles/main" as *;
.url {
.inactive {
font-size: $font-size-smaller;
font-size: $font-size-absolute-x-small;
}
}
</style>

View File

@@ -46,11 +46,11 @@ export default defineComponent({
margin: 0;
text-transform: uppercase;
font-family: $font-main;
font-size: $font-size-largest;
font-size: $font-size-absolute-xx-large;
}
.subtitle {
margin: 0;
font-size: $font-size-larger;
font-size: $font-size-absolute-x-large;
color: $color-primary;
font-family: $font-artistic;
font-weight: 500;

View File

@@ -113,7 +113,7 @@ export default defineComponent({
outline: none;
color: $color-primary;
font-family: $font-normal;
font-size: $font-size-normal;
font-size: $font-size-absolute-normal;
&:focus {
color: $color-primary-darker;
}
@@ -127,7 +127,7 @@ export default defineComponent({
text-align: center;
color: $color-on-primary;
border-radius: 0 5px 5px 0;
font-size: $font-size-large;
font-size: $font-size-absolute-large;
padding:5px;
}
</style>