Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2f36b6fac | ||
|
|
121f788c28 |
+30
@@ -0,0 +1,30 @@
|
|||||||
|
# OS Artifacts
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Environment & IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Scratch & Temporary Folders
|
||||||
|
scratch/
|
||||||
|
*.tmp
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Dynamic Uploaded Favicons (Keep default favicon.png)
|
||||||
|
assets/img/favicon_*.png
|
||||||
|
assets/img/favicon_*.jpg
|
||||||
|
assets/img/favicon_*.jpeg
|
||||||
|
assets/img/favicon_*.webp
|
||||||
|
assets/img/favicon_*.ico
|
||||||
|
assets/img/favicon_*.svg
|
||||||
|
|
||||||
|
# Installer Completed Marker (Optional)
|
||||||
|
first_start.php.completed
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# 📝 sld-blocknotes - Sistema Moderno de Notas en Bloques y Gestión de Comandos
|
||||||
|
|
||||||
|
> **🌐 Idiomas disponibles**: [🇬🇧 English](README.md) | [🇮🇹 Italiano](README.it.md) | [🇪🇸 Español](README.es.md) | [🇫🇷 Français](README.fr.md)
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
Una potente y moderna aplicación web sin dependencias externas para organizar notas estructuradas en bloques, comandos de terminal, fragmentos de código e instrucciones. Desarrollada en **Pure PHP**, **Vanilla CSS**, **Vanilla JS** y **MySQL/MariaDB**.
|
||||||
|
|
||||||
|
URL del Repositorio: [https://gitea.sld-server.org/sld-admin/sld-blocknotes](https://gitea.sld-server.org/sld-admin/sld-blocknotes)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Características Principales
|
||||||
|
|
||||||
|
- 📑 **Notas en Bloques y Subnotas Estructuradas**:
|
||||||
|
- Organiza tus notas dentro de blocknotes personalizados.
|
||||||
|
- Las notas contienen título, descripción y múltiples subnotas.
|
||||||
|
- **Notas Plegables**: Las notas están plegadas por defecto para optimizar el espacio vertical, con botones globales de **Expandir Todas / Contraer Todas**.
|
||||||
|
|
||||||
|
- ⚡ **Subnotas de Tipo Comando de Terminal**:
|
||||||
|
- Renderizado diferenciado entre subnotas de texto genérico y comandos de terminal.
|
||||||
|
- **Botón Copiar en 1 Clic**: Copia instantáneamente comandos al portapapeles con retroalimentación visual (`✓ ¡Copiado!`).
|
||||||
|
- Cuadro estilo IDE/Terminal de alto contraste con barra de desplazamiento vertical.
|
||||||
|
|
||||||
|
- 🔍 **Búsqueda en Tiempo Real**:
|
||||||
|
- Filtra títulos de notas, descripciones, subnotas y contenido de comandos en tiempo real dentro del blocknote activo.
|
||||||
|
- Expande automáticamente los bloques coincidentes durante la búsqueda.
|
||||||
|
|
||||||
|
- 📥 / 💾 **Importación y Exportación JSON**:
|
||||||
|
- Descarga el archivo JSON de cualquier blocknote con un clic (`export_blocknote.php`).
|
||||||
|
- Importador JSON interactivo compatible con formato estándar y formato antiguo.
|
||||||
|
|
||||||
|
- 🌐 **Interfaz Multilingüe (IT, EN, ES, FR)**:
|
||||||
|
- Localización completa en **Italiano 🇮🇹, Inglés 🇬🇧, Español 🇪🇸 y Francés 🇫🇷**.
|
||||||
|
- Selector de idioma rápido en la barra de navegación para visitantes y usuarios conectados.
|
||||||
|
- El idioma preferido de cada usuario se guarda en su perfil para futuros accesos.
|
||||||
|
|
||||||
|
- 🏠 **Página de Inicio Personalizable**:
|
||||||
|
- Elige entre acceder directamente al **Panel Principal (Todos los Blocknotes)** o abrir un **Blocknote Predeterminado específico** al iniciar sesión.
|
||||||
|
|
||||||
|
- 👑 **Rol Único de Propietario y Jerarquía Avanzada (`owner` > `admin` > `user`)**:
|
||||||
|
- **Cuenta de Propietario Inmune**: La cuenta inicial de Propietario (`owner`) no puede ser eliminada por ningún administrador ni por sí misma.
|
||||||
|
- Los administradores estándar no pueden degradar ni modificar el rol del Propietario.
|
||||||
|
|
||||||
|
- ⚙️ **Personalización del Sitio (Exclusivo Propietario)**:
|
||||||
|
- Cambia el **Título Global de la Aplicación**.
|
||||||
|
- Sube y valida un **Favicon de Alta Resolución** (mínimo 64x64px) con vista previa en tiempo real.
|
||||||
|
|
||||||
|
- 🚀 **Asistente de Configuración Inicial (`first_start.php`)**:
|
||||||
|
- Instalador automático para configurar credenciales MySQL, crear tablas y registrar los primeros usuarios privilegiados (`owner` y `admin`).
|
||||||
|
- Completamente multilingüe en los 4 idiomas.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Guía de Instalación
|
||||||
|
|
||||||
|
### Requisitos
|
||||||
|
- Servidor Web (Apache, Nginx, o servidor CLI integrado de PHP)
|
||||||
|
- PHP 7.4 o 8.x (con extensión `mysqli` habilitada)
|
||||||
|
- MySQL 5.7+ o MariaDB 10.x+
|
||||||
|
|
||||||
|
### Instalación Paso a Paso
|
||||||
|
|
||||||
|
1. **Clona el Repositorio**:
|
||||||
|
```bash
|
||||||
|
git clone https://gitea.sld-server.org/sld-admin/sld-blocknotes.git
|
||||||
|
cd sld-blocknotes
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Inicia el Servidor Web**:
|
||||||
|
Puedes utilizar el servidor integrado de PHP:
|
||||||
|
```bash
|
||||||
|
php -S localhost:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Ejecuta el Asistente de Configuración**:
|
||||||
|
Abre tu navegador en `http://localhost:8000/`.
|
||||||
|
El sistema detectará el primer inicio y abrirá automáticamente `first_start.php`:
|
||||||
|
- Introduce los datos de tu **Base de Datos MySQL** (Host, Nombre, Usuario, Contraseña).
|
||||||
|
- Configura las credenciales de la **Cuenta de Propietario**.
|
||||||
|
- Configura las credenciales de la **Primera Cuenta de Admin**.
|
||||||
|
- Haz clic en **⚡ Completar Instalación y Configurar Sistema**.
|
||||||
|
|
||||||
|
4. **Inicia Sesión**:
|
||||||
|
Al finalizar, serás redirigido a la página de inicio de sesión `index.php?page=login`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 Licencia
|
||||||
|
|
||||||
|
Este proyecto se distribuye bajo la licencia open-source [MIT License](LICENSE).
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# 📝 sld-blocknotes - Système Moderne de Notes par Blocs et Gestion de Commandes
|
||||||
|
|
||||||
|
> **🌐 Langues disponibles**: [🇬🇧 English](README.md) | [🇮🇹 Italiano](README.it.md) | [🇪🇸 Español](README.es.md) | [🇫🇷 Français](README.fr.md)
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
Une application Web moderne et puissante, sans dépendance externe, pour organiser des notes structurées en blocs, des commandes de terminal, des extraits de code et des instructions. Développée en **Pure PHP**, **Vanilla CSS**, **Vanilla JS** et **MySQL/MariaDB**.
|
||||||
|
|
||||||
|
URL du Dépôt: [https://gitea.sld-server.org/sld-admin/sld-blocknotes](https://gitea.sld-server.org/sld-admin/sld-blocknotes)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Fonctionnalités Principales
|
||||||
|
|
||||||
|
- 📑 **Notes en Blocs et Sous-notes Structurées**:
|
||||||
|
- Organisez vos notes au sein de blocknotes personnalisés.
|
||||||
|
- Les notes contiennent un titre, une description et plusieurs sous-notes.
|
||||||
|
- **Notes Repliables**: Les notes sont réduites par défaut pour optimiser l'espace vertical, avec des boutons globaux **Tout Développer / Tout Réduire**.
|
||||||
|
|
||||||
|
- ⚡ **Sous-notes de Type Commande de Terminal**:
|
||||||
|
- Rendu différencié entre sous-notes de texte générique et commandes de terminal.
|
||||||
|
- **Bouton Copier en 1 Clic**: Copiez instantanément les commandes dans le presse-papiers avec retour visuel (`✓ Copié!`).
|
||||||
|
- Cadre style IDE/Terminal à haut contraste avec barre de défilement verticale.
|
||||||
|
|
||||||
|
- 🔍 **Recherche en Temps Réel**:
|
||||||
|
- Filtrez les titres des notes, descriptions, sous-notes et contenus des commandes en temps réel au sein du blocknote ouvert.
|
||||||
|
- Développe automatiquement les blocs correspondants pendant la recherche.
|
||||||
|
|
||||||
|
- 📥 / 💾 **Importation et Exportation JSON**:
|
||||||
|
- Téléchargez le fichier JSON de chaque blocknote en un clic (`export_blocknote.php`).
|
||||||
|
- Importateur JSON interactif compatible avec le format standard et l'ancien format tableau.
|
||||||
|
|
||||||
|
- 🌐 **Interface Multilingue (IT, EN, ES, FR)**:
|
||||||
|
- Localisation complète en **Italien 🇮🇹, Anglais 🇬🇧, Espagnol 🇪🇸 et Français 🇫🇷**.
|
||||||
|
- Sélecteur de langue rapide dans la barre de navigation pour les visiteurs et utilisateurs connectés.
|
||||||
|
- La langue préférée de chaque utilisateur est enregistrée dans son profil pour les futures connexions.
|
||||||
|
|
||||||
|
- 🏠 **Page d'Accueil Personnalisable**:
|
||||||
|
- Choisissez entre accéder directement au **Panneau Principal (Tous les Blocknotes)** ou ouvrir un **Blocknote par Défaut spécifique** lors de la connexion.
|
||||||
|
|
||||||
|
- 👑 **Rôle Unique de Propriétaire et Hiérarchie Avancée (`owner` > `admin` > `user`)**:
|
||||||
|
- **Compte Propriétaire Protégé**: Le compte Propriétaire initial (`owner`) ne peut être supprimé ni par un administrateur ni par lui-même.
|
||||||
|
- Les administrateurs standard ne peuvent ni rétrograder ni modifier le rôle du Propriétaire.
|
||||||
|
|
||||||
|
- ⚙️ **Personnalisation du Site (Exclusif Propriétaire)**:
|
||||||
|
- Modifiez le **Titre Global de l'Application**.
|
||||||
|
- Téléversez et validez un **Favicon Haute Résolution** (minimum 64x64px) avec aperçu en temps réel.
|
||||||
|
|
||||||
|
- 🚀 **Assistant de Configuration Initiale (`first_start.php`)**:
|
||||||
|
- Installateur automatique pour configurer les identifiants MySQL, créer les tables et enregistrer les premiers utilisateurs privilégiés (`owner` et `admin`).
|
||||||
|
- Entièrement multilingue dans les 4 langues.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Guide d'Installation
|
||||||
|
|
||||||
|
### Prérequis
|
||||||
|
- Serveur Web (Apache, Nginx, ou serveur CLI intégré de PHP)
|
||||||
|
- PHP 7.4 ou 8.x (avec extension `mysqli` activée)
|
||||||
|
- MySQL 5.7+ or MariaDB 10.x+
|
||||||
|
|
||||||
|
### Installation Étape par Étape
|
||||||
|
|
||||||
|
1. **Cloner le Dépôt**:
|
||||||
|
```bash
|
||||||
|
git clone https://gitea.sld-server.org/sld-admin/sld-blocknotes.git
|
||||||
|
cd sld-blocknotes
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Démarrer le Serveur Web**:
|
||||||
|
Vous pouvez utiliser le serveur CLI intégré de PHP:
|
||||||
|
```bash
|
||||||
|
php -S localhost:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Exécuter l'Assistant de Configuration**:
|
||||||
|
Ouvrez votre navigateur sur `http://localhost:8000/`.
|
||||||
|
Le système détectera le premier démarrage et ouvrira automatiquement `first_start.php`:
|
||||||
|
- Entrez les informations de votre **Base de Données MySQL** (Hôte, Nom, Utilisateur, Mot de passe).
|
||||||
|
- Configurez les identifiants du **Compte Propriétaire**.
|
||||||
|
- Configurez les identifiants du **Premier Compte Admin**.
|
||||||
|
- Cliquez sur **⚡ Terminer l'Installation et Configurer le Système**.
|
||||||
|
|
||||||
|
4. **Se Connecter**:
|
||||||
|
Une fois terminé, vous serez redirigé vers la page de connexion `index.php?page=login`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 Licence
|
||||||
|
|
||||||
|
Ce projet est distribué sous licence open-source [MIT License](LICENSE).
|
||||||
+144
@@ -0,0 +1,144 @@
|
|||||||
|
# 📝 sld-blocknotes - Sistema Moderno di Note a Blocchi & Gestione Comandi
|
||||||
|
|
||||||
|
> **🌐 Lingue disponibili**: [🇬🇧 English](README.md) | [🇮🇹 Italiano](README.it.md) | [🇪🇸 Español](README.es.md) | [🇫🇷 Français](README.fr.md)
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
Una potente applicazione Web moderna senza dipendenze esterne per organizzare note strutturate a blocchi, comandi di terminale, frammenti di codice ed istruzioni. Realizzata in **Pure PHP**, **Vanilla CSS**, **Vanilla JS** e **MySQL/MariaDB**.
|
||||||
|
|
||||||
|
URL Repository: [https://gitea.sld-server.org/sld-admin/sld-blocknotes](https://gitea.sld-server.org/sld-admin/sld-blocknotes)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Funzionalità Principali
|
||||||
|
|
||||||
|
- 📑 **Note a Blocchi & Sottonote Strutturate**:
|
||||||
|
- Organizza le tue note all'interno di blocknotes personalizzati.
|
||||||
|
- Le note contengono titolo, descrizione e sottonote multiple.
|
||||||
|
- **Note Richiudibili**: Le note sono ridotte di default per ottimizzare lo spazio verticale, con pulsanti globali **Espandi Tutte / Riduci Tutte**.
|
||||||
|
|
||||||
|
- ⚡ **Sottonote di Tipo Comando Terminale**:
|
||||||
|
- Rendering differenziato tra sottonote di testo generico e comandi di terminale.
|
||||||
|
- **Pulsante Copia con 1-Click**: Copia istantaneamente i comandi negli appunti con feedback visivo (`✓ Copiato!`).
|
||||||
|
- Riquadro stile IDE/Terminale ad alto contrasto con barra di scorrimento verticale ad altezza limitata.
|
||||||
|
|
||||||
|
- 🔍 **Ricerca in Tempo Reale**:
|
||||||
|
- Filtra titoli delle note, descrizioni, sottonote e contenuti dei comandi in tempo reale all'interno del blocknote aperto.
|
||||||
|
- Espande automaticamente i blocchi corrisposti durante la ricerca.
|
||||||
|
|
||||||
|
- 📥 / 💾 **Importazione ed Esportazione JSON**:
|
||||||
|
- Scarica il file JSON di ogni blocknote con un click (`export_blocknote.php`).
|
||||||
|
- Importatore JSON interattivo con supporto per il formato standard e vecchio formato array.
|
||||||
|
|
||||||
|
- 🌐 **Interfaccia Multilingua (IT, EN, ES, FR)**:
|
||||||
|
- Localizzazione completa in **Italiano 🇮🇹, English 🇬🇧, Español 🇪🇸 e Français 🇫🇷**.
|
||||||
|
- Selector di lingua rapido nella navbar per ospiti ed utenti connessi.
|
||||||
|
- La lingua preferita di ciascun utente viene salvata nel profilo DB per i futuri accessi.
|
||||||
|
|
||||||
|
- 🏠 **Pagina di Primo Accesso Personalizzabile**:
|
||||||
|
- Scegli se accedere direttamente alla **Pagina Principale (Tutti i Blocknotes)** oppure aprire un **Blocknote Predefinito specifico** al login.
|
||||||
|
|
||||||
|
- 👑 **Ruolo Unico Owner & Gerarchia Avanzata (`owner` > `admin` > `user`)**:
|
||||||
|
- **Account Owner Ineliminabile**: L'account Owner iniziale (`owner`) non può essere eliminato da nessun admin né da se stesso.
|
||||||
|
- Gli amministratori standard non possono declassare né modificare il ruolo dell'Owner.
|
||||||
|
|
||||||
|
- ⚙️ **Personalizzazione Sito (Riservato Owner)**:
|
||||||
|
- Modifica il **Titolo Globale dell'Applicazione**.
|
||||||
|
- Carica e valida la **Favicon ad Alta Risoluzione** (minimo 64x64px) con anteprima in tempo reale.
|
||||||
|
|
||||||
|
- 🚀 **Wizard di Setup Iniziale (`first_start.php`)**:
|
||||||
|
- Installatore automatico per configurare i dati di connessione MySQL, creare le tabelle ed impostare i primi utenti privilegiati (`owner` ed `admin`).
|
||||||
|
- Completamente multilingua nelle 4 lingue.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Guida all'Installazione
|
||||||
|
|
||||||
|
### Requisiti
|
||||||
|
- Server Web (Apache, Nginx, o PHP CLI server integrato)
|
||||||
|
- PHP 7.4 o 8.x (con estensione `mysqli` abilitata)
|
||||||
|
- MySQL 5.7+ o MariaDB 10.x+
|
||||||
|
|
||||||
|
### Installazione Passo-Passo
|
||||||
|
|
||||||
|
1. **Clona il Repository**:
|
||||||
|
```bash
|
||||||
|
git clone https://gitea.sld-server.org/sld-admin/sld-blocknotes.git
|
||||||
|
cd sld-blocknotes
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Avvia il Server Web**:
|
||||||
|
Puoi utilizzare il server integrato di PHP:
|
||||||
|
```bash
|
||||||
|
php -S localhost:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Esegui il Wizard di Setup**:
|
||||||
|
Apri il browser su `http://localhost:8000/`.
|
||||||
|
Il sistema rileverà il primo avvio ed aprirà automaticamente `first_start.php`:
|
||||||
|
- Inserisci i dati del tuo **Database MySQL** (Host, Nome Database, Utente, Password).
|
||||||
|
- Configura le credenziali dell'**Account Owner**.
|
||||||
|
- Configura le credenziali del **Primo Account Admin**.
|
||||||
|
- Clicca su **⚡ Completa Installazione & Configura Sistema**.
|
||||||
|
|
||||||
|
4. **Accedi ed Inizia**:
|
||||||
|
Al termine del setup verrai reindirizzato alla pagina di login `index.php?page=login`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Struttura del Progetto
|
||||||
|
|
||||||
|
```text
|
||||||
|
sld-blocknotes/
|
||||||
|
├── assets/
|
||||||
|
│ ├── css/
|
||||||
|
│ │ └── style.css # Design system Vanilla CSS e variabili HSL
|
||||||
|
│ └── js/
|
||||||
|
│ └── main.js # Vanilla JS per modali, note richiudibili, cerca e copia
|
||||||
|
├── blocknotes/ # Cartella di salvataggio JSON dei blocknotes
|
||||||
|
│ └── .gitkeep
|
||||||
|
├── config/
|
||||||
|
│ ├── constants.php # Definizione costanti di percorso
|
||||||
|
│ ├── db.php # Connessione MySQL, auto-migrazione e redirect installer
|
||||||
|
│ ├── functions_init.php # Router e sanificazione pagine
|
||||||
|
│ ├── init.php # Inizializzazione sistema
|
||||||
|
│ └── settings.php # Template configurazione credenziali DB
|
||||||
|
├── functions/
|
||||||
|
│ ├── auth.php # Autenticazione, permessi e protezioni Owner
|
||||||
|
│ ├── blocknotes.php # Engine CRUD JSON blocknotes ed importatore
|
||||||
|
│ ├── functions.php # Autoloader moduli funzioni
|
||||||
|
│ ├── lang.php # Dizionario di traduzione multilingua (IT, EN, ES, FR)
|
||||||
|
│ └── site_settings.php # Gestione titolo sito e favicon ad alta risoluzione
|
||||||
|
├── includes/
|
||||||
|
│ ├── head_links.php # Inclusioni CSS ed head globali
|
||||||
|
│ └── js_footer.php # Inclusioni script footer globali
|
||||||
|
├── pages/
|
||||||
|
│ ├── 404.php # Pagina non trovata
|
||||||
|
│ ├── admin_users.php # Gestione utenti admin ed impostazioni sito Owner
|
||||||
|
│ ├── db-error.php # Template errore database
|
||||||
|
│ ├── export_blocknote.php # Endpoint download JSON blocknote
|
||||||
|
│ ├── home.php # Dashboard principale dei blocknotes
|
||||||
|
│ ├── login.php # Pagina di login multilingua
|
||||||
|
│ ├── logout.php # Disconnessione sessione
|
||||||
|
│ ├── profile.php # Profilo utente, password, colori tema, lingua e landing page
|
||||||
|
│ └── view_blocknote.php # Vista dettaglio blocknote, note richiudibili e cerca
|
||||||
|
├── templates/
|
||||||
|
│ ├── footer.php # Template footer pagina
|
||||||
|
│ └── header.php # Header navbar, logo, badge utenti e selector lingua
|
||||||
|
├── .gitignore
|
||||||
|
├── first_start.php # Wizard di installazione guidata primo avvio
|
||||||
|
├── index.php # Entry point applicazione e router
|
||||||
|
├── README.md # Documentazione Principale in Inglese
|
||||||
|
├── README.it.md # Documentazione in Italiano
|
||||||
|
├── README.es.md # Documentazione in Spagnolo
|
||||||
|
└── README.fr.md # Documentazione in Francese
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 Licenza
|
||||||
|
|
||||||
|
Questo progetto è rilasciato sotto licenza open-source [MIT License](LICENSE).
|
||||||
@@ -1,163 +1,144 @@
|
|||||||
# SLD PHP Template
|
# 📝 sld-blocknotes - Modern Block-based Notes & Command Management System
|
||||||
|
|
||||||
A lightweight and modular PHP starter framework for building plain PHP websites without heavy dependencies.
|
> **🌐 Languages**: [🇬🇧 English](README.md) | [🇮🇹 Italiano](README.it.md) | [🇪🇸 Español](README.es.md) | [🇫🇷 Français](README.fr.md)
|
||||||
|
|
||||||
This project is designed to provide a clean structure, reusable components, and a simple initialization system to speed up development while keeping full control over the codebase.
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
A powerful, modern, zero-dependency Web application for organizing structured block notes, technical commands, code snippets, and instructions. Built with **Pure PHP**, **Vanilla CSS**, **Vanilla JS**, and **MySQL/MariaDB**.
|
||||||
|
|
||||||
|
Repository URL: [https://gitea.sld-server.org/sld-admin/sld-blocknotes](https://gitea.sld-server.org/sld-admin/sld-blocknotes)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 Features
|
## ✨ Key Features
|
||||||
|
|
||||||
- ⚡ Lightweight and fast (no external frameworks required)
|
- 📑 **Structured Block Notes & Subnotes**:
|
||||||
- 📁 Clean and scalable folder structure
|
- Organize notes into customizable blocknotes.
|
||||||
- 🔧 Centralized configuration system
|
- Notes contain titles, descriptions, and multiple subnotes.
|
||||||
- 🧩 Modular templates (header, footer, includes)
|
- **Collapsible Notes**: Collapsed by default to maximize vertical screen space, with global **Expand All / Collapse All** buttons.
|
||||||
- 🎨 Basic frontend setup (CSS variables, animations, JS helpers)
|
|
||||||
- 🛠 Ready-to-use initialization system
|
- ⚡ **Terminal Command Subnotes**:
|
||||||
- ❌ Built-in error pages (environment & database)
|
- Separate rendering for generic text subnotes vs terminal command subnotes.
|
||||||
|
- **1-Click Clipboard Copy**: Copy command snippets instantly with visual feedback (`✓ Copied!`).
|
||||||
|
- High-contrast dark IDE code box with max-height vertical scrolling.
|
||||||
|
|
||||||
|
- 🔍 **Real-Time Live Search**:
|
||||||
|
- Filter note titles, descriptions, subnote titles, and terminal command contents instantly inside the active blocknote view.
|
||||||
|
- Automatically expands matched note blocks during search.
|
||||||
|
|
||||||
|
- 📥 / 💾 **JSON Import & Export**:
|
||||||
|
- Download blocknote JSON files with one click (`export_blocknote.php`).
|
||||||
|
- Interactive JSON importer supporting both standard and legacy JSON formats.
|
||||||
|
|
||||||
|
- 🌐 **Multilingual Interface (IT, EN, ES, FR)**:
|
||||||
|
- Full interface localization in **Italian 🇮🇹, English 🇬🇧, Spanish 🇪🇸, and French 🇫🇷**.
|
||||||
|
- Quick navbar language selector for guests and logged-in users.
|
||||||
|
- Each user's language choice is saved to their database profile for future logins.
|
||||||
|
|
||||||
|
- 🏠 **Customizable User Landing Page**:
|
||||||
|
- Choose between launching directly into the **Main Dashboard (All Blocknotes)** or opening a **Specific Default Blocknote** upon login.
|
||||||
|
|
||||||
|
- 👑 **Single Owner Role & Advanced Hierarchy (`owner` > `admin` > `user`)**:
|
||||||
|
- **Immune Owner Account**: The initial Owner account (`owner`) cannot be deleted by any admin or by themselves.
|
||||||
|
- Owners cannot be demoted or altered by standard administrators.
|
||||||
|
|
||||||
|
- ⚙️ **Site Customization (Owner Exclusive)**:
|
||||||
|
- Change the **Global Application / Site Title**.
|
||||||
|
- Upload & validate a **High-Resolution Favicon** (min 64x64px) with real-time preview.
|
||||||
|
|
||||||
|
- 🚀 **First Start Setup Installer Wizard (`first_start.php`)**:
|
||||||
|
- Automated first-time setup wizard for configuring MySQL database credentials, creating database tables, and setting up the initial `owner` and `admin` accounts.
|
||||||
|
- Fully localized in all 4 languages.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📂 Project Structure
|
## 🚀 Quickstart & Installation
|
||||||
|
|
||||||
```
|
### Requirements
|
||||||
.
|
- Web Server (Apache, Nginx, or PHP built-in CLI server)
|
||||||
├── admin/
|
- PHP 7.4 or 8.x (with `mysqli` extension enabled)
|
||||||
|
- MySQL 5.7+ or MariaDB 10.x+
|
||||||
|
|
||||||
|
### Step-by-Step Installation
|
||||||
|
|
||||||
|
1. **Clone the Repository**:
|
||||||
|
```bash
|
||||||
|
git clone https://gitea.sld-server.org/sld-admin/sld-blocknotes.git
|
||||||
|
cd sld-blocknotes
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Start Web Server**:
|
||||||
|
You can serve the directory with your local Web server or use PHP's built-in server:
|
||||||
|
```bash
|
||||||
|
php -S localhost:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Run Automated Setup Wizard**:
|
||||||
|
Navigate to `http://localhost:8000/` in your browser.
|
||||||
|
The system will automatically detect first-time run and launch `first_start.php`:
|
||||||
|
- Enter your **MySQL Database Credentials** (Host, Database Name, User, Password).
|
||||||
|
- Configure credentials for the **Owner Account**.
|
||||||
|
- Configure credentials for the **First Admin Account**.
|
||||||
|
- Click **⚡ Complete Installation & Configure System**.
|
||||||
|
|
||||||
|
4. **Login & Enjoy**:
|
||||||
|
After setup completes, you will be redirected to `index.php?page=login` to sign in.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Directory Structure
|
||||||
|
|
||||||
|
```text
|
||||||
|
sld-blocknotes/
|
||||||
├── assets/
|
├── assets/
|
||||||
│ ├── css/
|
│ ├── css/
|
||||||
│ ├── img/
|
│ │ └── style.css # Vanilla CSS design system & HSL variables
|
||||||
│ └── js/
|
│ └── js/
|
||||||
|
│ └── main.js # Vanilla JS modals, collapsible notes, search & copy
|
||||||
|
├── blocknotes/ # Blocknotes JSON storage directory
|
||||||
|
│ └── .gitkeep
|
||||||
├── config/
|
├── config/
|
||||||
|
│ ├── constants.php # Path constants definition
|
||||||
|
│ ├── db.php # MySQL connection, auto-migration & installer redirect
|
||||||
|
│ ├── functions_init.php # Router & page sanitization
|
||||||
|
│ ├── init.php # System initialization
|
||||||
|
│ └── settings.php # Database credentials configuration template
|
||||||
├── functions/
|
├── functions/
|
||||||
|
│ ├── auth.php # Authentication, role permissions & owner protections
|
||||||
|
│ ├── blocknotes.php # Blocknote JSON CRUD engine & JSON importer
|
||||||
|
│ ├── functions.php # Function module autoloader
|
||||||
|
│ ├── lang.php # Multilingual translation dictionary (IT, EN, ES, FR)
|
||||||
|
│ └── site_settings.php # Site title & high-resolution favicon manager
|
||||||
├── includes/
|
├── includes/
|
||||||
|
│ ├── head_links.php # Global CSS & favicon head includes
|
||||||
|
│ └── js_footer.php # Global footer script includes
|
||||||
├── pages/
|
├── pages/
|
||||||
|
│ ├── 404.php # Not found page
|
||||||
|
│ ├── admin_users.php # Admin user management & Owner site settings
|
||||||
|
│ ├── db-error.php # Database error template
|
||||||
|
│ ├── export_blocknote.php # Blocknote JSON download endpoint
|
||||||
|
│ ├── home.php # Main blocknotes dashboard
|
||||||
|
│ ├── login.php # Multilingual login page
|
||||||
|
│ ├── logout.php # Session logout
|
||||||
|
│ ├── profile.php # User profile, password, theme colors, language & landing page
|
||||||
|
│ └── view_blocknote.php # Detail view for blocknotes, collapsible notes & search
|
||||||
├── templates/
|
├── templates/
|
||||||
├── themes/
|
│ ├── footer.php # Page footer template
|
||||||
├── public/
|
│ └── header.php # Navbar header, logo, user badges & quick language selector
|
||||||
├── index.php
|
├── .gitignore
|
||||||
└── README.md
|
├── first_start.php # Automated First Start Installation Wizard
|
||||||
|
├── index.php # Application entry point & router
|
||||||
|
├── README.md # Main English Documentation
|
||||||
|
├── README.it.md # Italian Documentation
|
||||||
|
├── README.es.md # Spanish Documentation
|
||||||
|
└── README.fr.md # French Documentation
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚙️ Configuration
|
## 📄 License
|
||||||
|
|
||||||
All core configuration is located inside the `config/` directory:
|
This project is open-source software available under the [MIT License](LICENSE).
|
||||||
|
|
||||||
- `init.php` → Application bootstrap
|
|
||||||
- `constants.php` → Global constants
|
|
||||||
- `settings.php` → App settings
|
|
||||||
- `db.php` → Database connection
|
|
||||||
- `functions_init.php` → Function loader
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧠 How It Works
|
|
||||||
|
|
||||||
1. The application starts from `index.php`
|
|
||||||
2. Core initialization is handled by the config system
|
|
||||||
3. Templates are loaded dynamically (`header.php`, `footer.php`)
|
|
||||||
4. Pages are rendered from the `pages/` directory
|
|
||||||
5. Custom logic can be added via the `functions/` folder
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎨 Frontend
|
|
||||||
|
|
||||||
The project includes a minimal frontend setup:
|
|
||||||
|
|
||||||
### CSS System
|
|
||||||
- Variables-based color system with multiple shades
|
|
||||||
- Basic layout styles
|
|
||||||
- Animation utilities
|
|
||||||
|
|
||||||
### JavaScript
|
|
||||||
- Simple helper functions like dynamic year rendering
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛠 Usage
|
|
||||||
|
|
||||||
1. Clone the repository:
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/your-username/sld-php-template.git
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Move it into your web server directory
|
|
||||||
|
|
||||||
3. Configure:
|
|
||||||
- Database (`config/db.php`)
|
|
||||||
- Environment settings (`config/settings.php`)
|
|
||||||
|
|
||||||
4. Open in browser:
|
|
||||||
```
|
|
||||||
http://localhost/your-project
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📄 Pages
|
|
||||||
|
|
||||||
- `home.php` → Main homepage
|
|
||||||
- `db-error.php` → Database error page
|
|
||||||
- `env-error.php` → Environment error page
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧩 Templates
|
|
||||||
|
|
||||||
Reusable layout components:
|
|
||||||
|
|
||||||
- `templates/header.php`
|
|
||||||
- `templates/footer.php`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 Status
|
|
||||||
|
|
||||||
🚧 This project is currently in development.
|
|
||||||
New features and improvements will be added progressively.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📌 Roadmap (Planned)
|
|
||||||
|
|
||||||
- [ ] Routing system
|
|
||||||
- [ ] Theme engine
|
|
||||||
- [ ] Admin panel structure
|
|
||||||
- [ ] CLI tools
|
|
||||||
- [ ] Environment (.env) support
|
|
||||||
- [ ] Basic authentication system
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🤝 Contributing
|
|
||||||
|
|
||||||
Contributions, ideas, and feedback are welcome.
|
|
||||||
Feel free to open issues or pull requests.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📜 License
|
|
||||||
|
|
||||||
This project is licensed under the MIT License.
|
|
||||||
See the `LICENSE` file for details.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 👤 Author
|
|
||||||
|
|
||||||
Created by Simone (SLD)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💡 Philosophy
|
|
||||||
|
|
||||||
This project aims to stay:
|
|
||||||
|
|
||||||
- Simple
|
|
||||||
- Transparent
|
|
||||||
- Framework-free
|
|
||||||
- Easy to extend
|
|
||||||
|
|
||||||
No magic, just clean PHP.
|
|
||||||
+751
-9
@@ -1,18 +1,760 @@
|
|||||||
html { scroll-behavior: smooth; }
|
/* ==========================================================================
|
||||||
*{ box-sizing: border-box; margin:0; padding:0;}
|
SYSTEM BLOCKNOTES - MAIN CSS (VANILLA CSS WITH HSL THEME VARIABLES)
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Fira+Code:wght@400;500&display=swap');
|
||||||
|
|
||||||
.main-nav{
|
* {
|
||||||
display:flex;
|
box-sizing: border-box;
|
||||||
width:100%;
|
margin: 0;
|
||||||
gap: 1rem;
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background-color: #0b0f19;
|
||||||
|
color: #f1f5f9;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--secCol14, #38bdf8);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--secCol18, #bae6fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- HEADER & NAVIGATION --- */
|
||||||
|
.navbar-header {
|
||||||
|
background: rgba(15, 23, 42, 0.85);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-container {
|
||||||
|
max-width: 1240px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0.85rem 1.5rem;
|
||||||
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: space-between;
|
||||||
list-style-type: none;
|
}
|
||||||
padding:1rem;
|
|
||||||
|
.brand-logo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.6rem;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.35rem;
|
||||||
|
color: #ffffff;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-logo .logo-icon {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-nav a {
|
.main-nav a {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
padding: 0.5rem 0.9rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav a:hover,
|
||||||
|
.main-nav a.active {
|
||||||
|
color: #ffffff;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-link {
|
||||||
|
color: var(--thiCol15, #c084fc) !important;
|
||||||
|
background: rgba(192, 132, 252, 0.1) !important;
|
||||||
|
border: 1px solid rgba(192, 132, 252, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-link:hover,
|
||||||
|
.admin-link.active {
|
||||||
|
background: rgba(192, 132, 252, 0.25) !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 0.35rem 0.75rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-admin {
|
||||||
|
background: rgba(234, 179, 8, 0.15);
|
||||||
|
border-color: rgba(234, 179, 8, 0.3);
|
||||||
|
color: #fde047;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-logout-btn {
|
||||||
|
color: #ef4444 !important;
|
||||||
|
background: rgba(239, 68, 68, 0.08) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-logout-btn:hover {
|
||||||
|
background: rgba(239, 68, 68, 0.2) !important;
|
||||||
|
color: #fca5a5 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- MAIN WRAPPER & FOOTER --- */
|
||||||
|
.main-wrapper {
|
||||||
|
max-width: 1240px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem 1.5rem;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-footer {
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
background: #090d16;
|
||||||
|
padding: 1.5rem 0;
|
||||||
|
margin-top: auto;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-container {
|
||||||
|
max-width: 1240px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- PAGE HEADERS & CARDS --- */
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title-group h1 {
|
||||||
|
font-size: 1.85rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ffffff;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title-group p {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: #131b2e;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- BUTTONS --- */
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.65rem 1.2rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 600;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
padding: 0.4rem 0.75rem;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--priCol10, #22c55e);
|
||||||
|
color: #042f13;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: var(--priCol12, #4ade80);
|
||||||
|
color: #042f13;
|
||||||
|
box-shadow: 0 0 15px rgba(34, 197, 94, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--secCol8, #0284c7);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: var(--secCol10, #0ea5e9);
|
||||||
|
color: #ffffff;
|
||||||
|
box-shadow: 0 0 15px rgba(14, 165, 233, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-dark {
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
color: #e2e8f0;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-dark:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: rgba(239, 68, 68, 0.15);
|
||||||
|
color: #f87171;
|
||||||
|
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover {
|
||||||
|
background: #ef4444;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- BLOCKNOTE GRID & CARDS --- */
|
||||||
|
.blocknotes-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blocknote-card {
|
||||||
|
background: #131b2e;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blocknote-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
border-color: rgba(255, 255, 255, 0.2);
|
||||||
|
box-shadow: 0 12px 35px rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blocknote-card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blocknote-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blocknote-owner-tag {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 0.25rem 0.6rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: rgba(14, 165, 233, 0.15);
|
||||||
|
color: #7dd3fc;
|
||||||
|
border: 1px solid rgba(14, 165, 233, 0.3);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blocknote-card-body {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #94a3b8;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blocknote-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blocknote-card-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- VIEW BLOCKNOTE DETAILED NOTE BLOCKS --- */
|
||||||
|
.notes-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-block {
|
||||||
|
background: #131b2e;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.25);
|
||||||
|
transition: all 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-header {
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
padding: 1.1rem 1.5rem;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-header:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-icon {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--secCol14, #38bdf8);
|
||||||
|
transition: transform 0.25s ease;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-block.collapsed .collapse-icon {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-block:not(.collapsed) .collapse-icon {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subnote-count-badge {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 0.2rem 0.6rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: rgba(255, 255, 255, 0.07);
|
||||||
|
color: #94a3b8;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-block.collapsed .note-header {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-block.collapsed .note-body {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-description-box {
|
||||||
|
padding: 1rem 1.5rem 0.5rem 1.5rem;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
color: #94a3b8;
|
||||||
|
border-bottom: 1px dashed rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-title-area h3 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ffffff;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subnotes-list {
|
||||||
|
padding: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subnote-card {
|
||||||
|
background: #1a243a;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.07);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.25rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subnote-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subnote-title-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subnote-title {
|
||||||
|
font-size: 1.05rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-badge {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-badge.text {
|
||||||
|
background: rgba(148, 163, 184, 0.15);
|
||||||
|
color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-badge.command {
|
||||||
|
background: rgba(234, 179, 8, 0.15);
|
||||||
|
color: #fde047;
|
||||||
|
border: 1px solid rgba(234, 179, 8, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subnote-content-text {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #cbd5e1;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
padding: 0.85rem 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
border-left: 3px solid var(--secCol10, #0ea5e9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- COMMAND BLOCK WITH COPY BUTTON --- */
|
||||||
|
.command-block-wrapper {
|
||||||
|
position: relative;
|
||||||
|
background: #050811;
|
||||||
|
border: 1px solid rgba(234, 179, 8, 0.35);
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5), inset 0 0 10px rgba(234, 179, 8, 0.03);
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-block-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: rgba(234, 179, 8, 0.12);
|
||||||
|
padding: 0.45rem 0.9rem;
|
||||||
|
border-bottom: 1px solid rgba(234, 179, 8, 0.2);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-family: 'Fira Code', 'Monaco', 'Consolas', monospace;
|
||||||
|
color: #fde047;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-badge-title {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: #ffffff;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||||
|
padding: 0.3rem 0.75rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn:hover {
|
||||||
|
background: #eab308;
|
||||||
|
color: #000000;
|
||||||
|
border-color: #eab308;
|
||||||
|
box-shadow: 0 0 10px rgba(234, 179, 8, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn.copied {
|
||||||
|
background: #22c55e !important;
|
||||||
|
color: #042f13 !important;
|
||||||
|
border-color: #22c55e !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-content-code {
|
||||||
|
font-family: 'Fira Code', 'Cascadia Code', 'Monaco', 'Consolas', monospace;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #4ade80;
|
||||||
|
background: #03060d;
|
||||||
|
padding: 0.85rem 1rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 320px;
|
||||||
|
white-space: pre;
|
||||||
|
margin: 0;
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-content-code code {
|
||||||
|
font-family: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- FORMS & INPUTS --- */
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #cbd5e1;
|
||||||
|
margin-bottom: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #090d16;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--secCol10, #0ea5e9);
|
||||||
|
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.form-control {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
select.form-control {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- MODAL --- */
|
||||||
|
.modal-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: rgba(0, 0, 0, 0.75);
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: all 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-backdrop.active {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-card {
|
||||||
|
background: #131b2e;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 16px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 540px;
|
||||||
|
padding: 1.75rem;
|
||||||
|
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
|
||||||
|
transform: translateY(20px);
|
||||||
|
transition: transform 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-backdrop.active .modal-card {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
padding-bottom: 0.75rem;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header h3 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close-btn:hover {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- ALERTS & NOTIFICATIONS --- */
|
||||||
|
.alert {
|
||||||
|
padding: 0.9rem 1.2rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
font-weight: 500;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
background: rgba(34, 197, 94, 0.15);
|
||||||
|
border: 1px solid rgba(34, 197, 94, 0.3);
|
||||||
|
color: #4ade80;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-danger {
|
||||||
|
background: rgba(239, 68, 68, 0.15);
|
||||||
|
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||||
|
color: #f87171;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- TABLES (ADMIN) --- */
|
||||||
|
.table-responsive {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-table th {
|
||||||
|
background: rgba(255, 255, 255, 0.04);
|
||||||
|
color: #cbd5e1;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 0.85rem 1rem;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-table td {
|
||||||
|
padding: 0.85rem 1rem;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-table tr:hover td {
|
||||||
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- COLOR PICKER PREVIEW --- */
|
||||||
|
.color-preview-box {
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #0f172a;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Empty state */
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3rem 1rem;
|
||||||
|
background: #131b2e;
|
||||||
|
border: 1px border-dash rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state-icon {
|
||||||
|
font-size: 3rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state h3 {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ffffff;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state p {
|
||||||
|
color: #94a3b8;
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto 1.5rem auto;
|
||||||
|
}
|
||||||
|
|||||||
+210
-8
@@ -1,9 +1,211 @@
|
|||||||
/* @ default js*/
|
/**
|
||||||
function yearNow(textlocation) {
|
* Blocknotes Application Main JavaScript (Vanilla JS)
|
||||||
//This is a function to substitutes a value into the current year.
|
*/
|
||||||
//Usually good for the footer.
|
|
||||||
var data = new Date();
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
var year;
|
|
||||||
year = data.getFullYear();
|
// --- 1. MODALS OPEN & CLOSE HANDLERS ---
|
||||||
document.querySelector(textlocation).replaceWith(year);
|
document.querySelectorAll('[data-modal-target]').forEach(function(trigger) {
|
||||||
|
trigger.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const modalId = this.getAttribute('data-modal-target');
|
||||||
|
const modal = document.getElementById(modalId);
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.add('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.modal-close-btn, .modal-close-trigger').forEach(function(closeBtn) {
|
||||||
|
closeBtn.addEventListener('click', function() {
|
||||||
|
const modal = this.closest('.modal-backdrop');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close modal when clicking on backdrop outside modal-card
|
||||||
|
document.querySelectorAll('.modal-backdrop').forEach(function(backdrop) {
|
||||||
|
backdrop.addEventListener('click', function(e) {
|
||||||
|
if (e.target === this) {
|
||||||
|
this.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- 2. LIVE HSL THEME COLOR PREVIEW (PROFILE PAGE) ---
|
||||||
|
const priInput = document.getElementById('theme_pri');
|
||||||
|
const secInput = document.getElementById('theme_sec');
|
||||||
|
const thiInput = document.getElementById('theme_thi');
|
||||||
|
|
||||||
|
if (priInput && secInput && thiInput) {
|
||||||
|
function updateLiveTheme() {
|
||||||
|
const priVal = priInput.value;
|
||||||
|
const secVal = secInput.value;
|
||||||
|
const thiVal = thiInput.value;
|
||||||
|
|
||||||
|
document.documentElement.style.setProperty('--pri', priVal);
|
||||||
|
document.documentElement.style.setProperty('--sec', secVal);
|
||||||
|
document.documentElement.style.setProperty('--thi', thiVal);
|
||||||
|
|
||||||
|
const priValDisp = document.getElementById('pri_val');
|
||||||
|
const secValDisp = document.getElementById('sec_val');
|
||||||
|
const thiValDisp = document.getElementById('thi_val');
|
||||||
|
if (priValDisp) priValDisp.textContent = priVal + '°';
|
||||||
|
if (secValDisp) secValDisp.textContent = secVal + '°';
|
||||||
|
if (thiValDisp) thiValDisp.textContent = thiVal + '°';
|
||||||
|
}
|
||||||
|
|
||||||
|
priInput.addEventListener('input', updateLiveTheme);
|
||||||
|
secInput.addEventListener('input', updateLiveTheme);
|
||||||
|
thiInput.addEventListener('input', updateLiveTheme);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy text to clipboard with fallback and localized feedback
|
||||||
|
*/
|
||||||
|
function copyToClipboard(btnElement, textToCopy) {
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
navigator.clipboard.writeText(textToCopy).then(() => {
|
||||||
|
handleCopySuccess(btnElement);
|
||||||
|
}).catch(() => {
|
||||||
|
fallbackCopyText(btnElement, textToCopy);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
fallbackCopyText(btnElement, textToCopy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fallbackCopyText(btnElement, textToCopy) {
|
||||||
|
let textArea = document.createElement("textarea");
|
||||||
|
textArea.value = textToCopy;
|
||||||
|
textArea.style.position = "fixed";
|
||||||
|
textArea.style.left = "-999999px";
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.focus();
|
||||||
|
textArea.select();
|
||||||
|
try {
|
||||||
|
document.execCommand('copy');
|
||||||
|
handleCopySuccess(btnElement);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Fallback copy failed', err);
|
||||||
|
}
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCopySuccess(btnElement) {
|
||||||
|
if (!btnElement) return;
|
||||||
|
const originalHTML = btnElement.innerHTML;
|
||||||
|
const copiedLabel = btnElement.getAttribute('data-copied-text') || '✓ Copiato!';
|
||||||
|
btnElement.innerHTML = copiedLabel;
|
||||||
|
btnElement.classList.add('copied');
|
||||||
|
setTimeout(() => {
|
||||||
|
btnElement.innerHTML = originalHTML;
|
||||||
|
btnElement.classList.remove('copied');
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle collapse state for a specific note block
|
||||||
|
*/
|
||||||
|
function toggleNoteCollapse(noteId) {
|
||||||
|
const noteBlock = document.getElementById('note-block-' + noteId);
|
||||||
|
if (noteBlock) {
|
||||||
|
noteBlock.classList.toggle('collapsed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand all note blocks on page
|
||||||
|
*/
|
||||||
|
function expandAllNotes() {
|
||||||
|
document.querySelectorAll('.note-block').forEach(function(nb) {
|
||||||
|
nb.classList.remove('collapsed');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collapse all note blocks on page
|
||||||
|
*/
|
||||||
|
function collapseAllNotes() {
|
||||||
|
document.querySelectorAll('.note-block').forEach(function(nb) {
|
||||||
|
nb.classList.add('collapsed');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter notes and subnotes within the current blocknote in real-time
|
||||||
|
*/
|
||||||
|
function filterCurrentBlocknoteNotes(query) {
|
||||||
|
const term = query.toLowerCase().trim();
|
||||||
|
const clearBtn = document.getElementById('clear-search-btn');
|
||||||
|
const noResults = document.getElementById('no-search-results');
|
||||||
|
const noteBlocks = document.querySelectorAll('.note-block');
|
||||||
|
|
||||||
|
if (clearBtn) {
|
||||||
|
clearBtn.style.display = term ? 'inline-flex' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalMatches = 0;
|
||||||
|
|
||||||
|
noteBlocks.forEach(function(noteBlock) {
|
||||||
|
if (!term) {
|
||||||
|
// Reset search
|
||||||
|
noteBlock.style.display = '';
|
||||||
|
noteBlock.classList.add('collapsed');
|
||||||
|
noteBlock.querySelectorAll('.subnote-card').forEach(function(sub) {
|
||||||
|
sub.style.display = '';
|
||||||
|
});
|
||||||
|
totalMatches++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const noteTitle = (noteBlock.querySelector('.note-title-wrapper h3')?.innerText || '').toLowerCase();
|
||||||
|
const noteDesc = (noteBlock.querySelector('.note-description-box p')?.innerText || '').toLowerCase();
|
||||||
|
const noteTitleMatch = noteTitle.includes(term) || noteDesc.includes(term);
|
||||||
|
|
||||||
|
let subnoteMatchCount = 0;
|
||||||
|
const subnoteCards = noteBlock.querySelectorAll('.subnote-card');
|
||||||
|
|
||||||
|
subnoteCards.forEach(function(subCard) {
|
||||||
|
const subTitle = (subCard.querySelector('.subnote-title')?.innerText || '').toLowerCase();
|
||||||
|
const subContent = (subCard.querySelector('.subnote-content-text, .command-code')?.innerText || '').toLowerCase();
|
||||||
|
|
||||||
|
if (noteTitleMatch || subTitle.includes(term) || subContent.includes(term)) {
|
||||||
|
subCard.style.display = '';
|
||||||
|
subnoteMatchCount++;
|
||||||
|
} else {
|
||||||
|
subCard.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (noteTitleMatch || subnoteMatchCount > 0) {
|
||||||
|
noteBlock.style.display = '';
|
||||||
|
noteBlock.classList.remove('collapsed'); // Auto expand on match
|
||||||
|
totalMatches++;
|
||||||
|
} else {
|
||||||
|
noteBlock.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (noResults) {
|
||||||
|
if (term && totalMatches === 0) {
|
||||||
|
noResults.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
noResults.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear search input and restore view
|
||||||
|
*/
|
||||||
|
function clearNoteSearch() {
|
||||||
|
const input = document.getElementById('note-search-input');
|
||||||
|
if (input) {
|
||||||
|
input.value = '';
|
||||||
|
filterCurrentBlocknoteNotes('');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Keep blocknotes directory tracked in git while empty
|
||||||
@@ -54,8 +54,7 @@
|
|||||||
$full_path = $const_before . $const_value;
|
$full_path = $const_before . $const_value;
|
||||||
|
|
||||||
if (!is_dir($full_path) && !is_file($full_path)) {
|
if (!is_dir($full_path) && !is_file($full_path)) {
|
||||||
debugStatusMes("The folder/file declared into '$const_name' constant is missing or not accessible. Please check it.", "High");
|
@mkdir($full_path, 0777, true);
|
||||||
die;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!defined($const_name)) {
|
if (!defined($const_name)) {
|
||||||
|
|||||||
+95
-12
@@ -1,21 +1,104 @@
|
|||||||
<?php
|
<?php
|
||||||
// Exceptions PHP-MSQLI
|
// Exceptions PHP-MYSQLI
|
||||||
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
|
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
|
||||||
|
|
||||||
try {
|
// Check if first_start.php installer file exists and database setup is needed
|
||||||
// Create Connection
|
$first_start_path = ROOT_DIR . "first_start.php";
|
||||||
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
|
if (file_exists($first_start_path) && basename($_SERVER['SCRIPT_FILENAME'] ?? '') !== 'first_start.php') {
|
||||||
|
// Check connection first
|
||||||
// Connections Successfully
|
try {
|
||||||
if(DB_DEBUG == "yes"){
|
$test_conn = @new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
|
||||||
echo "Connected successfully";
|
if ($test_conn->connect_error) {
|
||||||
|
header("Location: first_start.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$test_conn->close();
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
header("Location: first_start.php");
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create Connection to MySQL host first (in case DB doesn't exist yet)
|
||||||
|
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS);
|
||||||
|
|
||||||
|
// Create Database if not exists
|
||||||
|
$db_name_escaped = str_replace('`', '``', DB_NAME);
|
||||||
|
$conn->query("CREATE DATABASE IF NOT EXISTS `$db_name_escaped` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||||
|
$conn->select_db(DB_NAME);
|
||||||
|
|
||||||
|
// Ensure BLOCKNOTES_FOLD directory exists
|
||||||
|
if (!is_dir(BLOCKNOTES_FOLD)) {
|
||||||
|
@mkdir(BLOCKNOTES_FOLD, 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-create users table if missing (with ENUM containing 'owner')
|
||||||
|
$sql_table = "CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
username VARCHAR(50) NOT NULL UNIQUE,
|
||||||
|
password VARCHAR(255) NOT NULL,
|
||||||
|
role ENUM('owner', 'admin', 'user') NOT NULL DEFAULT 'user',
|
||||||
|
theme_pri INT DEFAULT 98,
|
||||||
|
theme_sec INT DEFAULT 207,
|
||||||
|
theme_thi INT DEFAULT 280,
|
||||||
|
language VARCHAR(5) NOT NULL DEFAULT 'it',
|
||||||
|
default_blocknote VARCHAR(100) NOT NULL DEFAULT '',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
|
||||||
|
$conn->query($sql_table);
|
||||||
|
|
||||||
|
// Auto-migrate role column ENUM if missing 'owner'
|
||||||
|
$conn->query("ALTER TABLE users MODIFY COLUMN role ENUM('owner', 'admin', 'user') NOT NULL DEFAULT 'user'");
|
||||||
|
|
||||||
|
// Auto-migrate columns if missing in existing table
|
||||||
|
$check_lang = $conn->query("SHOW COLUMNS FROM users LIKE 'language'");
|
||||||
|
if ($check_lang->num_rows === 0) {
|
||||||
|
$conn->query("ALTER TABLE users ADD COLUMN language VARCHAR(5) NOT NULL DEFAULT 'it'");
|
||||||
|
}
|
||||||
|
|
||||||
|
$check_def_bn = $conn->query("SHOW COLUMNS FROM users LIKE 'default_blocknote'");
|
||||||
|
if ($check_def_bn->num_rows === 0) {
|
||||||
|
$conn->query("ALTER TABLE users ADD COLUMN default_blocknote VARCHAR(100) NOT NULL DEFAULT ''");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-create site_settings table
|
||||||
|
$sql_settings = "CREATE TABLE IF NOT EXISTS site_settings (
|
||||||
|
setting_key VARCHAR(50) PRIMARY KEY,
|
||||||
|
setting_value TEXT
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
|
||||||
|
$conn->query($sql_settings);
|
||||||
|
|
||||||
|
// Seed default site settings if empty
|
||||||
|
$conn->query("INSERT IGNORE INTO site_settings (setting_key, setting_value) VALUES ('site_title', 'sld-blocknotes')");
|
||||||
|
$conn->query("INSERT IGNORE INTO site_settings (setting_key, setting_value) VALUES ('site_favicon', 'assets/img/favicon.png')");
|
||||||
|
|
||||||
|
// Seed default owner account if table is empty or ensure admin user is owner if no owner exists
|
||||||
|
$res = $conn->query("SELECT COUNT(*) AS total FROM users");
|
||||||
|
$row = $res->fetch_assoc();
|
||||||
|
if ((int)$row['total'] === 0) {
|
||||||
|
$default_pass = password_hash("admin123", PASSWORD_DEFAULT);
|
||||||
|
$admin_user = "admin";
|
||||||
|
$stmt = $conn->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, 'owner')");
|
||||||
|
$stmt->bind_param("ss", $admin_user, $default_pass);
|
||||||
|
$stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
} else {
|
||||||
|
// Check if an owner exists, if not, promote the first user to owner
|
||||||
|
$check_owner = $conn->query("SELECT COUNT(*) AS total_owners FROM users WHERE role = 'owner'");
|
||||||
|
$owner_row = $check_owner->fetch_assoc();
|
||||||
|
if ((int)$owner_row['total_owners'] === 0) {
|
||||||
|
$conn->query("UPDATE users SET role = 'owner' ORDER BY id ASC LIMIT 1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (mysqli_sql_exception $e) {
|
} catch (mysqli_sql_exception $e) {
|
||||||
// Access Deny
|
if (file_exists($first_start_path) && basename($_SERVER['SCRIPT_FILENAME'] ?? '') !== 'first_start.php') {
|
||||||
|
header("Location: first_start.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
include(PAGES_FOLD . "db-error.php");
|
include(PAGES_FOLD . "db-error.php");
|
||||||
debugStatusMes("Database connection failed.", "High");
|
debugStatusMes("Database setup error: " . $e->getMessage(), "High");
|
||||||
|
|
||||||
|
|
||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
@@ -22,7 +22,7 @@ function get_current_page() {
|
|||||||
$page = $_GET["page"] ?? "home";
|
$page = $_GET["page"] ?? "home";
|
||||||
|
|
||||||
$page = strtolower(trim($page));
|
$page = strtolower(trim($page));
|
||||||
$page = preg_replace("/[^a-z0-9\-]/", "", $page);
|
$page = preg_replace("/[^a-z0-9\-_]/", "", $page);
|
||||||
|
|
||||||
if ($page === "") {
|
if ($page === "") {
|
||||||
$page = "home";
|
$page = "home";
|
||||||
|
|||||||
+7
-5
@@ -5,12 +5,14 @@
|
|||||||
switch($env){
|
switch($env){
|
||||||
/*------ DON'T TOUCH THIS PART ------ */
|
/*------ DON'T TOUCH THIS PART ------ */
|
||||||
case "generic":
|
case "generic":
|
||||||
define('WEB_ROOT', $_SERVER['DOCUMENT_ROOT']);
|
$doc_root = !empty($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : dirname(__DIR__);
|
||||||
define('DOMAIN', $_SERVER['HTTP_HOST']);
|
define('WEB_ROOT', $doc_root);
|
||||||
|
$host = !empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost';
|
||||||
|
define('DOMAIN', $host);
|
||||||
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https://' : 'http://';
|
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https://' : 'http://';
|
||||||
define('FULL_DOMAIN', $protocol . $_SERVER['HTTP_HOST']);
|
define('FULL_DOMAIN', $protocol . DOMAIN);
|
||||||
define("ROOT_URL", "http://" . DOMAIN );
|
define("ROOT_URL", $protocol . DOMAIN );
|
||||||
define("ROOT_DIR", WEB_ROOT . "/");
|
define("ROOT_DIR", rtrim(WEB_ROOT, '/') . "/");
|
||||||
break;
|
break;
|
||||||
/* ----------------------------------- */
|
/* ----------------------------------- */
|
||||||
default:
|
default:
|
||||||
|
|||||||
+6
-6
@@ -1,15 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
// -- GENERAL SETTING --
|
// -- GENERAL SETTING --
|
||||||
$debug="on";
|
$debug="off";
|
||||||
$php_debug="on";
|
$php_debug="off";
|
||||||
|
|
||||||
// --- DB SETTINGS ---
|
// --- DB SETTINGS ---
|
||||||
$db_needed="no";
|
$db_needed="yes";
|
||||||
$db_debug="yes";
|
$db_debug="yes";
|
||||||
$db_host="localhost";
|
$db_host="localhost";
|
||||||
$db_name="database-name";
|
$db_name="sld_blocknotes_db";
|
||||||
$db_user="database-user";
|
$db_user="root";
|
||||||
$db_pass="database-user-password";
|
$db_pass="";
|
||||||
|
|
||||||
//-------------------------------------------------------------------------------------
|
//-------------------------------------------------------------------------------------
|
||||||
// --- DON'T CHANGE ALL THE CODE BELOW!!! ---
|
// --- DON'T CHANGE ALL THE CODE BELOW!!! ---
|
||||||
|
|||||||
+320
@@ -0,0 +1,320 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Blocknotes System - First Start Setup Installer Wizard (Multilingual)
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define ROOT_DIR and include lang helper
|
||||||
|
define("ROOT_DIR", __DIR__ . "/");
|
||||||
|
require_once(__DIR__ . "/functions/lang.php");
|
||||||
|
|
||||||
|
// Handle language selection
|
||||||
|
if (isset($_GET['set_lang'])) {
|
||||||
|
set_active_lang($_GET['set_lang']);
|
||||||
|
header("Location: first_start.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$current_lang = get_current_lang();
|
||||||
|
|
||||||
|
$msg_error = "";
|
||||||
|
$msg_success = "";
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$db_host = trim($_POST['db_host'] ?? 'localhost');
|
||||||
|
$db_name = trim($_POST['db_name'] ?? 'sld_blocknotes_db');
|
||||||
|
$db_user = trim($_POST['db_user'] ?? '');
|
||||||
|
$db_pass = $_POST['db_pass'] ?? '';
|
||||||
|
|
||||||
|
$owner_user = trim($_POST['owner_user'] ?? '');
|
||||||
|
$owner_pass = $_POST['owner_pass'] ?? '';
|
||||||
|
|
||||||
|
$admin_user = trim($_POST['admin_user'] ?? '');
|
||||||
|
$admin_pass = $_POST['admin_pass'] ?? '';
|
||||||
|
|
||||||
|
if (empty($db_host) || empty($db_name) || empty($db_user)) {
|
||||||
|
$msg_error = __('err_setup_db_fields');
|
||||||
|
} elseif (empty($owner_user) || empty($owner_pass)) {
|
||||||
|
$msg_error = __('err_setup_owner_fields');
|
||||||
|
} elseif (empty($admin_user) || empty($admin_pass)) {
|
||||||
|
$msg_error = __('err_setup_admin_fields');
|
||||||
|
} elseif (strtolower($owner_user) === strtolower($admin_user)) {
|
||||||
|
$msg_error = __('err_setup_same_user');
|
||||||
|
} else {
|
||||||
|
mysqli_report(MYSQLI_REPORT_OFF);
|
||||||
|
$conn = @new mysqli($db_host, $db_user, $db_pass);
|
||||||
|
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
$msg_error = "Connessione MySQL fallita: " . $conn->connect_error;
|
||||||
|
} else {
|
||||||
|
// Create database
|
||||||
|
$db_name_escaped = str_replace('`', '``', $db_name);
|
||||||
|
if (!$conn->query("CREATE DATABASE IF NOT EXISTS `$db_name_escaped` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")) {
|
||||||
|
$msg_error = "Impossibile creare il database '$db_name': " . $conn->error;
|
||||||
|
} else {
|
||||||
|
$conn->select_db($db_name);
|
||||||
|
|
||||||
|
// Create users table
|
||||||
|
$sql_users = "CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
username VARCHAR(50) NOT NULL UNIQUE,
|
||||||
|
password VARCHAR(255) NOT NULL,
|
||||||
|
role ENUM('owner', 'admin', 'user') NOT NULL DEFAULT 'user',
|
||||||
|
theme_pri INT DEFAULT 98,
|
||||||
|
theme_sec INT DEFAULT 207,
|
||||||
|
theme_thi INT DEFAULT 280,
|
||||||
|
language VARCHAR(5) NOT NULL DEFAULT 'it',
|
||||||
|
default_blocknote VARCHAR(100) NOT NULL DEFAULT '',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
|
||||||
|
$conn->query($sql_users);
|
||||||
|
|
||||||
|
// Create site_settings table
|
||||||
|
$sql_settings = "CREATE TABLE IF NOT EXISTS site_settings (
|
||||||
|
setting_key VARCHAR(50) PRIMARY KEY,
|
||||||
|
setting_value TEXT
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
|
||||||
|
$conn->query($sql_settings);
|
||||||
|
|
||||||
|
// Insert site_settings default values
|
||||||
|
$conn->query("INSERT IGNORE INTO site_settings (setting_key, setting_value) VALUES ('site_title', 'sld-blocknotes')");
|
||||||
|
$conn->query("INSERT IGNORE INTO site_settings (setting_key, setting_value) VALUES ('site_favicon', 'assets/img/favicon.png')");
|
||||||
|
|
||||||
|
// Create Owner account
|
||||||
|
$hash_owner = password_hash($owner_pass, PASSWORD_DEFAULT);
|
||||||
|
$stmt = $conn->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, 'owner') ON DUPLICATE KEY UPDATE password = VALUES(password), role = 'owner'");
|
||||||
|
$stmt->bind_param("ss", $owner_user, $hash_owner);
|
||||||
|
$stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
// Create Admin account
|
||||||
|
$hash_admin = password_hash($admin_pass, PASSWORD_DEFAULT);
|
||||||
|
$stmt = $conn->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, 'admin') ON DUPLICATE KEY UPDATE password = VALUES(password), role = 'admin'");
|
||||||
|
$stmt->bind_param("ss", $admin_user, $hash_admin);
|
||||||
|
$stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
// Create blocknotes directory if missing
|
||||||
|
$blocknotes_dir = __DIR__ . "/blocknotes/";
|
||||||
|
if (!is_dir($blocknotes_dir)) {
|
||||||
|
@mkdir($blocknotes_dir, 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update config/settings.php file
|
||||||
|
$settings_file_content = '<?php
|
||||||
|
// -- GENERAL SETTING --
|
||||||
|
$debug="off";
|
||||||
|
$php_debug="off";
|
||||||
|
|
||||||
|
// --- DB SETTINGS ---
|
||||||
|
$db_needed="yes";
|
||||||
|
$db_debug="yes";
|
||||||
|
$db_host="' . addslashes($db_host) . '";
|
||||||
|
$db_name="' . addslashes($db_name) . '";
|
||||||
|
$db_user="' . addslashes($db_user) . '";
|
||||||
|
$db_pass="' . addslashes($db_pass) . '";
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------
|
||||||
|
// --- DON\'T CHANGE ALL THE CODE BELOW!!! ---
|
||||||
|
//-------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// --- GENERAL SECTION CONFIGURATION
|
||||||
|
if ($debug == "on"){ define("DEBUG",true);} else { define("DEBUG", false); }
|
||||||
|
|
||||||
|
if ($php_debug == "on") {
|
||||||
|
ini_set(\'display_errors\', 1);
|
||||||
|
ini_set(\'display_startup_errors\', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
} else {
|
||||||
|
ini_set(\'display_errors\', 0);
|
||||||
|
error_reporting(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- DB SECTION CONFIGURATION
|
||||||
|
if ($db_needed == "yes"){
|
||||||
|
define("DB_HOST", $db_host);
|
||||||
|
define("DB_USER", $db_user);
|
||||||
|
define("DB_PASS", $db_pass);
|
||||||
|
define("DB_NAME", $db_name);
|
||||||
|
define("DB_DEBUG", $db_debug);
|
||||||
|
require_once(CONFIG_FOLD . "db.php");
|
||||||
|
}
|
||||||
|
?>';
|
||||||
|
file_put_contents(__DIR__ . "/config/settings.php", $settings_file_content);
|
||||||
|
|
||||||
|
// Rename first_start.php to first_start.php.completed to prevent installer re-entry
|
||||||
|
@rename(__FILE__, __DIR__ . "/first_start.php.completed");
|
||||||
|
|
||||||
|
// Redirect to login page
|
||||||
|
header("Location: index.php?page=login&installed=1");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="<?= htmlspecialchars($current_lang) ?>">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title><?= __('setup_title') ?> - sld-blocknotes</title>
|
||||||
|
<link rel="stylesheet" href="assets/css/style.css">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: radial-gradient(circle at top left, #0f172a, #020617);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
color: #f8fafc;
|
||||||
|
}
|
||||||
|
.setup-container {
|
||||||
|
max-width: 680px;
|
||||||
|
width: 100%;
|
||||||
|
background: rgba(15, 23, 42, 0.75);
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 2.5rem;
|
||||||
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
.setup-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.setup-header h1 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #fff;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.setup-header p {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
.form-section-title {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #7dd3fc;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding-bottom: 0.4rem;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
.grid-2 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
@media (max-width: 580px) {
|
||||||
|
.grid-2 { grid-template-columns: 1fr; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="setup-container">
|
||||||
|
|
||||||
|
<!-- LANGUAGE SELECTOR -->
|
||||||
|
<div style="display: flex; justify-content: flex-end; margin-bottom: 1rem;">
|
||||||
|
<select onchange="location.href='first_start.php?set_lang=' + this.value;" style="background: rgba(0,0,0,0.4); color: #fff; border: 1px solid rgba(255,255,255,0.2); border-radius: 6px; padding: 0.35rem 0.6rem; font-size: 0.85rem; cursor: pointer;">
|
||||||
|
<option value="it" <?= ($current_lang === 'it') ? 'selected' : '' ?>>🇮🇹 IT</option>
|
||||||
|
<option value="en" <?= ($current_lang === 'en') ? 'selected' : '' ?>>🇬🇧 EN</option>
|
||||||
|
<option value="es" <?= ($current_lang === 'es') ? 'selected' : '' ?>>🇪🇸 ES</option>
|
||||||
|
<option value="fr" <?= ($current_lang === 'fr') ? 'selected' : '' ?>>🇫🇷 FR</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setup-header">
|
||||||
|
<h1><?= __('setup_title') ?></h1>
|
||||||
|
<p><?= __('setup_subtitle') ?></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!empty($msg_error)): ?>
|
||||||
|
<div class="alert alert-danger" style="background: rgba(239, 68, 68, 0.2); border: 1px solid rgba(239, 68, 68, 0.4); color: #fca5a5; padding: 0.85rem 1rem; border-radius: 8px; margin-bottom: 1.5rem; font-size: 0.9rem;">
|
||||||
|
⚠️ <?= htmlspecialchars($msg_error) ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="first_start.php" method="POST">
|
||||||
|
|
||||||
|
<!-- SEC 1: DATABASE -->
|
||||||
|
<div class="form-section-title">
|
||||||
|
<span>🗄️</span> <?= __('setup_sec1_title') ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="db_host"><?= __('setup_db_host') ?></label>
|
||||||
|
<input type="text" name="db_host" id="db_host" class="form-control" value="<?= htmlspecialchars($_POST['db_host'] ?? 'localhost') ?>" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="db_name"><?= __('setup_db_name') ?></label>
|
||||||
|
<input type="text" name="db_name" id="db_name" class="form-control" value="<?= htmlspecialchars($_POST['db_name'] ?? 'sld_blocknotes_db') ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="db_user"><?= __('setup_db_user') ?></label>
|
||||||
|
<input type="text" name="db_user" id="db_user" class="form-control" value="<?= htmlspecialchars($_POST['db_user'] ?? 'simone') ?>" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="db_pass"><?= __('setup_db_pass') ?></label>
|
||||||
|
<input type="password" name="db_pass" id="db_pass" class="form-control" placeholder="<?= htmlspecialchars(__('setup_db_pass_placeholder')) ?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SEC 2: OWNER ACCOUNT -->
|
||||||
|
<div class="form-section-title" style="color: #fde047;">
|
||||||
|
<span>👑</span> <?= __('setup_sec2_title') ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="owner_user"><?= __('setup_owner_user') ?></label>
|
||||||
|
<input type="text" name="owner_user" id="owner_user" class="form-control" value="<?= htmlspecialchars($_POST['owner_user'] ?? 'owner') ?>" required placeholder="owner">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="owner_pass"><?= __('setup_owner_pass') ?></label>
|
||||||
|
<input type="password" name="owner_pass" id="owner_pass" class="form-control" required placeholder="<?= htmlspecialchars(__('setup_owner_pass_placeholder')) ?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SEC 3: ADMIN ACCOUNT -->
|
||||||
|
<div class="form-section-title" style="color: #a7f3d0;">
|
||||||
|
<span>🛡️</span> <?= __('setup_sec3_title') ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="admin_user"><?= __('setup_admin_user') ?></label>
|
||||||
|
<input type="text" name="admin_user" id="admin_user" class="form-control" value="<?= htmlspecialchars($_POST['admin_user'] ?? 'admin') ?>" required placeholder="admin">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="admin_pass"><?= __('setup_admin_pass') ?></label>
|
||||||
|
<input type="password" name="admin_pass" id="admin_pass" class="form-control" required placeholder="<?= htmlspecialchars(__('setup_admin_pass_placeholder')) ?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 2rem;">
|
||||||
|
<button type="submit" class="btn btn-primary" style="width: 100%; justify-content: center; padding: 0.9rem; font-size: 1.05rem; font-weight: 700;">
|
||||||
|
<?= __('setup_btn_submit') ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Authentication and User Management Helper Functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a user is logged in.
|
||||||
|
*/
|
||||||
|
function is_logged_in() {
|
||||||
|
return isset($_SESSION['user']) && !empty($_SESSION['user']['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current logged in user details.
|
||||||
|
*/
|
||||||
|
function current_user() {
|
||||||
|
return $_SESSION['user'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if logged in user is owner.
|
||||||
|
*/
|
||||||
|
function is_owner() {
|
||||||
|
$user = current_user();
|
||||||
|
return $user && ($user['role'] === 'owner');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if logged in user is admin (or owner).
|
||||||
|
*/
|
||||||
|
function is_admin() {
|
||||||
|
$user = current_user();
|
||||||
|
return $user && ($user['role'] === 'admin' || $user['role'] === 'owner');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Require login to access a page.
|
||||||
|
*/
|
||||||
|
function require_login() {
|
||||||
|
if (!is_logged_in()) {
|
||||||
|
header("Location: index.php?page=login");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Require admin role to access a page.
|
||||||
|
*/
|
||||||
|
function require_admin() {
|
||||||
|
require_login();
|
||||||
|
if (!is_admin()) {
|
||||||
|
header("Location: index.php?page=home");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticate user with username and password.
|
||||||
|
*/
|
||||||
|
function login_user($username, $password) {
|
||||||
|
global $conn;
|
||||||
|
|
||||||
|
$username = trim($username);
|
||||||
|
if (empty($username) || empty($password)) {
|
||||||
|
return ['success' => false, 'message' => 'Inserisci sia username che password.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("SELECT id, username, password, role, theme_pri, theme_sec, theme_thi, language, default_blocknote FROM users WHERE username = ? LIMIT 1");
|
||||||
|
$stmt->bind_param("s", $username);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
if ($row = $result->fetch_assoc()) {
|
||||||
|
if (password_verify($password, $row['password'])) {
|
||||||
|
$_SESSION['user'] = [
|
||||||
|
'id' => $row['id'],
|
||||||
|
'username' => $row['username'],
|
||||||
|
'role' => $row['role'],
|
||||||
|
'theme_pri' => $row['theme_pri'] ?? 98,
|
||||||
|
'theme_sec' => $row['theme_sec'] ?? 207,
|
||||||
|
'theme_thi' => $row['theme_thi'] ?? 280,
|
||||||
|
'language' => $row['language'] ?? 'it',
|
||||||
|
'default_blocknote' => $row['default_blocknote'] ?? ''
|
||||||
|
];
|
||||||
|
$stmt->close();
|
||||||
|
return ['success' => true];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$stmt->close();
|
||||||
|
return ['success' => false, 'message' => 'Username o password errati.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout current user.
|
||||||
|
*/
|
||||||
|
function logout_user() {
|
||||||
|
$_SESSION = array();
|
||||||
|
if (ini_get("session.use_cookies")) {
|
||||||
|
$params = session_get_cookie_params();
|
||||||
|
setcookie(session_name(), '', time() - 42000,
|
||||||
|
$params["path"], $params["domain"],
|
||||||
|
$params["secure"], $params["httponly"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
session_destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update user password.
|
||||||
|
*/
|
||||||
|
function update_user_password($user_id, $new_password) {
|
||||||
|
global $conn;
|
||||||
|
if (strlen($new_password) < 4) {
|
||||||
|
return ['success' => false, 'message' => 'La password deve contenere almeno 4 caratteri.'];
|
||||||
|
}
|
||||||
|
$hash = password_hash($new_password, PASSWORD_DEFAULT);
|
||||||
|
$stmt = $conn->prepare("UPDATE users SET password = ? WHERE id = ?");
|
||||||
|
$stmt->bind_param("si", $hash, $user_id);
|
||||||
|
$res = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return $res ? ['success' => true, 'message' => 'Password aggiornata con successo!'] : ['success' => false, 'message' => 'Errore durante il salvataggio della password.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update user language preference.
|
||||||
|
*/
|
||||||
|
function update_user_language($user_id, $lang) {
|
||||||
|
global $conn;
|
||||||
|
if (!in_array($lang, ['it', 'en', 'es', 'fr'])) {
|
||||||
|
$lang = 'it';
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("UPDATE users SET language = ? WHERE id = ?");
|
||||||
|
$stmt->bind_param("si", $lang, $user_id);
|
||||||
|
$res = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
if ($res) {
|
||||||
|
if (isset($_SESSION['user']) && $_SESSION['user']['id'] == $user_id) {
|
||||||
|
$_SESSION['user']['language'] = $lang;
|
||||||
|
}
|
||||||
|
return ['success' => true, 'message' => 'Lingua aggiornata con successo!'];
|
||||||
|
}
|
||||||
|
return ['success' => false, 'message' => 'Errore durante l\'aggiornamento della lingua.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update user default landing blocknote preference.
|
||||||
|
*/
|
||||||
|
function update_user_default_blocknote($user_id, $bn_id) {
|
||||||
|
global $conn;
|
||||||
|
$bn_id = trim($bn_id);
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("UPDATE users SET default_blocknote = ? WHERE id = ?");
|
||||||
|
$stmt->bind_param("si", $bn_id, $user_id);
|
||||||
|
$res = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
if ($res) {
|
||||||
|
if (isset($_SESSION['user']) && $_SESSION['user']['id'] == $user_id) {
|
||||||
|
$_SESSION['user']['default_blocknote'] = $bn_id;
|
||||||
|
}
|
||||||
|
return ['success' => true, 'message' => 'Pagina di avvio aggiornata con successo!'];
|
||||||
|
}
|
||||||
|
return ['success' => false, 'message' => 'Errore durante l\'aggiornamento della pagina di avvio.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update user interface theme colors (HSL).
|
||||||
|
*/
|
||||||
|
function update_user_theme($user_id, $pri, $sec, $thi) {
|
||||||
|
global $conn;
|
||||||
|
$pri = max(0, min(360, (int)$pri));
|
||||||
|
$sec = max(0, min(360, (int)$sec));
|
||||||
|
$thi = max(0, min(360, (int)$thi));
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("UPDATE users SET theme_pri = ?, theme_sec = ?, theme_thi = ? WHERE id = ?");
|
||||||
|
$stmt->bind_param("iiii", $pri, $sec, $thi, $user_id);
|
||||||
|
$res = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
if ($res) {
|
||||||
|
if (isset($_SESSION['user']) && $_SESSION['user']['id'] == $user_id) {
|
||||||
|
$_SESSION['user']['theme_pri'] = $pri;
|
||||||
|
$_SESSION['user']['theme_sec'] = $sec;
|
||||||
|
$_SESSION['user']['theme_thi'] = $thi;
|
||||||
|
}
|
||||||
|
return ['success' => true, 'message' => 'Colori dell\'interfaccia aggiornati!'];
|
||||||
|
}
|
||||||
|
return ['success' => false, 'message' => 'Errore durante l\'aggiornamento dei colori.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin: Get list of all registered users.
|
||||||
|
*/
|
||||||
|
function get_all_users() {
|
||||||
|
global $conn;
|
||||||
|
$result = $conn->query("SELECT id, username, role, created_at FROM users ORDER BY id ASC");
|
||||||
|
$users = [];
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$users[] = $row;
|
||||||
|
}
|
||||||
|
return $users;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin: Register a new user.
|
||||||
|
*/
|
||||||
|
function admin_create_user($username, $password, $role = 'user') {
|
||||||
|
global $conn;
|
||||||
|
$username = trim($username);
|
||||||
|
if (empty($username) || empty($password)) {
|
||||||
|
return ['success' => false, 'message' => 'Username e password sono obbligatori.'];
|
||||||
|
}
|
||||||
|
if (!in_array($role, ['user', 'admin', 'owner'])) {
|
||||||
|
$role = 'user';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($role === 'owner') {
|
||||||
|
$check_owner = $conn->query("SELECT id FROM users WHERE role = 'owner'");
|
||||||
|
if ($check_owner->num_rows > 0) {
|
||||||
|
return ['success' => false, 'message' => 'Può esistere un solo account Owner nel sistema!'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if exists
|
||||||
|
$stmt = $conn->prepare("SELECT id FROM users WHERE username = ? LIMIT 1");
|
||||||
|
$stmt->bind_param("s", $username);
|
||||||
|
$stmt->execute();
|
||||||
|
if ($stmt->get_result()->num_rows > 0) {
|
||||||
|
$stmt->close();
|
||||||
|
return ['success' => false, 'message' => 'Un utente con questo username esiste già.'];
|
||||||
|
}
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
$stmt = $conn->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)");
|
||||||
|
$stmt->bind_param("sss", $username, $hash, $role);
|
||||||
|
$res = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return $res ? ['success' => true, 'message' => 'Utente creato con successo!'] : ['success' => false, 'message' => 'Impossibile creare l\'utente.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin: Delete a user by ID.
|
||||||
|
*/
|
||||||
|
function admin_delete_user($user_id) {
|
||||||
|
global $conn;
|
||||||
|
$user_id = (int)$user_id;
|
||||||
|
|
||||||
|
// Prevent self-deletion
|
||||||
|
if (current_user() && current_user()['id'] == $user_id) {
|
||||||
|
return ['success' => false, 'message' => 'Non puoi eliminare il tuo stesso account!'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check target user role - Owner can NEVER be deleted
|
||||||
|
$stmt = $conn->prepare("SELECT role FROM users WHERE id = ? LIMIT 1");
|
||||||
|
$stmt->bind_param("i", $user_id);
|
||||||
|
$stmt->execute();
|
||||||
|
$target_user = $stmt->get_result()->fetch_assoc();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
if ($target_user && $target_user['role'] === 'owner') {
|
||||||
|
return ['success' => false, 'message' => 'L\'account Owner non può essere eliminato da nessuno!'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("DELETE FROM users WHERE id = ?");
|
||||||
|
$stmt->bind_param("i", $user_id);
|
||||||
|
$res = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return $res ? ['success' => true, 'message' => 'Utente eliminato.'] : ['success' => false, 'message' => 'Errore nell\'eliminazione dell\'utente.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin: Update user role.
|
||||||
|
*/
|
||||||
|
function admin_update_user_role($user_id, $role) {
|
||||||
|
global $conn;
|
||||||
|
$user_id = (int)$user_id;
|
||||||
|
if (!in_array($role, ['admin', 'user', 'owner'])) return ['success' => false, 'message' => 'Ruolo non valido.'];
|
||||||
|
|
||||||
|
// Check target user role - Owner role cannot be changed
|
||||||
|
$stmt = $conn->prepare("SELECT role FROM users WHERE id = ? LIMIT 1");
|
||||||
|
$stmt->bind_param("i", $user_id);
|
||||||
|
$stmt->execute();
|
||||||
|
$target_user = $stmt->get_result()->fetch_assoc();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
if ($target_user && $target_user['role'] === 'owner') {
|
||||||
|
return ['success' => false, 'message' => 'Il ruolo dell\'account Owner non può essere modificato!'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($role === 'owner') {
|
||||||
|
$check_owner = $conn->query("SELECT id FROM users WHERE role = 'owner'");
|
||||||
|
if ($check_owner->num_rows > 0) {
|
||||||
|
return ['success' => false, 'message' => 'Può esistere un solo account Owner nel sistema!'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("UPDATE users SET role = ? WHERE id = ?");
|
||||||
|
$stmt->bind_param("si", $role, $user_id);
|
||||||
|
$res = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return $res ? ['success' => true, 'message' => 'Ruolo aggiornato.'] : ['success' => false, 'message' => 'Impossibile aggiornare il ruolo.'];
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,421 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Blocknotes JSON Storage Engine Functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure storage directory exists.
|
||||||
|
*/
|
||||||
|
function ensure_blocknotes_directory() {
|
||||||
|
if (!is_dir(BLOCKNOTES_FOLD)) {
|
||||||
|
@mkdir(BLOCKNOTES_FOLD, 0777, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan BLOCKNOTES_FOLD and load all JSON files.
|
||||||
|
*/
|
||||||
|
function get_all_blocknotes() {
|
||||||
|
ensure_blocknotes_directory();
|
||||||
|
$files = glob(BLOCKNOTES_FOLD . "*.json");
|
||||||
|
$blocknotes = [];
|
||||||
|
|
||||||
|
if ($files) {
|
||||||
|
foreach ($files as $filepath) {
|
||||||
|
$content = file_get_contents($filepath);
|
||||||
|
$data = json_decode($content, true);
|
||||||
|
if (is_array($data) && isset($data['id'])) {
|
||||||
|
$blocknotes[] = $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by updated_at descending
|
||||||
|
usort($blocknotes, function($a, $b) {
|
||||||
|
return strtotime($b['updated_at'] ?? 0) - strtotime($a['updated_at'] ?? 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
return $blocknotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get blocknotes assigned to a specific user (or all if admin).
|
||||||
|
*/
|
||||||
|
function get_user_blocknotes($username, $is_admin = false) {
|
||||||
|
$all = get_all_blocknotes();
|
||||||
|
if ($is_admin) {
|
||||||
|
return $all;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_filter($all, function($bn) use ($username) {
|
||||||
|
return isset($bn['username']) && strtolower($bn['username']) === strtolower($username);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find blocknote by ID.
|
||||||
|
*/
|
||||||
|
function get_blocknote_by_id($id) {
|
||||||
|
ensure_blocknotes_directory();
|
||||||
|
$filename = BLOCKNOTES_FOLD . preg_replace('/[^a-zA-Z0-9_\-]/', '', $id) . ".json";
|
||||||
|
|
||||||
|
if (file_exists($filename)) {
|
||||||
|
$content = file_get_contents($filename);
|
||||||
|
$data = json_decode($content, true);
|
||||||
|
if (is_array($data)) return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback search across all files if ID is stored inside
|
||||||
|
$all = get_all_blocknotes();
|
||||||
|
foreach ($all as $bn) {
|
||||||
|
if (isset($bn['id']) && $bn['id'] === $id) {
|
||||||
|
return $bn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save blocknote to JSON file.
|
||||||
|
*/
|
||||||
|
function save_blocknote($data) {
|
||||||
|
ensure_blocknotes_directory();
|
||||||
|
if (empty($data['id'])) {
|
||||||
|
$data['id'] = 'bn_' . uniqid();
|
||||||
|
}
|
||||||
|
$data['updated_at'] = date('Y-m-d H:i:s');
|
||||||
|
if (empty($data['created_at'])) {
|
||||||
|
$data['created_at'] = $data['updated_at'];
|
||||||
|
}
|
||||||
|
if (!isset($data['notes'])) {
|
||||||
|
$data['notes'] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$filepath = BLOCKNOTES_FOLD . preg_replace('/[^a-zA-Z0-9_\-]/', '', $data['id']) . ".json";
|
||||||
|
$json_string = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
|
return file_put_contents($filepath, $json_string) !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new blocknote.
|
||||||
|
*/
|
||||||
|
function create_blocknote($title, $username) {
|
||||||
|
$title = trim($title);
|
||||||
|
if (empty($title)) {
|
||||||
|
return ['success' => false, 'message' => 'Il titolo del blocknote è obbligatorio.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = 'bn_' . uniqid();
|
||||||
|
$data = [
|
||||||
|
'id' => $id,
|
||||||
|
'title' => $title,
|
||||||
|
'username' => $username,
|
||||||
|
'created_at' => date('Y-m-d H:i:s'),
|
||||||
|
'updated_at' => date('Y-m-d H:i:s'),
|
||||||
|
'notes' => []
|
||||||
|
];
|
||||||
|
|
||||||
|
if (save_blocknote($data)) {
|
||||||
|
return ['success' => true, 'id' => $id, 'message' => 'Blocknote creato con successo!'];
|
||||||
|
}
|
||||||
|
return ['success' => false, 'message' => 'Errore nel salvataggio del file JSON.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import a JSON string (supports standard format and legacy codes format).
|
||||||
|
*/
|
||||||
|
function import_blocknote_json($json_string, $username, $custom_title = '') {
|
||||||
|
$data = json_decode($json_string, true);
|
||||||
|
if (!is_array($data)) {
|
||||||
|
return ['success' => false, 'message' => 'File JSON non valido o malformato.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = 'bn_' . uniqid();
|
||||||
|
$title = !empty($custom_title) ? trim($custom_title) : 'Blocknote Importato';
|
||||||
|
|
||||||
|
// Case 1: Standard format with 'notes' array
|
||||||
|
if (isset($data['notes']) && is_array($data['notes'])) {
|
||||||
|
$bn = [
|
||||||
|
'id' => $id,
|
||||||
|
'title' => !empty($data['title']) ? $data['title'] : $title,
|
||||||
|
'username' => $username,
|
||||||
|
'created_at' => date('Y-m-d H:i:s'),
|
||||||
|
'updated_at' => date('Y-m-d H:i:s'),
|
||||||
|
'notes' => $data['notes']
|
||||||
|
];
|
||||||
|
if (save_blocknote($bn)) {
|
||||||
|
return ['success' => true, 'id' => $id, 'message' => 'Blocknote importato con successo!'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 2: Legacy format (array of notes with 'title', 'description', 'codes')
|
||||||
|
if (isset($data[0]) && is_array($data[0])) {
|
||||||
|
$notes = [];
|
||||||
|
foreach ($data as $n_idx => $legacy_note) {
|
||||||
|
$note_id = 'note_' . ($n_idx + 1) . '_' . uniqid();
|
||||||
|
$subnotes = [];
|
||||||
|
|
||||||
|
if (isset($legacy_note['codes']) && is_array($legacy_note['codes'])) {
|
||||||
|
foreach ($legacy_note['codes'] as $s_idx => $legacy_code) {
|
||||||
|
$subnotes[] = [
|
||||||
|
'id' => 'sub_' . ($s_idx + 1) . '_' . uniqid(),
|
||||||
|
'title' => $legacy_code['sub_title'] ?? 'Comando',
|
||||||
|
'type' => 'command',
|
||||||
|
'content' => $legacy_code['code'] ?? ''
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$notes[] = [
|
||||||
|
'id' => $note_id,
|
||||||
|
'title' => $legacy_note['title'] ?? 'Nota ' . ($n_idx + 1),
|
||||||
|
'description' => $legacy_note['description'] ?? '',
|
||||||
|
'subnotes' => $subnotes
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$bn = [
|
||||||
|
'id' => $id,
|
||||||
|
'title' => $title,
|
||||||
|
'username' => $username,
|
||||||
|
'created_at' => date('Y-m-d H:i:s'),
|
||||||
|
'updated_at' => date('Y-m-d H:i:s'),
|
||||||
|
'notes' => $notes
|
||||||
|
];
|
||||||
|
|
||||||
|
if (save_blocknote($bn)) {
|
||||||
|
return ['success' => true, 'id' => $id, 'message' => 'Blocknote in formato legacy importato e convertito con successo!'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['success' => false, 'message' => 'Formato JSON non riconosciuto.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check permission on blocknote.
|
||||||
|
*/
|
||||||
|
function can_access_blocknote($bn, $username, $is_admin) {
|
||||||
|
if (!$bn) return false;
|
||||||
|
if ($is_admin) return true;
|
||||||
|
return strtolower($bn['username'] ?? '') === strtolower($username);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rename a blocknote.
|
||||||
|
*/
|
||||||
|
function rename_blocknote($id, $new_title, $username, $is_admin) {
|
||||||
|
$bn = get_blocknote_by_id($id);
|
||||||
|
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
|
||||||
|
if (!can_access_blocknote($bn, $username, $is_admin)) {
|
||||||
|
return ['success' => false, 'message' => 'Non hai i permessi per modificare questo blocknote.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_title = trim($new_title);
|
||||||
|
if (empty($new_title)) return ['success' => false, 'message' => 'Il titolo non può essere vuoto.'];
|
||||||
|
|
||||||
|
$bn['title'] = $new_title;
|
||||||
|
if (save_blocknote($bn)) {
|
||||||
|
return ['success' => true, 'message' => 'Blocknote rinominato con successo.'];
|
||||||
|
}
|
||||||
|
return ['success' => false, 'message' => 'Errore nel salvataggio del file JSON.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a blocknote.
|
||||||
|
*/
|
||||||
|
function delete_blocknote($id, $username, $is_admin) {
|
||||||
|
$bn = get_blocknote_by_id($id);
|
||||||
|
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
|
||||||
|
if (!can_access_blocknote($bn, $username, $is_admin)) {
|
||||||
|
return ['success' => false, 'message' => 'Non hai i permessi per eliminare questo blocknote.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$filepath = BLOCKNOTES_FOLD . preg_replace('/[^a-zA-Z0-9_\-]/', '', $bn['id']) . ".json";
|
||||||
|
if (file_exists($filepath)) {
|
||||||
|
@unlink($filepath);
|
||||||
|
}
|
||||||
|
return ['success' => true, 'message' => 'Blocknote eliminato con successo.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new Note block to a Blocknote.
|
||||||
|
*/
|
||||||
|
function add_note($blocknote_id, $title, $description, $username, $is_admin) {
|
||||||
|
$bn = get_blocknote_by_id($blocknote_id);
|
||||||
|
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
|
||||||
|
if (!can_access_blocknote($bn, $username, $is_admin)) return ['success' => false, 'message' => 'Permesso negato.'];
|
||||||
|
|
||||||
|
$title = trim($title);
|
||||||
|
if (empty($title)) return ['success' => false, 'message' => 'Il titolo della nota è obbligatorio.'];
|
||||||
|
|
||||||
|
$note_id = 'note_' . uniqid();
|
||||||
|
$new_note = [
|
||||||
|
'id' => $note_id,
|
||||||
|
'title' => $title,
|
||||||
|
'description' => trim($description),
|
||||||
|
'subnotes' => []
|
||||||
|
];
|
||||||
|
|
||||||
|
$bn['notes'][] = $new_note;
|
||||||
|
if (save_blocknote($bn)) {
|
||||||
|
return ['success' => true, 'note_id' => $note_id, 'message' => 'Nota aggiunta con successo!'];
|
||||||
|
}
|
||||||
|
return ['success' => false, 'message' => 'Errore durante il salvataggio.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit an existing Note block.
|
||||||
|
*/
|
||||||
|
function edit_note($blocknote_id, $note_id, $title, $description, $username, $is_admin) {
|
||||||
|
$bn = get_blocknote_by_id($blocknote_id);
|
||||||
|
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
|
||||||
|
if (!can_access_blocknote($bn, $username, $is_admin)) return ['success' => false, 'message' => 'Permesso negato.'];
|
||||||
|
|
||||||
|
$title = trim($title);
|
||||||
|
if (empty($title)) return ['success' => false, 'message' => 'Il titolo della nota è obbligatorio.'];
|
||||||
|
|
||||||
|
$found = false;
|
||||||
|
foreach ($bn['notes'] as &$note) {
|
||||||
|
if ($note['id'] === $note_id) {
|
||||||
|
$note['title'] = $title;
|
||||||
|
$note['description'] = trim($description);
|
||||||
|
$found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$found) return ['success' => false, 'message' => 'Nota non trovata.'];
|
||||||
|
|
||||||
|
if (save_blocknote($bn)) {
|
||||||
|
return ['success' => true, 'message' => 'Nota modificata con successo!'];
|
||||||
|
}
|
||||||
|
return ['success' => false, 'message' => 'Errore nel salvataggio.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a Note block.
|
||||||
|
*/
|
||||||
|
function delete_note($blocknote_id, $note_id, $username, $is_admin) {
|
||||||
|
$bn = get_blocknote_by_id($blocknote_id);
|
||||||
|
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
|
||||||
|
if (!can_access_blocknote($bn, $username, $is_admin)) return ['success' => false, 'message' => 'Permesso negato.'];
|
||||||
|
|
||||||
|
$initial_count = count($bn['notes']);
|
||||||
|
$bn['notes'] = array_values(array_filter($bn['notes'], function($n) use ($note_id) {
|
||||||
|
return $n['id'] !== $note_id;
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (count($bn['notes']) === $initial_count) {
|
||||||
|
return ['success' => false, 'message' => 'Nota non trovata.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (save_blocknote($bn)) {
|
||||||
|
return ['success' => true, 'message' => 'Nota eliminata con successo.'];
|
||||||
|
}
|
||||||
|
return ['success' => false, 'message' => 'Errore nel salvataggio.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a Subnote to a Note block.
|
||||||
|
*/
|
||||||
|
function add_subnote($blocknote_id, $note_id, $sub_title, $type, $content, $username, $is_admin) {
|
||||||
|
$bn = get_blocknote_by_id($blocknote_id);
|
||||||
|
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
|
||||||
|
if (!can_access_blocknote($bn, $username, $is_admin)) return ['success' => false, 'message' => 'Permesso negato.'];
|
||||||
|
|
||||||
|
$sub_title = trim($sub_title);
|
||||||
|
if (empty($sub_title)) return ['success' => false, 'message' => 'Il titolo della sottonota è obbligatorio.'];
|
||||||
|
|
||||||
|
$type = ($type === 'command') ? 'command' : 'text';
|
||||||
|
$sub_id = 'sub_' . uniqid();
|
||||||
|
|
||||||
|
$found = false;
|
||||||
|
foreach ($bn['notes'] as &$note) {
|
||||||
|
if ($note['id'] === $note_id) {
|
||||||
|
if (!isset($note['subnotes'])) $note['subnotes'] = [];
|
||||||
|
$note['subnotes'][] = [
|
||||||
|
'id' => $sub_id,
|
||||||
|
'title' => $sub_title,
|
||||||
|
'type' => $type,
|
||||||
|
'content' => $content
|
||||||
|
];
|
||||||
|
$found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$found) return ['success' => false, 'message' => 'Nota genitore non trovata.'];
|
||||||
|
|
||||||
|
if (save_blocknote($bn)) {
|
||||||
|
return ['success' => true, 'subnote_id' => $sub_id, 'message' => 'Sottonota aggiunta con successo!'];
|
||||||
|
}
|
||||||
|
return ['success' => false, 'message' => 'Errore nel salvataggio.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit a Subnote.
|
||||||
|
*/
|
||||||
|
function edit_subnote($blocknote_id, $note_id, $sub_id, $sub_title, $type, $content, $username, $is_admin) {
|
||||||
|
$bn = get_blocknote_by_id($blocknote_id);
|
||||||
|
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
|
||||||
|
if (!can_access_blocknote($bn, $username, $is_admin)) return ['success' => false, 'message' => 'Permesso negato.'];
|
||||||
|
|
||||||
|
$sub_title = trim($sub_title);
|
||||||
|
if (empty($sub_title)) return ['success' => false, 'message' => 'Il titolo della sottonota è obbligatorio.'];
|
||||||
|
$type = ($type === 'command') ? 'command' : 'text';
|
||||||
|
|
||||||
|
$found = false;
|
||||||
|
foreach ($bn['notes'] as &$note) {
|
||||||
|
if ($note['id'] === $note_id) {
|
||||||
|
if (isset($note['subnotes'])) {
|
||||||
|
foreach ($note['subnotes'] as &$sub) {
|
||||||
|
if ($sub['id'] === $sub_id) {
|
||||||
|
$sub['title'] = $sub_title;
|
||||||
|
$sub['type'] = $type;
|
||||||
|
$sub['content'] = $content;
|
||||||
|
$found = true;
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$found) return ['success' => false, 'message' => 'Sottonota non trovata.'];
|
||||||
|
|
||||||
|
if (save_blocknote($bn)) {
|
||||||
|
return ['success' => true, 'message' => 'Sottonota modificata con successo!'];
|
||||||
|
}
|
||||||
|
return ['success' => false, 'message' => 'Errore nel salvataggio.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a Subnote.
|
||||||
|
*/
|
||||||
|
function delete_subnote($blocknote_id, $note_id, $sub_id, $username, $is_admin) {
|
||||||
|
$bn = get_blocknote_by_id($blocknote_id);
|
||||||
|
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
|
||||||
|
if (!can_access_blocknote($bn, $username, $is_admin)) return ['success' => false, 'message' => 'Permesso negato.'];
|
||||||
|
|
||||||
|
$found = false;
|
||||||
|
foreach ($bn['notes'] as &$note) {
|
||||||
|
if ($note['id'] === $note_id && isset($note['subnotes'])) {
|
||||||
|
$initial = count($note['subnotes']);
|
||||||
|
$note['subnotes'] = array_values(array_filter($note['subnotes'], function($s) use ($sub_id) {
|
||||||
|
return $s['id'] !== $sub_id;
|
||||||
|
}));
|
||||||
|
if (count($note['subnotes']) < $initial) {
|
||||||
|
$found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$found) return ['success' => false, 'message' => 'Sottonota non trovata.'];
|
||||||
|
|
||||||
|
if (save_blocknote($bn)) {
|
||||||
|
return ['success' => true, 'message' => 'Sottonota eliminata con successo.'];
|
||||||
|
}
|
||||||
|
return ['success' => false, 'message' => 'Errore nel salvataggio.'];
|
||||||
|
}
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
/*
|
/*
|
||||||
### LIST OF FUNCTIONS TO INCLUDE ###
|
### LIST OF FUNCTIONS TO INCLUDE ###
|
||||||
Include in this array all the function files you may want to include.
|
Include in this array all the function files you may want to include.
|
||||||
You don't need to specify the extension .php, the system will automatically add it.
|
You don't need to specify the extension .php, the system will automatically add it.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$fun_to_inc = array(
|
$fun_to_inc = array(
|
||||||
//"example",
|
"lang",
|
||||||
"demo-example"
|
"site_settings",
|
||||||
|
"auth",
|
||||||
|
"blocknotes"
|
||||||
);
|
);
|
||||||
|
|
||||||
// DON'T CHANGE THE CODE BELOW!!!
|
// DON'T CHANGE THE CODE BELOW!!!
|
||||||
@@ -17,9 +19,6 @@ if (isset($fun_to_inc) && is_array($fun_to_inc) && !empty($fun_to_inc)){
|
|||||||
require_once(FUNCTIONS_FOLD . $fun . ".php");
|
require_once(FUNCTIONS_FOLD . $fun . ".php");
|
||||||
} else {
|
} else {
|
||||||
debugStatusMes("Function file $fun.php not found in functions folder: '" . FUNCTIONS_FOLD . "'", "Medium");
|
debugStatusMes("Function file $fun.php not found in functions folder: '" . FUNCTIONS_FOLD . "'", "Medium");
|
||||||
if ($fun === "demo-example") {
|
|
||||||
debugStatusMes("The 'demo-example' function is only a demo. If you create a new function, needs to be included in the file specified above. Please remove the demo-example record from 'functions/functions.php' ", "Info");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,720 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Multilingual System Helper (IT, EN, ES, FR)
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current user language code.
|
||||||
|
*/
|
||||||
|
function get_current_lang() {
|
||||||
|
$user = $_SESSION['user'] ?? null;
|
||||||
|
$lang = $user['language'] ?? $_SESSION['guest_language'] ?? 'it';
|
||||||
|
if (!in_array($lang, ['it', 'en', 'es', 'fr'])) {
|
||||||
|
$lang = 'it';
|
||||||
|
}
|
||||||
|
return $lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set guest/active language.
|
||||||
|
*/
|
||||||
|
function set_active_lang($lang) {
|
||||||
|
if (in_array($lang, ['it', 'en', 'es', 'fr'])) {
|
||||||
|
if (isset($_SESSION['user'])) {
|
||||||
|
$_SESSION['user']['language'] = $lang;
|
||||||
|
global $conn;
|
||||||
|
if (isset($conn) && !empty($_SESSION['user']['id'])) {
|
||||||
|
$stmt = $conn->prepare("UPDATE users SET language = ? WHERE id = ?");
|
||||||
|
$stmt->bind_param("si", $lang, $_SESSION['user']['id']);
|
||||||
|
$stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$_SESSION['guest_language'] = $lang;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translations Dictionary
|
||||||
|
*/
|
||||||
|
function get_translations() {
|
||||||
|
return [
|
||||||
|
'it' => [
|
||||||
|
// Login Page
|
||||||
|
'login_title' => 'Accedi a Blocknotes',
|
||||||
|
'login_subtitle' => 'Gestisci le tue note e i tuoi comandi',
|
||||||
|
'label_username' => 'Nome Utente',
|
||||||
|
'label_password' => 'Password',
|
||||||
|
'placeholder_username' => 'Inserisci username',
|
||||||
|
'placeholder_password' => 'Inserisci password',
|
||||||
|
'btn_submit_login' => '🔑 Accedi',
|
||||||
|
'login_hint_footer' => 'Accredito predefinito per il primo accesso',
|
||||||
|
'login_installed_success' => '🎉 Setup completato! Accedi con le credenziali create.',
|
||||||
|
|
||||||
|
// Navbar
|
||||||
|
'nav_blocknotes' => '📓 Blocknotes',
|
||||||
|
'nav_colors_profile' => '🎨 Colori & Profilo',
|
||||||
|
'nav_admin' => '👑 Gestione Utenti',
|
||||||
|
'nav_logout' => '🚪 Esci',
|
||||||
|
'nav_login' => '🔑 Accedi',
|
||||||
|
'nav_language' => '🌐 Lingua',
|
||||||
|
|
||||||
|
// Dashboard (home)
|
||||||
|
'dashboard_title_user' => 'I Miei Blocknotes',
|
||||||
|
'dashboard_title_admin' => 'Tutti i Blocknotes (Vista Admin)',
|
||||||
|
'dashboard_desc_user' => 'Crea, organizza e consulta i tuoi blocknotes.',
|
||||||
|
'dashboard_desc_admin' => 'In qualità di Admin puoi visualizzare, gestire ed eliminare i blocknotes di tutti gli utenti.',
|
||||||
|
'btn_new_blocknote' => '➕ Nuovo Blocknote',
|
||||||
|
'btn_import_json' => '📥 Importa JSON',
|
||||||
|
'btn_export_json' => '💾 Esporta JSON',
|
||||||
|
'btn_open_edit' => '👁️ Apri & Modifica',
|
||||||
|
'btn_rename' => '✏️ Rinomina',
|
||||||
|
'btn_delete' => '🗑️ Elimina',
|
||||||
|
'empty_blocknotes_title' => 'Nessun Blocknote trovato',
|
||||||
|
'empty_blocknotes_desc_user' => 'Non hai ancora creato nessun blocknote. Inizia subito!',
|
||||||
|
'empty_blocknotes_desc_admin' => 'Non ci sono ancora blocknotes salvati nel sistema.',
|
||||||
|
'modal_create_title' => '➕ Crea Nuovo Blocknote',
|
||||||
|
'modal_rename_title' => '✏️ Rinomina Blocknote',
|
||||||
|
'modal_import_title' => '📥 Importa Blocknote (JSON)',
|
||||||
|
'label_title' => 'Titolo Blocknote',
|
||||||
|
'label_file_json' => 'Carica File .json',
|
||||||
|
'label_paste_json' => 'Oppure incolla qui il codice JSON',
|
||||||
|
'btn_cancel' => 'Annulla',
|
||||||
|
'btn_save' => 'Salva',
|
||||||
|
'btn_create' => 'Crea Blocknote',
|
||||||
|
'btn_import' => 'Importa Blocknote',
|
||||||
|
'note_single' => 'Nota',
|
||||||
|
'note_plural' => 'Note',
|
||||||
|
|
||||||
|
// View Blocknote
|
||||||
|
'btn_back_list' => '⬅️ Torna ai Blocknotes',
|
||||||
|
'btn_expand_all' => '📂 Espandi Tutte',
|
||||||
|
'btn_collapse_all' => '📁 Riduci Tutte',
|
||||||
|
'btn_new_note' => '➕ Nuova Nota',
|
||||||
|
'btn_add_subnote' => '➕ Sottonota',
|
||||||
|
'subnote_single' => 'sottonota',
|
||||||
|
'subnote_plural' => 'sottonote',
|
||||||
|
'badge_text' => '📄 Testo',
|
||||||
|
'badge_command' => '⚡ Comando',
|
||||||
|
'btn_copy' => '📋 Copia',
|
||||||
|
'btn_copied' => '✓ Copiato!',
|
||||||
|
'search_placeholder' => '🔍 Cerca note, sottonote o comandi in questo blocknote...',
|
||||||
|
'btn_clear_search' => '❌ Annulla Cerca',
|
||||||
|
'search_no_results' => 'Nessun risultato trovato',
|
||||||
|
'search_no_results_desc' => 'Nessuna nota o comando corrisponde al termine cercato in questo blocknote.',
|
||||||
|
'modal_add_note' => '➕ Nuova Nota a Blocchi',
|
||||||
|
'modal_edit_note' => '✏️ Modifica Nota',
|
||||||
|
'modal_add_subnote' => '➕ Aggiungi Sottonota',
|
||||||
|
'modal_edit_subnote' => '✏️ Modifica Sottonota',
|
||||||
|
'label_note_description' => 'Descrizione (opzionale)',
|
||||||
|
'label_subnote_title' => 'Titolo Sottonota',
|
||||||
|
'label_subnote_type' => 'Tipo di Sottonota',
|
||||||
|
'label_subnote_content' => 'Contenuto',
|
||||||
|
'type_option_text' => '📄 Testo generico',
|
||||||
|
'type_option_command' => '⚡ Comando (con pulsante Copia)',
|
||||||
|
|
||||||
|
// Profile & Settings
|
||||||
|
'profile_title' => '🎨 Impostazioni Profilo & Tema',
|
||||||
|
'profile_desc' => 'Personalizza password, colori dell\'interfaccia, lingua e pagina di avvio.',
|
||||||
|
'card_password_title' => '🔒 Cambio Password',
|
||||||
|
'card_colors_title' => '🎨 Personalizza Colori Interfaccia (HSL)',
|
||||||
|
'card_lang_title' => '🌍 Lingua dell\'Interfaccia',
|
||||||
|
'card_landing_title' => '🏠 Pagina di Primo Accesso (Landing Page)',
|
||||||
|
'label_new_pass' => 'Nuova Password',
|
||||||
|
'label_confirm_pass' => 'Conferma Nuova Password',
|
||||||
|
'label_select_lang' => 'Seleziona Lingua',
|
||||||
|
'label_select_landing' => 'Seleziona Pagina di Avvio',
|
||||||
|
'landing_option_dashboard' => '📋 Pagina Principale (Tutti i Blocknotes)',
|
||||||
|
'landing_option_bn_prefix' => '📌 Apri direttamente: ',
|
||||||
|
'btn_update_pass' => '🔑 Aggiorna Password',
|
||||||
|
'btn_save_colors' => '💾 Salva Preferenze Colori',
|
||||||
|
'btn_save_lang' => '🌍 Salva Lingua',
|
||||||
|
'btn_save_landing' => '🏠 Salva Pagina di Avvio',
|
||||||
|
|
||||||
|
// Admin Users
|
||||||
|
'admin_title' => '👑 Pannello Amministrazione Utenti',
|
||||||
|
'admin_desc' => 'Crea, gestisci e modifica i ruoli e gli accessi degli utenti.',
|
||||||
|
'btn_add_user' => '➕ Aggiungi Nuovo Utente',
|
||||||
|
'col_username' => 'Nome Utente',
|
||||||
|
'col_role' => 'Ruolo',
|
||||||
|
'col_bns' => 'Blocknotes Creati',
|
||||||
|
'col_created' => 'Data Registrazione',
|
||||||
|
'col_actions' => 'Azioni Admin',
|
||||||
|
'role_standard' => 'Standard User',
|
||||||
|
'role_admin' => 'Administrator',
|
||||||
|
'role_owner' => '👑 Owner (Proprietario)',
|
||||||
|
'badge_owner' => '👑 Owner',
|
||||||
|
'btn_reset_pass' => '🔑 Reset Pass',
|
||||||
|
'modal_create_user' => '➕ Registra Nuovo Utente',
|
||||||
|
'modal_reset_pass' => '🔑 Reset Password Utente',
|
||||||
|
// Confirm Dialogs
|
||||||
|
'confirm_delete_blocknote' => 'Sei sicuro di voler eliminare questo blocknote e tutte le sue note?',
|
||||||
|
'confirm_delete_note' => 'Sei sicuro di voler eliminare questa nota e tutte le sue sottonote?',
|
||||||
|
'confirm_delete_subnote' => 'Sei sicuro di voler eliminare questa sottonota?',
|
||||||
|
'confirm_delete_user' => 'Sei sicuro di voler eliminare questo utente?',
|
||||||
|
|
||||||
|
// Placeholders
|
||||||
|
'placeholder_bn_title' => 'Es. Comandi Server Linux, Note Progetto PHP...',
|
||||||
|
'placeholder_rename_title' => 'Nuovo titolo...',
|
||||||
|
'placeholder_import_title' => 'Es. Positive Internet',
|
||||||
|
'placeholder_paste_json' => 'Incolla qui il JSON nel vecchio o nuovo formato...',
|
||||||
|
'placeholder_note_title' => 'Es. Installazione MySQL, Server Setup...',
|
||||||
|
'placeholder_note_desc' => 'Breve descrizione di questo blocco di note...',
|
||||||
|
'placeholder_subnote_title' => 'Es. Comando creazione database, Note configurazione...',
|
||||||
|
'placeholder_subnote_content' => 'Inserisci il testo o le istruzioni del comando...',
|
||||||
|
'placeholder_site_title' => 'Es. Blocknotes, Enterprise Notes...',
|
||||||
|
'placeholder_username_new' => 'es. mario, giuseppe...',
|
||||||
|
'placeholder_password_new' => 'Inserisci nuova password',
|
||||||
|
'placeholder_password_confirm' => 'Ripeti nuova password',
|
||||||
|
|
||||||
|
// Extra Labels
|
||||||
|
'label_owner_prefix' => 'Proprietario: ',
|
||||||
|
'label_updated_prefix' => 'Ultimo aggiornamento: ',
|
||||||
|
'label_you' => 'Tu',
|
||||||
|
'label_unknown' => 'Sconosciuto',
|
||||||
|
'label_current_favicon' => 'Favicon Attuale: ',
|
||||||
|
|
||||||
|
// First Start Installer Setup
|
||||||
|
'setup_title' => '🚀 Setup Iniziale Blocknotes',
|
||||||
|
'setup_subtitle' => 'Configura il database MySQL e crea i primi utenti privilegiati (Owner & Admin).',
|
||||||
|
'setup_sec1_title' => '1. Configurazione Database MySQL',
|
||||||
|
'setup_db_host' => 'Host Database',
|
||||||
|
'setup_db_name' => 'Nome Database',
|
||||||
|
'setup_db_user' => 'Utente MySQL',
|
||||||
|
'setup_db_pass' => 'Password MySQL',
|
||||||
|
'setup_db_pass_placeholder' => 'Password (lascia vuoto se nessuna)',
|
||||||
|
'setup_sec2_title' => '2. Account Owner (Proprietario Unico)',
|
||||||
|
'setup_owner_user' => 'Username Owner',
|
||||||
|
'setup_owner_pass' => 'Password Owner',
|
||||||
|
'setup_owner_pass_placeholder' => 'Inserisci password per Owner',
|
||||||
|
'setup_sec3_title' => '3. Primo Account Amministratore (Admin)',
|
||||||
|
'setup_admin_user' => 'Username Admin',
|
||||||
|
'setup_admin_pass' => 'Password Admin',
|
||||||
|
'setup_admin_pass_placeholder' => 'Inserisci password per Admin',
|
||||||
|
'setup_btn_submit' => '⚡ Completa Installazione & Configura Sistema',
|
||||||
|
'err_setup_db_fields' => 'Compila tutti i dati di connessione al Database MySQL.',
|
||||||
|
'err_setup_owner_fields' => 'Inserisci username e password per l\'utente Owner (Proprietario).',
|
||||||
|
'err_setup_admin_fields' => 'Inserisci username e password per il primo utente Admin.',
|
||||||
|
'err_setup_same_user' => 'L\'utente Owner e l\'utente Admin devono avere nomi utente differenti.'
|
||||||
|
],
|
||||||
|
|
||||||
|
'en' => [
|
||||||
|
// Login Page
|
||||||
|
'login_title' => 'Login to Blocknotes',
|
||||||
|
'login_subtitle' => 'Manage your notes and commands',
|
||||||
|
'label_username' => 'Username',
|
||||||
|
'label_password' => 'Password',
|
||||||
|
'placeholder_username' => 'Enter username',
|
||||||
|
'placeholder_password' => 'Enter password',
|
||||||
|
'btn_submit_login' => '🔑 Login',
|
||||||
|
'login_hint_footer' => 'Default first login credential',
|
||||||
|
'login_installed_success' => '🎉 Setup completed! Login with your created credentials.',
|
||||||
|
|
||||||
|
// Navbar
|
||||||
|
'nav_blocknotes' => '📓 Blocknotes',
|
||||||
|
'nav_colors_profile' => '🎨 Colors & Profile',
|
||||||
|
'nav_admin' => '👑 User Management',
|
||||||
|
'nav_logout' => '🚪 Logout',
|
||||||
|
'nav_login' => '🔑 Login',
|
||||||
|
'nav_language' => '🌐 Language',
|
||||||
|
|
||||||
|
// Dashboard (home)
|
||||||
|
'dashboard_title_user' => 'My Blocknotes',
|
||||||
|
'dashboard_title_admin' => 'All Blocknotes (Admin View)',
|
||||||
|
'dashboard_desc_user' => 'Create, organize, and view your blocknotes.',
|
||||||
|
'dashboard_desc_admin' => 'As an Admin you can view, manage, and delete blocknotes of all users.',
|
||||||
|
'btn_new_blocknote' => '➕ New Blocknote',
|
||||||
|
'btn_import_json' => '📥 Import JSON',
|
||||||
|
'btn_export_json' => '💾 Export JSON',
|
||||||
|
'btn_open_edit' => '👁️ Open & Edit',
|
||||||
|
'btn_rename' => '✏️ Rename',
|
||||||
|
'btn_delete' => '🗑️ Delete',
|
||||||
|
'empty_blocknotes_title' => 'No Blocknotes found',
|
||||||
|
'empty_blocknotes_desc_user' => 'You haven\'t created any blocknote yet. Start now!',
|
||||||
|
'empty_blocknotes_desc_admin' => 'There are no blocknotes saved in the system yet.',
|
||||||
|
'modal_create_title' => '➕ Create New Blocknote',
|
||||||
|
'modal_rename_title' => '✏️ Rename Blocknote',
|
||||||
|
'modal_import_title' => '📥 Import Blocknote (JSON)',
|
||||||
|
'label_title' => 'Blocknote Title',
|
||||||
|
'label_file_json' => 'Upload .json File',
|
||||||
|
'label_paste_json' => 'Or paste JSON code here',
|
||||||
|
'btn_cancel' => 'Cancel',
|
||||||
|
'btn_save' => 'Save',
|
||||||
|
'btn_create' => 'Create Blocknote',
|
||||||
|
'btn_import' => 'Import Blocknote',
|
||||||
|
'note_single' => 'Note',
|
||||||
|
'note_plural' => 'Notes',
|
||||||
|
|
||||||
|
// View Blocknote
|
||||||
|
'btn_back_list' => '⬅️ Back to Blocknotes',
|
||||||
|
'btn_expand_all' => '📂 Expand All',
|
||||||
|
'btn_collapse_all' => '📁 Collapse All',
|
||||||
|
'btn_new_note' => '➕ New Note',
|
||||||
|
'btn_add_subnote' => '➕ Subnote',
|
||||||
|
'subnote_single' => 'subnote',
|
||||||
|
'subnote_plural' => 'subnotes',
|
||||||
|
'badge_text' => '📄 Text',
|
||||||
|
'badge_command' => '⚡ Command',
|
||||||
|
'btn_copy' => '📋 Copy',
|
||||||
|
'btn_copied' => '✓ Copied!',
|
||||||
|
'search_placeholder' => '🔍 Search notes, subnotes, or commands in this blocknote...',
|
||||||
|
'btn_clear_search' => '❌ Clear Search',
|
||||||
|
'search_no_results' => 'No results found',
|
||||||
|
'search_no_results_desc' => 'No notes or commands match your search term in this blocknote.',
|
||||||
|
'modal_add_note' => '➕ New Block Note',
|
||||||
|
'modal_edit_note' => '✏️ Edit Note',
|
||||||
|
'modal_add_subnote' => '➕ Add Subnote',
|
||||||
|
'modal_edit_subnote' => '✏️ Edit Subnote',
|
||||||
|
'label_note_description' => 'Description (optional)',
|
||||||
|
'label_subnote_title' => 'Subnote Title',
|
||||||
|
'label_subnote_type' => 'Subnote Type',
|
||||||
|
'label_subnote_content' => 'Content',
|
||||||
|
'type_option_text' => '📄 Generic Text',
|
||||||
|
'type_option_command' => '⚡ Command (with Copy button)',
|
||||||
|
|
||||||
|
// Profile & Settings
|
||||||
|
'profile_title' => '🎨 Profile & Theme Settings',
|
||||||
|
'profile_desc' => 'Customize password, UI colors, language, and default landing page.',
|
||||||
|
'card_password_title' => '🔒 Change Password',
|
||||||
|
'card_colors_title' => '🎨 Interface Colors (HSL)',
|
||||||
|
'card_lang_title' => '🌍 Interface Language',
|
||||||
|
'card_landing_title' => '🏠 Default Landing Page',
|
||||||
|
'label_new_pass' => 'New Password',
|
||||||
|
'label_confirm_pass' => 'Confirm New Password',
|
||||||
|
'label_select_lang' => 'Select Language',
|
||||||
|
'label_select_landing' => 'Select Landing Page',
|
||||||
|
'landing_option_dashboard' => '📋 Main Selection Page (All Blocknotes)',
|
||||||
|
'landing_option_bn_prefix' => '📌 Open directly: ',
|
||||||
|
'btn_update_pass' => '🔑 Update Password',
|
||||||
|
'btn_save_colors' => '💾 Save Color Preferences',
|
||||||
|
'btn_save_lang' => '🌍 Save Language',
|
||||||
|
'btn_save_landing' => '🏠 Save Landing Page',
|
||||||
|
|
||||||
|
// Admin Users
|
||||||
|
'admin_title' => '👑 User Administration Panel',
|
||||||
|
'admin_desc' => 'Create, manage, and edit user roles and access.',
|
||||||
|
'btn_add_user' => '➕ Add New User',
|
||||||
|
'col_username' => 'Username',
|
||||||
|
'col_role' => 'Role',
|
||||||
|
'col_bns' => 'Blocknotes Created',
|
||||||
|
'col_created' => 'Created Date',
|
||||||
|
'col_actions' => 'Admin Actions',
|
||||||
|
'role_standard' => 'Standard User',
|
||||||
|
'role_admin' => 'Administrator',
|
||||||
|
'role_owner' => '👑 Owner',
|
||||||
|
'badge_owner' => '👑 Owner',
|
||||||
|
'btn_reset_pass' => '🔑 Reset Pass',
|
||||||
|
'modal_create_user' => '➕ Register New User',
|
||||||
|
'modal_reset_pass' => '🔑 Reset User Password',
|
||||||
|
'card_site_settings_title' => '⚙️ Site Settings (Owner Only)',
|
||||||
|
'label_site_title' => 'Global Application / Site Title',
|
||||||
|
'label_favicon' => 'Site Favicon (High Resolution, min 64x64px)',
|
||||||
|
'btn_save_site_title' => '💾 Save Site Title',
|
||||||
|
'btn_upload_favicon' => '📤 Upload New Favicon',
|
||||||
|
|
||||||
|
// Confirm Dialogs
|
||||||
|
'confirm_delete_blocknote' => 'Are you sure you want to delete this blocknote and all its notes?',
|
||||||
|
'confirm_delete_note' => 'Are you sure you want to delete this note and all its subnotes?',
|
||||||
|
'confirm_delete_subnote' => 'Are you sure you want to delete this subnote?',
|
||||||
|
'confirm_delete_user' => 'Are you sure you want to delete this user?',
|
||||||
|
|
||||||
|
// Placeholders
|
||||||
|
'placeholder_bn_title' => 'E.g. Linux Server Commands, PHP Notes...',
|
||||||
|
'placeholder_rename_title' => 'New title...',
|
||||||
|
'placeholder_import_title' => 'E.g. Positive Internet',
|
||||||
|
'placeholder_paste_json' => 'Paste JSON here in old or new format...',
|
||||||
|
'placeholder_note_title' => 'E.g. MySQL Installation, Server Setup...',
|
||||||
|
'placeholder_note_desc' => 'Short description of this note block...',
|
||||||
|
'placeholder_subnote_title' => 'E.g. Database creation command...',
|
||||||
|
'placeholder_subnote_content' => 'Enter text or command instructions...',
|
||||||
|
'placeholder_site_title' => 'E.g. Blocknotes, Enterprise Notes...',
|
||||||
|
'placeholder_username_new' => 'e.g. john, mary...',
|
||||||
|
'placeholder_password_new' => 'Enter new password',
|
||||||
|
'placeholder_password_confirm' => 'Repeat new password',
|
||||||
|
|
||||||
|
// Extra Labels
|
||||||
|
'label_owner_prefix' => 'Owner: ',
|
||||||
|
'label_updated_prefix' => 'Last updated: ',
|
||||||
|
'label_you' => 'You',
|
||||||
|
'label_unknown' => 'Unknown',
|
||||||
|
'label_current_favicon' => 'Current Favicon: ',
|
||||||
|
|
||||||
|
// First Start Installer Setup
|
||||||
|
'setup_title' => '🚀 Blocknotes Initial Setup',
|
||||||
|
'setup_subtitle' => 'Configure MySQL database and create the initial privileged users (Owner & Admin).',
|
||||||
|
'setup_sec1_title' => '1. MySQL Database Configuration',
|
||||||
|
'setup_db_host' => 'Database Host',
|
||||||
|
'setup_db_name' => 'Database Name',
|
||||||
|
'setup_db_user' => 'MySQL User',
|
||||||
|
'setup_db_pass' => 'MySQL Password',
|
||||||
|
'setup_db_pass_placeholder' => 'Password (leave empty if none)',
|
||||||
|
'setup_sec2_title' => '2. Owner Account (Single Owner)',
|
||||||
|
'setup_owner_user' => 'Owner Username',
|
||||||
|
'setup_owner_pass' => 'Owner Password',
|
||||||
|
'setup_owner_pass_placeholder' => 'Enter password for Owner',
|
||||||
|
'setup_sec3_title' => '3. First Admin Account',
|
||||||
|
'setup_admin_user' => 'Admin Username',
|
||||||
|
'setup_admin_pass' => 'Admin Password',
|
||||||
|
'setup_admin_pass_placeholder' => 'Enter password for Admin',
|
||||||
|
'setup_btn_submit' => '⚡ Complete Installation & Configure System',
|
||||||
|
'err_setup_db_fields' => 'Please fill in all MySQL database connection fields.',
|
||||||
|
'err_setup_owner_fields' => 'Please enter username and password for the Owner user.',
|
||||||
|
'err_setup_admin_fields' => 'Please enter username and password for the first Admin user.',
|
||||||
|
'err_setup_same_user' => 'Owner user and Admin user must have different usernames.'
|
||||||
|
],
|
||||||
|
|
||||||
|
'es' => [
|
||||||
|
// Login Page
|
||||||
|
'login_title' => 'Iniciar sesión en Blocknotes',
|
||||||
|
'login_subtitle' => 'Gestiona tus notas y comandos',
|
||||||
|
'label_username' => 'Nombre de Usuario',
|
||||||
|
'label_password' => 'Contraseña',
|
||||||
|
'placeholder_username' => 'Introduce nombre de usuario',
|
||||||
|
'placeholder_password' => 'Introduce contraseña',
|
||||||
|
'btn_submit_login' => '🔑 Iniciar Sesión',
|
||||||
|
'login_hint_footer' => 'Credencial predeterminada para el primer acceso',
|
||||||
|
'login_installed_success' => '🎉 ¡Configuración completada! Inicia sesión con tus credenciales.',
|
||||||
|
|
||||||
|
// Navbar
|
||||||
|
'nav_blocknotes' => '📓 Blocknotes',
|
||||||
|
'nav_colors_profile' => '🎨 Colores y Perfil',
|
||||||
|
'nav_admin' => '👑 Gestión de Usuarios',
|
||||||
|
'nav_logout' => '🚪 Salir',
|
||||||
|
'nav_login' => '🔑 Iniciar Sesión',
|
||||||
|
'nav_language' => '🌐 Idioma',
|
||||||
|
|
||||||
|
// Dashboard (home)
|
||||||
|
'dashboard_title_user' => 'Mis Blocknotes',
|
||||||
|
'dashboard_title_admin' => 'Todos los Blocknotes (Vista Admin)',
|
||||||
|
'dashboard_desc_user' => 'Crea, organiza y consulta tus blocknotes.',
|
||||||
|
'dashboard_desc_admin' => 'Como Admin puedes ver, gestionar y eliminar los blocknotes de todos los usuarios.',
|
||||||
|
'btn_new_blocknote' => '➕ Nuevo Blocknote',
|
||||||
|
'btn_import_json' => '📥 Importar JSON',
|
||||||
|
'btn_export_json' => '💾 Exportar JSON',
|
||||||
|
'btn_open_edit' => '👁️ Abrir y Editar',
|
||||||
|
'btn_rename' => '✏️ Renombrar',
|
||||||
|
'btn_delete' => '🗑️ Eliminar',
|
||||||
|
'empty_blocknotes_title' => 'No se encontraron blocknotes',
|
||||||
|
'empty_blocknotes_desc_user' => 'Aún no has creado ningún blocknote. ¡Empieza ahora!',
|
||||||
|
'empty_blocknotes_desc_admin' => 'Aún no hay blocknotes guardados en el sistema.',
|
||||||
|
'modal_create_title' => '➕ Crear Nuevo Blocknote',
|
||||||
|
'modal_rename_title' => '✏️ Renombrar Blocknote',
|
||||||
|
'modal_import_title' => '📥 Importar Blocknote (JSON)',
|
||||||
|
'label_title' => 'Título del Blocknote',
|
||||||
|
'label_file_json' => 'Subir archivo .json',
|
||||||
|
'label_paste_json' => 'O pega el código JSON aquí',
|
||||||
|
'btn_cancel' => 'Cancelar',
|
||||||
|
'btn_save' => 'Guardar',
|
||||||
|
'btn_create' => 'Crear Blocknote',
|
||||||
|
'btn_import' => 'Importar Blocknote',
|
||||||
|
'note_single' => 'Nota',
|
||||||
|
'note_plural' => 'Notas',
|
||||||
|
|
||||||
|
// View Blocknote
|
||||||
|
'btn_back_list' => '⬅️ Volver a Blocknotes',
|
||||||
|
'btn_expand_all' => '📂 Expandir Todas',
|
||||||
|
'btn_collapse_all' => '📁 Contraer Todas',
|
||||||
|
'btn_new_note' => '➕ Nueva Nota',
|
||||||
|
'btn_add_subnote' => '➕ Subnota',
|
||||||
|
'subnote_single' => 'subnota',
|
||||||
|
'subnote_plural' => 'subnotas',
|
||||||
|
'badge_text' => '📄 Texto',
|
||||||
|
'badge_command' => '⚡ Comando',
|
||||||
|
'btn_copy' => '📋 Copiar',
|
||||||
|
'btn_copied' => '✓ ¡Copiado!',
|
||||||
|
'search_placeholder' => '🔍 Buscar notas, subnotas o comandos...',
|
||||||
|
'btn_clear_search' => '❌ Cancelar Búsqueda',
|
||||||
|
'search_no_results' => 'No se encontraron resultados',
|
||||||
|
'search_no_results_desc' => 'Ninguna nota o comando coincide con tu búsqueda.',
|
||||||
|
'modal_add_note' => '➕ Nueva Nota en Bloque',
|
||||||
|
'modal_edit_note' => '✏️ Editar Nota',
|
||||||
|
'modal_add_subnote' => '➕ Añadir Subnota',
|
||||||
|
'modal_edit_subnote' => '✏️ Editar Subnota',
|
||||||
|
'label_note_description' => 'Descripción (opcional)',
|
||||||
|
'label_subnote_title' => 'Título de Subnota',
|
||||||
|
'label_subnote_type' => 'Tipo de Subnota',
|
||||||
|
'label_subnote_content' => 'Contenido',
|
||||||
|
'type_option_text' => '📄 Texto genérico',
|
||||||
|
'type_option_command' => '⚡ Comando (con botón Copiar)',
|
||||||
|
|
||||||
|
// Profile & Settings
|
||||||
|
'profile_title' => '🎨 Ajustes de Perfil y Tema',
|
||||||
|
'profile_desc' => 'Personaliza contraseña, colores de interfaz, idioma y página de inicio.',
|
||||||
|
'card_password_title' => '🔒 Cambiar Contraseña',
|
||||||
|
'card_colors_title' => '🎨 Colores de Interfaz (HSL)',
|
||||||
|
'card_lang_title' => '🌍 Idioma de la Interfaz',
|
||||||
|
'card_landing_title' => '🏠 Página de Inicio Predeterminada',
|
||||||
|
'label_new_pass' => 'Nueva Contraseña',
|
||||||
|
'label_confirm_pass' => 'Confirmar Nueva Contraseña',
|
||||||
|
'label_select_lang' => 'Seleccionar Idioma',
|
||||||
|
'label_select_landing' => 'Seleccionar Página de Inicio',
|
||||||
|
'landing_option_dashboard' => '📋 Página Principal (Todos los Blocknotes)',
|
||||||
|
'landing_option_bn_prefix' => '📌 Abrir directamente: ',
|
||||||
|
'btn_update_pass' => '🔑 Actualizar Contraseña',
|
||||||
|
'btn_save_colors' => '💾 Guardar Colores',
|
||||||
|
'btn_save_lang' => '🌍 Guardar Idioma',
|
||||||
|
'btn_save_landing' => '🏠 Guardar Página de Inicio',
|
||||||
|
|
||||||
|
// Admin Users
|
||||||
|
'admin_title' => '👑 Panel de Administración de Usuarios',
|
||||||
|
'admin_desc' => 'Crea, gestiona y edita roles de usuarios y accesos.',
|
||||||
|
'btn_add_user' => '➕ Añadir Nuevo Usuario',
|
||||||
|
'col_username' => 'Nombre de Usuario',
|
||||||
|
'col_role' => 'Rol',
|
||||||
|
'col_bns' => 'Blocknotes Creados',
|
||||||
|
'col_created' => 'Fecha de Creación',
|
||||||
|
'col_actions' => 'Acciones de Admin',
|
||||||
|
'role_standard' => 'Usuario Estándar',
|
||||||
|
'role_admin' => 'Administrador',
|
||||||
|
'role_owner' => '👑 Propietario (Owner)',
|
||||||
|
'badge_owner' => '👑 Propietario',
|
||||||
|
'btn_reset_pass' => '🔑 Reset Pass',
|
||||||
|
'modal_create_user' => '➕ Registrar Nuevo Usuario',
|
||||||
|
'modal_reset_pass' => '🔑 Resetear Contraseña',
|
||||||
|
'card_site_settings_title' => '⚙️ Ajustes del Sitio (Solo Propietario)',
|
||||||
|
'label_site_title' => 'Título del Sitio / Aplicación',
|
||||||
|
'label_favicon' => 'Favicon del Sitio (Alta resolución, mín 64x64px)',
|
||||||
|
'btn_save_site_title' => '💾 Guardar Título',
|
||||||
|
'btn_upload_favicon' => '📤 Subir Nueva Favicon',
|
||||||
|
|
||||||
|
// Confirm Dialogs
|
||||||
|
'confirm_delete_blocknote' => '¿Estás seguro de que quieres eliminar este blocknote y todas sus notas?',
|
||||||
|
'confirm_delete_note' => '¿Estás seguro de que quieres eliminar esta nota y todas sus subnotas?',
|
||||||
|
'confirm_delete_subnote' => '¿Estás seguro de que quieres eliminar esta subnota?',
|
||||||
|
'confirm_delete_user' => '¿Estás seguro de que quieres eliminar este usuario?',
|
||||||
|
|
||||||
|
// Placeholders
|
||||||
|
'placeholder_bn_title' => 'Ej. Comandos Servidor Linux...',
|
||||||
|
'placeholder_rename_title' => 'Nuevo título...',
|
||||||
|
'placeholder_import_title' => 'Ej. Positive Internet',
|
||||||
|
'placeholder_paste_json' => 'Pega el JSON aquí en formato antiguo o nuevo...',
|
||||||
|
'placeholder_note_title' => 'Ej. Instalación MySQL...',
|
||||||
|
'placeholder_note_desc' => 'Breve descripción de este bloque de notas...',
|
||||||
|
'placeholder_subnote_title' => 'Ej. Comando creación base de datos...',
|
||||||
|
'placeholder_subnote_content' => 'Introduce el texto o instrucciones del comando...',
|
||||||
|
'placeholder_site_title' => 'Ej. Blocknotes...',
|
||||||
|
'placeholder_username_new' => 'ej. mario, juan...',
|
||||||
|
'placeholder_password_new' => 'Introduce nueva contraseña',
|
||||||
|
'placeholder_password_confirm' => 'Repite nueva contraseña',
|
||||||
|
|
||||||
|
// Extra Labels
|
||||||
|
'label_owner_prefix' => 'Propietario: ',
|
||||||
|
'label_updated_prefix' => 'Última actualización: ',
|
||||||
|
'label_you' => 'Tú',
|
||||||
|
'label_unknown' => 'Desconocido',
|
||||||
|
'label_current_favicon' => 'Favicon Actual: ',
|
||||||
|
|
||||||
|
// First Start Installer Setup
|
||||||
|
'setup_title' => '🚀 Configuración Inicial de Blocknotes',
|
||||||
|
'setup_subtitle' => 'Configura la base de datos MySQL y crea los primeros usuarios privilegiados (Propietario y Admin).',
|
||||||
|
'setup_sec1_title' => '1. Configuración de Base de Datos MySQL',
|
||||||
|
'setup_db_host' => 'Host de Base de Datos',
|
||||||
|
'setup_db_name' => 'Nombre de la Base de Datos',
|
||||||
|
'setup_db_user' => 'Usuario MySQL',
|
||||||
|
'setup_db_pass' => 'Contraseña MySQL',
|
||||||
|
'setup_db_pass_placeholder' => 'Contraseña (deja en blanco si no hay)',
|
||||||
|
'setup_sec2_title' => '2. Cuenta de Propietario (Único)',
|
||||||
|
'setup_owner_user' => 'Nombre de Usuario Propietario',
|
||||||
|
'setup_owner_pass' => 'Contraseña de Propietario',
|
||||||
|
'setup_owner_pass_placeholder' => 'Introduce contraseña para Propietario',
|
||||||
|
'setup_sec3_title' => '3. Primera Cuenta de Administrador (Admin)',
|
||||||
|
'setup_admin_user' => 'Nombre de Usuario Admin',
|
||||||
|
'setup_admin_pass' => 'Contraseña de Admin',
|
||||||
|
'setup_admin_pass_placeholder' => 'Introduce contraseña para Admin',
|
||||||
|
'setup_btn_submit' => '⚡ Completar Instalación y Configurar Sistema',
|
||||||
|
'err_setup_db_fields' => 'Por favor completa todos los campos de conexión a la base de datos MySQL.',
|
||||||
|
'err_setup_owner_fields' => 'Por favor introduce nombre de usuario y contraseña para el Propietario.',
|
||||||
|
'err_setup_admin_fields' => 'Por favor introduce nombre de usuario y contraseña para el primer Admin.',
|
||||||
|
'err_setup_same_user' => 'El usuario Propietario y el usuario Admin deben tener nombres diferentes.'
|
||||||
|
],
|
||||||
|
|
||||||
|
'fr' => [
|
||||||
|
// Login Page
|
||||||
|
'login_title' => 'Connexion à Blocknotes',
|
||||||
|
'login_subtitle' => 'Gérez vos notes et vos commandes',
|
||||||
|
'label_username' => 'Nom d\'Utilisateur',
|
||||||
|
'label_password' => 'Mot de Passe',
|
||||||
|
'placeholder_username' => 'Entrez nom d\'utilisateur',
|
||||||
|
'placeholder_password' => 'Entrez mot de passe',
|
||||||
|
'btn_submit_login' => '🔑 Connexion',
|
||||||
|
'login_hint_footer' => 'Identifiant par défaut pour le premier accès',
|
||||||
|
'login_installed_success' => '🎉 Configuration terminée! Connectez-vous avec vos identifiants.',
|
||||||
|
|
||||||
|
// Navbar
|
||||||
|
'nav_blocknotes' => '📓 Blocknotes',
|
||||||
|
'nav_colors_profile' => '🎨 Couleurs & Profil',
|
||||||
|
'nav_admin' => '👑 Gestion des Utilisateurs',
|
||||||
|
'nav_logout' => '🚪 Déconnexion',
|
||||||
|
'nav_login' => '🔑 Connexion',
|
||||||
|
'nav_language' => '🌐 Langue',
|
||||||
|
|
||||||
|
// Dashboard (home)
|
||||||
|
'dashboard_title_user' => 'Mes Blocknotes',
|
||||||
|
'dashboard_title_admin' => 'Tous les Blocknotes (Vue Admin)',
|
||||||
|
'dashboard_desc_user' => 'Créez, organisez et consultez vos blocknotes.',
|
||||||
|
'dashboard_desc_admin' => 'En tant qu\'Admin vous pouvez afficher, gérer et supprimer les blocknotes de tous les utilisateurs.',
|
||||||
|
'btn_new_blocknote' => '➕ Nouveau Blocknote',
|
||||||
|
'btn_import_json' => '📥 Importer JSON',
|
||||||
|
'btn_export_json' => '💾 Exporter JSON',
|
||||||
|
'btn_open_edit' => '👁️ Ouvrir & Modifier',
|
||||||
|
'btn_rename' => '✏️ Renommer',
|
||||||
|
'btn_delete' => '🗑️ Supprimer',
|
||||||
|
'empty_blocknotes_title' => 'Aucun blocknote trouvé',
|
||||||
|
'empty_blocknotes_desc_user' => 'Vous n\'avez pas encore créé de blocknote. Commencez dès maintenant!',
|
||||||
|
'empty_blocknotes_desc_admin' => 'Il n\'y a encore aucun blocknote enregistré dans le système.',
|
||||||
|
'modal_create_title' => '➕ Créer un Nouveau Blocknote',
|
||||||
|
'modal_rename_title' => '✏️ Renommer le Blocknote',
|
||||||
|
'modal_import_title' => '📥 Importer Blocknote (JSON)',
|
||||||
|
'label_title' => 'Titre du Blocknote',
|
||||||
|
'label_file_json' => 'Téléverser le fichier .json',
|
||||||
|
'label_paste_json' => 'Ou collez le code JSON ici',
|
||||||
|
'btn_cancel' => 'Annuler',
|
||||||
|
'btn_save' => 'Enregistrer',
|
||||||
|
'btn_create' => 'Créer Blocknote',
|
||||||
|
'btn_import' => 'Importer Blocknote',
|
||||||
|
'note_single' => 'Note',
|
||||||
|
'note_plural' => 'Notes',
|
||||||
|
|
||||||
|
// View Blocknote
|
||||||
|
'btn_back_list' => '⬅️ Retour aux Blocknotes',
|
||||||
|
'btn_expand_all' => '📂 Tout Développer',
|
||||||
|
'btn_collapse_all' => '📁 Tout Réduire',
|
||||||
|
'btn_new_note' => '➕ Nouvelle Note',
|
||||||
|
'btn_add_subnote' => '➕ Sous-note',
|
||||||
|
'subnote_single' => 'sous-note',
|
||||||
|
'subnote_plural' => 'sous-notes',
|
||||||
|
'badge_text' => '📄 Texte',
|
||||||
|
'badge_command' => '⚡ Commande',
|
||||||
|
'btn_copy' => '📋 Copier',
|
||||||
|
'btn_copied' => '✓ Copié!',
|
||||||
|
'search_placeholder' => '🔍 Rechercher notes, sous-notes ou commandes...',
|
||||||
|
'btn_clear_search' => '❌ Annuler la Recherche',
|
||||||
|
'search_no_results' => 'Aucun résultat trouvé',
|
||||||
|
'search_no_results_desc' => 'Aucune note ou commande ne correspond à votre recherche.',
|
||||||
|
'modal_add_note' => '➕ Nouvelle Note par Bloc',
|
||||||
|
'modal_edit_note' => '✏️ Modifier la Note',
|
||||||
|
'modal_add_subnote' => '➕ Ajouter une Sous-note',
|
||||||
|
'modal_edit_subnote' => '✏️ Modifier la Sous-note',
|
||||||
|
'label_note_description' => 'Description (facultatif)',
|
||||||
|
'label_subnote_title' => 'Titre de la Sous-note',
|
||||||
|
'label_subnote_type' => 'Type de Sous-note',
|
||||||
|
'label_subnote_content' => 'Contenu',
|
||||||
|
'type_option_text' => '📄 Texte générique',
|
||||||
|
'type_option_command' => '⚡ Commande (avec bouton Copier)',
|
||||||
|
|
||||||
|
// Profile & Settings
|
||||||
|
'profile_title' => '🎨 Paramètres Profil & Thème',
|
||||||
|
'profile_desc' => 'Personnalisez votre mot de passe, couleurs, langue et page d\'accueil.',
|
||||||
|
'card_password_title' => '🔒 Changer le Mot de Passe',
|
||||||
|
'card_colors_title' => '🎨 Couleurs de l\'Interface (HSL)',
|
||||||
|
'card_lang_title' => '🌍 Langue de l\'Interface',
|
||||||
|
'card_landing_title' => '🏠 Page d\'Accueil par Défaut',
|
||||||
|
'label_new_pass' => 'Nouveau Mot de Passe',
|
||||||
|
'label_confirm_pass' => 'Confirmer le Mot de Passe',
|
||||||
|
'label_select_lang' => 'Sélectionner la Langue',
|
||||||
|
'label_select_landing' => 'Sélectionner la Page d\'Accueil',
|
||||||
|
'landing_option_dashboard' => '📋 Page Principale (Tous les Blocknotes)',
|
||||||
|
'landing_option_bn_prefix' => '📌 Ouvrir directement: ',
|
||||||
|
'btn_update_pass' => '🔑 Mettre à Jour',
|
||||||
|
'btn_save_colors' => '💾 Enregistrer les Couleurs',
|
||||||
|
'btn_save_lang' => '🌍 Enregistrer la Langue',
|
||||||
|
'btn_save_landing' => '🏠 Enregistrer la Page d\'Accueil',
|
||||||
|
|
||||||
|
// Admin Users
|
||||||
|
'admin_title' => '👑 Panneau d\'Administration Utilisateurs',
|
||||||
|
'admin_desc' => 'Créez, gérez et modifiez les rôles et accès des utilisateurs.',
|
||||||
|
'btn_add_user' => '➕ Ajouter un Utilisateur',
|
||||||
|
'col_username' => 'Nom d\'Utilisateur',
|
||||||
|
'col_role' => 'Rôle',
|
||||||
|
'col_bns' => 'Blocknotes Créés',
|
||||||
|
'col_created' => 'Date de Création',
|
||||||
|
'col_actions' => 'Actions Admin',
|
||||||
|
'role_standard' => 'Utilisateur Standard',
|
||||||
|
'role_admin' => 'Administrateur',
|
||||||
|
'role_owner' => '👑 Propriétaire (Owner)',
|
||||||
|
'badge_owner' => '👑 Propriétaire',
|
||||||
|
'btn_reset_pass' => '🔑 Réinit Pass',
|
||||||
|
'modal_create_user' => '➕ Enregistrer un Utilisateur',
|
||||||
|
'modal_reset_pass' => '🔑 Réinitialiser le Mot de Passe',
|
||||||
|
'card_site_settings_title' => '⚙️ Paramètres du Site (Propriétaire Uniquement)',
|
||||||
|
'label_site_title' => 'Titre Global du Site',
|
||||||
|
'label_favicon' => 'Favicon du Site (Haute résolution, min 64x64px)',
|
||||||
|
'btn_save_site_title' => '💾 Enregistrer le Titre',
|
||||||
|
'btn_upload_favicon' => '📤 Téléverser la Favicon',
|
||||||
|
|
||||||
|
// Confirm Dialogs
|
||||||
|
'confirm_delete_blocknote' => 'Êtes-vous sûr de vouloir supprimer ce blocknote et toutes ses notes?',
|
||||||
|
'confirm_delete_note' => 'Êtes-vous sûr de vouloir supprimer cette note et toutes ses sous-notes?',
|
||||||
|
'confirm_delete_subnote' => 'Êtes-vous sûr de vouloir supprimer cette sous-note?',
|
||||||
|
'confirm_delete_user' => 'Êtes-vous sûr de vouloir supprimer cet utilisateur?',
|
||||||
|
|
||||||
|
// Placeholders
|
||||||
|
'placeholder_bn_title' => 'Ex. Commandes Serveur Linux...',
|
||||||
|
'placeholder_rename_title' => 'Nouveau titre...',
|
||||||
|
'placeholder_import_title' => 'Ex. Positive Internet',
|
||||||
|
'placeholder_paste_json' => 'Collez le JSON ici au format ancien ou nouveau...',
|
||||||
|
'placeholder_note_title' => 'Ex. Installation MySQL...',
|
||||||
|
'placeholder_note_desc' => 'Brève description de ce bloc de notes...',
|
||||||
|
'placeholder_subnote_title' => 'Ex. Commande création base de données...',
|
||||||
|
'placeholder_subnote_content' => 'Entrez le texte ou les instructions de la commande...',
|
||||||
|
'placeholder_site_title' => 'Ex. Blocknotes...',
|
||||||
|
'placeholder_username_new' => 'ex. pierre, marie...',
|
||||||
|
'placeholder_password_new' => 'Entrez nouveau mot de passe',
|
||||||
|
'placeholder_password_confirm' => 'Répétez le mot de passe',
|
||||||
|
|
||||||
|
// Extra Labels
|
||||||
|
'label_owner_prefix' => 'Propriétaire: ',
|
||||||
|
'label_updated_prefix' => 'Dernière mise à jour: ',
|
||||||
|
'label_you' => 'Vous',
|
||||||
|
'label_unknown' => 'Inconnu',
|
||||||
|
'label_current_favicon' => 'Favicon Actuelle: ',
|
||||||
|
|
||||||
|
// First Start Installer Setup
|
||||||
|
'setup_title' => '🚀 Configuration Initiale de Blocknotes',
|
||||||
|
'setup_subtitle' => 'Configurez la base de données MySQL et créez les premiers utilisateurs privilégiés (Propriétaire et Admin).',
|
||||||
|
'setup_sec1_title' => '1. Configuration de la Base de Données MySQL',
|
||||||
|
'setup_db_host' => 'Hôte de la Base de Données',
|
||||||
|
'setup_db_name' => 'Nom de la Base de Données',
|
||||||
|
'setup_db_user' => 'Utilisateur MySQL',
|
||||||
|
'setup_db_pass' => 'Mot de Passe MySQL',
|
||||||
|
'setup_db_pass_placeholder' => 'Mot de passe (laissez vide si aucun)',
|
||||||
|
'setup_sec2_title' => '2. Compte Propriétaire (Unique)',
|
||||||
|
'setup_owner_user' => 'Nom d\'Utilisateur Propriétaire',
|
||||||
|
'setup_owner_pass' => 'Mot de Passe Propriétaire',
|
||||||
|
'setup_owner_pass_placeholder' => 'Entrez le mot de passe pour le Propriétaire',
|
||||||
|
'setup_sec3_title' => '3. Premier Compte Administrateur (Admin)',
|
||||||
|
'setup_admin_user' => 'Nom d\'Utilisateur Admin',
|
||||||
|
'setup_admin_pass' => 'Mot de Passe Admin',
|
||||||
|
'setup_admin_pass_placeholder' => 'Entrez le mot de passe pour Admin',
|
||||||
|
'setup_btn_submit' => '⚡ Terminer l\'Installation et Configurer le Système',
|
||||||
|
'err_setup_db_fields' => 'Veuillez remplir tous les champs de connexion à la base de données MySQL.',
|
||||||
|
'err_setup_owner_fields' => 'Veuillez entrer le nom d\'utilisateur et le mot de passe pour le Propriétaire.',
|
||||||
|
'err_setup_admin_fields' => 'Veuillez entrer le nom d\'utilisateur et le mot de passe pour le premier Admin.',
|
||||||
|
'err_setup_same_user' => 'L\'utilisateur Propriétaire et l\'utilisateur Admin doivent avoir des noms différents.'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate key to current active language string.
|
||||||
|
*/
|
||||||
|
function __($key, $default = '') {
|
||||||
|
$lang = get_current_lang();
|
||||||
|
$translations = get_translations();
|
||||||
|
|
||||||
|
if (isset($translations[$lang][$key])) {
|
||||||
|
return $translations[$lang][$key];
|
||||||
|
}
|
||||||
|
if (isset($translations['it'][$key])) {
|
||||||
|
return $translations['it'][$key];
|
||||||
|
}
|
||||||
|
if (isset($translations['en'][$key])) {
|
||||||
|
return $translations['en'][$key];
|
||||||
|
}
|
||||||
|
return !empty($default) ? $default : $key;
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Global Site Settings & High-Resolution Favicon Management (Owner Exclusive)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get site setting value by key.
|
||||||
|
*/
|
||||||
|
function get_site_setting($key, $default = '') {
|
||||||
|
global $conn;
|
||||||
|
if (!isset($conn)) return $default;
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("SELECT setting_value FROM site_settings WHERE setting_key = ? LIMIT 1");
|
||||||
|
$stmt->bind_param("s", $key);
|
||||||
|
$stmt->execute();
|
||||||
|
$res = $stmt->get_result();
|
||||||
|
|
||||||
|
if ($row = $res->fetch_assoc()) {
|
||||||
|
$stmt->close();
|
||||||
|
return $row['setting_value'];
|
||||||
|
}
|
||||||
|
$stmt->close();
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update or insert site setting.
|
||||||
|
*/
|
||||||
|
function update_site_setting($key, $value) {
|
||||||
|
global $conn;
|
||||||
|
if (!isset($conn)) return false;
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("INSERT INTO site_settings (setting_key, setting_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
|
||||||
|
$stmt->bind_param("ss", $key, $value);
|
||||||
|
$res = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload and validate high-resolution site Favicon.
|
||||||
|
*/
|
||||||
|
function upload_site_favicon($file) {
|
||||||
|
if (empty($file) || $file['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
return ['success' => false, 'message' => 'Errore nel caricamento del file favicon.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||||
|
$allowed_extensions = ['png', 'ico', 'webp', 'svg', 'jpeg', 'jpg'];
|
||||||
|
|
||||||
|
if (!in_array($ext, $allowed_extensions)) {
|
||||||
|
return ['success' => false, 'message' => 'Formato favicon non supportato. Formati ammessi: PNG, ICO, WEBP, SVG, JPG.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// High resolution dimension check for raster formats (PNG, WEBP, JPG, ICO)
|
||||||
|
if (in_array($ext, ['png', 'webp', 'jpeg', 'jpg', 'ico'])) {
|
||||||
|
$image_info = @getimagesize($file['tmp_name']);
|
||||||
|
if ($image_info) {
|
||||||
|
$width = $image_info[0];
|
||||||
|
$height = $image_info[1];
|
||||||
|
|
||||||
|
// Minimum high resolution requirement: 64x64px
|
||||||
|
if ($width < 64 || $height < 64) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => "Risoluzione favicon troppo bassa ({$width}x{$height}px). Per garantire la massima qualità visiva, carica un'immagine ad alta risoluzione (almeno 64x64px)."
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure assets/img directory exists
|
||||||
|
$img_dir = ROOT_DIR . "assets/img/";
|
||||||
|
if (!is_dir($img_dir)) {
|
||||||
|
@mkdir($img_dir, 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_filename = "favicon_" . time() . "." . $ext;
|
||||||
|
$target_file = $img_dir . $new_filename;
|
||||||
|
$relative_path = "assets/img/" . $new_filename;
|
||||||
|
|
||||||
|
if (move_uploaded_file($file['tmp_name'], $target_file)) {
|
||||||
|
update_site_setting('site_favicon', $relative_path);
|
||||||
|
return ['success' => true, 'message' => 'Favicon aggiornata con successo!'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['success' => false, 'message' => 'Impossibile salvare il file favicon su disco.'];
|
||||||
|
}
|
||||||
@@ -0,0 +1,270 @@
|
|||||||
|
<?php
|
||||||
|
require_admin();
|
||||||
|
|
||||||
|
$msg_success = "";
|
||||||
|
$msg_error = "";
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$action = $_POST['action'] ?? '';
|
||||||
|
|
||||||
|
if ($action === 'create_user') {
|
||||||
|
$res = admin_create_user($_POST['username'] ?? '', $_POST['password'] ?? '', $_POST['role'] ?? 'user');
|
||||||
|
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
|
||||||
|
} elseif ($action === 'delete_user') {
|
||||||
|
$res = admin_delete_user($_POST['user_id'] ?? 0);
|
||||||
|
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
|
||||||
|
} elseif ($action === 'update_role') {
|
||||||
|
$res = admin_update_user_role($_POST['user_id'] ?? 0, $_POST['role'] ?? 'user');
|
||||||
|
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
|
||||||
|
} elseif ($action === 'reset_password') {
|
||||||
|
$res = update_user_password($_POST['user_id'] ?? 0, $_POST['new_password'] ?? '');
|
||||||
|
if ($res['success']) $msg_success = "Password resettata con successo!"; else $msg_error = $res['message'];
|
||||||
|
} elseif ($action === 'update_site_title' && is_owner()) {
|
||||||
|
$new_title = trim($_POST['site_title'] ?? '');
|
||||||
|
if (!empty($new_title)) {
|
||||||
|
update_site_setting('site_title', $new_title);
|
||||||
|
$msg_success = "Titolo del sito aggiornato con successo!";
|
||||||
|
} else {
|
||||||
|
$msg_error = "Il titolo del sito non può essere vuoto.";
|
||||||
|
}
|
||||||
|
} elseif ($action === 'upload_favicon' && is_owner()) {
|
||||||
|
$res = upload_site_favicon($_FILES['favicon_file'] ?? null);
|
||||||
|
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all users and all blocknotes to count per user
|
||||||
|
$users_list = get_all_users();
|
||||||
|
$all_blocknotes = get_all_blocknotes();
|
||||||
|
|
||||||
|
// Map blocknote count per username
|
||||||
|
$bn_counts = [];
|
||||||
|
foreach ($all_blocknotes as $bn) {
|
||||||
|
$uname = strtolower($bn['username'] ?? '');
|
||||||
|
if ($uname) {
|
||||||
|
$bn_counts[$uname] = ($bn_counts[$uname] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$site_title = get_site_setting('site_title', 'sld-blocknotes');
|
||||||
|
$site_favicon = get_site_setting('site_favicon', 'assets/img/favicon.png');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="main-content">
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="page-title-group">
|
||||||
|
<h1><?= __('admin_title') ?></h1>
|
||||||
|
<p><?= __('admin_desc') ?></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-primary" data-modal-target="modal-create-user">
|
||||||
|
<?= __('btn_add_user') ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!empty($msg_success)): ?>
|
||||||
|
<div class="alert alert-success">
|
||||||
|
✅ <?= htmlspecialchars($msg_success) ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($msg_error)): ?>
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
⚠️ <?= htmlspecialchars($msg_error) ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (is_owner()): ?>
|
||||||
|
<!-- CARD IMPOSTAZIONI SITO (ESCLUSIVO OWNER) -->
|
||||||
|
<div class="card" style="margin-bottom: 2rem; border-color: rgba(234, 179, 8, 0.3); background: rgba(234, 179, 8, 0.03);">
|
||||||
|
<div style="display: flex; align-items: center; gap: 0.6rem; margin-bottom: 1.25rem;">
|
||||||
|
<span style="font-size: 1.5rem;">👑</span>
|
||||||
|
<h2 style="font-size: 1.3rem; font-weight: 700; color: #fde047; margin: 0;"><?= __('card_site_settings_title') ?></h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 2rem;">
|
||||||
|
<!-- Modifica Titolo Sito -->
|
||||||
|
<form action="index.php?page=admin_users" method="POST">
|
||||||
|
<input type="hidden" name="action" value="update_site_title">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="site_title"><?= __('label_site_title') ?></label>
|
||||||
|
<input type="text" name="site_title" id="site_title" class="form-control" value="<?= htmlspecialchars($site_title) ?>" required placeholder="<?= htmlspecialchars(__('placeholder_site_title')) ?>">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary" style="margin-top: 0.5rem;">
|
||||||
|
<?= __('btn_save_site_title') ?>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Caricamento Favicon -->
|
||||||
|
<form action="index.php?page=admin_users" method="POST" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="action" value="upload_favicon">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="favicon_file"><?= __('label_favicon') ?></label>
|
||||||
|
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 0.75rem;">
|
||||||
|
<?php if (!empty($site_favicon) && file_exists(ROOT_DIR . $site_favicon)): ?>
|
||||||
|
<img src="<?= htmlspecialchars($site_favicon) ?>" alt="Favicon Preview" style="width: 42px; height: 42px; object-fit: contain; border-radius: 8px; border: 1px solid rgba(255,255,255,0.2); background: #000; padding: 4px;">
|
||||||
|
<?php endif; ?>
|
||||||
|
<span style="font-size: 0.82rem; color: #94a3b8;"><?= __('label_current_favicon') ?> <code><?= htmlspecialchars($site_favicon) ?></code></span>
|
||||||
|
</div>
|
||||||
|
<input type="file" name="favicon_file" id="favicon_file" class="form-control" accept=".png, .ico, .webp, .svg, .jpg, .jpeg" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-secondary" style="margin-top: 0.5rem;">
|
||||||
|
<?= __('btn_upload_favicon') ?>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- TABELLA UTENTI -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="custom-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th><?= __('col_username') ?></th>
|
||||||
|
<th><?= __('col_role') ?></th>
|
||||||
|
<th><?= __('col_bns') ?></th>
|
||||||
|
<th><?= __('col_created') ?></th>
|
||||||
|
<th style="text-align: right;"><?= __('col_actions') ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($users_list as $u): ?>
|
||||||
|
<?php
|
||||||
|
$uname_lower = strtolower($u['username']);
|
||||||
|
$count = $bn_counts[$uname_lower] ?? 0;
|
||||||
|
$is_self = (current_user()['id'] == $u['id']);
|
||||||
|
$is_user_owner = ($u['role'] === 'owner');
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td>#<?= (int)$u['id'] ?></td>
|
||||||
|
<td>
|
||||||
|
<strong style="color: #ffffff; font-size: 0.95rem;"><?= htmlspecialchars($u['username']) ?></strong>
|
||||||
|
<?php if ($is_self): ?>
|
||||||
|
<span style="font-size:0.75rem; color: #7dd3fc; background: rgba(14,165,233,0.15); padding: 0.15rem 0.4rem; border-radius: 4px; margin-left: 0.4rem;"><?= __('label_you') ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?php if ($is_user_owner): ?>
|
||||||
|
<span class="user-badge badge-owner" style="background: rgba(234, 179, 8, 0.25); border-color: rgba(234, 179, 8, 0.5); color: #fde047;">
|
||||||
|
👑 <?= __('badge_owner') ?>
|
||||||
|
</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<form action="index.php?page=admin_users" method="POST" style="display:inline-block;">
|
||||||
|
<input type="hidden" name="action" value="update_role">
|
||||||
|
<input type="hidden" name="user_id" value="<?= (int)$u['id'] ?>">
|
||||||
|
<select name="role" onchange="this.form.submit()" class="form-control" style="padding: 0.25rem 0.5rem; font-size: 0.8rem; width: auto; background: rgba(0,0,0,0.3); cursor: pointer;" <?= ($is_self || $is_user_owner) ? 'disabled' : '' ?>>
|
||||||
|
<option value="user" <?= ($u['role'] === 'user') ? 'selected' : '' ?>><?= __('role_standard') ?></option>
|
||||||
|
<option value="admin" <?= ($u['role'] === 'admin') ? 'selected' : '' ?>><?= __('role_admin') ?></option>
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="index.php?page=home&view=all" style="color: #4ade80; font-weight: 600;">
|
||||||
|
📓 <?= $count ?> Blocknotes
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td style="color: #94a3b8; font-size: 0.85rem;">
|
||||||
|
<?= date('d/m/Y H:i', strtotime($u['created_at'])) ?>
|
||||||
|
</td>
|
||||||
|
<td style="text-align: right;">
|
||||||
|
<div style="display: flex; gap: 0.4rem; justify-content: flex-end;">
|
||||||
|
<button class="btn btn-sm btn-dark" onclick="openResetPasswordModal(<?= (int)$u['id'] ?>, '<?= htmlspecialchars(addslashes($u['username'])) ?>')">
|
||||||
|
<?= __('btn_reset_pass') ?>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<?php if (!$is_self && !$is_user_owner): ?>
|
||||||
|
<form action="index.php?page=admin_users" method="POST" onsubmit="return confirm('<?= htmlspecialchars(addslashes(__('confirm_delete_user'))) ?>');" style="display:inline;">
|
||||||
|
<input type="hidden" name="action" value="delete_user">
|
||||||
|
<input type="hidden" name="user_id" value="<?= (int)$u['id'] ?>">
|
||||||
|
<button type="submit" class="btn btn-sm btn-danger" title="<?= __('btn_delete') ?>">
|
||||||
|
🗑️ <?= __('btn_delete') ?>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- MODAL CREA UTENTE -->
|
||||||
|
<div class="modal-backdrop" id="modal-create-user">
|
||||||
|
<div class="modal-card">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3><?= __('modal_create_user') ?></h3>
|
||||||
|
<button class="modal-close-btn">×</button>
|
||||||
|
</div>
|
||||||
|
<form action="index.php?page=admin_users" method="POST">
|
||||||
|
<input type="hidden" name="action" value="create_user">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="new_username"><?= __('col_username') ?></label>
|
||||||
|
<input type="text" name="username" id="new_username" class="form-control" required placeholder="<?= htmlspecialchars(__('placeholder_username_new')) ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="new_user_password"><?= __('label_password') ?></label>
|
||||||
|
<input type="password" name="password" id="new_user_password" class="form-control" required placeholder="<?= htmlspecialchars(__('placeholder_password')) ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="new_role"><?= __('col_role') ?></label>
|
||||||
|
<select name="role" id="new_role" class="form-control">
|
||||||
|
<option value="user"><?= __('role_standard') ?></option>
|
||||||
|
<option value="admin"><?= __('role_admin') ?></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.5rem;">
|
||||||
|
<button type="button" class="btn btn-dark modal-close-trigger"><?= __('btn_cancel') ?></button>
|
||||||
|
<button type="submit" class="btn btn-primary"><?= __('btn_save') ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MODAL RESET PASSWORD UTENTE -->
|
||||||
|
<div class="modal-backdrop" id="modal-reset-password">
|
||||||
|
<div class="modal-card">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3><?= __('modal_reset_pass') ?></h3>
|
||||||
|
<button class="modal-close-btn">×</button>
|
||||||
|
</div>
|
||||||
|
<form action="index.php?page=admin_users" method="POST">
|
||||||
|
<input type="hidden" name="action" value="reset_password">
|
||||||
|
<input type="hidden" name="user_id" id="reset_user_id">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label><?= __('col_username') ?></label>
|
||||||
|
<input type="text" id="reset_username_display" class="form-control" disabled style="opacity: 0.7;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="reset_new_password"><?= __('label_new_pass') ?></label>
|
||||||
|
<input type="password" name="new_password" id="reset_new_password" class="form-control" required placeholder="<?= htmlspecialchars(__('placeholder_password_new')) ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.5rem;">
|
||||||
|
<button type="button" class="btn btn-dark modal-close-trigger"><?= __('btn_cancel') ?></button>
|
||||||
|
<button type="submit" class="btn btn-secondary"><?= __('btn_save') ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function openResetPasswordModal(userId, username) {
|
||||||
|
document.getElementById('reset_user_id').value = userId;
|
||||||
|
document.getElementById('reset_username_display').value = username;
|
||||||
|
document.getElementById('modal-reset-password').classList.add('active');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
require_login();
|
||||||
|
|
||||||
|
$user = current_user();
|
||||||
|
$blocknote_id = $_GET['id'] ?? '';
|
||||||
|
$bn = get_blocknote_by_id($blocknote_id);
|
||||||
|
|
||||||
|
if (!$bn || !can_access_blocknote($bn, $user['username'], is_admin())) {
|
||||||
|
header("Location: index.php?page=home");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean output buffer before outputting binary/file response
|
||||||
|
if (ob_get_level()) {
|
||||||
|
ob_end_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
$safe_title = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $bn['title']);
|
||||||
|
$filename = "blocknote_" . $safe_title . "_" . $bn['id'] . ".json";
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
||||||
|
header('Pragma: no-cache');
|
||||||
|
header('Expires: 0');
|
||||||
|
|
||||||
|
echo json_encode($bn, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||||
|
exit;
|
||||||
+231
-4
@@ -1,6 +1,233 @@
|
|||||||
|
<?php
|
||||||
|
require_login();
|
||||||
|
|
||||||
|
$user = current_user();
|
||||||
|
|
||||||
|
// Check landing page redirection if default blocknote is set and user is not requesting explicitly view=all
|
||||||
|
if (!isset($_GET['view']) && !empty($user['default_blocknote'])) {
|
||||||
|
$def_bn = get_blocknote_by_id($user['default_blocknote']);
|
||||||
|
if ($def_bn && can_access_blocknote($def_bn, $user['username'], is_admin())) {
|
||||||
|
header("Location: index.php?page=view_blocknote&id=" . urlencode($user['default_blocknote']));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$msg_success = "";
|
||||||
|
$msg_error = "";
|
||||||
|
|
||||||
|
// Handle Form Submissions
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$action = $_POST['action'] ?? '';
|
||||||
|
|
||||||
|
if ($action === 'create_blocknote') {
|
||||||
|
$res = create_blocknote($_POST['title'] ?? '', $user['username']);
|
||||||
|
if ($res['success']) {
|
||||||
|
$msg_success = $res['message'];
|
||||||
|
} else {
|
||||||
|
$msg_error = $res['message'];
|
||||||
|
}
|
||||||
|
} elseif ($action === 'rename_blocknote') {
|
||||||
|
$res = rename_blocknote($_POST['id'] ?? '', $_POST['title'] ?? '', $user['username'], is_admin());
|
||||||
|
if ($res['success']) {
|
||||||
|
$msg_success = $res['message'];
|
||||||
|
} else {
|
||||||
|
$msg_error = $res['message'];
|
||||||
|
}
|
||||||
|
} elseif ($action === 'delete_blocknote') {
|
||||||
|
$res = delete_blocknote($_POST['id'] ?? '', $user['username'], is_admin());
|
||||||
|
if ($res['success']) {
|
||||||
|
$msg_success = $res['message'];
|
||||||
|
} else {
|
||||||
|
$msg_error = $res['message'];
|
||||||
|
}
|
||||||
|
} elseif ($action === 'import_blocknote') {
|
||||||
|
$json_content = "";
|
||||||
|
if (!empty($_FILES['json_file']['tmp_name'])) {
|
||||||
|
$json_content = file_get_contents($_FILES['json_file']['tmp_name']);
|
||||||
|
} else {
|
||||||
|
$json_content = $_POST['json_text'] ?? '';
|
||||||
|
}
|
||||||
|
$res = import_blocknote_json($json_content, $user['username'], $_POST['title'] ?? '');
|
||||||
|
if ($res['success']) {
|
||||||
|
$msg_success = $res['message'];
|
||||||
|
} else {
|
||||||
|
$msg_error = $res['message'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch blocknotes
|
||||||
|
$blocknotes = get_user_blocknotes($user['username'], is_admin());
|
||||||
|
?>
|
||||||
|
|
||||||
<main class="main-content">
|
<main class="main-content">
|
||||||
<section>
|
<div class="page-header">
|
||||||
<h1>Welcome to the Home Page</h1>
|
<div class="page-title-group">
|
||||||
<p>This is the home page.</p>
|
<h1>📓 <?= is_admin() ? __('dashboard_title_admin') : __('dashboard_title_user') ?></h1>
|
||||||
</section>
|
<p><?= is_admin() ? __('dashboard_desc_admin') : __('dashboard_desc_user') ?></p>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
||||||
|
<button class="btn btn-dark" data-modal-target="modal-import-blocknote">
|
||||||
|
<?= __('btn_import_json') ?>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary" data-modal-target="modal-create-blocknote">
|
||||||
|
<?= __('btn_new_blocknote') ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!empty($msg_success)): ?>
|
||||||
|
<div class="alert alert-success">
|
||||||
|
✅ <?= htmlspecialchars($msg_success) ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($msg_error)): ?>
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
⚠️ <?= htmlspecialchars($msg_error) ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (empty($blocknotes)): ?>
|
||||||
|
<div class="empty-state">
|
||||||
|
<div class="empty-state-icon">📋</div>
|
||||||
|
<h3><?= __('empty_blocknotes_title') ?></h3>
|
||||||
|
<p><?= is_admin() ? __('empty_blocknotes_desc_admin') : __('empty_blocknotes_desc_user') ?></p>
|
||||||
|
<button class="btn btn-primary" data-modal-target="modal-create-blocknote">
|
||||||
|
<?= __('btn_new_blocknote') ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="blocknotes-grid">
|
||||||
|
<?php foreach ($blocknotes as $bn): ?>
|
||||||
|
<?php
|
||||||
|
$note_count = count($bn['notes'] ?? []);
|
||||||
|
$is_owner = (strtolower($bn['username'] ?? '') === strtolower($user['username']));
|
||||||
|
?>
|
||||||
|
<div class="blocknote-card">
|
||||||
|
<div class="blocknote-card-header">
|
||||||
|
<h2 class="blocknote-title">
|
||||||
|
<a href="index.php?page=view_blocknote&id=<?= urlencode($bn['id']) ?>">
|
||||||
|
<?= htmlspecialchars($bn['title']) ?>
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
|
<?php if (is_admin() || !$is_owner): ?>
|
||||||
|
<span class="blocknote-owner-tag">
|
||||||
|
👤 <?= htmlspecialchars($bn['username'] ?? __('label_unknown')) ?>
|
||||||
|
</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="blocknote-card-body">
|
||||||
|
<div class="blocknote-meta">
|
||||||
|
<span>📄 <?= $note_count ?> <?= ($note_count === 1) ? __('note_single') : __('note_plural') ?></span>
|
||||||
|
<span>🕒 <?= date('d/m/Y H:i', strtotime($bn['updated_at'] ?? 'now')) ?></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="blocknote-card-actions">
|
||||||
|
<a href="index.php?page=view_blocknote&id=<?= urlencode($bn['id']) ?>" class="btn btn-sm btn-secondary" style="flex:1; justify-content:center;">
|
||||||
|
<?= __('btn_open_edit') ?>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="index.php?page=export_blocknote&id=<?= urlencode($bn['id']) ?>" class="btn btn-sm btn-dark" title="<?= __('btn_export_json') ?>">
|
||||||
|
💾
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button class="btn btn-sm btn-dark" title="<?= __('btn_rename') ?>" onclick="openRenameModal('<?= htmlspecialchars($bn['id']) ?>', '<?= htmlspecialchars(addslashes($bn['title'])) ?>')">
|
||||||
|
✏️
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<form action="index.php?page=home&view=all" method="POST" onsubmit="return confirm('<?= htmlspecialchars(addslashes(__('confirm_delete_blocknote'))) ?>');" style="display:inline;">
|
||||||
|
<input type="hidden" name="action" value="delete_blocknote">
|
||||||
|
<input type="hidden" name="id" value="<?= htmlspecialchars($bn['id']) ?>">
|
||||||
|
<button type="submit" class="btn btn-sm btn-danger" title="<?= __('btn_delete') ?>">
|
||||||
|
🗑️
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<!-- MODAL CREA BLOCKNOTE -->
|
||||||
|
<div class="modal-backdrop" id="modal-create-blocknote">
|
||||||
|
<div class="modal-card">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3><?= __('modal_create_title') ?></h3>
|
||||||
|
<button class="modal-close-btn">×</button>
|
||||||
|
</div>
|
||||||
|
<form action="index.php?page=home&view=all" method="POST">
|
||||||
|
<input type="hidden" name="action" value="create_blocknote">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="create_title"><?= __('label_title') ?></label>
|
||||||
|
<input type="text" name="title" id="create_title" class="form-control" required placeholder="<?= htmlspecialchars(__('placeholder_bn_title')) ?>">
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.5rem;">
|
||||||
|
<button type="button" class="btn btn-dark modal-close-trigger"><?= __('btn_cancel') ?></button>
|
||||||
|
<button type="submit" class="btn btn-primary"><?= __('btn_create') ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MODAL RINOMINA BLOCKNOTE -->
|
||||||
|
<div class="modal-backdrop" id="modal-rename-blocknote">
|
||||||
|
<div class="modal-card">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3><?= __('modal_rename_title') ?></h3>
|
||||||
|
<button class="modal-close-btn">×</button>
|
||||||
|
</div>
|
||||||
|
<form action="index.php?page=home&view=all" method="POST">
|
||||||
|
<input type="hidden" name="action" value="rename_blocknote">
|
||||||
|
<input type="hidden" name="id" id="rename_id">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="rename_title"><?= __('label_title') ?></label>
|
||||||
|
<input type="text" name="title" id="rename_title" class="form-control" required placeholder="<?= htmlspecialchars(__('placeholder_rename_title')) ?>">
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.5rem;">
|
||||||
|
<button type="button" class="btn btn-dark modal-close-trigger"><?= __('btn_cancel') ?></button>
|
||||||
|
<button type="submit" class="btn btn-secondary"><?= __('btn_save') ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MODAL IMPORTA BLOCKNOTE -->
|
||||||
|
<div class="modal-backdrop" id="modal-import-blocknote">
|
||||||
|
<div class="modal-card">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3><?= __('modal_import_title') ?></h3>
|
||||||
|
<button class="modal-close-btn">×</button>
|
||||||
|
</div>
|
||||||
|
<form action="index.php?page=home&view=all" method="POST" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="action" value="import_blocknote">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="import_title"><?= __('label_title') ?></label>
|
||||||
|
<input type="text" name="title" id="import_title" class="form-control" placeholder="<?= htmlspecialchars(__('placeholder_import_title')) ?>">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="json_file"><?= __('label_file_json') ?></label>
|
||||||
|
<input type="file" name="json_file" id="json_file" class="form-control" accept=".json">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="json_text"><?= __('label_paste_json') ?></label>
|
||||||
|
<textarea name="json_text" id="json_text" class="form-control" style="font-family: monospace; min-height: 120px;" placeholder="<?= htmlspecialchars(__('placeholder_paste_json')) ?>"></textarea>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.5rem;">
|
||||||
|
<button type="button" class="btn btn-dark modal-close-trigger"><?= __('btn_cancel') ?></button>
|
||||||
|
<button type="submit" class="btn btn-primary"><?= __('btn_import') ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function openRenameModal(id, currentTitle) {
|
||||||
|
document.getElementById('rename_id').value = id;
|
||||||
|
document.getElementById('rename_title').value = currentTitle;
|
||||||
|
document.getElementById('modal-rename-blocknote').classList.add('active');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
if (is_logged_in()) {
|
||||||
|
header("Location: index.php?page=home");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$error_msg = "";
|
||||||
|
$success_msg = "";
|
||||||
|
|
||||||
|
if (isset($_GET['installed'])) {
|
||||||
|
$success_msg = __('login_installed_success');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$username = $_POST['username'] ?? '';
|
||||||
|
$password = $_POST['password'] ?? '';
|
||||||
|
|
||||||
|
$res = login_user($username, $password);
|
||||||
|
if ($res['success']) {
|
||||||
|
header("Location: index.php?page=home");
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
$error_msg = $res['message'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="main-content">
|
||||||
|
<div style="max-width: 420px; margin: 3rem auto;">
|
||||||
|
<div class="card" style="padding: 2.25rem;">
|
||||||
|
<div style="text-align: center; margin-bottom: 2rem;">
|
||||||
|
<span style="font-size: 3rem;">📝</span>
|
||||||
|
<h2 style="font-size: 1.6rem; font-weight: 700; color: #fff; margin-top: 0.5rem;"><?= __('login_title') ?></h2>
|
||||||
|
<p style="color: #94a3b8; font-size: 0.9rem; margin-top: 0.25rem;"><?= __('login_subtitle') ?></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!empty($success_msg)): ?>
|
||||||
|
<div class="alert alert-success">
|
||||||
|
✅ <?= htmlspecialchars($success_msg) ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($error_msg)): ?>
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
⚠️ <?= htmlspecialchars($error_msg) ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="index.php?page=login" method="POST">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username"><?= __('label_username') ?></label>
|
||||||
|
<input type="text" name="username" id="username" class="form-control" required placeholder="<?= __('placeholder_username') ?>" autofocus value="<?= htmlspecialchars($_POST['username'] ?? '') ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password"><?= __('label_password') ?></label>
|
||||||
|
<input type="password" name="password" id="password" class="form-control" required placeholder="<?= __('placeholder_password') ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary" style="width: 100%; justify-content: center; margin-top: 1rem; padding: 0.8rem;">
|
||||||
|
<?= __('btn_submit_login') ?>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div style="margin-top: 1.5rem; text-align: center; padding-top: 1rem; border-top: 1px solid rgba(255,255,255,0.08); font-size: 0.82rem; color: #64748b;">
|
||||||
|
<?= __('login_hint_footer') ?>:<br>
|
||||||
|
<strong style="color: #94a3b8;"><?= __('label_username') ?>:</strong> admin | <strong style="color: #94a3b8;"><?= __('label_password') ?>:</strong> admin123
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?php
|
||||||
|
logout_user();
|
||||||
|
header("Location: index.php?page=login");
|
||||||
|
exit;
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
<?php
|
||||||
|
require_login();
|
||||||
|
|
||||||
|
$user = current_user();
|
||||||
|
$msg_password_success = "";
|
||||||
|
$msg_password_error = "";
|
||||||
|
$msg_theme_success = "";
|
||||||
|
$msg_theme_error = "";
|
||||||
|
$msg_lang_success = "";
|
||||||
|
$msg_lang_error = "";
|
||||||
|
$msg_landing_success = "";
|
||||||
|
$msg_landing_error = "";
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$action = $_POST['action'] ?? '';
|
||||||
|
|
||||||
|
if ($action === 'change_password') {
|
||||||
|
$new_pass = $_POST['new_password'] ?? '';
|
||||||
|
$confirm_pass = $_POST['confirm_password'] ?? '';
|
||||||
|
|
||||||
|
if ($new_pass !== $confirm_pass) {
|
||||||
|
$msg_password_error = "Le password inserite non coincidono.";
|
||||||
|
} else {
|
||||||
|
$res = update_user_password($user['id'], $new_pass);
|
||||||
|
if ($res['success']) {
|
||||||
|
$msg_password_success = $res['message'];
|
||||||
|
} else {
|
||||||
|
$msg_password_error = $res['message'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif ($action === 'update_theme') {
|
||||||
|
$pri = $_POST['theme_pri'] ?? 98;
|
||||||
|
$sec = $_POST['theme_sec'] ?? 207;
|
||||||
|
$thi = $_POST['theme_thi'] ?? 280;
|
||||||
|
|
||||||
|
$res = update_user_theme($user['id'], $pri, $sec, $thi);
|
||||||
|
if ($res['success']) {
|
||||||
|
$msg_theme_success = $res['message'];
|
||||||
|
$user = current_user(); // Refresh session data
|
||||||
|
} else {
|
||||||
|
$msg_theme_error = $res['message'];
|
||||||
|
}
|
||||||
|
} elseif ($action === 'update_language') {
|
||||||
|
$lang = $_POST['language'] ?? 'it';
|
||||||
|
$res = update_user_language($user['id'], $lang);
|
||||||
|
if ($res['success']) {
|
||||||
|
$msg_lang_success = $res['message'];
|
||||||
|
$user = current_user();
|
||||||
|
} else {
|
||||||
|
$msg_lang_error = $res['message'];
|
||||||
|
}
|
||||||
|
} elseif ($action === 'update_landing') {
|
||||||
|
$bn_id = $_POST['default_blocknote'] ?? '';
|
||||||
|
$res = update_user_default_blocknote($user['id'], $bn_id);
|
||||||
|
if ($res['success']) {
|
||||||
|
$msg_landing_success = $res['message'];
|
||||||
|
$user = current_user();
|
||||||
|
} else {
|
||||||
|
$msg_landing_error = $res['message'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pri = $user['theme_pri'] ?? 98;
|
||||||
|
$sec = $user['theme_sec'] ?? 207;
|
||||||
|
$thi = $user['theme_thi'] ?? 280;
|
||||||
|
$current_lang = $user['language'] ?? 'it';
|
||||||
|
$current_default_bn = $user['default_blocknote'] ?? '';
|
||||||
|
$user_blocknotes = get_user_blocknotes($user['username'], is_admin());
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="main-content">
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="page-title-group">
|
||||||
|
<h1><?= __('profile_title') ?></h1>
|
||||||
|
<p><?= __('profile_desc') ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); gap: 2rem;">
|
||||||
|
|
||||||
|
<!-- CARD LINGUA INTERFACCIA -->
|
||||||
|
<div class="card">
|
||||||
|
<h2 style="font-size: 1.3rem; font-weight: 700; color: #fff; margin-bottom: 1.25rem;"><?= __('card_lang_title') ?></h2>
|
||||||
|
|
||||||
|
<?php if (!empty($msg_lang_success)): ?>
|
||||||
|
<div class="alert alert-success">✅ <?= htmlspecialchars($msg_lang_success) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($msg_lang_error)): ?>
|
||||||
|
<div class="alert alert-danger">⚠️ <?= htmlspecialchars($msg_lang_error) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="index.php?page=profile" method="POST">
|
||||||
|
<input type="hidden" name="action" value="update_language">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="language"><?= __('label_select_lang') ?></label>
|
||||||
|
<select name="language" id="language" class="form-control">
|
||||||
|
<option value="it" <?= ($current_lang === 'it') ? 'selected' : '' ?>>🇮🇹 Italiano</option>
|
||||||
|
<option value="en" <?= ($current_lang === 'en') ? 'selected' : '' ?>>🇬🇧 English</option>
|
||||||
|
<option value="es" <?= ($current_lang === 'es') ? 'selected' : '' ?>>🇪🇸 Español</option>
|
||||||
|
<option value="fr" <?= ($current_lang === 'fr') ? 'selected' : '' ?>>🇫🇷 Français</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-secondary" style="margin-top: 1rem; width: 100%; justify-content: center;">
|
||||||
|
<?= __('btn_save_lang') ?>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CARD PAGINA DI PRIMO ACCESSO (LANDING PAGE) -->
|
||||||
|
<div class="card">
|
||||||
|
<h2 style="font-size: 1.3rem; font-weight: 700; color: #fff; margin-bottom: 1.25rem;"><?= __('card_landing_title') ?></h2>
|
||||||
|
|
||||||
|
<?php if (!empty($msg_landing_success)): ?>
|
||||||
|
<div class="alert alert-success">✅ <?= htmlspecialchars($msg_landing_success) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($msg_landing_error)): ?>
|
||||||
|
<div class="alert alert-danger">⚠️ <?= htmlspecialchars($msg_landing_error) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="index.php?page=profile" method="POST">
|
||||||
|
<input type="hidden" name="action" value="update_landing">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="default_blocknote"><?= __('label_select_landing') ?></label>
|
||||||
|
<select name="default_blocknote" id="default_blocknote" class="form-control">
|
||||||
|
<option value=""><?= __('landing_option_dashboard') ?></option>
|
||||||
|
<?php foreach ($user_blocknotes as $bn): ?>
|
||||||
|
<option value="<?= htmlspecialchars($bn['id']) ?>" <?= ($current_default_bn === $bn['id']) ? 'selected' : '' ?>>
|
||||||
|
<?= __('landing_option_bn_prefix') ?><?= htmlspecialchars($bn['title']) ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary" style="margin-top: 1rem; width: 100%; justify-content: center;">
|
||||||
|
<?= __('btn_save_landing') ?>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CARD CAMBIO PASSWORD -->
|
||||||
|
<div class="card">
|
||||||
|
<h2 style="font-size: 1.3rem; font-weight: 700; color: #fff; margin-bottom: 1.25rem;"><?= __('card_password_title') ?></h2>
|
||||||
|
|
||||||
|
<?php if (!empty($msg_password_success)): ?>
|
||||||
|
<div class="alert alert-success">✅ <?= htmlspecialchars($msg_password_success) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($msg_password_error)): ?>
|
||||||
|
<div class="alert alert-danger">⚠️ <?= htmlspecialchars($msg_password_error) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="index.php?page=profile" method="POST">
|
||||||
|
<input type="hidden" name="action" value="change_password">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="new_password"><?= __('label_new_pass') ?></label>
|
||||||
|
<input type="password" name="new_password" id="new_password" class="form-control" required placeholder="<?= htmlspecialchars(__('placeholder_password_new')) ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="confirm_password"><?= __('label_confirm_pass') ?></label>
|
||||||
|
<input type="password" name="confirm_password" id="confirm_password" class="form-control" required placeholder="<?= htmlspecialchars(__('placeholder_password_confirm')) ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary" style="margin-top: 0.5rem; width: 100%; justify-content: center;">
|
||||||
|
<?= __('btn_update_pass') ?>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CARD PERSONALIZZAZIONE COLORI TEMA -->
|
||||||
|
<div class="card">
|
||||||
|
<h2 style="font-size: 1.3rem; font-weight: 700; color: #fff; margin-bottom: 1.25rem;"><?= __('card_colors_title') ?></h2>
|
||||||
|
|
||||||
|
<?php if (!empty($msg_theme_success)): ?>
|
||||||
|
<div class="alert alert-success">✅ <?= htmlspecialchars($msg_theme_success) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($msg_theme_error)): ?>
|
||||||
|
<div class="alert alert-danger">⚠️ <?= htmlspecialchars($msg_theme_error) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="index.php?page=profile" method="POST">
|
||||||
|
<input type="hidden" name="action" value="update_theme">
|
||||||
|
|
||||||
|
<!-- Colore Primario -->
|
||||||
|
<div class="form-group">
|
||||||
|
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:0.3rem;">
|
||||||
|
<label for="theme_pri">Primary Color (HSL Hue)</label>
|
||||||
|
<span id="pri_val" style="font-weight:700; color:var(--priCol10);"><?= (int)$pri ?>°</span>
|
||||||
|
</div>
|
||||||
|
<input type="range" min="0" max="360" name="theme_pri" id="theme_pri" value="<?= (int)$pri ?>" class="form-control" style="cursor:pointer; height: 10px;">
|
||||||
|
<div class="color-preview-box" style="background: hsl(var(--pri), 100%, 50%); margin-top: 0.5rem; font-size: 0.82rem;">
|
||||||
|
Preview Primary
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Colore Secondario -->
|
||||||
|
<div class="form-group" style="margin-top: 1.5rem;">
|
||||||
|
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:0.3rem;">
|
||||||
|
<label for="theme_sec">Secondary Color (HSL Hue)</label>
|
||||||
|
<span id="sec_val" style="font-weight:700; color:var(--secCol10);"><?= (int)$sec ?>°</span>
|
||||||
|
</div>
|
||||||
|
<input type="range" min="0" max="360" name="theme_sec" id="theme_sec" value="<?= (int)$sec ?>" class="form-control" style="cursor:pointer; height: 10px;">
|
||||||
|
<div class="color-preview-box" style="background: hsl(var(--sec), 100%, 50%); margin-top: 0.5rem; font-size: 0.82rem; color: #fff;">
|
||||||
|
Preview Secondary
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Colore Terziario -->
|
||||||
|
<div class="form-group" style="margin-top: 1.5rem;">
|
||||||
|
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:0.3rem;">
|
||||||
|
<label for="theme_thi">Tertiary Color (HSL Hue)</label>
|
||||||
|
<span id="thi_val" style="font-weight:700; color:var(--thiCol10);"><?= (int)$thi ?>°</span>
|
||||||
|
</div>
|
||||||
|
<input type="range" min="0" max="360" name="theme_thi" id="theme_thi" value="<?= (int)$thi ?>" class="form-control" style="cursor:pointer; height: 10px;">
|
||||||
|
<div class="color-preview-box" style="background: hsl(var(--thi), 100%, 50%); margin-top: 0.5rem; font-size: 0.82rem; color: #fff;">
|
||||||
|
Preview Tertiary
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-secondary" style="margin-top: 1.5rem; width: 100%; justify-content: center;">
|
||||||
|
<?= __('btn_save_colors') ?>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
@@ -0,0 +1,375 @@
|
|||||||
|
<?php
|
||||||
|
require_login();
|
||||||
|
|
||||||
|
$user = current_user();
|
||||||
|
$blocknote_id = $_GET['id'] ?? '';
|
||||||
|
$bn = get_blocknote_by_id($blocknote_id);
|
||||||
|
|
||||||
|
// Check access permission
|
||||||
|
if (!$bn || !can_access_blocknote($bn, $user['username'], is_admin())) {
|
||||||
|
header("Location: index.php?page=home");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$msg_success = "";
|
||||||
|
$msg_error = "";
|
||||||
|
|
||||||
|
// Handle Form Submissions
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$action = $_POST['action'] ?? '';
|
||||||
|
|
||||||
|
if ($action === 'add_note') {
|
||||||
|
$res = add_note_to_blocknote($blocknote_id, $_POST['title'] ?? '', $_POST['description'] ?? '', $user['username'], is_admin());
|
||||||
|
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
|
||||||
|
} elseif ($action === 'edit_note') {
|
||||||
|
$res = edit_note_in_blocknote($blocknote_id, $_POST['note_id'] ?? '', $_POST['title'] ?? '', $_POST['description'] ?? '', $user['username'], is_admin());
|
||||||
|
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
|
||||||
|
} elseif ($action === 'delete_note') {
|
||||||
|
$res = delete_note_from_blocknote($blocknote_id, $_POST['note_id'] ?? '', $user['username'], is_admin());
|
||||||
|
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
|
||||||
|
} elseif ($action === 'add_subnote') {
|
||||||
|
$res = add_subnote_to_note($blocknote_id, $_POST['note_id'] ?? '', $_POST['sub_title'] ?? '', $_POST['type'] ?? 'text', $_POST['content'] ?? '', $user['username'], is_admin());
|
||||||
|
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
|
||||||
|
} elseif ($action === 'edit_subnote') {
|
||||||
|
$res = edit_subnote_in_note($blocknote_id, $_POST['note_id'] ?? '', $_POST['sub_id'] ?? '', $_POST['sub_title'] ?? '', $_POST['type'] ?? 'text', $_POST['content'] ?? '', $user['username'], is_admin());
|
||||||
|
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
|
||||||
|
} elseif ($action === 'delete_subnote') {
|
||||||
|
$res = delete_subnote_from_note($blocknote_id, $_POST['note_id'] ?? '', $_POST['sub_id'] ?? '', $user['username'], is_admin());
|
||||||
|
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh blocknote data
|
||||||
|
$bn = get_blocknote_by_id($blocknote_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$notes = $bn['notes'] ?? [];
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="main-content">
|
||||||
|
<div style="margin-bottom: 1.5rem;">
|
||||||
|
<a href="index.php?page=home&view=all" class="btn btn-sm btn-dark"><?= __('btn_back_list') ?></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-header" style="border-bottom: 1px solid rgba(255,255,255,0.08); padding-bottom: 1.5rem; margin-bottom: 2rem;">
|
||||||
|
<div class="page-title-group">
|
||||||
|
<h1>📓 <?= htmlspecialchars($bn['title']) ?></h1>
|
||||||
|
<p>
|
||||||
|
<?= __('label_owner_prefix') ?><strong style="color: #cbd5e1;"><?= htmlspecialchars($bn['username'] ?? '') ?></strong> •
|
||||||
|
<?= __('label_updated_prefix') ?><?= date('d/m/Y H:i', strtotime($bn['updated_at'] ?? 'now')) ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
||||||
|
<a href="index.php?page=export_blocknote&id=<?= urlencode($blocknote_id) ?>" class="btn btn-dark btn-sm" title="<?= __('btn_export_json') ?>">
|
||||||
|
<?= __('btn_export_json') ?>
|
||||||
|
</a>
|
||||||
|
<button class="btn btn-dark btn-sm" onclick="expandAllNotes()">
|
||||||
|
<?= __('btn_expand_all') ?>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-dark btn-sm" onclick="collapseAllNotes()">
|
||||||
|
<?= __('btn_collapse_all') ?>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary" data-modal-target="modal-add-note">
|
||||||
|
<?= __('btn_new_note') ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!empty($msg_success)): ?>
|
||||||
|
<div class="alert alert-success">
|
||||||
|
✅ <?= htmlspecialchars($msg_success) ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($msg_error)): ?>
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
⚠️ <?= htmlspecialchars($msg_error) ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($notes)): ?>
|
||||||
|
<!-- SEARCH TOOLBAR FOR CURRENT BLOCKNOTE -->
|
||||||
|
<div class="search-toolbar" style="margin-bottom: 1.5rem; display: flex; gap: 0.75rem; align-items: center;">
|
||||||
|
<div style="position: relative; flex: 1;">
|
||||||
|
<input type="text" id="note-search-input" class="form-control" placeholder="<?= __('search_placeholder') ?>" oninput="filterCurrentBlocknoteNotes(this.value)" style="padding-left: 2.5rem;">
|
||||||
|
<span style="position: absolute; left: 0.9rem; top: 50%; transform: translateY(-50%); font-size: 1rem; opacity: 0.5;">🔍</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-dark btn-sm" onclick="clearNoteSearch()" id="clear-search-btn" style="display: none; align-items: center; gap: 0.3rem;">
|
||||||
|
<?= __('btn_clear_search') ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SEARCH NO RESULTS STATE -->
|
||||||
|
<div id="no-search-results" class="empty-state" style="display: none; padding: 2.5rem 1rem;">
|
||||||
|
<div class="empty-state-icon">🔍</div>
|
||||||
|
<h3><?= __('search_no_results') ?></h3>
|
||||||
|
<p><?= __('search_no_results_desc') ?></p>
|
||||||
|
<button class="btn btn-dark btn-sm" onclick="clearNoteSearch()">
|
||||||
|
🔄 Reset
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (empty($notes)): ?>
|
||||||
|
<div class="empty-state">
|
||||||
|
<div class="empty-state-icon">📝</div>
|
||||||
|
<h3><?= __('empty_blocknotes_title') ?></h3>
|
||||||
|
<p><?= __('empty_blocknotes_desc_user') ?></p>
|
||||||
|
<button class="btn btn-primary" data-modal-target="modal-add-note">
|
||||||
|
<?= __('btn_new_note') ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="notes-container">
|
||||||
|
<?php foreach ($notes as $note): ?>
|
||||||
|
<?php $subnotes = $note['subnotes'] ?? []; ?>
|
||||||
|
<div class="note-block collapsed" id="note-block-<?= htmlspecialchars($note['id']) ?>">
|
||||||
|
<div class="note-header" onclick="toggleNoteCollapse('<?= htmlspecialchars($note['id']) ?>')">
|
||||||
|
<div class="note-title-area">
|
||||||
|
<div class="note-title-wrapper">
|
||||||
|
<span class="collapse-icon">▶</span>
|
||||||
|
<h3>📌 <?= htmlspecialchars($note['title']) ?></h3>
|
||||||
|
<span class="subnote-count-badge">
|
||||||
|
<?= count($subnotes) ?> <?= (count($subnotes) === 1) ? __('subnote_single') : __('subnote_plural') ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="note-actions" onclick="event.stopPropagation();">
|
||||||
|
<button class="btn btn-sm btn-secondary" onclick="openAddSubnoteModal('<?= htmlspecialchars($note['id']) ?>', '<?= htmlspecialchars(addslashes($note['title'])) ?>')">
|
||||||
|
<?= __('btn_add_subnote') ?>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="btn btn-sm btn-dark" onclick="openEditNoteModal('<?= htmlspecialchars($note['id']) ?>', '<?= htmlspecialchars(addslashes($note['title'])) ?>', '<?= htmlspecialchars(addslashes($note['description'] ?? '')) ?>')">
|
||||||
|
✏️
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<form action="index.php?page=view_blocknote&id=<?= urlencode($blocknote_id) ?>" method="POST" onsubmit="return confirm('<?= htmlspecialchars(addslashes(__('confirm_delete_note'))) ?>');" style="display:inline;">
|
||||||
|
<input type="hidden" name="action" value="delete_note">
|
||||||
|
<input type="hidden" name="note_id" value="<?= htmlspecialchars($note['id']) ?>">
|
||||||
|
<button type="submit" class="btn btn-sm btn-danger" title="<?= __('btn_delete') ?>">
|
||||||
|
🗑️
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="note-body">
|
||||||
|
<?php if (!empty($note['description'])): ?>
|
||||||
|
<div class="note-description-box">
|
||||||
|
<p><?= htmlspecialchars($note['description']) ?></p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="subnotes-list">
|
||||||
|
<?php if (empty($subnotes)): ?>
|
||||||
|
<div style="font-size: 0.88rem; color: #64748b; font-style: italic; text-align: center; padding: 1rem 0;">
|
||||||
|
<?= __('empty_blocknotes_desc_user') ?>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($subnotes as $sub): ?>
|
||||||
|
<div class="subnote-card">
|
||||||
|
<div class="subnote-header">
|
||||||
|
<div class="subnote-title-group">
|
||||||
|
<span class="subnote-title">📑 <?= htmlspecialchars($sub['title']) ?></span>
|
||||||
|
<span class="type-badge <?= htmlspecialchars($sub['type']) ?>">
|
||||||
|
<?= ($sub['type'] === 'command') ? __('badge_command') : __('badge_text') ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; gap: 0.4rem;">
|
||||||
|
<button class="btn btn-sm btn-dark" onclick="openEditSubnoteModal('<?= htmlspecialchars($note['id']) ?>', '<?= htmlspecialchars($sub['id']) ?>', '<?= htmlspecialchars(addslashes($sub['title'])) ?>', '<?= htmlspecialchars($sub['type']) ?>', '<?= htmlspecialchars(addslashes($sub['content'])) ?>')">
|
||||||
|
✏️
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<form action="index.php?page=view_blocknote&id=<?= urlencode($blocknote_id) ?>" method="POST" onsubmit="return confirm('<?= htmlspecialchars(addslashes(__('confirm_delete_subnote'))) ?>');" style="display:inline;">
|
||||||
|
<input type="hidden" name="action" value="delete_subnote">
|
||||||
|
<input type="hidden" name="note_id" value="<?= htmlspecialchars($note['id']) ?>">
|
||||||
|
<input type="hidden" name="sub_id" value="<?= htmlspecialchars($sub['id']) ?>">
|
||||||
|
<button type="submit" class="btn btn-sm btn-danger" title="<?= __('btn_delete') ?>">
|
||||||
|
🗑️
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content rendering depending on type -->
|
||||||
|
<?php if ($sub['type'] === 'command'): ?>
|
||||||
|
<div class="command-block-wrapper">
|
||||||
|
<div class="command-block-header">
|
||||||
|
<span class="command-badge-title">⚡ <?= __('badge_command') ?></span>
|
||||||
|
<button class="copy-btn" onclick="copyToClipboard(this, <?= htmlspecialchars(json_encode($sub['content'])) ?>)" data-copy-text="<?= htmlspecialchars(__('btn_copy')) ?>" data-copied-text="<?= htmlspecialchars(__('btn_copied')) ?>">
|
||||||
|
<?= __('btn_copy') ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<pre class="command-content-code"><code><?= htmlspecialchars($sub['content']) ?></code></pre>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="subnote-content-text">
|
||||||
|
<?= nl2br(htmlspecialchars($sub['content'])) ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- MODAL AGGIUNGI NOTA -->
|
||||||
|
<div class="modal-backdrop" id="modal-add-note">
|
||||||
|
<div class="modal-card">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3><?= __('modal_add_note') ?></h3>
|
||||||
|
<button class="modal-close-btn">×</button>
|
||||||
|
</div>
|
||||||
|
<form action="index.php?page=view_blocknote&id=<?= urlencode($blocknote_id) ?>" method="POST">
|
||||||
|
<input type="hidden" name="action" value="add_note">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="note_title"><?= __('label_title') ?></label>
|
||||||
|
<input type="text" name="title" id="note_title" class="form-control" required placeholder="<?= htmlspecialchars(__('placeholder_note_title')) ?>">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="note_description"><?= __('label_note_description') ?></label>
|
||||||
|
<textarea name="description" id="note_description" class="form-control" placeholder="<?= htmlspecialchars(__('placeholder_note_desc')) ?>"></textarea>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.5rem;">
|
||||||
|
<button type="button" class="btn btn-dark modal-close-trigger"><?= __('btn_cancel') ?></button>
|
||||||
|
<button type="submit" class="btn btn-primary"><?= __('btn_save') ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MODAL MODIFICA NOTA -->
|
||||||
|
<div class="modal-backdrop" id="modal-edit-note">
|
||||||
|
<div class="modal-card">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3><?= __('modal_edit_note') ?></h3>
|
||||||
|
<button class="modal-close-btn">×</button>
|
||||||
|
</div>
|
||||||
|
<form action="index.php?page=view_blocknote&id=<?= urlencode($blocknote_id) ?>" method="POST">
|
||||||
|
<input type="hidden" name="action" value="edit_note">
|
||||||
|
<input type="hidden" name="note_id" id="edit_note_id">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="edit_note_title"><?= __('label_title') ?></label>
|
||||||
|
<input type="text" name="title" id="edit_note_title" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="edit_note_description"><?= __('label_note_description') ?></label>
|
||||||
|
<textarea name="description" id="edit_note_description" class="form-control"></textarea>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.5rem;">
|
||||||
|
<button type="button" class="btn btn-dark modal-close-trigger"><?= __('btn_cancel') ?></button>
|
||||||
|
<button type="submit" class="btn btn-secondary"><?= __('btn_save') ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MODAL AGGIUNGI SOTTONOTA -->
|
||||||
|
<div class="modal-backdrop" id="modal-add-subnote">
|
||||||
|
<div class="modal-card">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3><?= __('modal_add_subnote') ?></h3>
|
||||||
|
<button class="modal-close-btn">×</button>
|
||||||
|
</div>
|
||||||
|
<form action="index.php?page=view_blocknote&id=<?= urlencode($blocknote_id) ?>" method="POST">
|
||||||
|
<input type="hidden" name="action" value="add_subnote">
|
||||||
|
<input type="hidden" name="note_id" id="add_subnote_parent_id">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label><?= __('note_single') ?></label>
|
||||||
|
<input type="text" id="add_subnote_parent_name" class="form-control" disabled style="opacity: 0.7;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sub_title"><?= __('label_subnote_title') ?></label>
|
||||||
|
<input type="text" name="sub_title" id="sub_title" class="form-control" required placeholder="<?= htmlspecialchars(__('placeholder_subnote_title')) ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sub_type"><?= __('label_subnote_type') ?></label>
|
||||||
|
<select name="type" id="sub_type" class="form-control">
|
||||||
|
<option value="text"><?= __('type_option_text') ?></option>
|
||||||
|
<option value="command"><?= __('type_option_command') ?></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sub_content"><?= __('label_subnote_content') ?></label>
|
||||||
|
<textarea name="content" id="sub_content" class="form-control" style="min-height: 120px; font-family: monospace;" required placeholder="<?= htmlspecialchars(__('placeholder_subnote_content')) ?>"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.5rem;">
|
||||||
|
<button type="button" class="btn btn-dark modal-close-trigger"><?= __('btn_cancel') ?></button>
|
||||||
|
<button type="submit" class="btn btn-primary"><?= __('btn_save') ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MODAL MODIFICA SOTTONOTA -->
|
||||||
|
<div class="modal-backdrop" id="modal-edit-subnote">
|
||||||
|
<div class="modal-card">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3><?= __('modal_edit_subnote') ?></h3>
|
||||||
|
<button class="modal-close-btn">×</button>
|
||||||
|
</div>
|
||||||
|
<form action="index.php?page=view_blocknote&id=<?= urlencode($blocknote_id) ?>" method="POST">
|
||||||
|
<input type="hidden" name="action" value="edit_subnote">
|
||||||
|
<input type="hidden" name="note_id" id="edit_subnote_parent_id">
|
||||||
|
<input type="hidden" name="sub_id" id="edit_subnote_id">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="edit_sub_title"><?= __('label_subnote_title') ?></label>
|
||||||
|
<input type="text" name="sub_title" id="edit_sub_title" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="edit_sub_type"><?= __('label_subnote_type') ?></label>
|
||||||
|
<select name="type" id="edit_sub_type" class="form-control">
|
||||||
|
<option value="text"><?= __('type_option_text') ?></option>
|
||||||
|
<option value="command"><?= __('type_option_command') ?></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="edit_sub_content"><?= __('label_subnote_content') ?></label>
|
||||||
|
<textarea name="content" id="edit_sub_content" class="form-control" style="min-height: 120px; font-family: monospace;" required></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.5rem;">
|
||||||
|
<button type="button" class="btn btn-dark modal-close-trigger"><?= __('btn_cancel') ?></button>
|
||||||
|
<button type="submit" class="btn btn-secondary"><?= __('btn_save') ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function openEditNoteModal(noteId, currentTitle, currentDesc) {
|
||||||
|
document.getElementById('edit_note_id').value = noteId;
|
||||||
|
document.getElementById('edit_note_title').value = currentTitle;
|
||||||
|
document.getElementById('edit_note_description').value = currentDesc;
|
||||||
|
document.getElementById('modal-edit-note').classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function openAddSubnoteModal(noteId, noteTitle) {
|
||||||
|
document.getElementById('add_subnote_parent_id').value = noteId;
|
||||||
|
document.getElementById('add_subnote_parent_name').value = noteTitle;
|
||||||
|
document.getElementById('modal-add-subnote').classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEditSubnoteModal(noteId, subId, title, type, content) {
|
||||||
|
document.getElementById('edit_subnote_parent_id').value = noteId;
|
||||||
|
document.getElementById('edit_subnote_id').value = subId;
|
||||||
|
document.getElementById('edit_sub_title').value = title;
|
||||||
|
document.getElementById('edit_sub_type').value = type;
|
||||||
|
document.getElementById('edit_sub_content').value = content;
|
||||||
|
document.getElementById('modal-edit-subnote').classList.add('active');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
|
</div> <!-- End .main-wrapper -->
|
||||||
<footer class="main-footer">
|
<footer class="main-footer">
|
||||||
<div class="footer-credit">© <year>year</year> - Company Name. All right reserved.</div>
|
<div class="footer-container">
|
||||||
<div class="webmaster_footer">Website designed by Simone Cusano - SimoLinuxDesign.org</div>
|
<div class="footer-credit">© <?= date('Y') ?> - Blocknotes System. Tutti i diritti riservati.</div>
|
||||||
</footer>
|
<div class="webmaster_footer">Sistema Blocknotes basato su PHP, JSON & MySQL</div>
|
||||||
<?php include(INCLUDES_FOLD . "js_footer.php"); ?>
|
</div>
|
||||||
<?php displayDebug(); ?>
|
</footer>
|
||||||
|
<?php include(INCLUDES_FOLD . "js_footer.php"); ?>
|
||||||
|
<?php displayDebug(); ?>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
+114
-10
@@ -1,20 +1,124 @@
|
|||||||
|
<?php
|
||||||
|
// Handle quick language switch if requested
|
||||||
|
if (isset($_GET['set_lang'])) {
|
||||||
|
set_active_lang($_GET['set_lang']);
|
||||||
|
$redirect_url = strtok($_SERVER["REQUEST_URI"], '?');
|
||||||
|
$get_params = $_GET;
|
||||||
|
unset($get_params['set_lang']);
|
||||||
|
if (!empty($get_params)) {
|
||||||
|
$redirect_url .= '?' . http_build_query($get_params);
|
||||||
|
}
|
||||||
|
header("Location: " . $redirect_url);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$current_lang = get_current_lang();
|
||||||
|
$site_title = get_site_setting('site_title', 'sld-blocknotes');
|
||||||
|
$site_favicon = get_site_setting('site_favicon', 'assets/img/favicon.png');
|
||||||
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="<?= htmlspecialchars($current_lang) ?>">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="icon" href="<?= htmlspecialchars($site_favicon) ?>">
|
||||||
<?php include(INCLUDES_FOLD . "head_links.php"); ?>
|
<?php include(INCLUDES_FOLD . "head_links.php"); ?>
|
||||||
<title><?php echo ucfirst($GLOBALS["current_page"] ?? "Home"); ?></title>
|
<?php
|
||||||
|
// Inject dynamic HSL theme colors from session if set
|
||||||
|
$user = current_user();
|
||||||
|
$pri = $user['theme_pri'] ?? 98;
|
||||||
|
$sec = $user['theme_sec'] ?? 207;
|
||||||
|
$thi = $user['theme_thi'] ?? 280;
|
||||||
|
?>
|
||||||
|
<style id="dynamic-theme-vars">
|
||||||
|
:root {
|
||||||
|
--pri: <?= (int)$pri ?>;
|
||||||
|
--sec: <?= (int)$sec ?>;
|
||||||
|
--thi: <?= (int)$thi ?>;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<title><?php
|
||||||
|
$cp = $GLOBALS["current_page"] ?? "home";
|
||||||
|
if ($cp === 'home') echo __('dashboard_title_user');
|
||||||
|
elseif ($cp === 'view_blocknote') echo __('nav_blocknotes');
|
||||||
|
elseif ($cp === 'profile') echo __('profile_title');
|
||||||
|
elseif ($cp === 'admin_users') echo __('admin_title');
|
||||||
|
elseif ($cp === 'login') echo __('nav_login');
|
||||||
|
else echo ucfirst($cp);
|
||||||
|
?> - <?= htmlspecialchars($site_title) ?></title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="debug-target-container"></div>
|
<div id="debug-target-container"></div>
|
||||||
|
|
||||||
<header>
|
<header class="navbar-header">
|
||||||
<ul class="main-nav">
|
<div class="nav-container">
|
||||||
<li><a href="#">Home</a></li>
|
<a href="index.php?page=home&view=all" class="brand-logo">
|
||||||
<li><a href="#">Page1</a></li>
|
<?php if (!empty($site_favicon) && file_exists(ROOT_DIR . $site_favicon)): ?>
|
||||||
<li><a href="#">Page2</a></li>
|
<img src="<?= htmlspecialchars($site_favicon) ?>" alt="Favicon" style="width:28px; height:28px; object-fit:contain; border-radius:4px;">
|
||||||
<li><a href="#">Page3</a></li>
|
<?php else: ?>
|
||||||
<li><a href="#">Page4</a></li>
|
<span class="logo-icon">📝</span>
|
||||||
</ul>
|
<?php endif; ?>
|
||||||
|
<span class="logo-text"><?= htmlspecialchars($site_title) ?></span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<ul class="main-nav">
|
||||||
|
<?php if (is_logged_in()): ?>
|
||||||
|
<li>
|
||||||
|
<a href="index.php?page=home&view=all" class="<?= ($cp === 'home' || $cp === 'view_blocknote') ? 'active' : '' ?>">
|
||||||
|
<?= __('nav_blocknotes') ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="index.php?page=profile" class="<?= ($cp === 'profile') ? 'active' : '' ?>">
|
||||||
|
<?= __('nav_colors_profile') ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<?php if (is_admin()): ?>
|
||||||
|
<li class="admin-nav-item">
|
||||||
|
<a href="index.php?page=admin_users" class="admin-link <?= ($cp === 'admin_users') ? 'active' : '' ?>">
|
||||||
|
<?= __('nav_admin') ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<li class="user-meta-item">
|
||||||
|
<?php if (is_owner()): ?>
|
||||||
|
<span class="user-badge badge-owner" style="background: rgba(234, 179, 8, 0.25); border-color: rgba(234, 179, 8, 0.5); color: #fde047; box-shadow: 0 0 10px rgba(234,179,8,0.3);">
|
||||||
|
👑 <?= htmlspecialchars($user['username']) ?> (<?= __('badge_owner') ?>)
|
||||||
|
</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="user-badge <?= is_admin() ? 'badge-admin' : 'badge-user' ?>">
|
||||||
|
👤 <?= htmlspecialchars($user['username']) ?> (<?= is_admin() ? 'Admin' : 'User' ?>)
|
||||||
|
</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Language Quick Selector -->
|
||||||
|
<li class="lang-selector-item">
|
||||||
|
<select onchange="location.href=this.value;" style="background: rgba(0,0,0,0.3); color: #fff; border: 1px solid rgba(255,255,255,0.15); border-radius: 6px; padding: 0.3rem 0.5rem; font-size: 0.82rem; cursor: pointer;">
|
||||||
|
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'it'])) ?>" <?= ($current_lang === 'it') ? 'selected' : '' ?>>🇮🇹 IT</option>
|
||||||
|
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'en'])) ?>" <?= ($current_lang === 'en') ? 'selected' : '' ?>>🇬🇧 EN</option>
|
||||||
|
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'es'])) ?>" <?= ($current_lang === 'es') ? 'selected' : '' ?>>🇪🇸 ES</option>
|
||||||
|
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'fr'])) ?>" <?= ($current_lang === 'fr') ? 'selected' : '' ?>>🇫🇷 FR</option>
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li><a href="index.php?page=logout" class="nav-logout-btn"><?= __('nav_logout') ?></a></li>
|
||||||
|
<?php else: ?>
|
||||||
|
<!-- Guest Language Quick Selector -->
|
||||||
|
<li class="lang-selector-item">
|
||||||
|
<select onchange="location.href=this.value;" style="background: rgba(0,0,0,0.3); color: #fff; border: 1px solid rgba(255,255,255,0.15); border-radius: 6px; padding: 0.3rem 0.5rem; font-size: 0.82rem; cursor: pointer;">
|
||||||
|
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'it'])) ?>" <?= ($current_lang === 'it') ? 'selected' : '' ?>>🇮🇹 IT</option>
|
||||||
|
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'en'])) ?>" <?= ($current_lang === 'en') ? 'selected' : '' ?>>🇬🇧 EN</option>
|
||||||
|
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'es'])) ?>" <?= ($current_lang === 'es') ? 'selected' : '' ?>>🇪🇸 ES</option>
|
||||||
|
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'fr'])) ?>" <?= ($current_lang === 'fr') ? 'selected' : '' ?>>🇫🇷 FR</option>
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
|
<li><a href="index.php?page=login" class="active"><?= __('nav_login') ?></a></li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
<div class="main-wrapper">
|
||||||
Reference in New Issue
Block a user