feat: sidebar Repository identique au Workspace-local, pages forge unifiees, GC miroirs vides
This commit is contained in:
+154
-39
@@ -5,8 +5,24 @@ var _regGiteaWs = () => {
|
||||
const owner = params.get('owner') || '';
|
||||
const repo = params.get('repo') || '';
|
||||
|
||||
// Forge is carried by the page route (/gitea-workspace vs /github-workspace);
|
||||
// the sidebar cookie prefix follows it so refresh APIs resolve the mirror.
|
||||
const forge = (window.location.pathname || '').indexOf('/github-workspace') === 0 ? 'github' : 'gitea';
|
||||
|
||||
return {
|
||||
owner, repo,
|
||||
owner, repo, forge,
|
||||
apiBase() {
|
||||
return '/api/' + this.forge + '/projects/' + this.owner + '/' + this.repo;
|
||||
},
|
||||
// ── Library-style toolbar (same as /local-workspace) ──
|
||||
viewMode: localStorage.getItem('fd_default_view') || 'tree',
|
||||
viewMenuOpen: false,
|
||||
sortOpen: false,
|
||||
filterOpen: false,
|
||||
searchOpen: false,
|
||||
sortBy: '',
|
||||
filterType: '',
|
||||
searchQuery: '',
|
||||
showFile: false,
|
||||
showEditor: false,
|
||||
showPrivate: false,
|
||||
@@ -27,6 +43,7 @@ var _regGiteaWs = () => {
|
||||
// File tree browser
|
||||
treeItems: [],
|
||||
sortedTreeItems: [],
|
||||
filteredTreeItems: [],
|
||||
treeLoading: false,
|
||||
folderStack: [],
|
||||
currentPath: '',
|
||||
@@ -34,17 +51,77 @@ var _regGiteaWs = () => {
|
||||
gwCtx: { visible: false, x: 0, y: 0, item: null },
|
||||
|
||||
_sortTree() {
|
||||
this.sortedTreeItems = [...this.treeItems].sort(function(a, b) {
|
||||
if (a.type === b.type) return a.name.localeCompare(b.name);
|
||||
return a.type === 'folder' ? -1 : 1;
|
||||
var items = [...this.treeItems];
|
||||
var by = this.sortBy || '';
|
||||
var typeRank = function(t) { return (t === 'folder' || t === 'dir') ? 0 : 1; };
|
||||
items.sort(function(a, b) {
|
||||
if (by === 'name-desc') return (b.name || '').localeCompare(a.name || '');
|
||||
if (by === 'type') {
|
||||
var d = typeRank(a.type) - typeRank(b.type);
|
||||
return d !== 0 ? d : (a.name || '').localeCompare(b.name || '');
|
||||
}
|
||||
// default name-asc: folders first
|
||||
var td = typeRank(a.type) - typeRank(b.type);
|
||||
if (td !== 0) return td;
|
||||
return (a.name || '').localeCompare(b.name || '');
|
||||
});
|
||||
this.sortedTreeItems = items;
|
||||
this.doFilter();
|
||||
},
|
||||
toggleViewMenu() { this.viewMenuOpen = !this.viewMenuOpen; this.sortOpen = false; this.filterOpen = false; },
|
||||
toggleSortMenu() { this.sortOpen = !this.sortOpen; this.viewMenuOpen = false; this.filterOpen = false; },
|
||||
toggleFilterMenu() { this.filterOpen = !this.filterOpen; this.viewMenuOpen = false; this.sortOpen = false; },
|
||||
toggleSearch() {
|
||||
this.searchOpen = !this.searchOpen;
|
||||
if (!this.searchOpen) { this.searchQuery = ''; this.doFilter(); }
|
||||
},
|
||||
doSort() { this._sortTree(); },
|
||||
filterByType(t) { this.filterType = (this.filterType === t ? '' : t); this.doFilter(); },
|
||||
doFilter() {
|
||||
var q = (this.searchQuery || '').toLowerCase();
|
||||
var ft = this.filterType || '';
|
||||
this.filteredTreeItems = (this.sortedTreeItems || []).filter(function(it) {
|
||||
if (ft === 'folder' && it.type !== 'folder') return false;
|
||||
if (ft && ft !== 'folder') {
|
||||
var ext = (it.name || '').split('.').pop().toLowerCase();
|
||||
var kind = (/^(png|jpe?g|gif|svg|webp|ico|bmp)$/.test(ext)) ? 'image'
|
||||
: (/^(pdf)$/.test(ext)) ? 'pdf'
|
||||
: (/^(js|jsx|ts|tsx|py|rb|rs|go|java|kt|c|cpp|h|cs|php|swift|r|lua|dart|sh|sql)$/.test(ext)) ? 'code'
|
||||
: (/^(md|txt|json|ya?ml|toml|ini|csv)$/.test(ext)) ? 'text'
|
||||
: 'page';
|
||||
if (kind !== ft && !(ft === 'page' && it.type === 'file' && kind === 'page')) {
|
||||
// 'page' filter = generic files; keep kind match only
|
||||
if (ft === 'page' && it.type !== 'file') return false;
|
||||
else if (ft !== 'page') return false;
|
||||
}
|
||||
}
|
||||
if (q && (it.name || '').toLowerCase().indexOf(q) < 0 && (it.path || '').toLowerCase().indexOf(q) < 0) return false;
|
||||
return true;
|
||||
});
|
||||
},
|
||||
createFolderAtRoot() {
|
||||
var name = prompt('Folder name:');
|
||||
if (!name || !name.trim()) return;
|
||||
this.createNewFolder(name.trim(), this.currentPath || '');
|
||||
},
|
||||
async createNewFolder(name, basePath) {
|
||||
var path = (basePath ? basePath.replace(/\/$/, '') + '/' : '') + name + '/.gitkeep';
|
||||
try {
|
||||
var r = await fetch(this.apiBase() + '/file', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCsrf() },
|
||||
body: JSON.stringify({ path: path, content: '', message: 'Create folder ' + name + ' via FlowDeck' })
|
||||
});
|
||||
if (r.ok) await this.refreshTree();
|
||||
else window.showToast('Create folder failed', 'error');
|
||||
} catch (e) { window.showToast('Create folder failed', 'error'); }
|
||||
},
|
||||
|
||||
init() {
|
||||
// Expose globally for header to access
|
||||
window._gwData = this;
|
||||
// Set cookie for sidebar
|
||||
document.cookie = 'flowdeck_workspace=gitea:' + this.owner + ':' + this.repo + ';path=/;SameSite=Lax';
|
||||
// Set cookie for sidebar (forge-prefixed so Workspace mirror resolves)
|
||||
document.cookie = 'flowdeck_workspace=' + this.forge + ':' + this.owner + ':' + this.repo + ';path=/;SameSite=Lax';
|
||||
// Load sidebar tree (into gitea section)
|
||||
this.loadSidebarTree();
|
||||
// Load main content tree
|
||||
@@ -57,14 +134,14 @@ var _regGiteaWs = () => {
|
||||
this.treeLoading = true;
|
||||
this.currentPath = path;
|
||||
try {
|
||||
var url = '/api/gitea/projects/' + this.owner + '/' + this.repo + '/tree';
|
||||
var url = this.apiBase() + '/tree';
|
||||
if (path) url += '?path=' + encodeURIComponent(path);
|
||||
var r = await fetch(url);
|
||||
if (!r.ok) { this.treeItems = []; this.sortedTreeItems = []; return; }
|
||||
if (!r.ok) { this.treeItems = []; this.sortedTreeItems = []; this.filteredTreeItems = []; return; }
|
||||
var d = await r.json();
|
||||
this.treeItems = d.tree || [];
|
||||
this._sortTree();
|
||||
} catch { this.treeItems = []; this.sortedTreeItems = []; }
|
||||
} catch { this.treeItems = []; this.sortedTreeItems = []; this.filteredTreeItems = []; }
|
||||
finally { this.treeLoading = false; }
|
||||
},
|
||||
|
||||
@@ -97,7 +174,7 @@ var _regGiteaWs = () => {
|
||||
var self = this;
|
||||
var oldPath = item.path;
|
||||
var newPath = oldPath.replace(/[^/]+$/, newName.trim());
|
||||
fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/file', {
|
||||
fetch(this.apiBase() + '/file', {
|
||||
method: 'PUT',
|
||||
headers: {'X-CSRF-Token': getCsrf(), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ path: oldPath, new_path: newPath, message: 'Rename ' + oldPath + ' to ' + newPath })
|
||||
@@ -114,7 +191,7 @@ var _regGiteaWs = () => {
|
||||
if (!item) return;
|
||||
if (!confirm('Delete ' + item.name + '?')) return;
|
||||
var self = this;
|
||||
fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/file?path=' + encodeURIComponent(item.path) + '&sha=' + (item.sha || '') + '&message=Delete ' + item.path, {headers: {'X-CSRF-Token': getCsrf()},
|
||||
fetch(this.apiBase() + '/file?path=' + encodeURIComponent(item.path) + '&sha=' + (item.sha || '') + '&message=Delete ' + item.path, {headers: {'X-CSRF-Token': getCsrf()},
|
||||
method: 'DELETE'
|
||||
}).then(function(r){
|
||||
if (r.ok) { self.refreshTree(); }
|
||||
@@ -124,7 +201,7 @@ var _regGiteaWs = () => {
|
||||
|
||||
async loadSidebarTree() {
|
||||
try {
|
||||
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/tree');
|
||||
var r = await fetch(this.apiBase() + '/tree');
|
||||
if (!r.ok) {
|
||||
// If API fails (e.g. no Gitea token), set a message
|
||||
var container = document.getElementById('sidebar-gitea-items');
|
||||
@@ -140,15 +217,31 @@ var _regGiteaWs = () => {
|
||||
window.appState.sectionsOpen.gitea = true;
|
||||
}
|
||||
container.innerHTML = '';
|
||||
// Build tree HTML as string and set once (more performant)
|
||||
// Same DOM contract as Workspace-local (_workspace_tree_macro):
|
||||
// .sidebar-item.ws-tree-item + chevron + .page-icon/.page-name.
|
||||
var html = '';
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var item = items[i];
|
||||
var icon = item.type === 'folder' ? getSvgIcon('folder',14) : getSvgIcon('file',14);
|
||||
html += '<li class="sidebar-item" data-gitea-path="' + item.path + '" data-gitea-type="' + (item.type === 'folder' ? 'folder' : 'file') + '" data-gitea-sha="' + (item.sha || '') + '" style="padding-left:12px;cursor:pointer;display:flex;align-items:center;gap:4px;">';
|
||||
html += '<span class="page-icon">' + icon + '</span>';
|
||||
html += '<span class="page-name">' + item.name + '</span>';
|
||||
html += '</li>';
|
||||
var sorted = [...items].sort(function(a, b) {
|
||||
var at = (a.type === 'folder' || a.type === 'dir') ? 0 : 1;
|
||||
var bt = (b.type === 'folder' || b.type === 'dir') ? 0 : 1;
|
||||
if (at !== bt) return at - bt;
|
||||
return (a.name || '').localeCompare(b.name || '');
|
||||
});
|
||||
for (var i = 0; i < sorted.length; i++) {
|
||||
var item = sorted[i];
|
||||
var isF = item.type === 'folder' || item.type === 'dir';
|
||||
var safe = (item.name || '').replace(/'/g, "\\'");
|
||||
var chev = isF
|
||||
? '<button class="tree-chevron" data-repo-toggle="' + item.path + '" title="Toggle folder">▶</button>'
|
||||
: '<span style="width:12px;flex-shrink:0;"></span>';
|
||||
var icon = isF ? getSvgIcon('folder',14) : getSvgIcon('file',14);
|
||||
var label = isF
|
||||
? '<span class="page-name tree-folder-link" title="Open folder">' + item.name + '</span>'
|
||||
: '<span class="page-name">' + item.name + '</span>';
|
||||
html += '<li class="sidebar-item ws-tree-item" data-gitea-path="' + item.path
|
||||
+ '" data-gitea-type="' + (isF ? 'folder' : 'file') + '" data-gitea-sha="' + (item.sha || '')
|
||||
+ '" data-is-folder="' + (isF ? 'true' : 'false') + '" data-depth="0" data-name="' + safe + '"'
|
||||
+ ' style="padding-left:4px;">' + chev
|
||||
+ '<span class="page-icon">' + icon + '</span>' + label + '</li>';
|
||||
}
|
||||
// Add Private Pages link
|
||||
html += '<li style="border-top:1px solid var(--border);margin:8px 0;"></li>';
|
||||
@@ -209,7 +302,7 @@ var _regGiteaWs = () => {
|
||||
if (!path) return;
|
||||
if (!confirm('Delete ' + path + ' from ' + self.owner + '/' + self.repo + '?')) return;
|
||||
var sha = el.getAttribute('data-gitea-sha') || '';
|
||||
fetch('/api/gitea/projects/' + self.owner + '/' + self.repo + '/file?path=' + encodeURIComponent(path) + '&sha=' + encodeURIComponent(sha) + '&message=' + encodeURIComponent('Delete via FlowDeck'), {headers: {'X-CSRF-Token': getCsrf()},
|
||||
fetch(self.apiBase() + '/file?path=' + encodeURIComponent(path) + '&sha=' + encodeURIComponent(sha) + '&message=' + encodeURIComponent('Delete via FlowDeck'), {headers: {'X-CSRF-Token': getCsrf()},
|
||||
method: 'DELETE',
|
||||
}).then(function(r) {
|
||||
if (r.ok) self.refreshTree();
|
||||
@@ -226,21 +319,26 @@ var _regGiteaWs = () => {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/tree?path=' + encodeURIComponent(path));
|
||||
var r = await fetch(this.apiBase() + '/tree?path=' + encodeURIComponent(path));
|
||||
if (!r.ok) return;
|
||||
var d = await r.json();
|
||||
var children = d.tree || [];
|
||||
ul = document.createElement('ul');
|
||||
ul.className = 'sidebar-items';
|
||||
ul.style.paddingLeft = '20px';
|
||||
ul.style.cssText = 'list-style:none;margin:0;padding:0;';
|
||||
var depth = parseInt(el.getAttribute('data-depth') || '0', 10) + 1;
|
||||
children.forEach(function(child) {
|
||||
var icon = child.type === 'folder' ? getSvgIcon('folder',14) : getSvgIcon('file',14);
|
||||
var isF = child.type === 'folder' || child.type === 'dir';
|
||||
var icon = isF ? getSvgIcon('folder',14) : getSvgIcon('file',14);
|
||||
var li = document.createElement('li');
|
||||
li.className = 'sidebar-item';
|
||||
li.className = 'sidebar-item ws-tree-item';
|
||||
li.setAttribute('data-gitea-path', child.path);
|
||||
li.setAttribute('data-gitea-type', child.type === 'folder' ? 'folder' : 'file');
|
||||
li.setAttribute('data-gitea-type', isF ? 'folder' : 'file');
|
||||
li.setAttribute('data-gitea-sha', child.sha || '');
|
||||
li.style.cssText = 'cursor:pointer;display:flex;align-items:center;gap:4px;';
|
||||
li.setAttribute('data-is-folder', isF ? 'true' : 'false');
|
||||
li.setAttribute('data-depth', String(depth));
|
||||
li.setAttribute('data-name', (child.name || '').replace(/'/g, "\\'"));
|
||||
li.style.cssText = 'padding-left:' + (4 + depth * 12) + 'px;';
|
||||
// Checkbox
|
||||
var cb = document.createElement('input');
|
||||
cb.type = 'checkbox';
|
||||
@@ -251,14 +349,29 @@ var _regGiteaWs = () => {
|
||||
self.selectItem(child.path, li, ev);
|
||||
});
|
||||
li.appendChild(cb);
|
||||
// Chevron (folders) — same as workspace-local
|
||||
if (isF) {
|
||||
var chev = document.createElement('button');
|
||||
chev.className = 'tree-chevron';
|
||||
chev.setAttribute('data-repo-toggle', child.path);
|
||||
chev.title = 'Toggle folder';
|
||||
chev.textContent = '▶';
|
||||
li.appendChild(chev);
|
||||
} else {
|
||||
var sp = document.createElement('span');
|
||||
sp.style.cssText = 'width:12px;flex-shrink:0;';
|
||||
li.appendChild(sp);
|
||||
}
|
||||
// Icon
|
||||
var iconSpan = document.createElement('span');
|
||||
iconSpan.className = 'sidebar-icon';
|
||||
iconSpan.innerHTML = icon; // icon = HTML SVG from getSvgIcon(), NOT text
|
||||
iconSpan.className = 'page-icon';
|
||||
iconSpan.innerHTML = icon;
|
||||
li.appendChild(iconSpan);
|
||||
// Name
|
||||
var nameSpan = document.createElement('span');
|
||||
nameSpan.className = 'page-name' + (isF ? ' tree-folder-link' : '');
|
||||
nameSpan.textContent = child.name;
|
||||
if (isF) nameSpan.title = 'Open folder';
|
||||
nameSpan.addEventListener('dblclick', function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
@@ -268,6 +381,8 @@ var _regGiteaWs = () => {
|
||||
ul.appendChild(li);
|
||||
});
|
||||
el.appendChild(ul);
|
||||
var chevBtn = el.querySelector('[data-repo-toggle]');
|
||||
if (chevBtn) chevBtn.classList.add('open');
|
||||
} catch { /* volontaire */ }
|
||||
},
|
||||
|
||||
@@ -279,7 +394,7 @@ var _regGiteaWs = () => {
|
||||
this.fileLoading = true;
|
||||
this.fileLanguage = this.getLanguage(path);
|
||||
try {
|
||||
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/file?path=' + encodeURIComponent(path));
|
||||
var r = await fetch(this.apiBase() + '/file?path=' + encodeURIComponent(path));
|
||||
if (r.ok) {
|
||||
var d = await r.json();
|
||||
this.fileContent = d.content || '';
|
||||
@@ -334,7 +449,7 @@ var _regGiteaWs = () => {
|
||||
async saveFile() {
|
||||
if (!this.filePath) return;
|
||||
try {
|
||||
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/file', {
|
||||
var r = await fetch(this.apiBase() + '/file', {
|
||||
method: 'PUT',
|
||||
headers: {'Content-Type': 'application/json', 'X-CSRF-Token': this.getCsrfToken()},
|
||||
body: JSON.stringify({
|
||||
@@ -361,7 +476,7 @@ var _regGiteaWs = () => {
|
||||
if (!this.filePath) return;
|
||||
if (!confirm('Delete ' + this.filePath + ' from ' + this.owner + '/' + this.repo + '?')) return;
|
||||
try {
|
||||
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/file?path=' + encodeURIComponent(this.filePath) + '&sha=' + encodeURIComponent(this.fileSha) + '&message=' + encodeURIComponent('Delete via FlowDeck'), {headers: {'X-CSRF-Token': getCsrf()},
|
||||
var r = await fetch(this.apiBase() + '/file?path=' + encodeURIComponent(this.filePath) + '&sha=' + encodeURIComponent(this.fileSha) + '&message=' + encodeURIComponent('Delete via FlowDeck'), {headers: {'X-CSRF-Token': getCsrf()},
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (r.ok) {
|
||||
@@ -403,7 +518,7 @@ var _regGiteaWs = () => {
|
||||
|
||||
async loadPrivatePages() {
|
||||
try {
|
||||
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/private-pages');
|
||||
var r = await fetch(this.apiBase() + '/private-pages');
|
||||
if (r.ok) { var d = await r.json(); this.privatePages = d.pages || []; }
|
||||
} catch { /* volontaire */ }
|
||||
},
|
||||
@@ -416,7 +531,7 @@ var _regGiteaWs = () => {
|
||||
|
||||
async openPrivate(id) {
|
||||
try {
|
||||
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/private-pages/' + id);
|
||||
var r = await fetch(this.apiBase() + '/private-pages/' + id);
|
||||
if (r.ok) {
|
||||
var d = await r.json();
|
||||
this.editingPrivate = id;
|
||||
@@ -427,7 +542,7 @@ var _regGiteaWs = () => {
|
||||
},
|
||||
|
||||
async savePrivate() {
|
||||
var url = '/api/gitea/projects/' + this.owner + '/' + this.repo + '/private-pages';
|
||||
var url = this.apiBase() + '/private-pages';
|
||||
var method = 'POST';
|
||||
if (this.editingPrivate !== 'new') {
|
||||
url += '/' + this.editingPrivate;
|
||||
@@ -455,7 +570,7 @@ var _regGiteaWs = () => {
|
||||
async createNewFile() {
|
||||
if (!this.newFilePath.trim()) return;
|
||||
try {
|
||||
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/file', {
|
||||
var r = await fetch(this.apiBase() + '/file', {
|
||||
method: 'PUT',
|
||||
headers: {'Content-Type':'application/json', 'X-CSRF-Token': this.getCsrfToken()},
|
||||
body: JSON.stringify({path: this.newFilePath.trim(), content: this.newFileContent, message: this.newFileMsg || 'Create via FlowDeck'})
|
||||
@@ -483,7 +598,7 @@ var _regGiteaWs = () => {
|
||||
form.append('file', files[i]);
|
||||
form.append('message', 'Upload ' + files[i].name + ' via FlowDeck');
|
||||
try {
|
||||
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/upload', {
|
||||
var r = await fetch(this.apiBase() + '/upload', {
|
||||
method: 'POST',
|
||||
headers: {'X-CSRF-Token': this.getCsrfToken()},
|
||||
body: form
|
||||
@@ -498,7 +613,7 @@ var _regGiteaWs = () => {
|
||||
async deletePrivate(id) {
|
||||
if (!confirm('Delete this private page?')) return;
|
||||
try {
|
||||
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/private-pages/' + id, {
|
||||
var r = await fetch(this.apiBase() + '/private-pages/' + id, {
|
||||
method: 'DELETE', headers: {'X-CSRF-Token': this.getCsrfToken ? this.getCsrfToken() : ''}
|
||||
});
|
||||
if (r.ok) await this.loadPrivatePages();
|
||||
@@ -625,7 +740,7 @@ var _regGiteaWs = () => {
|
||||
var parts = path.split('/');
|
||||
parts.pop();
|
||||
var newPath = (parts.length > 0 ? parts.join('/') + '/' : '') + newName;
|
||||
fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/rename', {
|
||||
fetch(this.apiBase() + '/rename', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json', 'X-CSRF-Token': this.getCsrfToken()},
|
||||
body: JSON.stringify({old_path: path, new_path: newPath, new_name: newName})
|
||||
|
||||
@@ -155,6 +155,16 @@ function workspacesPage() {
|
||||
document.cookie = 'flowdeck_workspace=gitea:' + owner + ':' + repo + ';path=/;SameSite=Lax';
|
||||
window.location = `/gitea-workspace?owner=${encodeURIComponent(owner)}&repo=${encodeURIComponent(repo)}`;
|
||||
}
|
||||
},
|
||||
openGithub(proj) {
|
||||
const full = proj.full_name || '';
|
||||
const parts = full.split('/');
|
||||
const owner = proj.owner?.login || parts[0] || '';
|
||||
const repo = proj.name || parts[1] || '';
|
||||
if (owner && repo) {
|
||||
document.cookie = 'flowdeck_workspace=github:' + owner + ':' + repo + ';path=/;SameSite=Lax';
|
||||
window.location = `/github-workspace?owner=${encodeURIComponent(owner)}&repo=${encodeURIComponent(repo)}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user