Merge pull request #157 from gamosoft/features/readable-line-length
readable line length toggle
This commit is contained in:
commit
7df04bf985
147
frontend/app.js
147
frontend/app.js
|
|
@ -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
|
||||||
try {
|
loadLocalSettings() {
|
||||||
const saved = localStorage.getItem('syntaxHighlightEnabled');
|
for (const [prop, config] of Object.entries(LOCAL_SETTINGS)) {
|
||||||
this.syntaxHighlightEnabled = saved === 'true';
|
try {
|
||||||
} catch (error) {
|
const saved = localStorage.getItem(config.key);
|
||||||
console.error('Error loading syntax highlight setting:', error);
|
|
||||||
|
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)
|
// 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());
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -234,7 +234,9 @@
|
||||||
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"account": "アカウント",
|
"account": "アカウント",
|
||||||
"logout": "ログアウト"
|
"logout": "ログアウト",
|
||||||
|
"readable_line_length": "読みやすい行幅",
|
||||||
|
"readable_line_length_desc": "プレビューの幅を制限して読みやすくする"
|
||||||
},
|
},
|
||||||
|
|
||||||
"homepage": {
|
"homepage": {
|
||||||
|
|
|
||||||
|
|
@ -234,7 +234,9 @@
|
||||||
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"account": "Аккаунт",
|
"account": "Аккаунт",
|
||||||
"logout": "Выйти"
|
"logout": "Выйти",
|
||||||
|
"readable_line_length": "Удобная длина строки",
|
||||||
|
"readable_line_length_desc": "Ограничить ширину предпросмотра для удобства чтения"
|
||||||
},
|
},
|
||||||
|
|
||||||
"homepage": {
|
"homepage": {
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -234,7 +234,9 @@
|
||||||
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"account": "账户",
|
"account": "账户",
|
||||||
"logout": "登出"
|
"logout": "登出",
|
||||||
|
"readable_line_length": "可读行宽",
|
||||||
|
"readable_line_length_desc": "限制预览宽度以便于阅读"
|
||||||
},
|
},
|
||||||
|
|
||||||
"homepage": {
|
"homepage": {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue