Bryan Mutai 2025-11-19 06:22:14 +03:00 committed by GitHub
commit ea61ebf24f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 422 additions and 50 deletions

View File

@ -1354,8 +1354,11 @@ editor.this_file_locked = File is locked
editor.must_be_on_a_branch = You must be on a branch to make or propose changes to this file.
editor.fork_before_edit = You must fork this repository to make or propose changes to this file.
editor.delete_this_file = Delete File
editor.delete_this_directory = Delete Directory
editor.must_have_write_access = You must have write access to make or propose changes to this file.
editor.file_delete_success = File "%s" has been deleted.
editor.directory_delete_success = Directory "%s" has been deleted.
editor.delete_directory = Delete directory '%s'
editor.name_your_file = Name your file…
editor.filename_help = Add a directory by typing its name followed by a slash ('/'). Remove a directory by typing backspace at the beginning of the input field.
editor.or = or

View File

@ -280,6 +280,8 @@ func EditFile(ctx *context.Context) {
return
}
prepareHomeTreeSideBarSwitch(ctx)
// on the "New File" page, we should add an empty path field to make end users could input a new name
prepareTreePathFieldsAndPaths(ctx, util.Iif(isNewFile, ctx.Repo.TreePath+"/", ctx.Repo.TreePath))
@ -384,7 +386,7 @@ func DeleteFile(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplDeleteFile)
}
// DeleteFilePost response for deleting file
// DeleteFilePost response for deleting file or directory
func DeleteFilePost(ctx *context.Context) {
parsed := prepareEditorCommitSubmittedForm[*forms.DeleteRepoFileForm](ctx)
if ctx.Written() {
@ -392,33 +394,80 @@ func DeleteFilePost(ctx *context.Context) {
}
treePath := ctx.Repo.TreePath
_, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{
LastCommitID: parsed.form.LastCommit,
OldBranch: parsed.OldBranchName,
NewBranch: parsed.NewBranchName,
Files: []*files_service.ChangeRepoFile{
// Check if the path is a directory
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treePath)
if err != nil {
ctx.NotFoundOrServerError("GetTreeEntryByPath", git.IsErrNotExist, err)
return
}
var filesToDelete []*files_service.ChangeRepoFile
var commitMessage string
if entry.IsDir() {
// Get all files in the directory recursively
tree, err := ctx.Repo.Commit.SubTree(treePath)
if err != nil {
ctx.ServerError("SubTree", err)
return
}
entries, err := tree.ListEntriesRecursiveFast()
if err != nil {
ctx.ServerError("ListEntriesRecursiveFast", err)
return
}
// Create delete operations for all files in the directory
for _, e := range entries {
if !e.IsDir() && !e.IsSubModule() {
filesToDelete = append(filesToDelete, &files_service.ChangeRepoFile{
Operation: "delete",
TreePath: treePath + "/" + e.Name(),
})
}
}
commitMessage = parsed.GetCommitMessage(ctx.Locale.TrString("repo.editor.delete_directory", treePath))
} else {
// Single file deletion
filesToDelete = []*files_service.ChangeRepoFile{
{
Operation: "delete",
TreePath: treePath,
},
},
Message: parsed.GetCommitMessage(ctx.Locale.TrString("repo.editor.delete", treePath)),
Signoff: parsed.form.Signoff,
Author: parsed.GitCommitter,
Committer: parsed.GitCommitter,
}
commitMessage = parsed.GetCommitMessage(ctx.Locale.TrString("repo.editor.delete", treePath))
}
_, err = files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{
LastCommitID: parsed.form.LastCommit,
OldBranch: parsed.OldBranchName,
NewBranch: parsed.NewBranchName,
Files: filesToDelete,
Message: commitMessage,
Signoff: parsed.form.Signoff,
Author: parsed.GitCommitter,
Committer: parsed.GitCommitter,
})
if err != nil {
editorHandleFileOperationError(ctx, parsed.NewBranchName, err)
return
}
ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", treePath))
if entry.IsDir() {
ctx.Flash.Success(ctx.Tr("repo.editor.directory_delete_success", treePath))
} else {
ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", treePath))
}
redirectTreePath := getClosestParentWithFiles(ctx.Repo.GitRepo, parsed.NewBranchName, treePath)
redirectForCommitChoice(ctx, parsed, redirectTreePath)
}
func UploadFile(ctx *context.Context) {
ctx.Data["PageIsUpload"] = true
prepareHomeTreeSideBarSwitch(ctx)
prepareTreePathFieldsAndPaths(ctx, ctx.Repo.TreePath)
opts := prepareEditorCommitFormOptions(ctx, "_upload")
if ctx.Written() {

View File

@ -14,6 +14,7 @@ import (
)
func NewDiffPatch(ctx *context.Context) {
prepareHomeTreeSideBarSwitch(ctx)
prepareEditorCommitFormOptions(ctx, "_diffpatch")
if ctx.Written() {
return

View File

@ -1,15 +1,25 @@
{{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content repository file editor edit">
{{template "repo/header" .}}
<div class="ui container">
<div class="ui container fluid padded">
{{template "base/alert" .}}
<form class="ui edit form form-fetch-action" method="post" action="{{.CommitFormOptions.TargetFormAction}}"
<div class="repo-view-container">
<div class="tw-flex tw-flex-col repo-view-file-tree-container not-mobile {{if not .UserSettingCodeViewShowFileTree}}tw-hidden{{end}}" {{if .IsSigned}}data-user-is-signed-in{{end}}>
{{template "repo/view_file_tree" .}}
</div>
<div class="repo-view-content">
<form class="ui edit form form-fetch-action" method="post" action="{{.CommitFormOptions.TargetFormAction}}"
data-text-empty-confirm-header="{{ctx.Locale.Tr "repo.editor.commit_empty_file_header"}}"
data-text-empty-confirm-content="{{ctx.Locale.Tr "repo.editor.commit_empty_file_text"}}"
>
{{.CsrfTokenHtml}}
{{template "repo/editor/common_top" .}}
<div class="repo-editor-header">
<div class="repo-editor-header tw-flex tw-items-center tw-gap-2">
<button type="button" class="repo-view-file-tree-toggle-show ui compact basic button icon not-mobile {{if .UserSettingCodeViewShowFileTree}}tw-hidden{{end}}"
data-global-click="onRepoViewFileTreeToggle" data-toggle-action="show"
data-tooltip-content="{{ctx.Locale.Tr "repo.diff.show_file_tree"}}">
{{svg "octicon-sidebar-collapse"}}
</button>
{{template "repo/editor/common_breadcrumb" .}}
</div>
{{if not .NotEditableReason}}
@ -47,7 +57,9 @@
</div>
{{end}}
{{template "repo/editor/commit_form" .}}
</form>
</form>
</div>
</div>
</div>
</div>
{{template "base/footer" .}}

View File

@ -1,19 +1,27 @@
{{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content repository file editor edit">
{{template "repo/header" .}}
<div class="ui container">
<div class="ui container fluid padded">
{{template "base/alert" .}}
<form class="ui edit form form-fetch-action" method="post" action="{{.CommitFormOptions.TargetFormAction}}"
<div class="repo-view-container">
<div class="tw-flex tw-flex-col repo-view-file-tree-container not-mobile {{if not .UserSettingCodeViewShowFileTree}}tw-hidden{{end}}" {{if .IsSigned}}data-user-is-signed-in{{end}}>
{{template "repo/view_file_tree" .}}
</div>
<div class="repo-view-content">
<form class="ui edit form form-fetch-action" method="post" action="{{.CommitFormOptions.TargetFormAction}}"
data-text-empty-confirm-header="{{ctx.Locale.Tr "repo.editor.commit_empty_file_header"}}"
data-text-empty-confirm-content="{{ctx.Locale.Tr "repo.editor.commit_empty_file_text"}}"
>
{{.CsrfTokenHtml}}
{{template "repo/editor/common_top" .}}
<div class="repo-editor-header">
<div class="repo-editor-header tw-flex tw-items-center tw-gap-2">
<button type="button" class="repo-view-file-tree-toggle-show ui compact basic button icon not-mobile {{if .UserSettingCodeViewShowFileTree}}tw-hidden{{end}}"
data-global-click="onRepoViewFileTreeToggle" data-toggle-action="show"
data-tooltip-content="{{ctx.Locale.Tr "repo.diff.show_file_tree"}}">
{{svg "octicon-sidebar-collapse"}}
</button>
<div class="breadcrumb">
{{ctx.Locale.Tr "repo.editor.patching"}}
<a class="section" href="{{$.RepoLink}}">{{.Repository.FullName}}</a>
<div class="breadcrumb-divider">:</div>
<a class="section" href="{{$.BranchLink}}">{{.BranchName}}</a>
<span>{{ctx.Locale.Tr "repo.editor.or"}} <a href="{{$.BranchLink}}">{{ctx.Locale.Tr "repo.editor.cancel_lower"}}</a></span>
<input type="hidden" name="tree_path" value="__dummy_for_EditRepoFileForm.TreePath(Required)__">
@ -33,7 +41,9 @@
</div>
</div>
{{template "repo/editor/commit_form" .}}
</form>
</form>
</div>
</div>
</div>
</div>
{{template "base/footer" .}}

View File

@ -1,19 +1,31 @@
{{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content repository file editor upload">
{{template "repo/header" .}}
<div class="ui container">
<div class="ui container fluid padded">
{{template "base/alert" .}}
<form class="ui comment form form-fetch-action" method="post" action="{{.CommitFormOptions.TargetFormAction}}">
<div class="repo-view-container">
<div class="tw-flex tw-flex-col repo-view-file-tree-container not-mobile {{if not .UserSettingCodeViewShowFileTree}}tw-hidden{{end}}" {{if .IsSigned}}data-user-is-signed-in{{end}}>
{{template "repo/view_file_tree" .}}
</div>
<div class="repo-view-content">
<form class="ui comment form form-fetch-action" method="post" action="{{.CommitFormOptions.TargetFormAction}}">
{{.CsrfTokenHtml}}
{{template "repo/editor/common_top" .}}
<div class="repo-editor-header">
<div class="repo-editor-header tw-flex tw-items-center tw-gap-2">
<button type="button" class="repo-view-file-tree-toggle-show ui compact basic button icon not-mobile {{if .UserSettingCodeViewShowFileTree}}tw-hidden{{end}}"
data-global-click="onRepoViewFileTreeToggle" data-toggle-action="show"
data-tooltip-content="{{ctx.Locale.Tr "repo.diff.show_file_tree"}}">
{{svg "octicon-sidebar-collapse"}}
</button>
{{template "repo/editor/common_breadcrumb" .}}
</div>
<div class="field">
{{template "repo/upload" .}}
</div>
{{template "repo/editor/commit_form" .}}
</form>
</form>
</div>
</div>
</div>
</div>
{{template "base/footer" .}}

View File

@ -42,26 +42,6 @@
<a href="{{.Repository.Link}}/find/{{.RefTypeNameSubURL}}" class="ui compact basic button">{{ctx.Locale.Tr "repo.find_file.go_to_file"}}</a>
{{end}}
{{if and .RefFullName.IsBranch (not .IsViewFile)}}
<button class="ui dropdown basic compact jump button repo-add-file" {{if not .Repository.CanEnableEditor}}disabled{{end}}>
{{ctx.Locale.Tr "repo.editor.add_file"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu">
<a class="item" href="{{.RepoLink}}/_new/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
{{ctx.Locale.Tr "repo.editor.new_file"}}
</a>
{{if .RepositoryUploadEnabled}}
<a class="item" href="{{.RepoLink}}/_upload/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
{{ctx.Locale.Tr "repo.editor.upload_file"}}
</a>
{{end}}
<a class="item" href="{{.RepoLink}}/_diffpatch/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
{{ctx.Locale.Tr "repo.editor.patch"}}
</a>
</div>
</button>
{{end}}
{{if and $isTreePathRoot .Repository.IsTemplate}}
<a role="button" class="ui primary compact button" href="{{AppSubUrl}}/repo/create?template_id={{.Repository.ID}}">
{{ctx.Locale.Tr "repo.use_template"}}
@ -86,6 +66,52 @@
</div>
<div class="repo-button-row-right">
{{if .RefFullName.IsBranch}}
{{$addFilePath := .TreePath}}
{{if .IsViewFile}}
{{if gt (len .TreeNames) 1}}
{{$addFilePath = StringUtils.Join (slice .TreeNames 0 (Eval (len .TreeNames) "-" 1)) "/"}}
{{else}}
{{$addFilePath = ""}}
{{end}}
{{end}}
<button class="ui dropdown basic compact jump button repo-add-file" {{if not .Repository.CanEnableEditor}}disabled{{end}}>
{{ctx.Locale.Tr "repo.editor.add_file"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu">
<a class="item" href="{{.RepoLink}}/_new/{{.BranchName | PathEscapeSegments}}/{{$addFilePath | PathEscapeSegments}}">
{{svg "octicon-file-added" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.editor.new_file"}}
</a>
{{if .RepositoryUploadEnabled}}
<a class="item" href="{{.RepoLink}}/_upload/{{.BranchName | PathEscapeSegments}}/{{$addFilePath | PathEscapeSegments}}">
{{svg "octicon-upload" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.editor.upload_file"}}
</a>
{{end}}
<a class="item" href="{{.RepoLink}}/_diffpatch/{{.BranchName | PathEscapeSegments}}/{{$addFilePath | PathEscapeSegments}}">
{{svg "octicon-diff" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.editor.patch"}}
</a>
</div>
</button>
{{if not .IsViewFile}}
<button class="ui dropdown basic compact jump button icon repo-file-actions-dropdown" data-tooltip-content="{{ctx.Locale.Tr "repo.more_operations"}}">
{{svg "octicon-kebab-horizontal"}}
<div class="menu">
<a class="item" data-clipboard-text="{{.TreePath}}">
{{svg "octicon-copy" 16 "tw-mr-2"}}{{ctx.Locale.Tr "copy_path"}}
</a>
<a class="item" data-clipboard-text="{{AppUrl}}{{StringUtils.TrimPrefix .Repository.Link "/"}}/src/commit/{{.CommitID}}/{{PathEscapeSegments .TreePath}}">
{{svg "octicon-link" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_copy_permalink"}}
</a>
{{if and (.Permission.CanWrite ctx.Consts.RepoUnitTypeCode) (not .Repository.IsArchived) (not $isTreePathRoot)}}
<div class="divider"></div>
<a class="item danger" href="{{.RepoLink}}/_delete/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
{{svg "octicon-trash" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.editor.delete_this_directory"}}
</a>
{{end}}
</div>
</button>
{{end}}
{{end}}
<!-- Only show clone panel in repository home page -->
{{if $isTreePathRoot}}
{{template "repo/clone_panel" .}}

View File

@ -7,9 +7,14 @@
<b>{{ctx.Locale.Tr "files"}}</b>
</div>
<div class="ui small input tw-w-full tw-px-2 tw-pb-2">
<input id="file-tree-search" type="text" placeholder="{{ctx.Locale.Tr "repo.find_file.go_to_file"}}" autocomplete="off">
</div>
{{/* TODO: Dynamically move components such as refSelector and createPR here */}}
<div id="view-file-tree" class="tw-overflow-auto tw-h-full is-loading"
<div id="view-file-tree" class="tw-overflow-y-auto tw-overflow-x-visible tw-h-full is-loading"
data-repo-link="{{.RepoLink}}"
data-tree-path="{{$.TreePath}}"
data-current-ref-name-sub-url="{{.RefTypeNameSubURL}}"
data-tree-list-url="{{.RepoLink}}/tree-list/{{.RefTypeNameSubURL}}"
></div>

View File

@ -63,6 +63,7 @@
@import "./repo/issue-list.css";
@import "./repo/list-header.css";
@import "./repo/file-view.css";
@import "./repo/file-actions.css";
@import "./repo/wiki.css";
@import "./repo/header.css";
@import "./repo/home.css";

View File

@ -0,0 +1,28 @@
/* Repository file actions dropdown and centered content */
.ui.dropdown.repo-add-file > .menu {
margin-top: 4px !important;
}
.ui.dropdown.repo-file-actions-dropdown > .menu {
margin-top: 4px !important;
min-width: 200px;
}
.repo-file-actions-dropdown .menu .item {
cursor: pointer;
}
.repo-file-actions-dropdown .menu .divider {
margin: 0.5rem 0;
}
.ui.dropdown.repo-file-actions-dropdown > .menu > .item.danger,
.ui.dropdown.repo-file-actions-dropdown > .menu > .item.danger svg {
color: var(--color-red) !important;
}
.ui.dropdown.repo-file-actions-dropdown > .menu > .item.danger:hover,
.ui.dropdown.repo-file-actions-dropdown > .menu > .item.danger:hover svg {
color: var(--color-red) !important;
background: var(--color-red-badge-hover-bg) !important;
}

View File

@ -1,9 +1,17 @@
<script lang="ts" setup>
import ViewFileTreeItem from './ViewFileTreeItem.vue';
import {onMounted, useTemplateRef} from 'vue';
import {onMounted, onUnmounted, useTemplateRef, ref, computed, watch, nextTick} from 'vue';
import {createViewFileTreeStore} from './ViewFileTreeStore.ts';
import {GET} from '../modules/fetch.ts';
import {filterRepoFilesWeighted} from '../features/repo-findfile.ts';
import {pathEscapeSegments} from '../utils/url.ts';
import {svg} from '../svg.ts';
const elRoot = useTemplateRef('elRoot');
const searchResults = useTemplateRef('searchResults');
const searchQuery = ref('');
const allFiles = ref<string[]>([]);
const selectedIndex = ref(0);
const props = defineProps({
repoLink: {type: String, required: true},
@ -12,27 +20,244 @@ const props = defineProps({
});
const store = createViewFileTreeStore(props);
const filteredFiles = computed(() => {
if (!searchQuery.value) return [];
return filterRepoFilesWeighted(allFiles.value, searchQuery.value);
});
const treeLink = computed(() => `${props.repoLink}/src/${props.currentRefNameSubURL}`);
let searchInputElement: HTMLInputElement | null = null;
const handleSearchInput = (e: Event) => {
searchQuery.value = (e.target as HTMLInputElement).value;
selectedIndex.value = 0;
};
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape' && searchQuery.value) {
e.preventDefault();
clearSearch();
return;
}
if (!searchQuery.value || filteredFiles.value.length === 0) return;
if (e.key === 'ArrowDown') {
e.preventDefault();
selectedIndex.value = Math.min(selectedIndex.value + 1, filteredFiles.value.length - 1);
scrollSelectedIntoView();
} else if (e.key === 'ArrowUp') {
e.preventDefault();
selectedIndex.value = Math.max(selectedIndex.value - 1, 0);
scrollSelectedIntoView();
} else if (e.key === 'Enter') {
e.preventDefault();
const selectedFile = filteredFiles.value[selectedIndex.value];
if (selectedFile) {
handleSearchResultClick(selectedFile.matchResult.join(''));
}
}
};
const clearSearch = () => {
searchQuery.value = '';
if (searchInputElement) searchInputElement.value = '';
};
const scrollSelectedIntoView = () => {
nextTick(() => {
const resultsEl = searchResults.value;
if (!resultsEl) return;
const selectedEl = resultsEl.querySelector('.file-tree-search-result-item.selected');
if (selectedEl) {
selectedEl.scrollIntoView({block: 'nearest', behavior: 'smooth'});
}
});
};
const handleClickOutside = (e: MouseEvent) => {
if (!searchQuery.value) return;
const target = e.target as HTMLElement;
const resultsEl = searchResults.value;
if (searchInputElement && !searchInputElement.contains(target) &&
resultsEl && !resultsEl.contains(target)) {
clearSearch();
}
};
onMounted(async () => {
store.rootFiles = await store.loadChildren('', props.treePath);
elRoot.value.closest('.is-loading')?.classList?.remove('is-loading');
// Load all files for search
const treeListUrl = elRoot.value.closest('#view-file-tree')?.getAttribute('data-tree-list-url');
if (treeListUrl) {
const response = await GET(treeListUrl);
allFiles.value = await response.json();
}
searchInputElement = document.querySelector('#file-tree-search');
if (searchInputElement) {
searchInputElement.addEventListener('input', handleSearchInput);
searchInputElement.addEventListener('keydown', handleKeyDown);
}
document.addEventListener('click', handleClickOutside);
window.addEventListener('popstate', (e) => {
store.selectedItem = e.state?.treePath || '';
if (e.state?.url) store.loadViewContent(e.state.url);
});
});
// Position search results below the input
watch(searchQuery, async () => {
if (searchQuery.value && searchInputElement) {
await nextTick();
const resultsEl = searchResults.value;
if (resultsEl) {
const rect = searchInputElement.getBoundingClientRect();
resultsEl.style.top = `${rect.bottom + 4}px`;
resultsEl.style.left = `${rect.left}px`;
}
}
});
onUnmounted(() => {
if (searchInputElement) {
searchInputElement.removeEventListener('input', handleSearchInput);
searchInputElement.removeEventListener('keydown', handleKeyDown);
}
document.removeEventListener('click', handleClickOutside);
});
function handleSearchResultClick(filePath: string) {
clearSearch();
window.location.href = `${treeLink.value}/${pathEscapeSegments(filePath)}`;
}
</script>
<template>
<div class="view-file-tree-items" ref="elRoot">
<ViewFileTreeItem v-for="item in store.rootFiles" :key="item.name" :item="item" :store="store"/>
<div ref="elRoot" class="file-tree-root">
<Teleport to="body">
<div v-if="searchQuery && filteredFiles.length > 0" ref="searchResults" class="file-tree-search-results">
<div
v-for="(result, idx) in filteredFiles"
:key="result.matchResult.join('')"
:class="['file-tree-search-result-item', {'selected': idx === selectedIndex}]"
@click="handleSearchResultClick(result.matchResult.join(''))"
@mouseenter="selectedIndex = idx"
:title="result.matchResult.join('')"
>
<!-- eslint-disable-next-line vue/no-v-html -->
<span v-html="svg('octicon-file', 16)"/>
<span class="file-tree-search-result-path">
<span
v-for="(part, index) in result.matchResult"
:key="index"
:class="{'search-match': index % 2 === 1}"
>{{ part }}</span>
</span>
</div>
</div>
<div v-if="searchQuery && filteredFiles.length === 0" ref="searchResults" class="file-tree-search-results file-tree-search-no-results">
<div class="file-tree-no-results-content">
<!-- eslint-disable-next-line vue/no-v-html -->
<span v-html="svg('octicon-search', 24)"/>
<span>No matching files found</span>
</div>
</div>
</Teleport>
<div class="view-file-tree-items">
<ViewFileTreeItem v-for="item in store.rootFiles" :key="item.name" :item="item" :store="store"/>
</div>
</div>
</template>
<style scoped>
.file-tree-root {
position: relative;
}
.view-file-tree-items {
display: flex;
flex-direction: column;
gap: 1px;
margin-right: .5rem;
}
.file-tree-search-results {
position: fixed;
display: flex;
flex-direction: column;
max-height: 400px;
overflow-y: auto;
background: var(--color-box-body);
border: 1px solid var(--color-secondary);
border-radius: 6px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
min-width: 300px;
width: max-content;
max-width: 600px;
z-index: 99999;
}
.file-tree-search-result-item {
display: flex;
align-items: flex-start;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
cursor: pointer;
transition: background-color 0.1s;
border-bottom: 1px solid var(--color-secondary);
}
.file-tree-search-result-item > span:first-child {
flex-shrink: 0;
margin-top: 0.125rem;
}
.file-tree-search-result-item:last-child {
border-bottom: none;
}
.file-tree-search-result-item:hover,
.file-tree-search-result-item.selected {
background-color: var(--color-hover);
}
.file-tree-search-result-path {
flex: 1;
font-size: 14px;
word-break: break-all;
overflow-wrap: break-word;
}
.search-match {
color: var(--color-red);
font-weight: var(--font-weight-semibold);
}
.file-tree-search-no-results {
padding: 0;
}
.file-tree-no-results-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
padding: 1.5rem;
color: var(--color-text-light-2);
font-size: 14px;
}
.file-tree-no-results-content > span:first-child {
opacity: 0.5;
}
</style>