diff --git a/src/App.vue b/src/App.vue index 51fb334..b4e9897 100644 --- a/src/App.vue +++ b/src/App.vue @@ -31,6 +31,10 @@ type BookmarkSummary = { profileIds: string[]; }; +type ProfileSortKey = "name" | "email" | "id"; +type ExtensionSortKey = "name" | "id"; +type BookmarkSortKey = "title" | "url"; + type BrowserView = { browserId: string; browserName: string; @@ -52,6 +56,9 @@ const selectedBrowserId = ref(""); const activeSection = ref<"profiles" | "extensions" | "bookmarks">("profiles"); const expandedExtensionIds = ref([]); const expandedBookmarkUrls = ref([]); +const profileSortKey = ref("name"); +const extensionSortKey = ref("name"); +const bookmarkSortKey = ref("title"); const browsers = computed(() => response.value.browsers); const currentBrowser = computed(() => @@ -59,6 +66,40 @@ const currentBrowser = computed(() => browsers.value[0] ?? 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( browsers, @@ -144,6 +185,34 @@ function bookmarkProfilesExpanded(url: string) { 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(() => { void scanBrowsers(); }); @@ -231,9 +300,20 @@ onMounted(() => {
-
+
+ +
+ +
@@ -260,9 +340,19 @@ onMounted(() => {
-
+
+ +
+ +
@@ -313,9 +403,19 @@ onMounted(() => {
-
+
+ +
+ +
diff --git a/src/styles.css b/src/styles.css index 6a082d6..52a2974 100644 --- a/src/styles.css +++ b/src/styles.css @@ -289,6 +289,30 @@ button { 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 { display: flex; gap: 10px; @@ -517,6 +541,20 @@ button { } @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, .extension-card, .bookmark-card {