Major Features:
- Enhanced Mode v2: Smart Adaptive section selection strategy
* AI automatically selects ONE optimal section per iteration
* Intelligent selection based on quality assessment and strategic importance
* Section tracking to avoid duplicate modifications
* Fixed header level preservation (## stays ##, ### stays ###)
* Updated document code block format (document)
- Mermaid Auto-Fix System
* New POST /api/ai/fix-mermaid endpoint for automatic diagram correction
* Automatic error detection in preview and presentation modes
* Inter-window communication for seamless editor updates
* AI-powered syntax error resolution
- Comprehensive Logging System
* Centralized logger utility (utils/logger.js)
* API request/response middleware logging
* AI operations: rephrase, check-inconsistencies, check-duplications, give-advice, liberty-mode, fix-mermaid
* Storage operations: create, read, update, delete journals
* Export operations: PDF and Web ZIP generation
* Structured logging with timestamps, levels, categories, and metadata
Improvements:
- Table of Contents: Clean display with markdown formatting stripped from titles
- Section badges: Fixed visibility with hardcoded blue background (#2563eb)
- Internationalization: Complete English translation (lang, UI text, placeholders, comments)
- Code cleanup: Removed all emojis from codebase
Technical Changes:
- routes/ai.js: Enhanced mode implementation, Mermaid fix endpoint, comprehensive logging
- routes/api.js: Storage operation logging
- routes/export.js: Export operation logging
- routes/index.js: Presentation mode Mermaid auto-fix
- assets/js/app.js: TOC markdown stripping, Mermaid error handling, message listeners
- assets/css/style.css: Dark mode comment, English placeholders, header icon
- views/page.js: English lang attribute, favicon, scroll-to-top button
- views/header.js: Theme toggle icon
- utils/logger.js: New centralized logging system
- app.js: API request/response logging middleware
262 lines
7.4 KiB
JavaScript
262 lines
7.4 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { v4: uuidv4 } = require('uuid');
|
|
const { logger } = require('../utils/logger');
|
|
|
|
// Import export module
|
|
const exportRouter = require('./export');
|
|
|
|
function modifMd(id, modifications) {
|
|
if (id === undefined) throw new Error('id required');
|
|
if (!Array.isArray(modifications) || modifications.length === 0) throw new Error('modifications required');
|
|
|
|
const dataDir = path.resolve(__dirname, '../data');
|
|
const mapPath = path.join(dataDir, 'uuid_map.json');
|
|
if (!fs.existsSync(mapPath)) throw new Error('uuid_map.json does not exist');
|
|
|
|
const map = JSON.parse(fs.readFileSync(mapPath, 'utf8'));
|
|
const uuid = map[id];
|
|
if (!uuid) throw new Error(`No file for id ${id}`);
|
|
|
|
const mdPath = path.join(dataDir, `${uuid}.md`);
|
|
if (!fs.existsSync(mdPath)) throw new Error('Markdown file does not exist');
|
|
let lignes = fs.readFileSync(mdPath, 'utf8').split('\n');
|
|
|
|
modifications = modifications.slice().sort((a, b) => a.debut - b.debut);
|
|
|
|
let offset = 0;
|
|
for (let m of modifications) {
|
|
let debut = m.debut + offset;
|
|
let fin = m.fin + offset;
|
|
const remplacement = Array.isArray(m.contenu) ? m.contenu : m.contenu.split('\n');
|
|
if (debut < 0 || fin >= lignes.length || debut > fin)
|
|
throw new Error('Invalid range (start/end) for a modification');
|
|
|
|
const avant = lignes.slice(0, debut);
|
|
const apres = lignes.slice(fin + 1);
|
|
lignes = [...avant, ...remplacement, ...apres];
|
|
offset += remplacement.length - (fin - debut + 1);
|
|
}
|
|
|
|
const nouveau = lignes.join('\n');
|
|
fs.writeFileSync(mdPath, nouveau, { encoding: 'utf8', flag: 'w' });
|
|
return { id, uuid, path: mdPath, modifications };
|
|
}
|
|
|
|
|
|
function createMd(markdownContent = "# Title\nContent...") {
|
|
const uuid = uuidv4();
|
|
|
|
const dataDir = path.resolve(__dirname, '../data');
|
|
const mdPath = path.join(dataDir, `${uuid}.md`);
|
|
const mapPath = path.join(dataDir, 'uuid_map.json');
|
|
|
|
fs.writeFileSync(mdPath, markdownContent, { encoding: 'utf8', flag: 'w' });
|
|
|
|
let map = {};
|
|
if (fs.existsSync(mapPath)) {
|
|
map = JSON.parse(fs.readFileSync(mapPath, 'utf8'));
|
|
}
|
|
let id = Object.keys(map).at(-1);
|
|
if (id == undefined){
|
|
map[0] = uuid;
|
|
} else {
|
|
id = +id+1
|
|
map[id] = uuid;
|
|
}
|
|
|
|
fs.writeFileSync(mapPath, JSON.stringify(map, null, 2), 'utf8');
|
|
|
|
return { id, uuid, path: mdPath, markdownContent};
|
|
}
|
|
|
|
function readMd(id = undefined) {
|
|
const dataDir = path.resolve(__dirname, '../data');
|
|
const mapPath = path.join(dataDir, 'uuid_map.json');
|
|
|
|
let map = {};
|
|
if (fs.existsSync(mapPath)) {
|
|
map = JSON.parse(fs.readFileSync(mapPath, 'utf8'));
|
|
}
|
|
|
|
if (id !== undefined) {
|
|
const uuid = map[id];
|
|
if (!uuid) {
|
|
throw new Error(`No file found for id ${id}`);
|
|
}
|
|
const mdPath = path.join(dataDir, `${uuid}.md`);
|
|
if (!fs.existsSync(mdPath)) {
|
|
throw new Error(`File ${mdPath} does not exist`);
|
|
}
|
|
const markdownContent = fs.readFileSync(mdPath, 'utf8');
|
|
return [{ id, uuid, path: mdPath, markdownContent }];
|
|
} else {
|
|
const results = [];
|
|
for (const [curId, uuid] of Object.entries(map)) {
|
|
const mdPath = path.join(dataDir, `${uuid}.md`);
|
|
if (fs.existsSync(mdPath)) {
|
|
const markdownContent = fs.readFileSync(mdPath, 'utf8');
|
|
results.push({ id: curId, uuid, path: mdPath, markdownContent });
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
}
|
|
|
|
function updateMd(id, newMarkdownContent) {
|
|
if (id === undefined) throw new Error('id required');
|
|
const dataDir = path.resolve(__dirname, '../data');
|
|
const mapPath = path.join(dataDir, 'uuid_map.json');
|
|
|
|
if (!fs.existsSync(mapPath)) throw new Error('uuid_map.json does not exist');
|
|
let map = JSON.parse(fs.readFileSync(mapPath, 'utf8'));
|
|
const uuid = map[id];
|
|
if (!uuid) throw new Error(`No file found for id ${id}`);
|
|
|
|
const mdPath = path.join(dataDir, `${uuid}.md`);
|
|
if (!fs.existsSync(mdPath)) throw new Error('Markdown file does not exist');
|
|
fs.writeFileSync(mdPath, newMarkdownContent, { encoding: 'utf8', flag: 'w' });
|
|
return { id, uuid, path: mdPath, newMarkdownContent };
|
|
}
|
|
|
|
function deteMd(id) {
|
|
if (id === undefined) throw new Error('id required');
|
|
const dataDir = path.resolve(__dirname, '../data');
|
|
const mapPath = path.join(dataDir, 'uuid_map.json');
|
|
|
|
if (!fs.existsSync(mapPath)) throw new Error('uuid_map.json does not exist');
|
|
let map = JSON.parse(fs.readFileSync(mapPath, 'utf8'));
|
|
const uuid = map[id];
|
|
if (!uuid) throw new Error(`No file found for id ${id}`);
|
|
|
|
const mdPath = path.join(dataDir, `${uuid}.md`);
|
|
if (fs.existsSync(mdPath)) fs.unlinkSync(mdPath);
|
|
delete map[id];
|
|
|
|
fs.writeFileSync(mapPath, JSON.stringify(map, null, 2), 'utf8');
|
|
return { id, deleted: true };
|
|
}
|
|
|
|
// GET /api/journals - Get all journals
|
|
router.get('/journals', (req, res) => {
|
|
try {
|
|
logger.storageRead('get-all-journals', 'all');
|
|
const data = readMd();
|
|
res.json({
|
|
success: true,
|
|
data: data
|
|
});
|
|
} catch (error) {
|
|
logger.storageError('get-all-journals', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// POST /api/journals - Create a new journal
|
|
router.post('/journals', (req, res) => {
|
|
try {
|
|
const { content } = req.body;
|
|
|
|
const result = createMd(content);
|
|
logger.storageWrite('create-journal', result.uuid);
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
data: result
|
|
});
|
|
} catch (error) {
|
|
logger.storageError('create-journal', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET /api/journals/:id - Get a specific journal
|
|
router.get('/journals/:id', (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
logger.storageRead('get-journal', id);
|
|
const data = readMd(id);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: data
|
|
});
|
|
} catch (error) {
|
|
logger.storageError('get-journal', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// PUT /api/journals/:id - Update a journal
|
|
router.put('/journals/:id', (req, res) => {
|
|
const { id } = req.params;
|
|
const { content, modifications } = req.body;
|
|
|
|
try {
|
|
let result;
|
|
if (content) {
|
|
// Complete content update
|
|
logger.storageWrite('update-journal-content', id);
|
|
result = updateMd(id, content);
|
|
} else if (modifications) {
|
|
// Partial modifications (for future compatibility)
|
|
logger.storageWrite('update-journal-modifications', id);
|
|
result = modifMd(id, modifications);
|
|
} else {
|
|
logger.storageError('update-journal', new Error('Content or modifications required'));
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Content or modifications required'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result
|
|
});
|
|
} catch (error) {
|
|
logger.storageError('update-journal', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// DELETE /api/journals/:id - Delete a journal
|
|
router.delete('/journals/:id', (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
logger.storageWrite('delete-journal', id);
|
|
const data = deteMd(id);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: data
|
|
});
|
|
} catch (error) {
|
|
logger.storageError('delete-journal', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// Integrate export routes
|
|
router.use('/export', exportRouter);
|
|
|
|
module.exports = router; |