forgejo/web_src/js/webcomponents/citation-information.js
oliverpool 8f28cdefe0
Some checks are pending
/ release (push) Waiting to run
testing-integration / test-unit (push) Waiting to run
testing-integration / test-sqlite (push) Waiting to run
testing-integration / test-mariadb (v10.6) (push) Waiting to run
testing-integration / test-mariadb (v11.8) (push) Waiting to run
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions
feat(ui): add switch between formats when previewing CITATION.{cff,bib} files (#9103)
See #8222 for context (loosely related to #4595).

## Implemented changes

The conversion logic is kept in the frontend and the related npm libraries are lazy-loaded (unchanged).

### Show some tabs on the preview of the `CITATION.*` file to switch between the formats:

![image](/attachments/be02656f-d906-4191-aa84-d666ee5a90ba)
![image](/attachments/240384e3-dec8-4f02-94e6-261143193541)

### Convert the "Cite repository" to a simple link to the citation file

So that this change can be considered non-breaking

## Current state (before this PR)

The last non-test call of `git.Blob.GetBlobContent` is made to retrieve the content of an eventual CITATION file.
This is available in the `...` menu near the clone URL:
![image](/attachments/ef79128d-ee3f-4e43-a74d-a00e4dcfe6b4)
And is displayed as a popup:

![image](/attachments/7aa930f9-0766-47b9-8145-cbebb5b051b0)

Co-authored-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/9103
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
Co-authored-by: oliverpool <git@olivier.pfad.fr>
Co-committed-by: oliverpool <git@olivier.pfad.fr>
2025-11-14 14:39:20 +01:00

114 lines
3.6 KiB
JavaScript

// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
import '@citation-js/plugin-software-formats';
import '@citation-js/plugin-bibtex';
import {Cite, plugins} from '@citation-js/core';
import {getCurrentLocale} from '../utils.js';
import {initTab} from '../modules/tab.ts';
window.customElements.define(
'citation-information',
class extends HTMLElement {
connectedCallback() {
const children = this.children; // eslint-disable-line wc/no-child-traversal-in-connectedcallback
if (children.length !== 1) {
// developer error
throw new Error(
`<citation-information> expected one child, got ${children.length}`,
);
}
const lang = getCurrentLocale() || 'en-US';
const raw = children[0];
raw.dataset.tab = 'raw';
raw.classList.add('tab', 'active');
// like in copy-content
const lineEls = raw.querySelectorAll('.lines-code');
const code = Array.from(lineEls, (el) => el.textContent).join('');
const inputType = plugins.input.type(code);
let parsed;
try {
parsed = new Cite(code, {forceType: inputType});
} catch (err) {
const elContainer = document.createElement('div');
elContainer.classList.add('ui', 'warning', 'message');
const elHeader = document.createElement('div');
elHeader.classList.add('header');
elHeader.textContent = `Could not parse citation-information (format ${inputType})`; // ideally this message should be localized, however the error below will likely be in english
elContainer.append(elHeader);
const elParagraph = document.createElement('pre');
elParagraph.textContent = err;
elContainer.append(elParagraph);
this.prepend(elContainer);
return;
}
const toggleBar = document.createElement('div');
toggleBar.classList.add('switch');
const newButton = (txt, id, tooltip, active) => {
const el = document.createElement('button');
el.textContent = txt;
el.dataset.tab = id;
if (tooltip) {
el.dataset.tooltipContent = tooltip;
}
el.classList.add('item');
if (active) {
el.classList.add('active');
}
return el;
};
let originalText = 'Original';
let originalTooltip = '';
switch (inputType) {
case '@biblatex/text':
originalText = 'BibTeX';
break;
case '@else/yaml':
originalText = 'CFF';
originalTooltip = 'Citation File Format';
break;
}
toggleBar.append(newButton(originalText, 'raw', originalTooltip, true));
const appendTab = (id, btnLabel, btnTooltip, tabContent) => {
const el = document.createElement('pre');
el.textContent = tabContent;
el.dataset.tab = id;
el.classList.add('tab');
el.style.padding = '1rem';
el.style.margin = 0;
this.append(el);
toggleBar.append(newButton(btnLabel, id, btnTooltip));
};
if (inputType !== '@biblatex/text') {
appendTab(
'bibtex',
'BibTeX',
'',
parsed.format('bibtex', {lang}).trim(),
);
}
if (inputType !== '@else/yaml') {
appendTab(
'cff',
'CFF',
'Citation File Format',
parsed.format('cff', {lang}).trim(),
);
}
const toggleBarParent = document.querySelector('.file-header-left');
toggleBarParent.prepend(toggleBar);
initTab(toggleBarParent);
}
},
);