Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion web_src/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
@import "./features/captcha.css";

@import "./markup/content.css";
@import "./markup/codecopy.css";
@import "./markup/codeblock.css";
@import "./markup/codepreview.css";
@import "./markup/asciicast.css";

Expand Down
10 changes: 10 additions & 0 deletions web_src/css/markup/codeblock.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.markup .ui.button.code-copy {
top: 8px;
right: 6px;
margin: 0;
}

.markup .mermaid-block .view-controller {
right: 6px;
bottom: 5px;
}
40 changes: 0 additions & 40 deletions web_src/css/markup/codecopy.css

This file was deleted.

37 changes: 37 additions & 0 deletions web_src/css/markup/content.css
Original file line number Diff line number Diff line change
Expand Up @@ -601,3 +601,40 @@ In markup content, we always use bottom margin for all elements */
.file-view.markup.orgmode li.indeterminate > p {
display: inline-block;
}

/* auto-hide-control is a control element or a container for control elements,
it floats over the code-block and only shows when the code-block is hovered. */
.markup .auto-hide-control {
position: absolute;
z-index: 1;
visibility: hidden; /* prevent from click events even opacity=0 */
opacity: 0;
transition: var(--transition-hover-fade);
}

/* all rendered code-block elements are in their container,
the manually written code-block elements on "packages" pages don't have the container */
.markup .code-block-container:hover .auto-hide-control,
.markup .code-block:hover .auto-hide-control {
visibility: visible;
opacity: 1;
}

@media (hover: none) {
.markup .auto-hide-control {
visibility: visible;
opacity: 1;
}
}

/* can not use regular transparent button colors for hover and active states
because we need opaque colors here as code can appear behind the button */
.markup .auto-hide-control.ui.button:hover,
.markup .auto-hide-control .ui.button:hover {
background: var(--color-secondary) !important;
}

.markup .auto-hide-control.ui.button:active,
.markup .auto-hide-control .ui.button:active {
background: var(--color-secondary-dark-1) !important;
}
24 changes: 12 additions & 12 deletions web_src/js/markup/codecopy.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import {svg} from '../svg.ts';
import {queryElems} from '../utils/dom.ts';
import {createElementFromAttrs, queryElems} from '../utils/dom.ts';

export function makeCodeCopyButton(attrs: Record<string, string> = {}): HTMLButtonElement {
const button = document.createElement('button');
button.classList.add('code-copy', 'ui', 'button');
button.innerHTML = svg('octicon-copy');
for (const [key, value] of Object.entries(attrs)) {
button.setAttribute(key, value);
}
return button;
const btn = createElementFromAttrs<HTMLButtonElement>('button', {
class: 'ui compact icon button code-copy auto-hide-control',
...attrs,
});
btn.innerHTML = svg('octicon-copy');
return btn;
}

export function initMarkupCodeCopy(elMarkup: HTMLElement): void {
// .markup .code-block code
queryElems(elMarkup, '.code-block code', (el) => {
if (!el.textContent) return;
const btn = makeCodeCopyButton();
// remove final trailing newline introduced during HTML rendering
btn.setAttribute('data-clipboard-text', el.textContent.replace(/\r?\n$/, ''));
const btn = makeCodeCopyButton({
'data-clipboard-text': el.textContent.replace(/\r?\n$/, ''),
});
// we only want to use `.code-block-container` if it exists, no matter `.code-block` exists or not.
const btnContainer = el.closest('.code-block-container') ?? el.closest('.code-block');
btnContainer!.append(btn);
const btnContainer = el.closest('.code-block-container') ?? el.closest('.code-block')!;
btnContainer.append(btn);
});
}
66 changes: 18 additions & 48 deletions web_src/js/markup/mermaid.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,18 @@
import {isDarkTheme, parseDom} from '../utils.ts';
import {makeCodeCopyButton} from './codecopy.ts';
import {displayError} from './common.ts';
import {createElementFromAttrs, createElementFromHTML, getCssRootVariablesText, queryElems} from '../utils/dom.ts';
import {html} from '../utils/html.ts';
import {createElementFromAttrs, createElementFromHTML, queryElems} from '../utils/dom.ts';
import {html, htmlRaw} from '../utils/html.ts';
import {load as loadYaml} from 'js-yaml';
import type {MermaidConfig} from 'mermaid';
import {svg} from '../svg.ts';
Comment thread
wxiaoguang marked this conversation as resolved.

