Merge pull request #157 from gamosoft/features/readable-line-length

readable line length toggle
This commit is contained in:
Guillermo Villar 2026-01-26 16:48:50 +01:00 committed by GitHub
commit 7df04bf985
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 120 additions and 94 deletions

View File

@ -11,6 +11,22 @@ const CONFIG = {
DEFAULT_SIDEBAR_WIDTH: 256, // px - Default sidebar width (w-64 in Tailwind) 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 // Centralized error handling
const ErrorHandler = { const ErrorHandler = {
/** /**
@ -190,6 +206,9 @@ function noteApp() {
syntaxHighlightEnabled: false, syntaxHighlightEnabled: false,
syntaxHighlightTimeout: null, syntaxHighlightTimeout: null,
// Readable line length (preview max-width)
readableLineLength: true,
// Icon rail / panel state // Icon rail / panel state
activePanel: 'files', // 'files', 'search', 'tags', 'settings' activePanel: 'files', // 'files', 'search', 'tags', 'settings'
@ -458,13 +477,7 @@ function noteApp() {
await this.loadSharedNotePaths(); await this.loadSharedNotePaths();
await this.loadTemplates(); await this.loadTemplates();
await this.checkStatsPlugin(); await this.checkStatsPlugin();
this.loadSidebarWidth(); this.loadLocalSettings();
this.loadEditorWidth();
this.loadViewMode();
this.loadTagsExpanded();
this.loadFavorites();
this.loadFavoritesExpanded();
this.loadSyntaxHighlightSetting();
// Parse URL and load specific note if provided // Parse URL and load specific note if provided
this.loadItemFromURL(); 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 { try {
const saved = localStorage.getItem('syntaxHighlightEnabled'); const saved = localStorage.getItem(config.key);
this.syntaxHighlightEnabled = saved === 'true';
} catch (error) { if (saved === null) {
console.error('Error loading syntax highlight setting:', error); // 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) // Update syntax highlight overlay (debounced, called on input)
@ -1439,20 +1490,6 @@ function noteApp() {
// ==================== FAVORITES ==================== // ==================== 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 // Save favorites to localStorage
saveFavorites() { saveFavorites() {
try { try {
@ -1503,17 +1540,6 @@ function noteApp() {
.filter(Boolean); // Remove nulls (deleted notes) .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() { saveFavoritesExpanded() {
try { try {
localStorage.setItem('favoritesExpanded', this.favoritesExpanded.toString()); localStorage.setItem('favoritesExpanded', this.favoritesExpanded.toString());
@ -4770,34 +4796,11 @@ function noteApp() {
return Array.isArray(this.noteMetadata.tags) ? this.noteMetadata.tags : [this.noteMetadata.tags]; 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 // Save sidebar width to localStorage
saveSidebarWidth() { saveSidebarWidth() {
localStorage.setItem('sidebarWidth', this.sidebarWidth.toString()); 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 // Save view mode to localStorage
saveViewMode() { saveViewMode() {
try { 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() { saveTagsExpanded() {
try { try {
localStorage.setItem('tagsExpanded', this.tagsExpanded.toString()); 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 // Save editor width to localStorage
saveEditorWidth() { saveEditorWidth() {
localStorage.setItem('editorWidth', this.editorWidth.toString()); localStorage.setItem('editorWidth', this.editorWidth.toString());

View File

@ -170,6 +170,13 @@
font-size: 16px; 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 */ /* Remove top margin from first element */
.markdown-preview > *:first-child { .markdown-preview > *:first-child {
margin-top: 0 !important; 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> <p class="text-xs mt-1" style="color: var(--text-tertiary);" x-text="t('syntax_highlight.description')"></p>
</div> </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 --> <!-- Logout (if auth enabled) - uses authEnabled from main app state -->
<div x-show="authEnabled" class="mb-4"> <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> <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 <div
class="p-6 markdown-preview" class="p-6 markdown-preview"
:class="{ 'pt-2': getHasMetadata() && currentNote }" :class="{ 'pt-2': getHasMetadata() && currentNote, 'readable-width': readableLineLength }"
x-html="renderedMarkdown" x-html="renderedMarkdown"
@click="handleInternalLink($event)" @click="handleInternalLink($event)"
></div> ></div>

View File

@ -235,7 +235,9 @@
"settings": { "settings": {
"account": "Konto", "account": "Konto",
"logout": "Abmelden" "logout": "Abmelden",
"readable_line_length": "Lesbare Zeilenlänge",
"readable_line_length_desc": "Vorschaubreite für bessere Lesbarkeit begrenzen"
}, },
"homepage": { "homepage": {

View File

@ -234,7 +234,9 @@
"settings": { "settings": {
"account": "Account", "account": "Account",
"logout": "Logout" "logout": "Logout",
"readable_line_length": "Readable line length",
"readable_line_length_desc": "Limit preview width for easier reading"
}, },
"homepage": { "homepage": {

View File

@ -235,7 +235,9 @@
"settings": { "settings": {
"account": "Account", "account": "Account",
"logout": "Logout" "logout": "Logout",
"readable_line_length": "Readable line length",
"readable_line_length_desc": "Limit preview width for easier reading"
}, },
"homepage": { "homepage": {

View File

@ -235,7 +235,9 @@
"settings": { "settings": {
"account": "Cuenta", "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": { "homepage": {

View File

@ -235,7 +235,9 @@
"settings": { "settings": {
"account": "Compte", "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": { "homepage": {

View File

@ -234,7 +234,9 @@
"settings": { "settings": {
"account": "Account", "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": { "homepage": {

View File

@ -234,7 +234,9 @@
"settings": { "settings": {
"account": "アカウント", "account": "アカウント",
"logout": "ログアウト" "logout": "ログアウト",
"readable_line_length": "読みやすい行幅",
"readable_line_length_desc": "プレビューの幅を制限して読みやすくする"
}, },
"homepage": { "homepage": {

View File

@ -234,7 +234,9 @@
"settings": { "settings": {
"account": "Аккаунт", "account": "Аккаунт",
"logout": "Выйти" "logout": "Выйти",
"readable_line_length": "Удобная длина строки",
"readable_line_length_desc": "Ограничить ширину предпросмотра для удобства чтения"
}, },
"homepage": { "homepage": {

View File

@ -234,7 +234,9 @@
"settings": { "settings": {
"account": "Račun", "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": { "homepage": {

View File

@ -234,7 +234,9 @@
"settings": { "settings": {
"account": "账户", "account": "账户",
"logout": "登出" "logout": "登出",
"readable_line_length": "可读行宽",
"readable_line_length_desc": "限制预览宽度以便于阅读"
}, },
"homepage": { "homepage": {