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:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user