support sort
This commit is contained in:
112
src/App.vue
112
src/App.vue
@@ -31,6 +31,10 @@ type BookmarkSummary = {
|
|||||||
profileIds: string[];
|
profileIds: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ProfileSortKey = "name" | "email" | "id";
|
||||||
|
type ExtensionSortKey = "name" | "id";
|
||||||
|
type BookmarkSortKey = "title" | "url";
|
||||||
|
|
||||||
type BrowserView = {
|
type BrowserView = {
|
||||||
browserId: string;
|
browserId: string;
|
||||||
browserName: string;
|
browserName: string;
|
||||||
@@ -52,6 +56,9 @@ const selectedBrowserId = ref("");
|
|||||||
const activeSection = ref<"profiles" | "extensions" | "bookmarks">("profiles");
|
const activeSection = ref<"profiles" | "extensions" | "bookmarks">("profiles");
|
||||||
const expandedExtensionIds = ref<string[]>([]);
|
const expandedExtensionIds = ref<string[]>([]);
|
||||||
const expandedBookmarkUrls = ref<string[]>([]);
|
const expandedBookmarkUrls = ref<string[]>([]);
|
||||||
|
const profileSortKey = ref<ProfileSortKey>("name");
|
||||||
|
const extensionSortKey = ref<ExtensionSortKey>("name");
|
||||||
|
const bookmarkSortKey = ref<BookmarkSortKey>("title");
|
||||||
|
|
||||||
const browsers = computed(() => response.value.browsers);
|
const browsers = computed(() => response.value.browsers);
|
||||||
const currentBrowser = computed(() =>
|
const currentBrowser = computed(() =>
|
||||||
@@ -59,6 +66,40 @@ const currentBrowser = computed(() =>
|
|||||||
browsers.value[0] ??
|
browsers.value[0] ??
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
const sortedProfiles = computed(() => {
|
||||||
|
const profiles = [...(currentBrowser.value?.profiles ?? [])];
|
||||||
|
return profiles.sort((left, right) => {
|
||||||
|
if (profileSortKey.value === "email") {
|
||||||
|
return (
|
||||||
|
compareText(left.email ?? "", right.email ?? "") ||
|
||||||
|
compareText(left.name, right.name) ||
|
||||||
|
compareProfileId(left.id, right.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (profileSortKey.value === "id") {
|
||||||
|
return compareProfileId(left.id, right.id);
|
||||||
|
}
|
||||||
|
return compareText(left.name, right.name) || compareProfileId(left.id, right.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const sortedExtensions = computed(() => {
|
||||||
|
const extensions = [...(currentBrowser.value?.extensions ?? [])];
|
||||||
|
return extensions.sort((left, right) => {
|
||||||
|
if (extensionSortKey.value === "id") {
|
||||||
|
return compareText(left.id, right.id);
|
||||||
|
}
|
||||||
|
return compareText(left.name, right.name) || compareText(left.id, right.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const sortedBookmarks = computed(() => {
|
||||||
|
const bookmarks = [...(currentBrowser.value?.bookmarks ?? [])];
|
||||||
|
return bookmarks.sort((left, right) => {
|
||||||
|
if (bookmarkSortKey.value === "url") {
|
||||||
|
return compareText(left.url, right.url);
|
||||||
|
}
|
||||||
|
return compareText(left.title, right.title) || compareText(left.url, right.url);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
browsers,
|
browsers,
|
||||||
@@ -144,6 +185,34 @@ function bookmarkProfilesExpanded(url: string) {
|
|||||||
return expandedBookmarkUrls.value.includes(url);
|
return expandedBookmarkUrls.value.includes(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function compareText(left: string, right: string) {
|
||||||
|
return left.localeCompare(right, undefined, {
|
||||||
|
sensitivity: "base",
|
||||||
|
numeric: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareProfileId(left: string, right: string) {
|
||||||
|
const leftKey = profileSortKeyValue(left);
|
||||||
|
const rightKey = profileSortKeyValue(right);
|
||||||
|
if (leftKey.group !== rightKey.group) return leftKey.group - rightKey.group;
|
||||||
|
if (leftKey.number !== rightKey.number) return leftKey.number - rightKey.number;
|
||||||
|
return compareText(leftKey.text, rightKey.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function profileSortKeyValue(profileId: string) {
|
||||||
|
if (profileId === "Default") {
|
||||||
|
return { group: 0, number: 0, text: profileId };
|
||||||
|
}
|
||||||
|
const profileNumber = profileId.startsWith("Profile ")
|
||||||
|
? Number(profileId.slice("Profile ".length))
|
||||||
|
: Number.NaN;
|
||||||
|
if (!Number.isNaN(profileNumber)) {
|
||||||
|
return { group: 1, number: profileNumber, text: profileId };
|
||||||
|
}
|
||||||
|
return { group: 2, number: Number.MAX_SAFE_INTEGER, text: profileId };
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
void scanBrowsers();
|
void scanBrowsers();
|
||||||
});
|
});
|
||||||
@@ -231,9 +300,20 @@ onMounted(() => {
|
|||||||
|
|
||||||
<div class="content-scroll-area">
|
<div class="content-scroll-area">
|
||||||
<section v-if="activeSection === 'profiles'" class="content-section">
|
<section v-if="activeSection === 'profiles'" class="content-section">
|
||||||
<div v-if="currentBrowser.profiles.length" class="stack-list">
|
<div class="sort-bar">
|
||||||
|
<label class="sort-control">
|
||||||
|
<span>Sort by</span>
|
||||||
|
<select v-model="profileSortKey">
|
||||||
|
<option value="name">Name</option>
|
||||||
|
<option value="email">Email</option>
|
||||||
|
<option value="id">Profile ID</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="sortedProfiles.length" class="stack-list">
|
||||||
<article
|
<article
|
||||||
v-for="profile in currentBrowser.profiles"
|
v-for="profile in sortedProfiles"
|
||||||
:key="profile.id"
|
:key="profile.id"
|
||||||
class="profile-card"
|
class="profile-card"
|
||||||
>
|
>
|
||||||
@@ -260,9 +340,19 @@ onMounted(() => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section v-else-if="activeSection === 'extensions'" class="content-section">
|
<section v-else-if="activeSection === 'extensions'" class="content-section">
|
||||||
<div v-if="currentBrowser.extensions.length" class="stack-list">
|
<div class="sort-bar">
|
||||||
|
<label class="sort-control">
|
||||||
|
<span>Sort by</span>
|
||||||
|
<select v-model="extensionSortKey">
|
||||||
|
<option value="name">Name</option>
|
||||||
|
<option value="id">Extension ID</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="sortedExtensions.length" class="stack-list">
|
||||||
<article
|
<article
|
||||||
v-for="extension in currentBrowser.extensions"
|
v-for="extension in sortedExtensions"
|
||||||
:key="extension.id"
|
:key="extension.id"
|
||||||
class="extension-card"
|
class="extension-card"
|
||||||
>
|
>
|
||||||
@@ -313,9 +403,19 @@ onMounted(() => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section v-else class="content-section">
|
<section v-else class="content-section">
|
||||||
<div v-if="currentBrowser.bookmarks.length" class="bookmark-list">
|
<div class="sort-bar">
|
||||||
|
<label class="sort-control">
|
||||||
|
<span>Sort by</span>
|
||||||
|
<select v-model="bookmarkSortKey">
|
||||||
|
<option value="title">Name</option>
|
||||||
|
<option value="url">URL</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="sortedBookmarks.length" class="bookmark-list">
|
||||||
<article
|
<article
|
||||||
v-for="bookmark in currentBrowser.bookmarks"
|
v-for="bookmark in sortedBookmarks"
|
||||||
:key="bookmark.url"
|
:key="bookmark.url"
|
||||||
class="bookmark-card"
|
class="bookmark-card"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -289,6 +289,30 @@ button {
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sort-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-control {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.84rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-control select {
|
||||||
|
min-width: 132px;
|
||||||
|
padding: 7px 28px 7px 10px;
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.26);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
color: var(--text);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
.section-tabs {
|
.section-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
@@ -517,6 +541,20 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
|
.sort-bar {
|
||||||
|
justify-content: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-control {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-control select {
|
||||||
|
min-width: 0;
|
||||||
|
width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
.profile-card,
|
.profile-card,
|
||||||
.extension-card,
|
.extension-card,
|
||||||
.bookmark-card {
|
.bookmark-card {
|
||||||
|
|||||||
Reference in New Issue
Block a user