readable line length toggle
This commit is contained in:
parent
1c0aa37c46
commit
baeca1760e
145
frontend/app.js
145
frontend/app.js
|
|
@ -11,6 +11,22 @@ const CONFIG = {
|
|||
DEFAULT_SIDEBAR_WIDTH: 256, // px - Default sidebar width (w-64 in Tailwind)
|
||||
};
|
||||
|
||||
// localStorage settings configuration - centralized definition of all persisted settings
|
||||
const LOCAL_SETTINGS = {
|
||||
// Boolean settings
|
||||
syntaxHighlightEnabled: { key: 'syntaxHighlightEnabled', type: 'boolean', default: false },
|
||||
readableLineLength: { key: 'readableLineLength', type: 'boolean', default: true },
|
||||
favoritesExpanded: { key: 'favoritesExpanded', type: 'boolean', default: true },
|
||||
tagsExpanded: { key: 'tagsExpanded', type: 'boolean', default: false },
|
||||
// Number settings with validation
|
||||
sidebarWidth: { key: 'sidebarWidth', type: 'number', default: CONFIG.DEFAULT_SIDEBAR_WIDTH, min: 200, max: 600 },
|
||||
editorWidth: { key: 'editorWidth', type: 'number', default: 50, min: 20, max: 80 },
|
||||
// String settings with validation
|
||||
viewMode: { key: 'viewMode', type: 'string', default: 'split', valid: ['edit', 'split', 'preview'] },
|
||||
// JSON settings
|
||||
favorites: { key: 'noteFavorites', type: 'json', default: [] },
|
||||
};
|
||||
|
||||
// Centralized error handling
|
||||
const ErrorHandler = {
|
||||
/**
|
||||
|
|
@ -190,6 +206,9 @@ function noteApp() {
|
|||
syntaxHighlightEnabled: false,
|
||||
syntaxHighlightTimeout: null,
|
||||
|
||||
// Readable line length (preview max-width)
|
||||
readableLineLength: true,
|
||||
|
||||
// Icon rail / panel state
|
||||
activePanel: 'files', // 'files', 'search', 'tags', 'settings'
|
||||
|
||||
|
|
@ -458,13 +477,7 @@ function noteApp() {
|
|||
await this.loadSharedNotePaths();
|
||||
await this.loadTemplates();
|
||||
await this.checkStatsPlugin();
|
||||
this.loadSidebarWidth();
|
||||
this.loadEditorWidth();
|
||||
this.loadViewMode();
|
||||
this.loadTagsExpanded();
|
||||
this.loadFavorites();
|
||||
this.loadFavoritesExpanded();
|
||||
this.loadSyntaxHighlightSetting();
|
||||
this.loadLocalSettings();
|
||||
|
||||
// Parse URL and load specific note if provided
|
||||
this.loadItemFromURL();
|
||||
|
|
@ -745,13 +758,51 @@ function noteApp() {
|
|||
}
|
||||
},
|
||||
|
||||
loadSyntaxHighlightSetting() {
|
||||
// Load all localStorage settings at once using centralized config
|
||||
loadLocalSettings() {
|
||||
for (const [prop, config] of Object.entries(LOCAL_SETTINGS)) {
|
||||
try {
|
||||
const saved = localStorage.getItem('syntaxHighlightEnabled');
|
||||
this.syntaxHighlightEnabled = saved === 'true';
|
||||
} catch (error) {
|
||||
console.error('Error loading syntax highlight setting:', error);
|
||||
const saved = localStorage.getItem(config.key);
|
||||
|
||||
if (saved === null) {
|
||||
// Use default value if not set
|
||||
this[prop] = config.default;
|
||||
} else if (config.type === 'boolean') {
|
||||
this[prop] = saved === 'true';
|
||||
} else if (config.type === 'number') {
|
||||
const num = parseFloat(saved);
|
||||
// Validate range if specified
|
||||
if (!isNaN(num) &&
|
||||
(config.min === undefined || num >= config.min) &&
|
||||
(config.max === undefined || num <= config.max)) {
|
||||
this[prop] = num;
|
||||
} else {
|
||||
this[prop] = config.default;
|
||||
}
|
||||
} else if (config.type === 'string') {
|
||||
// Validate against allowed values if specified
|
||||
if (!config.valid || config.valid.includes(saved)) {
|
||||
this[prop] = saved;
|
||||
} else {
|
||||
this[prop] = config.default;
|
||||
}
|
||||
} else if (config.type === 'json') {
|
||||
this[prop] = JSON.parse(saved);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error loading setting ${prop}:`, error);
|
||||
this[prop] = config.default;
|
||||
}
|
||||
}
|
||||
|
||||
// Special case: favorites also needs to update the Set for O(1) lookups
|
||||
this.favoritesSet = new Set(this.favorites);
|
||||
},
|
||||
|
||||
// Readable line length toggle (for preview max-width)
|
||||
toggleReadableLineLength() {
|
||||
this.readableLineLength = !this.readableLineLength;
|
||||
localStorage.setItem('readableLineLength', this.readableLineLength);
|
||||
},
|
||||
|
||||
// Update syntax highlight overlay (debounced, called on input)
|
||||
|
|
@ -1439,20 +1490,6 @@ function noteApp() {
|
|||
|
||||
// ==================== FAVORITES ====================
|
||||
|
||||
// Load favorites from localStorage
|
||||
loadFavorites() {
|
||||
try {
|
||||
const stored = localStorage.getItem('noteFavorites');
|
||||
if (stored) {
|
||||
this.favorites = JSON.parse(stored);
|
||||
this.favoritesSet = new Set(this.favorites);
|
||||
}
|
||||
} catch (e) {
|
||||
this.favorites = [];
|
||||
this.favoritesSet = new Set();
|
||||
}
|
||||
},
|
||||
|
||||
// Save favorites to localStorage
|
||||
saveFavorites() {
|
||||
try {
|
||||
|
|
@ -1503,17 +1540,6 @@ function noteApp() {
|
|||
.filter(Boolean); // Remove nulls (deleted notes)
|
||||
},
|
||||
|
||||
loadFavoritesExpanded() {
|
||||
try {
|
||||
const saved = localStorage.getItem('favoritesExpanded');
|
||||
if (saved !== null) {
|
||||
this.favoritesExpanded = saved === 'true';
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading favorites expanded state:', e);
|
||||
}
|
||||
},
|
||||
|
||||
saveFavoritesExpanded() {
|
||||
try {
|
||||
localStorage.setItem('favoritesExpanded', this.favoritesExpanded.toString());
|
||||
|
|
@ -4770,34 +4796,11 @@ function noteApp() {
|
|||
return Array.isArray(this.noteMetadata.tags) ? this.noteMetadata.tags : [this.noteMetadata.tags];
|
||||
},
|
||||
|
||||
// Load sidebar width from localStorage
|
||||
loadSidebarWidth() {
|
||||
const saved = localStorage.getItem('sidebarWidth');
|
||||
if (saved) {
|
||||
const width = parseInt(saved, 10);
|
||||
if (width >= 200 && width <= 600) {
|
||||
this.sidebarWidth = width;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Save sidebar width to localStorage
|
||||
saveSidebarWidth() {
|
||||
localStorage.setItem('sidebarWidth', this.sidebarWidth.toString());
|
||||
},
|
||||
|
||||
// Load view mode from localStorage
|
||||
loadViewMode() {
|
||||
try {
|
||||
const saved = localStorage.getItem('viewMode');
|
||||
if (saved && ['edit', 'split', 'preview'].includes(saved)) {
|
||||
this.viewMode = saved;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading view mode:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// Save view mode to localStorage
|
||||
saveViewMode() {
|
||||
try {
|
||||
|
|
@ -4807,17 +4810,6 @@ function noteApp() {
|
|||
}
|
||||
},
|
||||
|
||||
loadTagsExpanded() {
|
||||
try {
|
||||
const saved = localStorage.getItem('tagsExpanded');
|
||||
if (saved !== null) {
|
||||
this.tagsExpanded = saved === 'true';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading tags expanded state:', error);
|
||||
}
|
||||
},
|
||||
|
||||
saveTagsExpanded() {
|
||||
try {
|
||||
localStorage.setItem('tagsExpanded', this.tagsExpanded.toString());
|
||||
|
|
@ -4916,17 +4908,6 @@ function noteApp() {
|
|||
}
|
||||
},
|
||||
|
||||
// Load editor width from localStorage
|
||||
loadEditorWidth() {
|
||||
const saved = localStorage.getItem('editorWidth');
|
||||
if (saved) {
|
||||
const width = parseFloat(saved);
|
||||
if (width >= 20 && width <= 80) {
|
||||
this.editorWidth = width;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Save editor width to localStorage
|
||||
saveEditorWidth() {
|
||||
localStorage.setItem('editorWidth', this.editorWidth.toString());
|
||||
|
|
|
|||
|
|
@ -170,6 +170,13 @@
|
|||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Readable line length - constrain text width for better readability */
|
||||
.markdown-preview.readable-width {
|
||||
max-width: 750px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
/* Remove top margin from first element */
|
||||
.markdown-preview > *:first-child {
|
||||
margin-top: 0 !important;
|
||||
|
|
@ -1829,6 +1836,24 @@
|
|||
<p class="text-xs mt-1" style="color: var(--text-tertiary);" x-text="t('syntax_highlight.description')"></p>
|
||||
</div>
|
||||
|
||||
<!-- Readable Line Length Toggle -->
|
||||
<div class="mb-4">
|
||||
<label class="flex items-center justify-between cursor-pointer">
|
||||
<span class="text-xs font-medium" style="color: var(--text-secondary);" x-text="t('settings.readable_line_length')"></span>
|
||||
<div
|
||||
@click="toggleReadableLineLength()"
|
||||
class="relative w-10 h-5 rounded-full transition-colors cursor-pointer"
|
||||
:style="readableLineLength ? 'background-color: var(--accent-primary)' : 'background-color: var(--bg-tertiary)'"
|
||||
>
|
||||
<div
|
||||
class="absolute top-0.5 left-0.5 w-4 h-4 rounded-full transition-transform"
|
||||
:style="'background-color: var(--bg-primary);' + (readableLineLength ? ' transform: translateX(20px);' : '')"
|
||||
></div>
|
||||
</div>
|
||||
</label>
|
||||
<p class="text-xs mt-1" style="color: var(--text-tertiary);" x-text="t('settings.readable_line_length_desc')"></p>
|
||||
</div>
|
||||
|
||||
<!-- Logout (if auth enabled) - uses authEnabled from main app state -->
|
||||
<div x-show="authEnabled" class="mb-4">
|
||||
<label class="block text-xs font-medium mb-2" style="color: var(--text-secondary);" x-text="t('settings.account')"></label>
|
||||
|
|
@ -2576,7 +2601,7 @@
|
|||
|
||||
<div
|
||||
class="p-6 markdown-preview"
|
||||
:class="{ 'pt-2': getHasMetadata() && currentNote }"
|
||||
:class="{ 'pt-2': getHasMetadata() && currentNote, 'readable-width': readableLineLength }"
|
||||
x-html="renderedMarkdown"
|
||||
@click="handleInternalLink($event)"
|
||||
></div>
|
||||
|
|
|
|||
|
|
@ -235,7 +235,9 @@
|
|||
|
||||
"settings": {
|
||||
"account": "Konto",
|
||||
"logout": "Abmelden"
|
||||
"logout": "Abmelden",
|
||||
"readable_line_length": "Lesbare Zeilenlänge",
|
||||
"readable_line_length_desc": "Vorschaubreite für bessere Lesbarkeit begrenzen"
|
||||
},
|
||||
|
||||
"homepage": {
|
||||
|
|
|
|||
|
|
@ -234,7 +234,9 @@
|
|||
|
||||
"settings": {
|
||||
"account": "Account",
|
||||
"logout": "Logout"
|
||||
"logout": "Logout",
|
||||
"readable_line_length": "Readable line length",
|
||||
"readable_line_length_desc": "Limit preview width for easier reading"
|
||||
},
|
||||
|
||||
"homepage": {
|
||||
|
|
|
|||
|
|
@ -235,7 +235,9 @@
|
|||
|
||||
"settings": {
|
||||
"account": "Account",
|
||||
"logout": "Logout"
|
||||
"logout": "Logout",
|
||||
"readable_line_length": "Readable line length",
|
||||
"readable_line_length_desc": "Limit preview width for easier reading"
|
||||
},
|
||||
|
||||
"homepage": {
|
||||
|
|
|
|||
|
|
@ -235,7 +235,9 @@
|
|||
|
||||
"settings": {
|
||||
"account": "Cuenta",
|
||||
"logout": "Cerrar sesión"
|
||||
"logout": "Cerrar sesión",
|
||||
"readable_line_length": "Longitud de línea legible",
|
||||
"readable_line_length_desc": "Limitar el ancho de vista previa para facilitar la lectura"
|
||||
},
|
||||
|
||||
"homepage": {
|
||||
|
|
|
|||
|
|
@ -235,7 +235,9 @@
|
|||
|
||||
"settings": {
|
||||
"account": "Compte",
|
||||
"logout": "Déconnexion"
|
||||
"logout": "Déconnexion",
|
||||
"readable_line_length": "Longueur de ligne lisible",
|
||||
"readable_line_length_desc": "Limiter la largeur de l'aperçu pour une lecture plus facile"
|
||||
},
|
||||
|
||||
"homepage": {
|
||||
|
|
|
|||
|
|
@ -234,7 +234,9 @@
|
|||
|
||||
"settings": {
|
||||
"account": "Account",
|
||||
"logout": "Esci"
|
||||
"logout": "Esci",
|
||||
"readable_line_length": "Larghezza riga leggibile",
|
||||
"readable_line_length_desc": "Limita la larghezza dell'anteprima per una lettura più facile"
|
||||
},
|
||||
|
||||
"homepage": {
|
||||
|
|
|
|||
|
|
@ -234,7 +234,9 @@
|
|||
|
||||
"settings": {
|
||||
"account": "アカウント",
|
||||
"logout": "ログアウト"
|
||||
"logout": "ログアウト",
|
||||
"readable_line_length": "読みやすい行幅",
|
||||
"readable_line_length_desc": "プレビューの幅を制限して読みやすくする"
|
||||
},
|
||||
|
||||
"homepage": {
|
||||
|
|
|
|||
|
|
@ -234,7 +234,9 @@
|
|||
|
||||
"settings": {
|
||||
"account": "Аккаунт",
|
||||
"logout": "Выйти"
|
||||
"logout": "Выйти",
|
||||
"readable_line_length": "Удобная длина строки",
|
||||
"readable_line_length_desc": "Ограничить ширину предпросмотра для удобства чтения"
|
||||
},
|
||||
|
||||
"homepage": {
|
||||
|
|
|
|||
|
|
@ -234,7 +234,9 @@
|
|||
|
||||
"settings": {
|
||||
"account": "Račun",
|
||||
"logout": "Odjava"
|
||||
"logout": "Odjava",
|
||||
"readable_line_length": "Berljiva dolžina vrstice",
|
||||
"readable_line_length_desc": "Omejite širino predogleda za lažje branje"
|
||||
},
|
||||
|
||||
"homepage": {
|
||||
|
|
|
|||
|
|
@ -234,7 +234,9 @@
|
|||
|
||||
"settings": {
|
||||
"account": "账户",
|
||||
"logout": "登出"
|
||||
"logout": "登出",
|
||||
"readable_line_length": "可读行宽",
|
||||
"readable_line_length_desc": "限制预览宽度以便于阅读"
|
||||
},
|
||||
|
||||
"homepage": {
|
||||
|
|
|
|||
Loading…
Reference in New Issue