mirror of
https://github.com/go-gitea/gitea
synced 2025-07-04 01:27:20 +00:00
77 lines
3.3 KiB
TypeScript
77 lines
3.3 KiB
TypeScript
import type {FileRenderPlugin} from '../render/plugin.ts';
|
|
import {newRenderPlugin3DViewer} from '../render/plugins/3d-viewer.ts';
|
|
import {newRenderPluginPdfViewer} from '../render/plugins/pdf-viewer.ts';
|
|
import {registerGlobalInitFunc} from '../modules/observer.ts';
|
|
import {createElementFromHTML, showElem, toggleClass} from '../utils/dom.ts';
|
|
import {htmlEscape} from 'escape-goat';
|
|
import {basename} from '../utils.ts';
|
|
|
|
const plugins: FileRenderPlugin[] = [];
|
|
|
|
function initPluginsOnce(): void {
|
|
if (plugins.length) return;
|
|
plugins.push(newRenderPlugin3DViewer(), newRenderPluginPdfViewer());
|
|
}
|
|
|
|
function findFileRenderPlugin(filename: string, mimeType: string): FileRenderPlugin | null {
|
|
return plugins.find((plugin) => plugin.canHandle(filename, mimeType)) || null;
|
|
}
|
|
|
|
function showRenderRawFileButton(elFileView: HTMLElement, renderContainer: HTMLElement | null): void {
|
|
const toggleButtons = elFileView.querySelector('.file-view-toggle-buttons');
|
|
showElem(toggleButtons);
|
|
const displayingRendered = Boolean(renderContainer);
|
|
toggleClass(toggleButtons.querySelectorAll('.file-view-toggle-source'), 'active', !displayingRendered); // it may not exist
|
|
toggleClass(toggleButtons.querySelector('.file-view-toggle-rendered'), 'active', displayingRendered);
|
|
// TODO: if there is only one button, hide it?
|
|
}
|
|
|
|
async function renderRawFileToContainer(container: HTMLElement, rawFileLink: string, mimeType: string) {
|
|
const elViewRawPrompt = container.querySelector('.file-view-raw-prompt');
|
|
if (!rawFileLink || !elViewRawPrompt) throw new Error('unexpected file view container');
|
|
|
|
let rendered = false, errorMsg = '';
|
|
try {
|
|
const plugin = findFileRenderPlugin(basename(rawFileLink), mimeType);
|
|
if (plugin) {
|
|
container.classList.add('is-loading');
|
|
container.setAttribute('data-render-name', plugin.name); // not used yet
|
|
await plugin.render(container, rawFileLink);
|
|
rendered = true;
|
|
}
|
|
} catch (e) {
|
|
errorMsg = `${e}`;
|
|
} finally {
|
|
container.classList.remove('is-loading');
|
|
}
|
|
|
|
if (rendered) {
|
|
elViewRawPrompt.remove();
|
|
return;
|
|
}
|
|
|
|
// remove all children from the container, and only show the raw file link
|
|
container.replaceChildren(elViewRawPrompt);
|
|
|
|
if (errorMsg) {
|
|
const elErrorMessage = createElementFromHTML(htmlEscape`<div class="ui error message">${errorMsg}</div>`);
|
|
elViewRawPrompt.insertAdjacentElement('afterbegin', elErrorMessage);
|
|
}
|
|
}
|
|
|
|
export function initRepoFileView(): void {
|
|
registerGlobalInitFunc('initRepoFileView', async (elFileView: HTMLElement) => {
|
|
initPluginsOnce();
|
|
const rawFileLink = elFileView.getAttribute('data-raw-file-link');
|
|
const mimeType = elFileView.getAttribute('data-mime-type') || ''; // not used yet
|
|
// TODO: we should also provide the prefetched file head bytes to let the plugin decide whether to render or not
|
|
const plugin = findFileRenderPlugin(basename(rawFileLink), mimeType);
|
|
if (!plugin) return;
|
|
|
|
const renderContainer = elFileView.querySelector<HTMLElement>('.file-view-render-container');
|
|
showRenderRawFileButton(elFileView, renderContainer);
|
|
// maybe in the future multiple plugins can render the same file, so we should not assume only one plugin will render it
|
|
if (renderContainer) await renderRawFileToContainer(renderContainer, rawFileLink, mimeType);
|
|
});
|
|
}
|