Refactor dashboard repo list to Vue SFC (#23405)

Similar to #23394

The dashboard repo list mixes jQuery/Fomantic UI/Vue together, it's very
diffcult to maintain and causes unfixable a11y problems.

This PR uses two steps to refactor the repo list:

1. move `data-` attributes to JS object and use Vue data as much as
possible
d3adc0dcac
2. move the code into a Vue SFC
7ebe55df6e

Total: +516 −585

Screenshots:

<details>

![image](https://user-images.githubusercontent.com/2114189/224271457-a23e05be-d7d3-4247-a803-f0ee30c36f44.png)

![image](https://user-images.githubusercontent.com/2114189/224271504-76fbd3da-4d7a-4725-b0d1-fbff83caac63.png)

![image](https://user-images.githubusercontent.com/2114189/224271845-f007cadf-6c49-46bd-a65c-a3fc75bdba3b.png)

</details>

---------

Co-authored-by: John Olheiser <john.olheiser@gmail.com>
This commit is contained in:
2023-03-14 12:09:06 +08:00
committed by GitHub
parent b942838bd4
commit e82f1b15c7
8 changed files with 516 additions and 585 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -51,7 +51,7 @@
<script>
import VueBarGraph from 'vue-bar-graph';
import {initVueApp} from './VueComponentLoader.js';
import {createApp} from 'vue';
const sfc = {
components: {VueBarGraph},
@ -102,8 +102,11 @@ const sfc = {
};
export function initRepoActivityTopAuthorsChart() {
initVueApp('#repo-activity-top-authors-chart', sfc);
const el = document.getElementById('repo-activity-top-authors-chart');
if (el) {
createApp(sfc).mount(el);
}
}
export default sfc; // this line is necessary to activate the IDE's Vue plugin
export default sfc; // activate the IDE's Vue plugin
</script>

View File

@ -1,6 +1,5 @@
import {createApp, nextTick} from 'vue';
import $ from 'jquery';
import {vueDelimiters} from './VueComponentLoader.js';
export function initRepoBranchTagDropdown(selector) {
$(selector).each(function (dropdownIndex, elRoot) {
@ -39,7 +38,7 @@ export function initRepoBranchTagDropdown(selector) {
}
const view = createApp({
delimiters: vueDelimiters,
delimiters: ['${', '}'],
data() {
return data;
},

View File

@ -1,49 +0,0 @@
import {createApp} from 'vue';
import {svgs} from '../svg.js';
export const vueDelimiters = ['${', '}'];
let vueEnvInited = false;
export function initVueEnv() {
if (vueEnvInited) return;
vueEnvInited = true;
// As far as I could tell, this is no longer possible.
// But there seem not to be a guide what to do instead.
// const isProd = window.config.runModeIsProd;
// Vue.config.devtools = !isProd;
}
let vueSvgInited = false;
export function initVueSvg(app) {
if (vueSvgInited) return;
vueSvgInited = true;
// register svg icon vue components, e.g. <octicon-repo size="16"/>
for (const [name, htmlString] of Object.entries(svgs)) {
const template = htmlString
.replace(/height="[0-9]+"/, 'v-bind:height="size"')
.replace(/width="[0-9]+"/, 'v-bind:width="size"');
app.component(name, {
props: {
size: {
type: String,
default: '16',
},
},
template,
});
}
}
export function initVueApp(el, opts = {}) {
if (typeof el === 'string') {
el = document.querySelector(el);
}
if (!el) return null;
return createApp(
{delimiters: vueDelimiters, ...opts}
).mount(el);
}

View File

@ -2,9 +2,8 @@
import './bootstrap.js';
import $ from 'jquery';
import {initVueEnv} from './components/VueComponentLoader.js';
import {initRepoActivityTopAuthorsChart} from './components/RepoActivityTopAuthors.vue';
import {initDashboardRepoList} from './components/DashboardRepoList.js';
import {initDashboardRepoList} from './components/DashboardRepoList.vue';
import {attachTribute} from './features/tribute.js';
import {initGlobalCopyToClipboardListener} from './features/clipboard.js';
@ -100,7 +99,6 @@ $.fn.tab.settings.silent = true;
// Disable the behavior of fomantic to toggle the checkbox when you press enter on a checkbox element.
$.fn.checkbox.settings.enableEnterKey = false;
initVueEnv();
$(document).ready(() => {
initGlobalCommon();

View File

@ -31,8 +31,17 @@ import octiconSkip from '../../public/img/svg/octicon-skip.svg';
import octiconMeter from '../../public/img/svg/octicon-meter.svg';
import octiconBlocked from '../../public/img/svg/octicon-blocked.svg';
import octiconSync from '../../public/img/svg/octicon-sync.svg';
import octiconFilter from '../../public/img/svg/octicon-filter.svg';
import octiconPlus from '../../public/img/svg/octicon-plus.svg';
import octiconSearch from '../../public/img/svg/octicon-search.svg';
import octiconArchive from '../../public/img/svg/octicon-archive.svg';
import octiconStar from '../../public/img/svg/octicon-star.svg';
import giteaDoubleChevronLeft from '../../public/img/svg/gitea-double-chevron-left.svg';
import giteaDoubleChevronRight from '../../public/img/svg/gitea-double-chevron-right.svg';
import octiconChevronLeft from '../../public/img/svg/octicon-chevron-left.svg';
import octiconOrganization from '../../public/img/svg/octicon-organization.svg';
export const svgs = {
const svgs = {
'octicon-blocked': octiconBlocked,
'octicon-check-circle-fill': octiconCheckCircleFill,
'octicon-chevron-down': octiconChevronDown,
@ -66,14 +75,25 @@ export const svgs = {
'octicon-triangle-down': octiconTriangleDown,
'octicon-x': octiconX,
'octicon-x-circle-fill': octiconXCircleFill,
'octicon-filter': octiconFilter,
'octicon-plus': octiconPlus,
'octicon-search': octiconSearch,
'octicon-archive': octiconArchive,
'octicon-star': octiconStar,
'gitea-double-chevron-left': giteaDoubleChevronLeft,
'gitea-double-chevron-right': giteaDoubleChevronRight,
'octicon-chevron-left': octiconChevronLeft,
'octicon-organization': octiconOrganization,
};
// TODO: use a more general approach to access SVG icons. At the moment, developers must check, pick and fill the names manually, most of the SVG icons in assets couldn't be used directly.
const parser = new DOMParser();
const serializer = new XMLSerializer();
// retrieve a HTML string for given SVG icon name, size and additional classes
// retrieve an HTML string for given SVG icon name, size and additional classes
export function svg(name, size = 16, className = '') {
if (!(name in svgs)) return '';
if (!(name in svgs)) throw new Error(`Unknown SVG icon: ${name}`);
if (size === 16 && !className) return svgs[name];
const document = parser.parseFromString(svgs[name], 'image/svg+xml');