4 Commits

3 changed files with 117 additions and 15 deletions

View File

@@ -36,14 +36,32 @@
<!-- Theme detection script - runs before Vue loads to prevent flash of unstyled content --> <!-- Theme detection script - runs before Vue loads to prevent flash of unstyled content -->
<script> <script>
(function() { (function() {
// Check for saved theme preference or system preference // Check for saved theme preference only
const savedTheme = localStorage.getItem('theme'); const savedTheme = localStorage.getItem('theme');
if (!savedTheme) {
return; // Let Vue handle default theme
}
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const theme = savedTheme || (prefersDark ? 'dark' : 'light'); let theme = savedTheme;
// Handle 'auto' theme preference
if (theme === 'auto') {
theme = prefersDark ? 'dark' : 'light';
}
// Apply theme to html element // Apply theme to html element
document.documentElement.setAttribute('data-bs-theme', theme); document.documentElement.setAttribute('data-bs-theme', theme);
document.documentElement.setAttribute('data-theme', theme); document.documentElement.setAttribute('data-theme', theme);
// Apply theme classes
if (theme === 'dark') {
document.documentElement.classList.add('dark-theme');
document.documentElement.classList.remove('light-theme');
} else {
document.documentElement.classList.add('light-theme');
document.documentElement.classList.remove('dark-theme');
}
})(); })();
</script> </script>
</head> </head>

View File

@@ -13,11 +13,12 @@ export default {
data() { data() {
return { return {
ascending: this.ascending, ascending: this.ascending,
filter: window.location.href.split("filter=")[1] || "", filter: decodeURIComponent(window.location.href.split("filter=")[1] || ""),
sortColumn: this.sortColumn, sortColumn: this.sortColumn,
sortMethod: 'score',
topics: [], topics: [],
isMobile: false, isMobile: false,
currentTheme: 'auto', currentTheme: typeof localStorage !== 'undefined' ? (localStorage.getItem('theme') || 'auto') : 'auto',
mediaQueryListener: null, mediaQueryListener: null,
vuetifyTheme: null, vuetifyTheme: null,
darkModeQuery: null, darkModeQuery: null,
@@ -28,11 +29,11 @@ export default {
window.addEventListener("keydown", this.handleKeyDown); window.addEventListener("keydown", this.handleKeyDown);
this.detectMobile(); this.detectMobile();
this.fetchDeals(); this.fetchDeals();
// Initialize theme on next tick // Initialize sort method from local storage
this.$nextTick(() => { this.initializeSortMethod();
this.initializeTheme(); // Initialize theme immediately to prevent flash
this.setupThemeListener(); this.initializeTheme();
}); this.setupThemeListener();
}, },
beforeUnmount() { beforeUnmount() {
window.removeEventListener("keydown", this.handleKeyDown); window.removeEventListener("keydown", this.handleKeyDown);
@@ -47,11 +48,11 @@ export default {
const savedTheme = localStorage.getItem('theme'); const savedTheme = localStorage.getItem('theme');
if (!savedTheme) { if (!savedTheme) {
this.currentTheme = 'auto'; this.currentTheme = 'auto';
this.applyTheme('auto'); this.applyTheme('auto', true); // skipSave=true to avoid redundant write
} else { } else {
this.currentTheme = savedTheme; this.currentTheme = savedTheme;
// Apply saved theme // Apply saved theme (skipSave=true since it's already saved)
this.applyTheme(savedTheme); this.applyTheme(savedTheme, true);
} }
}, },
setupThemeListener() { setupThemeListener() {
@@ -172,7 +173,7 @@ export default {
}; };
}, },
filteredTopics() { filteredTopics() {
return this.topics let filtered = this.topics
.filter((row) => { .filter((row) => {
const titles = ( const titles = (
row.title.toString() + row.title.toString() +
@@ -182,8 +183,16 @@ export default {
).toLowerCase(); ).toLowerCase();
const filterTerm = this.filter.toLowerCase(); const filterTerm = this.filter.toLowerCase();
return titles.includes(filterTerm); return titles.includes(filterTerm);
}) });
.sort((a, b) => b.score - a.score); // Always sort by score descending
// Sort based on selected method
if (this.sortMethod === 'score') {
return filtered.sort((a, b) => b.score - a.score);
} else if (this.sortMethod === 'views') {
return filtered.sort((a, b) => b.total_views - a.total_views);
} else {
return filtered.sort((a, b) => new Date(b.last_post_time) - new Date(a.last_post_time));
}
}, },
highlightMatches() { highlightMatches() {
return (v) => { return (v) => {
@@ -223,6 +232,41 @@ export default {
return 'Theme: Dark (click for Auto)'; return 'Theme: Dark (click for Auto)';
} }
}, },
initializeSortMethod() {
const saved = localStorage.getItem('sortMethod');
if (saved) {
this.sortMethod = saved;
}
},
toggleSort() {
// Cycle through: score -> views -> recency -> score
if (this.sortMethod === 'score') {
this.sortMethod = 'views';
} else if (this.sortMethod === 'views') {
this.sortMethod = 'recency';
} else {
this.sortMethod = 'score';
}
localStorage.setItem('sortMethod', this.sortMethod);
},
getSortIcon() {
if (this.sortMethod === 'score') {
return 'trending_up';
} else if (this.sortMethod === 'views') {
return 'visibility';
} else {
return 'schedule';
}
},
getSortTitle() {
if (this.sortMethod === 'score') {
return 'Sort by Score (click for Views)';
} else if (this.sortMethod === 'views') {
return 'Sort by Views (click for Recency)';
} else {
return 'Sort by Recency (click for Score)';
}
},
}, },
}; };
</script> </script>
@@ -242,6 +286,9 @@ export default {
@keyup.esc="$refs.filter.blur()" @keyup.esc="$refs.filter.blur()"
class="search-input" class="search-input"
/> />
<button @click="toggleSort" class="sort-toggle" :title="getSortTitle">
<span class="material-symbols-outlined">{{ getSortIcon }}</span>
</button>
<button @click="toggleTheme" class="theme-toggle" :title="getThemeTitle"> <button @click="toggleTheme" class="theme-toggle" :title="getThemeTitle">
<span class="material-symbols-outlined">{{ getThemeIcon }}</span> <span class="material-symbols-outlined">{{ getThemeIcon }}</span>
</button> </button>

View File

@@ -155,6 +155,43 @@ html.dark-theme .search-input:focus {
color: var(--text-secondary); color: var(--text-secondary);
} }
.sort-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border: 1px solid #cccccc;
border-radius: 4px;
background-color: #f5f5f5;
color: var(--text-primary);
cursor: pointer;
transition: all 0.2s ease;
font-size: 18px;
padding: 0;
flex-shrink: 0;
}
.sort-toggle:hover {
background-color: #e8e8e8;
border-color: #999999;
}
.sort-toggle:active {
transform: scale(0.95);
}
html.dark-theme .sort-toggle {
border-color: #555555;
background-color: #1a1a1a;
color: #e0e0e0;
}
html.dark-theme .sort-toggle:hover {
background-color: #2a2a2a;
border-color: #777777;
}
.theme-toggle { .theme-toggle {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;