const {mermaidMaxSourceCharacters} = window.config;

function getIframeCss(): string {
// Inherit some styles (e.g.: root variables) from parent document.
// The buttons should use the same styles as `button.code-copy`, and align with it.
return `
${getCssRootVariablesText()}

html, body { height: 100%; }
body { margin: 0; padding: 0; overflow: hidden; }
#mermaid { display: block; margin: 0 auto; }

.view-controller {
position: absolute;
z-index: 1;
right: 5px;
bottom: 5px;
display: flex;
gap: 4px;
visibility: hidden;
opacity: 0;
transition: var(--transition-hover-fade);
margin-right: 0.25em;
}
body:hover .view-controller { visibility: visible; opacity: 1; }
@media (hover: none) {
.view-controller { visibility: visible; opacity: 1; }
}
.view-controller button {
cursor: pointer;
display: inline-flex;
justify-content: center;
align-items: center;
line-height: 1;
padding: 7.5px 10px;
border: 1px solid var(--color-light-border);
border-radius: var(--border-radius);
background: var(--color-button);
color: var(--color-text);
user-select: none;
}
.view-controller button:hover { background: var(--color-secondary); }
.view-controller button:active { background: var(--color-secondary-dark-1); }
`;
}

Expand Down Expand Up @@ -117,10 +81,9 @@ async function loadMermaid(needElkRender: boolean) {
};
}

