Files
gitea/web_src/js/components/ViewFileTreeStore.ts
T
silverwind 9f93e8d808 test: add e2e tests for comments, file tree, actions and branch protection (#40313)
Add e2e tests for flows that had no browser coverage: editing and
quote-replying an issue with an uploaded image, file tree and go-to-file
navigation, the Actions run view and workflow dispatch, and branch
protection rules with their priority.

The tests exposed bugs that are fixed here:

- Quote reply into an empty comment box left the Comment button disabled
- Removing an uploaded attachment did not update the comment buttons
- Going back from a file opened via the file tree to the initially
loaded page kept showing the other file

Assisted-by: Claude Code:claude-opus-5-5
2026-11-12 07:12:55 +00:00

66 lines
2.8 KiB
TypeScript

import {reactive} from 'vue';
import {GET} from '../modules/fetch.ts';
import {pathEscapeSegments, pathWithQuery} from '../utils/url.ts';
import {createElementFromHTML} from '../utils/dom.ts';
import {html} from '../utils/html.ts';
export type FileTreeItem = {
entryName: string;
entryMode: 'blob' | 'exec' | 'tree' | 'commit' | 'symlink' | 'unknown';
entryIcon: string;
entryIconOpen: string;
fullPath: string;
submoduleUrl?: string;
children?: Array<FileTreeItem>;
};
export function createViewFileTreeStore(props: {repoLink: string, treePath: string, currentRefNameSubURL: string}) {
const store = reactive({
rootFiles: [] as Array<FileTreeItem>,
selectedItem: props.treePath,
viewContentUrl: pathWithQuery(window.location),
async loadChildren(treePath: string, subPath: string = '') {
// there is no git ref if no commits were made yet (an empty repo)
if (!props.currentRefNameSubURL) return null;
const response = await GET(`${props.repoLink}/tree-view/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}?sub_path=${encodeURIComponent(subPath)}`);
const json = await response.json();
const poolSvgs = [];
for (const [svgId, svgContent] of Object.entries(json.renderedIconPool ?? {})) {
if (!document.querySelector(`.global-svg-icon-pool #${svgId}`)) poolSvgs.push(svgContent);
}
if (poolSvgs.length) {
const svgContainer = createElementFromHTML(html`<div class="global-svg-icon-pool svg-icon-container"></div>`);
svgContainer.innerHTML = poolSvgs.join('');
document.body.append(svgContainer);
}
return json.fileTreeNodes ?? null;
},
async loadViewContent(url: string) {
const u = new URL(url, window.origin);
store.viewContentUrl = pathWithQuery(u);
u.searchParams.set('only_content', 'true');
const response = await GET(u.href);
const elViewContent = document.querySelector('.repo-view-content')!;
elViewContent.innerHTML = await response.text();
const elViewContentData = elViewContent.querySelector('.repo-view-content-data');
if (!elViewContentData) return; // if error occurs, there is no such element
const t1 = elViewContentData.getAttribute('data-document-title');
const t2 = elViewContentData.getAttribute('data-document-title-common');
document.title = `${t1} - ${t2}`; // follow the format in head.tmpl: <head><title>...</title></head>
},
async navigateTreeView(treePath: string) {
const url = store.buildTreePathWebUrl(treePath);
window.history.pushState({treePath, url}, '', url);
store.selectedItem = treePath;
await store.loadViewContent(url);
},
buildTreePathWebUrl: (treePath: string) => `${props.repoLink}/src/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}`,
});
return store;
}