- Select remplacé: class sort-select (mal stylé) → settings-select (adapté) - 5 vues: Tree, List, Details, Cards, Content (plus seulement 3) - x-model="defaultView" + @change="saveDefaultView()" - Sauvegarde localStorage 'fd_default_view' - workspace page lit localStorage pour viewMode initial - Settings → Default view persiste entre sessions et s'applique au workspace
343 lines
16 KiB
HTML
343 lines
16 KiB
HTML
{% extends "base.html" %}
|
||
{% block page_title %}Settings{% endblock %}
|
||
{% block page_icon %}⚙️{% endblock %}
|
||
|
||
{% block topbar %}
|
||
<header class="topbar">
|
||
<button class="hamburger-btn" @click="mobileSidebarOpen = true; sidebarCollapsed = false;" title="Menu">
|
||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
||
</button>
|
||
<div class="topbar-left">
|
||
<span class="page-title-topbar">⚙️ Settings</span>
|
||
</div>
|
||
</header>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<style>
|
||
.settings-overlay{position:fixed;inset:0;z-index:500;display:flex;background:rgba(0,0,0,.5);backdrop-filter:blur(2px);}
|
||
.settings-panel{display:flex;width:820px;max-width:95vw;height:85vh;margin:auto;background:#1E1E1E;border:1px solid var(--border-strong);border-radius:14px;box-shadow:0 16px 48px rgba(0,0,0,.6);overflow:hidden;}
|
||
.settings-nav{width:200px;min-width:200px;background:var(--bg-sidebar);border-right:1px solid var(--border);padding:8px 0;overflow-y:auto;}
|
||
.settings-nav-header{padding:12px 16px;font-size:13px;font-weight:600;color:var(--text-dim);text-transform:uppercase;letter-spacing:.5px;}
|
||
.settings-nav-item{display:flex;align-items:center;gap:8px;padding:6px 16px;font-size:13px;color:var(--text-secondary);cursor:pointer;transition:background 100ms;border-radius:0;}
|
||
.settings-nav-item:hover{background:var(--bg-hover);}
|
||
.settings-nav-item.active{background:rgba(35,131,226,.12);color:var(--text);font-weight:500;}
|
||
.settings-content{flex:1;overflow-y:auto;padding:24px 32px;}
|
||
.settings-content h2{font-size:18px;font-weight:600;margin-bottom:4px;}
|
||
.settings-content .section-desc{font-size:12px;color:var(--text-dim);margin-bottom:24px;}
|
||
.setting-group{margin-bottom:28px;}
|
||
.setting-group h3{font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:var(--text-dim);margin-bottom:8px;padding-bottom:4px;border-bottom:1px solid var(--border);}
|
||
.setting-row{display:flex;align-items:center;justify-content:space-between;padding:10px 0;border-bottom:1px solid var(--border);}
|
||
.setting-row:last-child{border-bottom:none;}
|
||
.setting-label{font-size:14px;color:var(--text);}
|
||
.setting-desc{font-size:12px;color:var(--text-dim);margin-top:2px;}
|
||
.setting-control{flex-shrink:0;margin-left:16px;}
|
||
|
||
/* Avatar */
|
||
.avatar-section{display:flex;align-items:center;gap:16px;padding:12px 0;}
|
||
.avatar-preview{width:64px;height:64px;border-radius:50%;background:var(--bg-tertiary);display:flex;align-items:center;justify-content:center;font-size:28px;font-weight:700;color:var(--text);overflow:hidden;flex-shrink:0;}
|
||
.avatar-preview img{width:100%;height:100%;object-fit:cover;}
|
||
.avatar-upload-label{cursor:pointer;font-size:13px;color:var(--accent);padding:6px 12px;border:1px solid var(--accent);border-radius:6px;transition:all 150ms;}
|
||
.avatar-upload-label:hover{background:rgba(35,131,226,.1);}
|
||
.avatar-upload-input{display:none;}
|
||
|
||
/* Avatar color swatches */
|
||
.avatar-color-swatch{width:40px;height:40px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700;color:#fff;cursor:pointer;border:2px solid transparent;transition:all 120ms;}
|
||
.avatar-color-swatch:hover{transform:scale(1.1);}
|
||
.avatar-color-swatch.selected{border-color:var(--accent);box-shadow:0 0 0 2px rgba(35,131,226,.4);}
|
||
|
||
/* Tags */
|
||
.tag-mgmt-item{display:flex;align-items:center;gap:10px;padding:8px 10px;background:var(--bg-tertiary);border-radius:8px;margin-bottom:4px;}
|
||
.tag-mgmt-item .tag-color-dot{width:14px;height:14px;border-radius:50%;flex-shrink:0;}
|
||
.tag-name{flex:1;font-size:13px;color:var(--text);}
|
||
.tag-count{font-size:12px;color:var(--text-dim);}
|
||
.new-tag-form{display:flex;align-items:center;gap:10px;margin-top:8px;padding:8px 10px;background:var(--bg-tertiary);border:1px dashed var(--border);border-radius:8px;}
|
||
.new-tag-form input{flex:1;background:none;border:none;color:var(--text);font-size:13px;outline:none;}
|
||
.color-swatch{width:22px;height:22px;border-radius:6px;cursor:pointer;border:2px solid transparent;transition:all 120ms;}
|
||
.color-swatch:hover{transform:scale(1.15);}
|
||
.color-swatch.selected{border-color:var(--text);box-shadow:0 0 0 2px var(--accent);}
|
||
|
||
/* Close button */
|
||
.settings-close{position:absolute;top:12px;right:12px;width:32px;height:32px;background:none;border:none;color:var(--text-dim);font-size:20px;cursor:pointer;border-radius:6px;display:flex;align-items:center;justify-content:center;}
|
||
.settings-close:hover{background:var(--bg-hover);color:var(--text);}
|
||
.settings-select{background:var(--bg-tertiary);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:6px 10px;font-size:13px;outline:none;cursor:pointer;min-width:140px;}
|
||
.settings-select:focus{border-color:var(--accent);}
|
||
</style>
|
||
|
||
<div class="settings-overlay" x-data="settingsInit()" @keydown.escape="window.history.back()">
|
||
<div class="settings-panel" style="position:relative;">
|
||
<button class="settings-close" @click="window.history.back()" title="Close">×</button>
|
||
|
||
<!-- Left navigation -->
|
||
<div class="settings-nav">
|
||
<div class="settings-nav-header">Account</div>
|
||
<div class="settings-nav-item" :class="{ active: activeSection==='account' }" @click="activeSection='account'">👤 My account</div>
|
||
<div class="settings-nav-item" :class="{ active: activeSection==='notifications' }" @click="activeSection='notifications'">🔔 Notifications</div>
|
||
<div class="settings-nav-header">Workspace</div>
|
||
<div class="settings-nav-item" :class="{ active: activeSection==='workspace' }" @click="activeSection='workspace'">📋 General</div>
|
||
<div class="settings-nav-item" :class="{ active: activeSection==='tags' }" @click="activeSection='tags'">🏷️ Tags</div>
|
||
<div class="settings-nav-header">Features</div>
|
||
<div class="settings-nav-item" :class="{ active: activeSection==='features' }" @click="activeSection='features'">🧩 Integrations</div>
|
||
<div class="settings-nav-header">Admin</div>
|
||
<div class="settings-nav-item" :class="{ active: activeSection==='admin' }" @click="activeSection='admin'">🔧 Administration</div>
|
||
</div>
|
||
|
||
<!-- Right content -->
|
||
<div class="settings-content">
|
||
|
||
<!-- Account -->
|
||
<div x-show="activeSection==='account'">
|
||
<h2>My account</h2>
|
||
<p class="section-desc">Manage your profile and preferences.</p>
|
||
<div class="setting-group">
|
||
<h3>Profile</h3>
|
||
<div class="avatar-section">
|
||
<div class="avatar-preview" :style="{background: avatarColor || '#3A3A3A'}">
|
||
<img x-show="avatarUrl" :src="avatarUrl" alt="Avatar">
|
||
<span x-show="!avatarUrl">{{ user.get('full_name', user.get('login',''))[:1].upper() }}</span>
|
||
</div>
|
||
<div>
|
||
<label class="avatar-upload-label">
|
||
Upload photo
|
||
<input type="file" class="avatar-upload-input" accept="image/*" @change="uploadAvatar($event)">
|
||
</label>
|
||
<div style="font-size:11px;color:var(--text-dim);margin-top:4px;">JPG, PNG or GIF — max 2MB</div>
|
||
</div>
|
||
</div>
|
||
<div style="margin-top:8px;">
|
||
<div style="font-size:12px;color:var(--text-dim);margin-bottom:6px;">Or pick a color:</div>
|
||
<div style="display:flex;gap:6px;flex-wrap:wrap;">
|
||
<template x-for="c in avatarColors" :key="c">
|
||
<div class="avatar-color-swatch" :style="{background: c}"
|
||
:class="{ selected: avatarColor === c }"
|
||
@click="selectAvatarColor(c)">
|
||
<span>{{ user.get('full_name', user.get('login',''))[:1].upper() }}</span>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div>
|
||
<div class="setting-label">Full name</div>
|
||
<div class="setting-desc" x-text="userInfo.full_name || userInfo.login"></div>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div>
|
||
<div class="setting-label">Email</div>
|
||
<div class="setting-desc" x-text="userInfo.email || '—'"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Notifications -->
|
||
<div x-show="activeSection==='notifications'">
|
||
<h2>Notifications</h2>
|
||
<p class="section-desc">Choose when and how you want to be notified.</p>
|
||
<div class="setting-group">
|
||
<h3>Email notifications</h3>
|
||
<div class="setting-row">
|
||
<div>
|
||
<div class="setting-label">Comments</div>
|
||
<div class="setting-desc">When someone comments on your pages</div>
|
||
</div>
|
||
<div class="setting-control"><span style="font-size:12px;color:var(--text-dim);">Coming soon</span></div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div>
|
||
<div class="setting-label">Mentions</div>
|
||
<div class="setting-desc">When someone @mentions you</div>
|
||
</div>
|
||
<div class="setting-control"><span style="font-size:12px;color:var(--text-dim);">Coming soon</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Workspace -->
|
||
<div x-show="activeSection==='workspace'">
|
||
<h2>Workspace</h2>
|
||
<p class="section-desc">General workspace settings.</p>
|
||
<div class="setting-group">
|
||
<h3>General</h3>
|
||
<div class="setting-row">
|
||
<div>
|
||
<div class="setting-label">Workspace name</div>
|
||
<div class="setting-desc" x-text="'{{ workspace_name }}'"></div>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div>
|
||
<div class="setting-label">Default view</div>
|
||
<div class="setting-desc">Choose the default view for new pages</div>
|
||
</div>
|
||
<div class="setting-control">
|
||
<select class="settings-select" x-model="defaultView" @change="saveDefaultView()">
|
||
<option value="tree">🌳 Tree</option>
|
||
<option value="list">📋 List</option>
|
||
<option value="details">📊 Details</option>
|
||
<option value="title">🏷️ Cards</option>
|
||
<option value="content">📝 Content</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tags -->
|
||
<div x-show="activeSection==='tags'">
|
||
<h2>Tags</h2>
|
||
<p class="section-desc">Manage all tags across your workspace.</p>
|
||
<div class="setting-group">
|
||
<h3>All tags</h3>
|
||
<template x-for="tag in allTags" :key="tag.id">
|
||
<div class="tag-mgmt-item">
|
||
<div class="tag-color-dot" :style="{background: tag.color}"></div>
|
||
<span class="tag-name" x-text="tag.name"></span>
|
||
<span class="tag-count" x-text="tag.count + ' items'"></span>
|
||
<input type="color" :value="tag.color" @input="updateTagColor(tag.id, $event.target.value)" style="width:24px;height:24px;border:none;cursor:pointer;background:none;padding:0;" title="Change color">
|
||
<button class="tag-chip rm-btn" @click="deleteTag(tag.id)" title="Delete">×</button>
|
||
</div>
|
||
</template>
|
||
<div class="new-tag-form">
|
||
<template x-for="c in presetColors" :key="c">
|
||
<div class="color-swatch" :class="{ selected: newTagColor === c }" :style="{background: c}" @click="newTagColor=c"></div>
|
||
</template>
|
||
<input type="text" placeholder="New tag..." x-model="newTagName" @keydown.enter="createTag()">
|
||
<button class="btn btn-primary" @click="createTag()" :disabled="!newTagName.trim()" style="padding:4px 12px;font-size:12px;">Add</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Features -->
|
||
<div x-show="activeSection==='features'">
|
||
<h2>Features</h2>
|
||
<p class="section-desc">Integrations and feature toggles.</p>
|
||
<div class="setting-group">
|
||
<h3>Integrations</h3>
|
||
<div class="setting-row">
|
||
<div>
|
||
<div class="setting-label">Gitea</div>
|
||
<div class="setting-desc">Connected to git.dracodev.net</div>
|
||
</div>
|
||
<div class="setting-control"><span style="font-size:12px;color:#0F7B6C;">✅ Active</span></div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div>
|
||
<div class="setting-label">GitHub</div>
|
||
<div class="setting-desc">Connect your GitHub account</div>
|
||
</div>
|
||
<div class="setting-control"><span style="font-size:12px;color:var(--text-dim);">Coming soon</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Admin -->
|
||
<div x-show="activeSection==='admin'">
|
||
<h2>Administration</h2>
|
||
<p class="section-desc">Workspace administration and security.</p>
|
||
<div class="setting-group">
|
||
<h3>Security</h3>
|
||
<div class="setting-row">
|
||
<div>
|
||
<div class="setting-label">Session timeout</div>
|
||
<div class="setting-desc">Automatically log out after inactivity</div>
|
||
</div>
|
||
<div class="setting-control"><span style="font-size:12px;color:var(--text-dim);">30 days</span></div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div>
|
||
<div class="setting-label">Two-factor authentication</div>
|
||
<div class="setting-desc">Add an extra layer of security</div>
|
||
</div>
|
||
<div class="setting-control"><span style="font-size:12px;color:var(--text-dim);">Coming soon</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
document.addEventListener('alpine:init', function() {
|
||
Alpine.data('settingsInit', function() {
|
||
return {
|
||
activeSection: 'account',
|
||
allTags: [],
|
||
newTagName: '',
|
||
newTagColor: '#787774',
|
||
presetColors: ['#787774','#E03E3E','#D9730D','#DFAB01','#0F7B6C','#0B6E99','#6940A5','#AD1A72','#E16259','#D4A72C','#448361','#337EA9','#9065B0','#C94D8B'],
|
||
avatarUrl: '{{ avatar_url or "" }}',
|
||
avatarColor: '{{ avatar_color or "#3A3A3A" }}',
|
||
avatarColors: ['#E03E3E','#D9730D','#DFAB01','#0F7B6C','#0B6E99','#6940A5','#AD1A72','#E16259','#448361','#337EA9','#9065B0','#C94D8B','#787774','#3A3A3A'],
|
||
defaultView: localStorage.getItem('fd_default_view') || 'tree',
|
||
userInfo: {full_name: '{{ user.full_name or "" }}', login: '{{ user.login or "" }}', email: '{{ user.email or "" }}'},
|
||
|
||
async init() {
|
||
await this.loadTags();
|
||
},
|
||
|
||
async loadTags() {
|
||
try {
|
||
var r = await fetch('/api/settings/tags/all');
|
||
var d = await r.json();
|
||
this.allTags = d.tags || [];
|
||
} catch(e) { this.allTags = []; }
|
||
},
|
||
|
||
async createTag() {
|
||
var n = this.newTagName.trim();
|
||
if (!n) return;
|
||
var r = await fetch('/api/settings/tags', {
|
||
method: 'POST', headers: {'Content-Type':'application/json'},
|
||
body: JSON.stringify({name: n, color: this.newTagColor})
|
||
});
|
||
if (r.ok) { this.newTagName = ''; this.newTagColor = '#787774'; await this.loadTags(); }
|
||
},
|
||
|
||
async updateTagColor(id, color) {
|
||
await fetch('/api/settings/tags/' + id, {
|
||
method: 'PUT', headers: {'Content-Type':'application/json'},
|
||
body: JSON.stringify({color: color})
|
||
});
|
||
await this.loadTags();
|
||
},
|
||
|
||
async deleteTag(id) {
|
||
if (!confirm('Delete this tag?')) return;
|
||
await fetch('/api/settings/tags/' + id, { method: 'DELETE' });
|
||
await this.loadTags();
|
||
},
|
||
|
||
async uploadAvatar(ev) {
|
||
var file = ev.target.files[0];
|
||
if (!file) return;
|
||
var form = new FormData();
|
||
form.append('file', file);
|
||
var r = await fetch('/api/settings/avatar', { method: 'POST', body: form });
|
||
if (r.ok) {
|
||
var d = await r.json();
|
||
this.avatarUrl = d.avatar_url + '?t=' + Date.now();
|
||
this.avatarColor = '';
|
||
}
|
||
},
|
||
|
||
async selectAvatarColor(color) {
|
||
this.avatarColor = color;
|
||
var r = await fetch('/api/settings/avatar-color', {
|
||
method: 'POST', headers: {'Content-Type':'application/json'},
|
||
body: JSON.stringify({color: color})
|
||
});
|
||
if (r.ok) { this.avatarUrl = ''; }
|
||
},
|
||
|
||
saveDefaultView() {
|
||
localStorage.setItem('fd_default_view', this.defaultView);
|
||
}
|
||
};
|
||
});
|
||
});
|
||
</script>
|
||
{% endblock %}
|