function initMermaidViewController(dragElement: SVGSVGElement) {
function initMermaidViewController(viewController: HTMLElement, dragElement: SVGSVGElement) {
let inited = false, isDragging = false;
let currentScale = 1, initLeft = 0, lastLeft = 0, lastTop = 0, lastPageX = 0, lastPageY = 0;
const container = dragElement.parentElement!;

const resetView = () => {
currentScale = 1;
Expand All @@ -136,11 +99,12 @@ function initMermaidViewController(dragElement: SVGSVGElement) {
if (inited) return;
// if we need to drag or zoom, use absolute position and get the current "left" from the "margin: auto" layout.
inited = true;
const container = dragElement.parentElement!;
initLeft = container.getBoundingClientRect().width / 2 - dragElement.getBoundingClientRect().width / 2;
resetView();
};

for (const el of queryElems(container, '[data-control-action]')) {
for (const el of viewController.querySelectorAll('[data-control-action]')) {
el.addEventListener('click', () => {
initAbsolutePosition();
switch (el.getAttribute('data-control-action')) {
Expand All @@ -161,7 +125,8 @@ function initMermaidViewController(dragElement: SVGSVGElement) {
dragElement.addEventListener('mousedown', (e) => {
if (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return; // only left mouse button can drag
const target = e.target as Element;
if (target.closest('div, p, a, span, button, input')) return; // don't start the drag if the click is on an interactive element (e.g.: link, button) or text element
// don't start the drag if the click is on an interactive element (e.g.: link, button) or text element
if (target.closest('div, p, a, span, button, input, text')) return;

initAbsolutePosition();
isDragging = true;
Expand Down Expand Up @@ -239,7 +204,14 @@ export async function initMarkupCodeMermaid(elMarkup: HTMLElement): Promise<void
const svgDoc = parseDom(svgText, 'image/svg+xml');
const svgNode = (svgDoc.documentElement as unknown) as SVGSVGElement;

const viewControllerHtml = html`<div class="view-controller"><button data-control-action="zoom-in">+</button><button data-control-action="reset">reset</button><button data-control-action="zoom-out">-</button></div>`;
const viewControllerHtml = html`
<div class="view-controller auto-hide-control flex-text-block">
<button type="button" class="ui tiny compact icon button" data-control-action="zoom-in">${htmlRaw(svg('octicon-zoom-in', 12))}</button>
<button type="button" class="ui tiny compact icon button" data-control-action="reset">${htmlRaw(svg('octicon-sync', 12))}</button>
<button type="button" class="ui tiny compact icon button" data-control-action="zoom-out">${htmlRaw(svg('octicon-zoom-out', 12))}</button>
</div>
`;
const viewController = createElementFromHTML(viewControllerHtml);

// create an iframe to sandbox the svg with styles, and set correct height by reading svg's viewBox height
const iframe = document.createElement('iframe');
Expand All @@ -262,17 +234,15 @@ export async function initMarkupCodeMermaid(elMarkup: HTMLElement): Promise<void
const iframeBody = iframe.contentDocument!.body;
iframeBody.append(svgNode);
bindFunctions?.(iframeBody); // follow "mermaid.render" doc, attach event handlers to the svg's container
iframeBody.append(createElementFromHTML(viewControllerHtml));

// according to mermaid, the viewBox height should always exist, here just a fallback for unknown cases.
// and keep in mind: clientHeight can be 0 if the element is hidden (display: none).
if (!iframeHeightFromViewBox) applyMermaidIframeHeight(iframe, iframeBody.clientHeight);
iframe.classList.remove('is-loading');

initMermaidViewController(svgNode);
initMermaidViewController(viewController, svgNode);
});

const container = createElementFromAttrs('div', {class: 'mermaid-block'}, iframe, makeCodeCopyButton({'data-clipboard-text': source}));
const container = createElementFromAttrs('div', {class: 'mermaid-block'}, iframe, viewController);
Comment thread
wxiaoguang marked this conversation as resolved.
parentContainer.replaceWith(container);
} catch (err) {
displayError(parentContainer, err);
Expand Down
4 changes: 4 additions & 0 deletions web_src/js/svg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ import octiconTrash from '../../public/assets/img/svg/octicon-trash.svg';
import octiconTriangleDown from '../../public/assets/img/svg/octicon-triangle-down.svg';
import octiconX from '../../public/assets/img/svg/octicon-x.svg';
import octiconXCircleFill from '../../public/assets/img/svg/octicon-x-circle-fill.svg';
import octiconZoomIn from '../../public/assets/img/svg/octicon-zoom-in.svg';
import octiconZoomOut from '../../public/assets/img/svg/octicon-zoom-out.svg';

const svgs = {
'gitea-double-chevron-left': giteaDoubleChevronLeft,
Expand Down Expand Up @@ -161,6 +163,8 @@ const svgs = {
'octicon-triangle-down': octiconTriangleDown,
'octicon-x': octiconX,
'octicon-x-circle-fill': octiconXCircleFill,
'octicon-zoom-in': octiconZoomIn,
'octicon-zoom-out': octiconZoomOut,
};

export type SvgName = keyof typeof svgs;
Expand Down
20 changes: 2 additions & 18 deletions web_src/js/utils/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ export function createElementFromHTML<T extends HTMLElement>(htmlString: string)
return div.firstChild as T;
}

export function createElementFromAttrs(tagName: string, attrs: Record<string, any> | null, ...children: (Node | string)[]): HTMLElement {
export function createElementFromAttrs<T extends HTMLElement>(tagName: string, attrs: Record<string, any> | null, ...children: (Node | string)[]): T {
const el = document.createElement(tagName);
for (const [key, value] of Object.entries(attrs || {})) {
if (value === undefined || value === null) continue;
Expand All @@ -314,7 +314,7 @@ export function createElementFromAttrs(tagName: string, attrs: Record<string, an
for (const child of children) {
el.append(child instanceof Node ? child : document.createTextNode(child));
}
return el;
return el as T;
}

export function animateOnce(el: Element, animationClassName: string): Promise<void> {
Expand Down Expand Up @@ -352,22 +352,6 @@ export function isPlainClick(e: MouseEvent) {
return e.button === 0 && !e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey;
}

let cssRootVariablesTextCache: string = '';
export function getCssRootVariablesText(): string {
if (cssRootVariablesTextCache) return cssRootVariablesTextCache;
const style = getComputedStyle(document.documentElement);
let text = ':root {\n';
for (let i = 0; i < style.length; i++) {
const name = style.item(i);
if (name.startsWith('--')) {
text += ` ${name}: ${style.getPropertyValue(name)};\n`;
}
}
text += '}\n';
cssRootVariablesTextCache = text;
return text;
}

let elemIdCounter = 0;
export function generateElemId(prefix: string = ''): string {
return `${prefix}${elemIdCounter++}`;
Expand Down