Fix security issues.
This commit is contained in:
@@ -2,99 +2,156 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="page-container">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<h1 class="page-title">{{ note.title }}</h1>
|
||||
<div class="page-meta">
|
||||
<span class="visibility-badge visibility-{{ note.visibility.value.lower() }}">
|
||||
{% if note.visibility.value == 'Private' %}<i class="ti ti-lock"></i>{% elif note.visibility.value == 'Team' %}<i class="ti ti-users"></i>{% else %}<i class="ti ti-building"></i>{% endif %}
|
||||
{{ note.visibility.value }}
|
||||
</span>
|
||||
{% if note.is_pinned %}
|
||||
<span class="pin-badge">
|
||||
<span class="icon"><i class="ti ti-pin"></i></span>
|
||||
Pinned
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="meta-divider">•</span>
|
||||
<span class="author">
|
||||
<span class="icon"><i class="ti ti-user"></i></span>
|
||||
{{ note.created_by.username }}
|
||||
</span>
|
||||
<span class="meta-divider">•</span>
|
||||
<span class="date">
|
||||
<span class="icon"><i class="ti ti-calendar"></i></span>
|
||||
Created {{ note.created_at|format_date }}
|
||||
</span>
|
||||
{% if note.updated_at > note.created_at %}
|
||||
<span class="meta-divider">•</span>
|
||||
<span class="date">
|
||||
<span class="icon"><i class="ti ti-refresh"></i></span>
|
||||
Updated {{ note.updated_at|format_date }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if note.folder %}
|
||||
<span class="meta-divider">•</span>
|
||||
<span class="folder">
|
||||
<span class="icon"><i class="ti ti-folder"></i></span>
|
||||
<a href="{{ url_for('notes.notes_list', folder=note.folder) }}" class="folder-link">
|
||||
{{ note.folder }}
|
||||
</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Compact Unified Header -->
|
||||
<div class="note-header-compact">
|
||||
<!-- Title Bar -->
|
||||
<div class="header-title-bar">
|
||||
<button class="btn-icon" onclick="window.location.href='{{ url_for('notes.notes_list') }}'">
|
||||
<i class="ti ti-arrow-left"></i>
|
||||
</button>
|
||||
|
||||
<h1 class="note-title">{{ note.title }}</h1>
|
||||
|
||||
<div class="header-actions">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-secondary dropdown-toggle" type="button" id="downloadDropdown" data-toggle="dropdown">
|
||||
<span class="icon"><i class="ti ti-download"></i></span>
|
||||
Download
|
||||
<!-- Context-Specific Primary Actions -->
|
||||
{% if note.is_file_based and note.file_type == 'document' and note.original_filename.endswith('.pdf') %}
|
||||
<!-- PDF Actions -->
|
||||
<div class="zoom-controls">
|
||||
<button class="btn-icon" onclick="pdfZoomOut()" title="Zoom Out">
|
||||
<i class="ti ti-zoom-out"></i>
|
||||
</button>
|
||||
<span class="zoom-level" id="zoom-level">100%</span>
|
||||
<button class="btn-icon" onclick="pdfZoomIn()" title="Zoom In">
|
||||
<i class="ti ti-zoom-in"></i>
|
||||
</button>
|
||||
</div>
|
||||
<a href="{{ note.file_url }}" class="btn btn-primary btn-sm" download>
|
||||
<i class="ti ti-download"></i>
|
||||
<span class="btn-text">Download PDF</span>
|
||||
</a>
|
||||
{% elif note.is_image %}
|
||||
<!-- Image Actions -->
|
||||
<button class="btn-icon" onclick="toggleFullscreen()" title="Fullscreen">
|
||||
<i class="ti ti-maximize"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a href="{{ note.file_url }}" class="btn btn-primary btn-sm" download>
|
||||
<i class="ti ti-download"></i>
|
||||
<span class="btn-text">Download</span>
|
||||
</a>
|
||||
{% else %}
|
||||
<!-- Markdown/Text Actions -->
|
||||
{% if note.can_user_edit(g.user) %}
|
||||
<a href="{{ url_for('notes.edit_note', slug=note.slug) }}" class="btn btn-primary btn-sm">
|
||||
<i class="ti ti-pencil"></i>
|
||||
<span class="btn-text">Edit</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('notes.view_note_mindmap', slug=note.slug) }}" class="btn btn-secondary btn-sm">
|
||||
<i class="ti ti-brain"></i>
|
||||
<span class="btn-text">Mind Map</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<!-- Common Actions -->
|
||||
{% if note.can_user_edit(g.user) %}
|
||||
<button class="btn btn-secondary btn-sm" onclick="showShareModal()">
|
||||
<i class="ti ti-share"></i>
|
||||
<span class="btn-text">Share</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
<!-- More Actions Dropdown -->
|
||||
<div class="dropdown">
|
||||
<button class="btn-icon" data-toggle="dropdown" title="More actions">
|
||||
<i class="ti ti-dots-vertical"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
{% if not (note.is_file_based and note.file_type == 'document' and note.original_filename.endswith('.pdf')) %}
|
||||
<!-- Download options for non-PDF -->
|
||||
<h6 class="dropdown-header">Download as</h6>
|
||||
<a class="dropdown-item" href="{{ url_for('notes_download.download_note', slug=note.slug, format='md') }}">
|
||||
<span class="icon"><i class="ti ti-file-text"></i></span>
|
||||
Markdown (.md)
|
||||
<i class="ti ti-file-text"></i> Markdown
|
||||
</a>
|
||||
<a class="dropdown-item" href="{{ url_for('notes_download.download_note', slug=note.slug, format='html') }}">
|
||||
<span class="icon"><i class="ti ti-world"></i></span>
|
||||
HTML (.html)
|
||||
<i class="ti ti-world"></i> HTML
|
||||
</a>
|
||||
<a class="dropdown-item" href="{{ url_for('notes_download.download_note', slug=note.slug, format='txt') }}">
|
||||
<span class="icon"><i class="ti ti-file"></i></span>
|
||||
Plain Text (.txt)
|
||||
<i class="ti ti-file"></i> Plain Text
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
{% endif %}
|
||||
|
||||
{% if note.is_pinned %}
|
||||
<a class="dropdown-item" href="#">
|
||||
<i class="ti ti-pin-filled"></i> Pinned
|
||||
</a>
|
||||
{% else %}
|
||||
<a class="dropdown-item" href="#">
|
||||
<i class="ti ti-pin"></i> Pin Note
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<a class="dropdown-item" onclick="window.print()">
|
||||
<i class="ti ti-printer"></i> Print
|
||||
</a>
|
||||
|
||||
{% if note.can_user_edit(g.user) %}
|
||||
<div class="dropdown-divider"></div>
|
||||
<form method="POST" action="{{ url_for('notes.delete_note', slug=note.slug) }}"
|
||||
style="display: inline;"
|
||||
onsubmit="return confirm('Are you sure you want to delete this note?')">
|
||||
<button type="submit" class="dropdown-item text-danger">
|
||||
<i class="ti ti-trash"></i> Delete Note
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{ url_for('notes.view_note_mindmap', slug=note.slug) }}" class="btn btn-secondary">
|
||||
<span class="icon"><i class="ti ti-brain"></i></span>
|
||||
Mind Map
|
||||
</a>
|
||||
{% if note.can_user_edit(g.user) %}
|
||||
<button type="button" class="btn btn-secondary" onclick="showShareModal()">
|
||||
<span class="icon"><i class="ti ti-link"></i></span>
|
||||
Share
|
||||
</button>
|
||||
<a href="{{ url_for('notes.edit_note', slug=note.slug) }}" class="btn btn-primary">
|
||||
<span class="icon"><i class="ti ti-pencil"></i></span>
|
||||
Edit
|
||||
</a>
|
||||
<form method="POST" action="{{ url_for('notes.delete_note', slug=note.slug) }}"
|
||||
style="display: inline;"
|
||||
onsubmit="return confirm('Are you sure you want to delete this note?')">
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<span class="icon"><i class="ti ti-trash"></i></span>
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('notes.notes_list') }}" class="btn btn-secondary">
|
||||
<span class="icon"><i class="ti ti-arrow-left"></i></span>
|
||||
Back to Notes
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Metadata Bar -->
|
||||
<div class="header-meta-bar">
|
||||
{% if note.folder %}
|
||||
<span class="meta-item">
|
||||
<i class="ti ti-folder"></i>
|
||||
<a href="{{ url_for('notes.notes_list', folder=note.folder) }}">{{ note.folder }}</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if note.tags %}
|
||||
<span class="meta-item">
|
||||
<i class="ti ti-tag"></i>
|
||||
{% for tag in note.get_tags_list() %}
|
||||
<a href="{{ url_for('notes.notes_list', tag=tag) }}" class="tag-link">{{ tag }}</a>{% if not loop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
<span class="meta-item">
|
||||
<i class="ti ti-user"></i> {{ note.created_by.username }}
|
||||
</span>
|
||||
|
||||
<span class="meta-item">
|
||||
<i class="ti ti-clock"></i>
|
||||
{% if note.updated_at > note.created_at %}
|
||||
Updated {{ note.updated_at|format_date }}
|
||||
{% else %}
|
||||
Created {{ note.created_at|format_date }}
|
||||
{% endif %}
|
||||
</span>
|
||||
|
||||
<span class="visibility-badge visibility-{{ note.visibility.value.lower() }}">
|
||||
{% if note.visibility.value == 'Private' %}
|
||||
<i class="ti ti-lock"></i>
|
||||
{% elif note.visibility.value == 'Team' %}
|
||||
<i class="ti ti-users"></i>
|
||||
{% else %}
|
||||
<i class="ti ti-building"></i>
|
||||
{% endif %}
|
||||
{{ note.visibility.value }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Note Metadata Card -->
|
||||
@@ -153,24 +210,15 @@
|
||||
<!-- Note Content -->
|
||||
<div class="content-card">
|
||||
{% if note.is_file_based and note.file_type == 'document' and note.original_filename.endswith('.pdf') %}
|
||||
<!-- PDF Preview -->
|
||||
<!-- PDF Preview (toolbar moved to unified header) -->
|
||||
<div class="pdf-preview-container">
|
||||
<div class="pdf-toolbar">
|
||||
<button class="btn btn-sm btn-secondary" onclick="pdfZoomIn()">
|
||||
<i class="ti ti-zoom-in"></i> Zoom In
|
||||
</button>
|
||||
<button class="btn btn-sm btn-secondary" onclick="pdfZoomOut()">
|
||||
<i class="ti ti-zoom-out"></i> Zoom Out
|
||||
</button>
|
||||
<button class="btn btn-sm btn-secondary" onclick="pdfZoomReset()">
|
||||
<i class="ti ti-zoom-reset"></i> Reset
|
||||
</button>
|
||||
<a href="{{ note.file_url }}" class="btn btn-sm btn-primary" download>
|
||||
<i class="ti ti-download"></i> Download PDF
|
||||
</a>
|
||||
</div>
|
||||
<iframe id="pdf-viewer" src="{{ note.file_url }}" class="pdf-viewer"></iframe>
|
||||
</div>
|
||||
{% elif note.is_image %}
|
||||
<!-- Image Preview -->
|
||||
<div class="image-preview-container">
|
||||
<img src="{{ note.file_url }}" alt="{{ note.title }}" class="note-image" id="note-image">
|
||||
</div>
|
||||
{% else %}
|
||||
<!-- Regular Content -->
|
||||
<div class="markdown-content">
|
||||
@@ -311,14 +359,197 @@
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Page Header - Time Tracking style */
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
/* Compact Unified Header */
|
||||
.note-header-compact {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||
margin-bottom: 1.5rem;
|
||||
position: sticky;
|
||||
top: 10px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.header-title-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 1rem 1.5rem;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.note-title {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
background: white;
|
||||
color: #495057;
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-icon:hover {
|
||||
background: #f8f9fa;
|
||||
border-color: #adb5bd;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.zoom-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.25rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.zoom-level {
|
||||
min-width: 60px;
|
||||
text-align: center;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.header-meta-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: #f8f9fa;
|
||||
border-top: 1px solid #e9ecef;
|
||||
border-radius: 0 0 12px 12px;
|
||||
font-size: 0.875rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.meta-item i {
|
||||
font-size: 1rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.meta-item a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.meta-item a:hover {
|
||||
color: #495057;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.tag-link {
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.tag-link:hover {
|
||||
color: #5a67d8;
|
||||
}
|
||||
|
||||
/* Updated button styles for compact header */
|
||||
.btn-sm {
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
border-radius: 6px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.btn-primary.btn-sm {
|
||||
background: #667eea;
|
||||
border-color: #667eea;
|
||||
color: white;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.btn-primary.btn-sm:hover {
|
||||
background: #5a67d8;
|
||||
border-color: #5a67d8;
|
||||
}
|
||||
|
||||
.btn-secondary.btn-sm {
|
||||
background: white;
|
||||
border: 1px solid #dee2e6;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.btn-secondary.btn-sm:hover {
|
||||
background: #f8f9fa;
|
||||
border-color: #adb5bd;
|
||||
}
|
||||
|
||||
/* Mobile Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.header-title-bar {
|
||||
padding: 0.75rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.note-title {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 0.375rem 0.5rem;
|
||||
}
|
||||
|
||||
/* Hide button text on mobile */
|
||||
.btn-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.btn-sm i {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-meta-bar {
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.zoom-controls {
|
||||
padding: 0.125rem;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.header-content {
|
||||
@@ -745,6 +976,32 @@
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
margin: 0 0 0.5rem 0;
|
||||
{% if g.user.preferences and g.user.preferences.note_preview_font and g.user.preferences.note_preview_font != 'system' %}
|
||||
{% set font = g.user.preferences.note_preview_font %}
|
||||
{% if font == 'sans-serif' %}
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
{% elif font == 'serif' %}
|
||||
font-family: "Times New Roman", Times, serif;
|
||||
{% elif font == 'monospace' %}
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
{% elif font == 'georgia' %}
|
||||
font-family: Georgia, serif;
|
||||
{% elif font == 'palatino' %}
|
||||
font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif;
|
||||
{% elif font == 'garamond' %}
|
||||
font-family: Garamond, serif;
|
||||
{% elif font == 'bookman' %}
|
||||
font-family: "Bookman Old Style", serif;
|
||||
{% elif font == 'comic-sans' %}
|
||||
font-family: "Comic Sans MS", cursive;
|
||||
{% elif font == 'trebuchet' %}
|
||||
font-family: "Trebuchet MS", sans-serif;
|
||||
{% elif font == 'arial-black' %}
|
||||
font-family: "Arial Black", sans-serif;
|
||||
{% elif font == 'impact' %}
|
||||
font-family: Impact, sans-serif;
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
.linked-note-meta {
|
||||
@@ -1013,6 +1270,27 @@
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
/* Image preview styles */
|
||||
.image-preview-container {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.note-image {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
.note-image:fullscreen {
|
||||
cursor: zoom-out;
|
||||
object-fit: contain;
|
||||
padding: 2rem;
|
||||
background: black;
|
||||
}
|
||||
|
||||
/* Responsive PDF viewer */
|
||||
@media (max-width: 768px) {
|
||||
.pdf-viewer {
|
||||
@@ -1048,13 +1326,37 @@ function pdfZoomReset() {
|
||||
|
||||
function updatePdfZoom() {
|
||||
const viewer = document.getElementById('pdf-viewer');
|
||||
const zoomLevel = document.getElementById('zoom-level');
|
||||
if (viewer) {
|
||||
viewer.style.transform = `scale(${pdfZoom})`;
|
||||
viewer.style.transformOrigin = 'top center';
|
||||
}
|
||||
if (zoomLevel) {
|
||||
zoomLevel.textContent = Math.round(pdfZoom * 100) + '%';
|
||||
}
|
||||
}
|
||||
|
||||
// Image viewer functions
|
||||
function toggleFullscreen() {
|
||||
const image = document.getElementById('note-image');
|
||||
if (image) {
|
||||
if (!document.fullscreenElement) {
|
||||
image.requestFullscreen().catch(err => {
|
||||
console.error(`Error attempting to enable fullscreen: ${err.message}`);
|
||||
});
|
||||
} else {
|
||||
document.exitFullscreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize zoom level display for PDFs
|
||||
const zoomLevel = document.getElementById('zoom-level');
|
||||
if (zoomLevel) {
|
||||
zoomLevel.textContent = '100%';
|
||||
}
|
||||
|
||||
// Download dropdown functionality
|
||||
const downloadBtn = document.getElementById('downloadDropdown');
|
||||
const downloadMenu = downloadBtn.nextElementSibling;
|
||||
|
||||
@@ -47,6 +47,10 @@
|
||||
<span class="icon"><i class="ti ti-settings"></i></span>
|
||||
Manage Folders
|
||||
</a>
|
||||
<button type="button" class="btn btn-secondary" id="preferences-btn">
|
||||
<span class="icon"><i class="ti ti-adjustments"></i></span>
|
||||
Preferences
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -399,6 +403,54 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Note Preferences Modal -->
|
||||
<div id="preferences-modal" class="move-modal">
|
||||
<div class="move-modal-content" style="max-width: 500px;">
|
||||
<div class="move-modal-header">
|
||||
<h3><i class="ti ti-adjustments"></i> Note Preferences</h3>
|
||||
<button type="button" class="close-btn" onclick="closePreferencesModal()">
|
||||
<i class="ti ti-x"></i>
|
||||
</button>
|
||||
</div>
|
||||
<form id="note-preferences-form" method="POST" action="{{ url_for('notes.update_note_preferences') }}">
|
||||
<div class="move-modal-body">
|
||||
<div class="form-group">
|
||||
<label for="note_preview_font" class="form-label">Preview Font</label>
|
||||
<select id="note_preview_font" name="note_preview_font" class="form-control">
|
||||
<option value="system" {% if not g.user.preferences or g.user.preferences.note_preview_font == 'system' %}selected{% endif %}>System Default</option>
|
||||
<option value="sans-serif" {% if g.user.preferences and g.user.preferences.note_preview_font == 'sans-serif' %}selected{% endif %}>Sans-serif (Arial, Helvetica)</option>
|
||||
<option value="serif" {% if g.user.preferences and g.user.preferences.note_preview_font == 'serif' %}selected{% endif %}>Serif (Times, Georgia)</option>
|
||||
<option value="monospace" {% if g.user.preferences and g.user.preferences.note_preview_font == 'monospace' %}selected{% endif %}>Monospace (Courier, Consolas)</option>
|
||||
<option value="georgia" {% if g.user.preferences and g.user.preferences.note_preview_font == 'georgia' %}selected{% endif %}>Georgia</option>
|
||||
<option value="palatino" {% if g.user.preferences and g.user.preferences.note_preview_font == 'palatino' %}selected{% endif %}>Palatino</option>
|
||||
<option value="garamond" {% if g.user.preferences and g.user.preferences.note_preview_font == 'garamond' %}selected{% endif %}>Garamond</option>
|
||||
<option value="bookman" {% if g.user.preferences and g.user.preferences.note_preview_font == 'bookman' %}selected{% endif %}>Bookman</option>
|
||||
<option value="comic-sans" {% if g.user.preferences and g.user.preferences.note_preview_font == 'comic-sans' %}selected{% endif %}>Comic Sans MS</option>
|
||||
<option value="trebuchet" {% if g.user.preferences and g.user.preferences.note_preview_font == 'trebuchet' %}selected{% endif %}>Trebuchet MS</option>
|
||||
<option value="arial-black" {% if g.user.preferences and g.user.preferences.note_preview_font == 'arial-black' %}selected{% endif %}>Arial Black</option>
|
||||
<option value="impact" {% if g.user.preferences and g.user.preferences.note_preview_font == 'impact' %}selected{% endif %}>Impact</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">Choose the font family for note previews in the list view</small>
|
||||
</div>
|
||||
|
||||
<div class="preview-section mt-4">
|
||||
<label class="form-label">Preview</label>
|
||||
<div class="preview-box" id="fontPreview">
|
||||
<p class="mb-2">This is how your note previews will look with the selected font.</p>
|
||||
<p class="mb-0 text-muted">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="move-modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closePreferencesModal()">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="ti ti-check"></i> Save Preferences
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Container */
|
||||
.notes-container {
|
||||
@@ -1516,6 +1568,30 @@ td.checkbox-column {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* Preferences Modal Styles */
|
||||
.preview-box {
|
||||
padding: 1rem;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
background-color: #f9f9f9;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.preview-box p {
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.preview-section {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.preview-section .form-label {
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.bulk-actions-bar {
|
||||
flex-wrap: wrap;
|
||||
@@ -2147,7 +2223,87 @@ window.addEventListener('click', function(e) {
|
||||
if (e.target === moveModal) {
|
||||
closeMoveModal();
|
||||
}
|
||||
|
||||
const preferencesModal = document.getElementById('preferences-modal');
|
||||
if (e.target === preferencesModal) {
|
||||
closePreferencesModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Font families mapping
|
||||
const fontFamilies = {
|
||||
'system': 'inherit',
|
||||
'sans-serif': 'Arial, Helvetica, sans-serif',
|
||||
'serif': '"Times New Roman", Times, serif',
|
||||
'monospace': '"Courier New", Courier, monospace',
|
||||
'georgia': 'Georgia, serif',
|
||||
'palatino': '"Palatino Linotype", "Book Antiqua", Palatino, serif',
|
||||
'garamond': 'Garamond, serif',
|
||||
'bookman': '"Bookman Old Style", serif',
|
||||
'comic-sans': '"Comic Sans MS", cursive',
|
||||
'trebuchet': '"Trebuchet MS", sans-serif',
|
||||
'arial-black': '"Arial Black", sans-serif',
|
||||
'impact': 'Impact, sans-serif'
|
||||
};
|
||||
|
||||
// Handle font preview in modal
|
||||
document.getElementById('note_preview_font').addEventListener('change', function() {
|
||||
const selectedFont = this.value;
|
||||
const previewBox = document.getElementById('fontPreview');
|
||||
previewBox.style.fontFamily = fontFamilies[selectedFont] || 'inherit';
|
||||
});
|
||||
|
||||
// Handle preferences form submission
|
||||
document.getElementById('note-preferences-form').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(this);
|
||||
|
||||
try {
|
||||
const response = await fetch(this.action, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// Apply font immediately without page reload
|
||||
const font = data.font;
|
||||
|
||||
// Update all note previews
|
||||
document.querySelectorAll('.note-preview').forEach(preview => {
|
||||
preview.style.fontFamily = fontFamilies[font] || 'inherit';
|
||||
});
|
||||
|
||||
// Close modal
|
||||
closePreferencesModal();
|
||||
|
||||
// Show success toast or feedback
|
||||
// You could add a toast notification here
|
||||
} else {
|
||||
alert('Error saving preferences: ' + (data.error || 'Unknown error'));
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error saving preferences: ' + error.message);
|
||||
}
|
||||
});
|
||||
|
||||
// Preferences modal functions
|
||||
document.getElementById('preferences-btn').addEventListener('click', function() {
|
||||
document.getElementById('preferences-modal').classList.add('active');
|
||||
// Initialize preview font
|
||||
const currentFont = document.getElementById('note_preview_font').value;
|
||||
const previewBox = document.getElementById('fontPreview');
|
||||
previewBox.style.fontFamily = fontFamilies[currentFont] || 'inherit';
|
||||
});
|
||||
|
||||
function closePreferencesModal() {
|
||||
document.getElementById('preferences-modal').classList.remove('active');
|
||||
}
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user