mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-11-09 19:20:09 +01:00
style: normalize headers, overtype editor rounded corners, remove duplicate cover preview, thesis-add-header grid layout, subtitle below header with top gradient
This commit is contained in:
1 parent
7ccadbb224
commit
21c2b55bfb
23 files changed
+1855
-1531
No files matched your search
@@ -0,0 +1,16 @@
|
||||
-- Add name (human-readable title) and enabled (show/hide toggle) columns
|
||||
-- to form_help_blocks. Drop sort_order — blocks are now fixed-position,
|
||||
-- one per fieldset, no reordering.
|
||||
|
||||
ALTER TABLE form_help_blocks ADD COLUMN name TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE form_help_blocks ADD COLUMN enabled INTEGER NOT NULL DEFAULT 1;
|
||||
|
||||
-- Backfill names from the FORM_HELP_LABELS mapping.
|
||||
UPDATE form_help_blocks SET name = 'Introduction' WHERE key = 'partage_intro';
|
||||
UPDATE form_help_blocks SET name = 'Informations du TFE' WHERE key = 'fieldset_tfe_info';
|
||||
UPDATE form_help_blocks SET name = 'Note Synopsis' WHERE key = 'fieldset_synopsis';
|
||||
UPDATE form_help_blocks SET name = 'Composition du jury' WHERE key = 'fieldset_jury';
|
||||
UPDATE form_help_blocks SET name = 'Cadre académique' WHERE key = 'fieldset_academic';
|
||||
UPDATE form_help_blocks SET name = 'Fichiers' WHERE key = 'fieldset_files';
|
||||
UPDATE form_help_blocks SET name = 'Visibilité / Accès' WHERE key = 'fieldset_access';
|
||||
UPDATE form_help_blocks SET name = 'E-mail de confirmation' WHERE key = 'fieldset_email';
|
||||
@@ -0,0 +1,8 @@
|
||||
-- Add missing form help block keys for all student-form fieldsets.
|
||||
-- fieldset_synopsis is already seeded but injected inside Informations du TFE via $synopsisExtra.
|
||||
-- fieldset_academic already exists but was never wired in form.php for partage mode.
|
||||
|
||||
INSERT OR IGNORE INTO form_help_blocks (key, name, content, enabled) VALUES
|
||||
('fieldset_languages', 'Langue(s)', '', 1),
|
||||
('fieldset_keywords', 'Mots-clés', '', 1),
|
||||
('fieldset_metadata', 'Métadonnées complémentaires', '', 1);
|
||||
@@ -1,47 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* HTMX handler: persist the new drag-and-drop sort order for form help blocks.
|
||||
*
|
||||
* Expects POST fields:
|
||||
* csrf_token — standard admin CSRF token
|
||||
* block[] — ordered list of block keys (one hidden input per block, submitted by
|
||||
* Sortable+htmx via the form's `end` event trigger)
|
||||
*
|
||||
* Returns a 204 No Content on success (htmx will not swap anything).
|
||||
* On error, returns a 400 with a plain-text message.
|
||||
* Legacy endpoint — no longer used (blocks are now static, non-sortable).
|
||||
* Returns 204 No Content for backwards compatibility.
|
||||
*/
|
||||
require_once __DIR__ . '/../../../bootstrap.php';
|
||||
require_once __DIR__ . '/../../../src/AdminAuth.php';
|
||||
AdminAuth::requireLogin();
|
||||
|
||||
if (!isset($_POST['csrf_token'], $_SESSION['csrf_token'])
|
||||
|| !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||
http_response_code(403);
|
||||
echo 'Token invalide.';
|
||||
exit;
|
||||
}
|
||||
|
||||
$keys = $_POST['block'] ?? [];
|
||||
if (!is_array($keys) || empty($keys)) {
|
||||
http_response_code(400);
|
||||
echo 'Paramètre block[] manquant.';
|
||||
exit;
|
||||
}
|
||||
|
||||
// Sanitise: keep only scalar strings, deduplicate.
|
||||
$keys = array_values(array_unique(array_filter($keys, 'is_string')));
|
||||
|
||||
require_once APP_ROOT . '/src/Database.php';
|
||||
$db = new Database();
|
||||
|
||||
try {
|
||||
$db->reorderFormHelpBlocks($keys);
|
||||
} catch (Exception $e) {
|
||||
error_log('form-help-reorder error: ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo 'Erreur lors de la sauvegarde.';
|
||||
exit;
|
||||
}
|
||||
|
||||
http_response_code(204);
|
||||
exit;
|
||||
@@ -27,7 +27,7 @@ $autofocusField = App::consumeAutofocus();
|
||||
$siteSettings = Database::getInstance()->getAllSettings();
|
||||
// Form help blocks
|
||||
$helpBlocks = Database::getInstance()->getAllFormHelpBlocks();
|
||||
$helpFn = fn(string $key) => $helpBlocks[$key]['content'] ?? '';
|
||||
$helpFn = fn(string $key) => empty($helpBlocks[$key]['enabled']) ? '' : ($helpBlocks[$key]['content'] ?? '');
|
||||
|
||||
function withAutofocus(string $fieldName, array $attrs = []): array {
|
||||
global $autofocusField;
|
||||
|
||||
@@ -50,7 +50,7 @@ try {
|
||||
} elseif ($formHelpKey) {
|
||||
$editType = "form_help";
|
||||
$formHelpContent = $db->getFormHelpBlock($formHelpKey);
|
||||
$editTitle = Database::FORM_HELP_LABELS[$formHelpKey] ?? $formHelpKey;
|
||||
$editTitle = $formHelpKey;
|
||||
} else {
|
||||
$editType = "apropos";
|
||||
$value = $db->getAproposContent($aproposKey);
|
||||
|
||||
@@ -19,7 +19,7 @@ $autofocusField = App::consumeAutofocus();
|
||||
|
||||
// Form help blocks for editable généralités
|
||||
$helpBlocks = Database::getInstance()->getAllFormHelpBlocks();
|
||||
$helpFn = fn(string $key) => $helpBlocks[$key]['content'] ?? '';
|
||||
$helpFn = fn(string $key) => empty($helpBlocks[$key]['enabled']) ? '' : ($helpBlocks[$key]['content'] ?? '');
|
||||
|
||||
function old($key, $default = "") {
|
||||
global $formData;
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
/**
|
||||
* HTMX fragment: expand/collapse/editor for a single form help block.
|
||||
*
|
||||
* GET ?key=xxx → render collapsed chip: 3/4 left (name + MD preview), 1/4 right (edit + toggle dot)
|
||||
* GET ?key=xxx&edit=1 → render inline OverType editor + name field
|
||||
* POST → save content + name, return collapsed view
|
||||
* POST _action=toggle → toggle enabled, return collapsed view
|
||||
*/
|
||||
require_once __DIR__ . '/../../bootstrap.php';
|
||||
require_once __DIR__ . '/../../src/AdminAuth.php';
|
||||
AdminAuth::requireLogin();
|
||||
require_once __DIR__ . '/../../src/Database.php';
|
||||
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
$key = $_GET['key'] ?? '';
|
||||
|
||||
if (!in_array($key, Database::FORM_HELP_KEYS, true)) {
|
||||
http_response_code(400);
|
||||
echo 'Clé invalide.';
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── POST ─────────────────────────────────────────────────────────────────────
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = $_POST['_action'] ?? 'save';
|
||||
$db = new Database();
|
||||
|
||||
// Toggle doesn't need CSRF — low-risk action behind admin auth
|
||||
if ($action === 'toggle') {
|
||||
$db->toggleFormHelpBlock($key);
|
||||
renderCollapsed($db, $key);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Save requires CSRF
|
||||
if (!isset($_POST['csrf_token'], $_SESSION['csrf_token'])
|
||||
|| !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||
http_response_code(403);
|
||||
echo 'Token invalide.';
|
||||
exit;
|
||||
}
|
||||
|
||||
// save
|
||||
$content = $_POST['content'] ?? '';
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
try {
|
||||
$db->setFormHelpBlock($key, $content);
|
||||
if ($name !== '') {
|
||||
$db->setFormHelpBlockName($key, $name);
|
||||
}
|
||||
require_once __DIR__ . '/../../src/AdminLogger.php';
|
||||
AdminLogger::make()->logFormStructureEdit($key);
|
||||
} catch (Exception $e) {
|
||||
error_log('form-help-inline save error: ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo 'Erreur lors de la sauvegarde.';
|
||||
exit;
|
||||
}
|
||||
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
renderCollapsed($db, $key);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── GET ──────────────────────────────────────────────────────────────────────
|
||||
$db = new Database();
|
||||
$editMode = ($_GET['edit'] ?? '') === '1';
|
||||
|
||||
if ($editMode) {
|
||||
renderEditor($db, $key);
|
||||
} else {
|
||||
renderCollapsed($db, $key);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// RENDER HELPERS
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
function renderCollapsed(Database $db, string $key): void
|
||||
{
|
||||
$blocks = $db->getAllFormHelpBlocks();
|
||||
$b = $blocks[$key] ?? ['content' => '', 'name' => '', 'enabled' => 0];
|
||||
$name = $b['name'] ?: $key;
|
||||
$content = $b['content'] ?? '';
|
||||
$enabled = (int)($b['enabled'] ?? 1);
|
||||
$hasContent = trim($content) !== '';
|
||||
|
||||
$mdHtml = '';
|
||||
if ($hasContent) {
|
||||
require_once APP_ROOT . '/src/Parsedown.php';
|
||||
$pd = new Parsedown();
|
||||
$pd->setSafeMode(true);
|
||||
$mdHtml = $pd->text($content);
|
||||
}
|
||||
?>
|
||||
<div class="fhb-inline <?= $enabled ? '' : 'fhb-inline--disabled' ?>" data-key="<?= htmlspecialchars($key) ?>">
|
||||
|
||||
<!-- Left side: name + content (content hidden when disabled) -->
|
||||
<div class="fhb-inline-body">
|
||||
<div class="fhb-inline-name"><?= htmlspecialchars($name) ?></div>
|
||||
<?php if ($enabled && $hasContent): ?>
|
||||
<div class="fhb-md-preview"><?= $mdHtml ?></div>
|
||||
<?php elseif ($enabled): ?>
|
||||
<div class="fhb-inline-empty">— vide —</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Right side: dot above edit button (edit hidden when disabled) -->
|
||||
<div class="fhb-inline-actions">
|
||||
<form hx-post="/admin/form-help-inline-fragment.php?key=<?= urlencode($key) ?>"
|
||||
hx-target="closest .fhb-inline"
|
||||
hx-swap="outerHTML"
|
||||
class="fhb-toggle-form">
|
||||
<input type="hidden" name="_action" value="toggle">
|
||||
<button type="submit"
|
||||
class="fhb-dot <?= $enabled ? 'fhb-dot--on' : 'fhb-dot--off' ?>"
|
||||
title="<?= $enabled ? 'Désactiver ce bloc' : 'Activer ce bloc' ?>"
|
||||
aria-label="<?= $enabled ? 'Désactiver ce bloc' : 'Activer ce bloc' ?>"></button>
|
||||
</form>
|
||||
|
||||
<?php if ($enabled): ?>
|
||||
<button type="button" class="btn btn--primary btn--sm"
|
||||
hx-get="/admin/form-help-inline-fragment.php?key=<?= urlencode($key) ?>&edit=1"
|
||||
hx-target="closest .fhb-inline"
|
||||
hx-swap="outerHTML">Éditer</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
function renderEditor(Database $db, string $key): void
|
||||
{
|
||||
$blocks = $db->getAllFormHelpBlocks();
|
||||
$b = $blocks[$key] ?? ['content' => '', 'name' => ''];
|
||||
$name = $b['name'] ?: $key;
|
||||
$content = $b['content'] ?? '';
|
||||
?>
|
||||
<div class="fhb-inline fhb-inline--editing" data-key="<?= htmlspecialchars($key) ?>">
|
||||
<form hx-post="/admin/form-help-inline-fragment.php?key=<?= urlencode($key) ?>"
|
||||
hx-swap="outerHTML"
|
||||
hx-target="closest .fhb-inline"
|
||||
class="fhb-inline-form">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
|
||||
<div class="fhb-edit-name-row">
|
||||
<label for="fhb-name-<?= htmlspecialchars($key) ?>" class="fhb-edit-label">Nom du bloc :</label>
|
||||
<input type="text" id="fhb-name-<?= htmlspecialchars($key) ?>" name="name"
|
||||
value="<?= htmlspecialchars($name) ?>"
|
||||
class="fhb-name-input"
|
||||
placeholder="Nom du bloc d'aide">
|
||||
</div>
|
||||
|
||||
<label for="fhb-ed-<?= htmlspecialchars($key) ?>" class="fhb-edit-label">Contenu (Markdown) :</label>
|
||||
<input type="hidden" id="fhb-content-<?= htmlspecialchars($key) ?>" name="content"
|
||||
value="<?= htmlspecialchars($content) ?>">
|
||||
<div id="fhb-editor-<?= htmlspecialchars($key) ?>" class="fhb-overtype-editor"></div>
|
||||
|
||||
<div class="fhb-edit-buttons">
|
||||
<button type="submit" class="btn btn--primary btn--sm">Enregistrer</button>
|
||||
<button type="button" class="btn btn--secondary btn--sm"
|
||||
hx-get="/admin/form-help-inline-fragment.php?key=<?= urlencode($key) ?>"
|
||||
hx-target="closest .fhb-inline"
|
||||
hx-swap="outerHTML">Annuler</button>
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
(function(){
|
||||
var hidden = document.getElementById('fhb-content-<?= htmlspecialchars($key) ?>');
|
||||
var ed = document.getElementById('fhb-editor-<?= htmlspecialchars($key) ?>');
|
||||
if (hidden && ed && typeof OverType !== 'undefined') {
|
||||
var OT = OverType.default || OverType;
|
||||
new OT(ed, {
|
||||
value: hidden.value,
|
||||
minHeight: '400px',
|
||||
spellcheck: false,
|
||||
onChange: function(v) { hidden.value = v; }
|
||||
});
|
||||
} else {
|
||||
ed.innerHTML = '<textarea name="content" style="width:100%;min-height:400px;font-family:monospace">'
|
||||
+ hidden.value.replace(/</g,'<').replace(/>/g,'>') + '</textarea>';
|
||||
var ta = ed.querySelector('textarea');
|
||||
ta.addEventListener('input', function() {
|
||||
hidden.value = this.value;
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* HTMX fragment (admin): renders the licence section with conditional required states.
|
||||
*
|
||||
* POST: access_type_id, license_id, license_custom, cc2r, admin_mode
|
||||
*
|
||||
* Admin mode: never required.
|
||||
*/
|
||||
require_once __DIR__ . '/../../bootstrap.php';
|
||||
require_once __DIR__ . '/../../src/AdminAuth.php';
|
||||
AdminAuth::requireLogin();
|
||||
|
||||
$accessTypeId = $_POST['access_type_id'] ?? '2';
|
||||
$licenseId = $_POST['license_id'] ?? '';
|
||||
$licenseCustom = $_POST['license_custom'] ?? '';
|
||||
$cc2r = !empty($_POST['cc2r']);
|
||||
$adminMode = true;
|
||||
|
||||
require_once APP_ROOT . '/src/Database.php';
|
||||
$db = Database::getInstance();
|
||||
$licenseTypes = $db->getAllLicenseTypes();
|
||||
?>
|
||||
|
||||
<div class="licence-license-choice">
|
||||
<?php
|
||||
$name = 'license_id'; $label = 'Licence :'; $options = $licenseTypes;
|
||||
$selected = $licenseId; $placeholder = '— Sélectionner —'; $required = false;
|
||||
include APP_ROOT . '/templates/partials/form/select-field.php';
|
||||
?>
|
||||
|
||||
<?php
|
||||
$name = 'license_custom'; $label = 'Ou précisez une autre licence :';
|
||||
$value = htmlspecialchars($licenseCustom);
|
||||
$hint = 'Ex: CC BY-NC 4.0, Tous droits réservés...';
|
||||
include APP_ROOT . '/templates/partials/form/text-field.php';
|
||||
?>
|
||||
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="cc2r" value="1"
|
||||
<?= $cc2r ? 'checked' : '' ?>>
|
||||
J'accepte les Conditions Collectives de Réutilisation (CC2r)
|
||||
</label>
|
||||
<small><a href="https://cc2r.net/" target="_blank" rel="noopener">En savoir plus sur la CC2r ↗</a></small>
|
||||
</div>
|
||||
</div>
|
||||
+213
-189
@@ -16,7 +16,6 @@
|
||||
white-space: nowrap;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
.admin-body header nav .nav-left,
|
||||
.admin-body header nav .nav-right-links {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -1480,7 +1479,7 @@
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
Form Help Blocks — drag-and-drop builder (contenus.php)
|
||||
Form Help Blocks — static structure view (contenus.php)
|
||||
═══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
.fhb-hint {
|
||||
@@ -1489,225 +1488,250 @@
|
||||
margin-bottom: var(--space-m);
|
||||
}
|
||||
|
||||
.fhb-layout {
|
||||
/* ── Structure container ───────────────────────────────────────────────────── */
|
||||
|
||||
.fhb-structure {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-m);
|
||||
align-items: start;
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-xs);
|
||||
max-width: 100%;
|
||||
margin-top: var(--space-m);
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.fhb-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
/* ── Fieldset cards (static reference) ─────────────────────────────────────── */
|
||||
|
||||
/* ── Panels ─────────────────────────────────────────────────────────────── */
|
||||
|
||||
.fhb-sortable-panel,
|
||||
.fhb-form-preview-panel {
|
||||
.fhb-fieldset-card {
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: var(--radius);
|
||||
padding: var(--space-s);
|
||||
padding: var(--space-xs) var(--space-s);
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.fhb-panel-title {
|
||||
.fhb-fieldset-card-legend {
|
||||
font-size: var(--step-0);
|
||||
font-weight: 600;
|
||||
margin: 0 0 var(--space-3xs) 0;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.fhb-panel-desc {
|
||||
font-size: var(--step--2);
|
||||
color: var(--text-secondary);
|
||||
margin: 0 0 var(--space-xs) 0;
|
||||
}
|
||||
|
||||
/* ── Saving indicator ─────────────────────────────────────────────────────── */
|
||||
|
||||
.fhb-saving {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: var(--space-2xs);
|
||||
font-size: var(--step--1);
|
||||
color: var(--accent-primary);
|
||||
padding: var(--space-2xs) 0;
|
||||
}
|
||||
|
||||
.fhb-saving.htmx-request {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* ── Draggable block cards ─────────────────────────────────────────────────── */
|
||||
|
||||
.fhb-sortable {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2xs);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.fhb-block-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-primary);
|
||||
border-left: 4px solid var(--accent-primary);
|
||||
border-radius: var(--radius);
|
||||
padding: var(--space-2xs) var(--space-xs);
|
||||
cursor: default;
|
||||
transition: box-shadow 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.fhb-block-card:hover {
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.fhb-drag-handle {
|
||||
font-size: 1.2em;
|
||||
color: var(--text-tertiary);
|
||||
cursor: grab;
|
||||
flex-shrink: 0;
|
||||
line-height: 1;
|
||||
user-select: none;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
.fhb-drag-handle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.fhb-block-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.fhb-block-label {
|
||||
font-size: var(--step--1);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.fhb-block-preview {
|
||||
font-size: var(--step--2);
|
||||
color: var(--text-secondary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.fhb-block-empty {
|
||||
font-size: var(--step--2);
|
||||
color: var(--text-tertiary);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.fhb-edit-btn {
|
||||
flex-shrink: 0;
|
||||
font-size: var(--step--2) !important;
|
||||
padding: 2px var(--space-xs) !important;
|
||||
}
|
||||
|
||||
/* ── SortableJS state classes ─────────────────────────────────────────────── */
|
||||
|
||||
.fhb-ghost {
|
||||
opacity: 0.35;
|
||||
background: var(--accent-muted);
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.fhb-chosen {
|
||||
box-shadow: 0 4px 16px rgba(149, 87, 181, 0.25);
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.fhb-dragging {
|
||||
opacity: 0.9;
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
/* ── Form structure preview (right panel) ─────────────────────────────────── */
|
||||
|
||||
.fhb-form-preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2xs);
|
||||
}
|
||||
|
||||
.fhb-fieldset-preview {
|
||||
border: 1px solid var(--border-secondary);
|
||||
border-radius: var(--radius);
|
||||
padding: var(--space-xs);
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.fhb-fieldset-legend {
|
||||
font-size: var(--step--1);
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: var(--space-3xs);
|
||||
padding-bottom: var(--space-3xs);
|
||||
border-bottom: 1px solid var(--border-primary);
|
||||
}
|
||||
|
||||
.fhb-fieldset-inputs {
|
||||
.fhb-fieldset-card-inputs {
|
||||
margin: 0;
|
||||
padding: 0 0 0 var(--space-s);
|
||||
list-style: disc;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-3xs) var(--space-m);
|
||||
}
|
||||
|
||||
.fhb-fieldset-inputs li {
|
||||
.fhb-fieldset-card-inputs li {
|
||||
font-size: var(--step--2);
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.fhb-anchor {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2xs);
|
||||
border-radius: var(--radius);
|
||||
padding: var(--space-3xs) var(--space-xs);
|
||||
font-size: var(--step--2);
|
||||
border: 1px dashed var(--border-primary);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.fhb-anchor--filled {
|
||||
border-color: var(--accent-primary);
|
||||
background: var(--accent-muted);
|
||||
color: var(--accent-secondary);
|
||||
}
|
||||
|
||||
.fhb-anchor--empty {
|
||||
.fhb-fieldset-card-inputs li::before {
|
||||
content: '· ';
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.fhb-anchor-icon {
|
||||
flex-shrink: 0;
|
||||
font-style: normal;
|
||||
/* ── Help block wrapper ────────────────────────────────────────────────────── */
|
||||
|
||||
.fhb-block-wrapper {
|
||||
/* container for the fhb-inline, one per help block */
|
||||
}
|
||||
|
||||
.fhb-anchor-label {
|
||||
flex: 1;
|
||||
}
|
||||
/* ── Inline help block (collapsed state) ───────────────────────────────────── */
|
||||
|
||||
.fhb-anchor-pos {
|
||||
font-size: var(--step--2);
|
||||
font-weight: 600;
|
||||
color: var(--accent-primary);
|
||||
background: var(--accent-muted);
|
||||
.fhb-inline {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: var(--space-s);
|
||||
align-items: center;
|
||||
border: 1px solid var(--border-primary);
|
||||
border-left: 4px solid var(--accent-primary);
|
||||
border-radius: var(--radius);
|
||||
padding: 0 4px;
|
||||
padding: var(--space-xs) var(--space-s);
|
||||
background: var(--bg-primary);
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
.fhb-inline:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* Disabled state — one line only, content hidden */
|
||||
.fhb-inline--disabled {
|
||||
border-left-color: var(--text-tertiary);
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.fhb-inline--disabled:hover {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.fhb-inline--disabled .fhb-md-preview,
|
||||
.fhb-inline--disabled .fhb-inline-empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Editing state */
|
||||
.fhb-inline--editing {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
border-color: var(--accent-primary);
|
||||
box-shadow: 0 4px 16px rgba(149, 87, 181, 0.15);
|
||||
padding: var(--space-s);
|
||||
min-height: 50vh;
|
||||
}
|
||||
|
||||
.fhb-inline--editing .fhb-inline-form {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto auto 1fr auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
/* ── Left side: name + content ─────────────────────────────────────────────── */
|
||||
|
||||
.fhb-inline-body {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.fhb-inline-name {
|
||||
font-size: var(--step--1);
|
||||
font-weight: 600;
|
||||
color: var(--accent-secondary);
|
||||
margin-bottom: var(--space-3xs);
|
||||
}
|
||||
|
||||
/* ── Small rendered Markdown preview ──────────────────────────────────────── */
|
||||
|
||||
.fhb-md-preview {
|
||||
font-size: var(--step--2);
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.45;
|
||||
max-height: 6em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fhb-md-preview p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.fhb-md-preview p + p {
|
||||
margin-top: var(--space-3xs);
|
||||
}
|
||||
|
||||
.fhb-md-preview ul,
|
||||
.fhb-md-preview ol {
|
||||
margin: var(--space-3xs) 0;
|
||||
padding-left: var(--space-s);
|
||||
}
|
||||
|
||||
.fhb-md-preview li {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.fhb-md-preview strong { font-weight: 600; }
|
||||
.fhb-md-preview em { font-style: italic; }
|
||||
.fhb-md-preview code {
|
||||
font-size: 0.9em;
|
||||
background: var(--bg-secondary);
|
||||
padding: 0 var(--space-4xs);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.fhb-inline-empty {
|
||||
font-size: var(--step--2);
|
||||
color: var(--text-tertiary);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ── Right side: edit button + live dot ───────────────────────────────────── */
|
||||
|
||||
.fhb-inline-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: var(--space-2xs);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.fhb-toggle-form {
|
||||
margin: 0;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
/* Live dot — green when on, red when off */
|
||||
.fhb-dot {
|
||||
display: block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
.fhb-dot:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.fhb-dot--on {
|
||||
background: #2d6a4f;
|
||||
}
|
||||
|
||||
.fhb-dot--off {
|
||||
background: #c0392b;
|
||||
}
|
||||
|
||||
/* ── Editor form ──────────────────────────────────────────────────────────── */
|
||||
|
||||
.fhb-edit-name-row {
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
|
||||
.fhb-edit-label {
|
||||
display: block;
|
||||
font-size: var(--step--2);
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: var(--space-3xs);
|
||||
}
|
||||
|
||||
.fhb-name-input {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
padding: var(--space-2xs) var(--space-xs);
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: var(--radius);
|
||||
font-size: var(--step--1);
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
|
||||
.fhb-name-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-primary);
|
||||
box-shadow: 0 0 0 2px var(--accent-muted);
|
||||
}
|
||||
|
||||
.fhb-overtype-editor .--type-container {
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.fhb-edit-buttons {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: max-content;
|
||||
gap: var(--space-xs);
|
||||
margin-top: var(--space-xs);
|
||||
}
|
||||
|
||||
.fhb-inline-form {
|
||||
margin-top: var(--space-xs);
|
||||
}
|
||||
+415
-406
File diff suppressed because it is too large.
Load diff
+559
-513
File diff suppressed because it is too large.
Load diff
+126
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* OverType v2.3.10
|
||||
* A lightweight markdown editor library with perfect WYSIWYG alignment
|
||||
* @license MIT
|
||||
* @author David Miranda
|
||||
* https://github.com/panphora/overtype
|
||||
*/
|
||||
var OverTypeEditor=(()=>{var ke=Object.defineProperty;var xn=Object.getOwnPropertyDescriptor;var kn=Object.getOwnPropertyNames;var Ln=Object.prototype.hasOwnProperty;var Sn=(n,e,t)=>e in n?ke(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t;var lt=(n,e)=>{for(var t in e)ke(n,t,{get:e[t],enumerable:!0})},_n=(n,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of kn(e))!Ln.call(n,o)&&o!==t&&ke(n,o,{get:()=>e[o],enumerable:!(i=xn(e,o))||i.enumerable});return n};var En=n=>_n(ke({},"__esModule",{value:!0}),n);var C=(n,e,t)=>(Sn(n,typeof e!="symbol"?e+"":e,t),t);var xi={};lt(xi,{default:()=>bi});var E=class{static resetLinkIndex(){this.linkIndex=0}static setCodeHighlighter(e){this.codeHighlighter=e}static setCustomSyntax(e){this.customSyntax=e}static applyCustomSyntax(e){return this.customSyntax?this.customSyntax(e):e}static escapeHtml(e){let t={"&":"&","<":"<",">":">",'"':""","'":"'"};return e.replace(/[&<>"']/g,i=>t[i])}static preserveIndentation(e,t){let o=t.match(/^(\s*)/)[1].replace(/ /g," ");return e.replace(/^\s*/,o)}static parseHeader(e){return e.replace(/^(#{1,3})\s(.+)$/,(t,i,o)=>{let r=i.length;return o=this.parseInlineElements(o),`<h${r}><span class="syntax-marker">${i} </span>${o}</h${r}>`})}static parseHorizontalRule(e){return e.match(/^(-{3,}|\*{3,}|_{3,})$/)?`<div><span class="hr-marker">${e}</span></div>`:null}static parseBlockquote(e){return e.replace(/^> (.+)$/,(t,i)=>`<span class="blockquote"><span class="syntax-marker">></span> ${i}</span>`)}static parseBulletList(e){return e.replace(/^((?: )*)([-*+])\s(.+)$/,(t,i,o,r)=>(r=this.parseInlineElements(r),`${i}<li class="bullet-list"><span class="syntax-marker">${o} </span>${r}</li>`))}static parseTaskList(e,t=!1){return e.replace(/^((?: )*)-(\s+)\[([ xX])\](\s*)(.*)$/,(i,o,r,s,a,l)=>{if(l=this.parseInlineElements(l),t){let p=s.toLowerCase()==="x";return`${o}<li class="task-list"><input type="checkbox" ${p?"checked":""}> ${l}</li>`}else return`${o}<li class="task-list"><span class="syntax-marker">-${r}[${s}]${a}</span>${l}</li>`})}static parseNumberedList(e){return e.replace(/^((?: )*)(\d+\.)\s(.+)$/,(t,i,o,r)=>(r=this.parseInlineElements(r),`${i}<li class="ordered-list"><span class="syntax-marker">${o} </span>${r}</li>`))}static parseCodeBlock(e){return/^`{3}[^`]*$/.test(e)?`<div><span class="code-fence">${e}</span></div>`:null}static parseBold(e){return e=e.replace(/\*\*(.+?)\*\*/g,'<strong><span class="syntax-marker">**</span>$1<span class="syntax-marker">**</span></strong>'),e=e.replace(/__(.+?)__/g,'<strong><span class="syntax-marker">__</span>$1<span class="syntax-marker">__</span></strong>'),e}static parseItalic(e){return e=e.replace(new RegExp("(?<![\\*>])\\*(?!\\*)(.+?)(?<!\\*)\\*(?!\\*)","g"),'<em><span class="syntax-marker">*</span>$1<span class="syntax-marker">*</span></em>'),e=e.replace(new RegExp("(?<=^|\\s)_(?!_)(.+?)(?<!_)_(?!_)(?=\\s|$)","g"),'<em><span class="syntax-marker">_</span>$1<span class="syntax-marker">_</span></em>'),e}static parseStrikethrough(e){return e=e.replace(new RegExp("(?<!~)~~(?!~)(.+?)(?<!~)~~(?!~)","g"),'<del><span class="syntax-marker">~~</span>$1<span class="syntax-marker">~~</span></del>'),e=e.replace(new RegExp("(?<!~)~(?!~)(.+?)(?<!~)~(?!~)","g"),'<del><span class="syntax-marker">~</span>$1<span class="syntax-marker">~</span></del>'),e}static parseInlineCode(e){return e.replace(new RegExp("(?<!`)(`+)(?!`)((?:(?!\\1).)+?)(\\1)(?!`)","g"),'<code><span class="syntax-marker">$1</span>$2<span class="syntax-marker">$3</span></code>')}static sanitizeUrl(e){let t=e.trim(),i=t.toLowerCase(),r=["http://","https://","mailto:","ftp://","ftps://"].some(a=>i.startsWith(a)),s=t.startsWith("/")||t.startsWith("#")||t.startsWith("?")||t.startsWith(".")||!t.includes(":")&&!t.includes("//");return r||s?e:"#"}static parseLinks(e){return e.replace(/\[(.+?)\]\((.+?)\)/g,(t,i,o)=>{let r=`--link-${this.linkIndex++}`;return`<a href="${this.sanitizeUrl(o)}" style="anchor-name: ${r}"><span class="syntax-marker">[</span>${i}<span class="syntax-marker url-part">](${o})</span></a>`})}static identifyAndProtectSanctuaries(e){let t=new Map,i=0,o=e,r=[],s=/\[([^\]]+)\]\(([^)]+)\)/g,a;for(;(a=s.exec(e))!==null;){let h=a.index+a[0].indexOf("](")+2,u=h+a[2].length;r.push({start:h,end:u})}let l=new RegExp("(?<!`)(`+)(?!`)((?:(?!\\1).)+?)(\\1)(?!`)","g"),p,c=[];for(;(p=l.exec(e))!==null;){let d=p.index,h=p.index+p[0].length;r.some(f=>d>=f.start&&h<=f.end)||c.push({match:p[0],index:p.index,openTicks:p[1],content:p[2],closeTicks:p[3]})}return c.sort((d,h)=>h.index-d.index),c.forEach(d=>{let h=`\uE000${i++}\uE001`;t.set(h,{type:"code",original:d.match,openTicks:d.openTicks,content:d.content,closeTicks:d.closeTicks}),o=o.substring(0,d.index)+h+o.substring(d.index+d.match.length)}),o=o.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(d,h,u)=>{let f=`\uE000${i++}\uE001`;return t.set(f,{type:"link",original:d,linkText:h,url:u}),f}),{protectedText:o,sanctuaries:t}}static restoreAndTransfLine truncated
|
||||
`),a=!1,p=s.map((c,d)=>{if(i&&d===t)return`<div class="raw-line">${this.escapeHtml(c)||" "}</div>`;if(/^```[^`]*$/.test(c))return a=!a,this.applyCustomSyntax(this.parseLine(c,r));if(a){let u=this.escapeHtml(c);return`<div>${this.preserveIndentation(u,c)||" "}</div>`}return this.applyCustomSyntax(this.parseLine(c,r))}).join("");return this.postProcessHTML(p,o)}static postProcessHTML(e,t){if(typeof document>"u"||!document)return this.postProcessHTMLManual(e,t);let i=document.createElement("div");i.innerHTML=e;let o=null,r=null,s=null,a=!1,l=Array.from(i.children);for(let p=0;p<l.length;p++){let c=l[p];if(!c.parentNode)continue;let d=c.querySelector(".code-fence");if(d){let u=d.textContent;if(u.startsWith("```"))if(a){let f=t||this.codeHighlighter;if(s&&f&&s._codeContent)try{let m=f(s._codeContent,s._language||"");m&&typeof m.then=="function"?console.warn("Async highlighters are not supported in parse() because it returns an HTML string. The caller creates new DOM elements from that string, breaking references to the elements we would update. Use synchronous highlighters only."):m&&typeof m=="string"&&m.trim()&&(s._codeElement.innerHTML=m)}catch(m){console.warn("Code highlighting failed:",m)}a=!1,s=null;continue}else{a=!0,s=document.createElement("pre");let f=document.createElement("code");s.appendChild(f),s.className="code-block";let m=u.slice(3).trim();m&&(f.className=`language-${m}`),i.insertBefore(s,c.nextSibling),s._codeElement=f,s._language=m,s._codeContent="";continue}}if(a&&s&&c.tagName==="DIV"&&!c.querySelector(".code-fence")){let u=s._codeElement||s.querySelector("code");s._codeContent.length>0&&(s._codeContent+=`
|
||||
`);let f=c.textContent.replace(/\u00A0/g," ");s._codeContent+=f,u.textContent.length>0&&(u.textContent+=`
|
||||
`),u.textContent+=f,c.remove();continue}let h=null;if(c.tagName==="DIV"&&(h=c.querySelector("li")),h){let u=h.classList.contains("bullet-list"),f=h.classList.contains("ordered-list");if(!u&&!f){o=null,r=null;continue}let m=u?"ul":"ol";(!o||r!==m)&&(o=document.createElement(m),i.insertBefore(o,c),r=m);let g=[];for(let y of c.childNodes)if(y.nodeType===3&&y.textContent.match(/^\u00A0+$/))g.push(y.cloneNode(!0));else if(y===h)break;g.forEach(y=>{h.insertBefore(y,h.firstChild)}),o.appendChild(h),c.remove()}else o=null,r=null}return i.innerHTML}static postProcessHTMLManual(e,t){let i=e;i=i.replace(/((?:<div>(?: )*<li class="bullet-list">.*?<\/li><\/div>\s*)+)/gs,r=>{let s=r.match(/<div>(?: )*<li class="bullet-list">.*?<\/li><\/div>/gs)||[];return s.length>0?"<ul>"+s.map(l=>{let p=l.match(/<div>((?: )*)<li/),c=l.match(/<li class="bullet-list">.*?<\/li>/);if(p&&c){let d=p[1];return c[0].replace(/<li class="bullet-list">/,`<li class="bullet-list">${d}`)}return c?c[0]:""}).filter(Boolean).join("")+"</ul>":r}),i=i.replace(/((?:<div>(?: )*<li class="ordered-list">.*?<\/li><\/div>\s*)+)/gs,r=>{let s=r.match(/<div>(?: )*<li class="ordered-list">.*?<\/li><\/div>/gs)||[];return s.length>0?"<ol>"+s.map(l=>{let p=l.match(/<div>((?: )*)<li/),c=l.match(/<li class="ordered-list">.*?<\/li>/);if(p&&c){let d=p[1];return c[0].replace(/<li class="ordered-list">/,`<li class="ordered-list">${d}`)}return c?c[0]:""}).filter(Boolean).join("")+"</ol>":r});let o=/<div><span class="code-fence">(```[^<]*)<\/span><\/div>(.*?)<div><span class="code-fence">(```)<\/span><\/div>/gs;return i=i.replace(o,(r,s,a,l)=>{let c=(a.match(/<div>(.*?)<\/div>/gs)||[]).map(g=>g.replace(/<div>(.*?)<\/div>/s,"$1").replace(/ /g," ")).join(`
|
||||
`),d=s.slice(3).trim(),h=d?` class="language-${d}"`:"",u=c,f=t||this.codeHighlighter;if(f)try{let g=c.replace(/"/g,'"').replace(/'/g,"'").replace(/</g,"<").replace(/>/g,">").replace(/&/g,"&"),y=f(g,d);y&&typeof y.then=="function"?console.warn("Async highlighters are not supported in Node.js (non-DOM) context. Use synchronous highlighters for server-side rendering."):y&&typeof y=="string"&&y.trim()&&(u=y)}catch(g){console.warn("Code highlighting failed:",g)}let m=`<div><span class="code-fence">${s}</span></div>`;return m+=`<pre class="code-block"><code${h}>${u}</code></pre>`,m+=`<div><span class="code-fence">${l}</span></div>`,m}),i}static getListContext(e,t){let i=e.split(`
|
||||
`),o=0,r=0,s=0;for(let h=0;h<i.length;h++){let u=i[h].length;if(o+u>=t){r=h,s=o;break}o+=u+1}let a=i[r],l=s+a.length,p=a.match(this.LIST_PATTERNS.checkbox);if(p)return{inList:!0,listType:"checkbox",indent:p[1],marker:"-",checked:p[2]==="x",content:p[3],lineStart:s,lineEnd:l,markerEndPos:s+p[1].length+p[2].length+5};let c=a.match(this.LIST_PATTERNS.bullet);if(c)return{inList:!0,listType:"bullet",indent:c[1],marker:c[2],content:c[3],lineStart:s,lineEnd:l,markerEndPos:s+c[1].length+c[2].length+1};let d=a.match(this.LIST_PATTERNS.numbered);return d?{inList:!0,listType:"numbered",indent:d[1],marker:parseInt(d[2]),content:d[3],lineStart:s,lineEnd:l,markerEndPos:s+d[1].length+d[2].length+2}:{inList:!1,listType:null,indent:"",marker:null,content:a,lineStart:s,lineEnd:l,markerEndPos:s}}static createNewListItem(e){switch(e.listType){case"bullet":return`${e.indent}${e.marker} `;case"numbered":return`${e.indent}${e.marker+1}. `;case"checkbox":return`${e.indent}- [ ] `;default:return""}}static renumberLists(e){let t=e.split(`
|
||||
`),i=new Map,o=!1;return t.map(s=>{let a=s.match(this.LIST_PATTERNS.numbered);if(a){let l=a[1],p=l.length,c=a[3];o||i.clear();let d=(i.get(p)||0)+1;i.set(p,d);for(let[h]of i)h>p&&i.delete(h);return o=!0,`${l}${d}. ${c}`}else return(s.trim()===""||!s.match(/^\s/))&&(o=!1,i.clear()),s}).join(`
|
||||
`)}};C(E,"linkIndex",0),C(E,"codeHighlighter",null),C(E,"customSyntax",null),C(E,"LIST_PATTERNS",{bullet:/^(\s*)([-*+])\s+(.*)$/,numbered:/^(\s*)(\d+)\.\s+(.*)$/,checkbox:/^(\s*)-\s+\[([ x])\]\s+(.*)$/});var re=class{constructor(e){this.editor=e}handleKeydown(e){if(!(navigator.platform.toLowerCase().includes("mac")?e.metaKey:e.ctrlKey))return!1;let o=null;switch(e.key.toLowerCase()){case"b":e.shiftKey||(o="toggleBold");break;case"i":e.shiftKey||(o="toggleItalic");break;case"k":e.shiftKey||(o="insertLink");break;case"7":e.shiftKey&&(o="toggleNumberedList");break;case"8":e.shiftKey&&(o="toggleBulletList");break}return o?(e.preventDefault(),this.editor.performAction(o,e),!0):!1}destroy(){}};var z={name:"solar",colors:{bgPrimary:"#faf0ca",bgSecondary:"#ffffff",text:"#0d3b66",textPrimary:"#0d3b66",textSecondary:"#5a7a9b",h1:"#f95738",h2:"#ee964b",h3:"#3d8a51",strong:"#ee964b",em:"#f95738",del:"#ee964b",link:"#0d3b66",code:"#0d3b66",codeBg:"rgba(244, 211, 94, 0.4)",blockquote:"#5a7a9b",hr:"#5a7a9b",syntaxMarker:"rgba(13, 59, 102, 0.52)",syntax:"#999999",cursor:"#f95738",selection:"rgba(244, 211, 94, 0.4)",listMarker:"#ee964b",rawLine:"#5a7a9b",border:"#e0e0e0",hoverBg:"#f0f0f0",primary:"#0d3b66",toolbarBg:"#ffffff",toolbarIcon:"#0d3b66",toolbarHover:"#f5f5f5",toolbarActive:"#faf0ca",placeholder:"#999999"},previewColors:{text:"#0d3b66",h1:"inherit",h2:"inherit",h3:"inherit",strong:"inherit",em:"inherit",link:"#0d3b66",code:"#0d3b66",codeBg:"rgba(244, 211, 94, 0.4)",blockquote:"#5a7a9b",hr:"#5a7a9b",bg:"transparent"}},ct={name:"cave",colors:{bgPrimary:"#141E26",bgSecondary:"#1D2D3E",text:"#c5dde8",textPrimary:"#c5dde8",textSecondary:"#9fcfec",h1:"#d4a5ff",h2:"#f6ae2d",h3:"#9fcfec",strong:"#f6ae2d",em:"#9fcfec",del:"#f6ae2d",link:"#9fcfec",code:"#c5dde8",codeBg:"#1a232b",blockquote:"#9fcfec",hr:"#c5dde8",syntaxMarker:"rgba(159, 207, 236, 0.73)",syntax:"#7a8c98",cursor:"#f26419",selection:"rgba(51, 101, 138, 0.4)",listMarker:"#f6ae2d",rawLine:"#9fcfec",border:"#2a3f52",hoverBg:"#243546",primary:"#9fcfec",toolbarBg:"#1D2D3E",toolbarIcon:"#c5dde8",toolbarHover:"#243546",toolbarActive:"#2a3f52",placeholder:"#6a7a88"},previewColors:{text:"#c5dde8",h1:"inherit",h2:"inherit",h3:"inherit",strong:"inherit",em:"inherit",link:"#9fcfec",code:"#c5dde8",codeBg:"#1a232b",blockquote:"#9fcfec",hr:"#c5dde8",bg:"transparent"}},pt={solar:z,cave:ct,auto:z,light:z,dark:ct};function R(n){return typeof n=="string"?{...pt[n]||pt.solar,name:n}:n}function Ze(n){if(n!=="auto")return n;let e=window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)");return e!=null&&e.matches?"cave":"solar"}function Q(n,e){let t=[];for(let[i,o]of Object.entries(n)){let r=i.replace(/([A-Z])/g,"-$1").toLowerCase();t.push(`--${r}: ${o};`)}if(e)for(let[i,o]of Object.entries(e)){let r=i.replace(/([A-Z])/g,"-$1").toLowerCase();t.push(`--preview-${r}-default: ${o};`)}return t.join(`
|
||||
`)}function dt(n,e={},t={}){return{...n,colors:{...n.colors,...e},previewColors:{...n.previewColors,...t}}}function Le(n={}){let{fontSize:e="14px",lineHeight:t=1.6,fontFamily:i='"SF Mono", SFMono-Regular, Menlo, Monaco, "Cascadia Code", Consolas, "Roboto Mono", "Noto Sans Mono", "Droid Sans Mono", "Ubuntu Mono", "DejaVu Sans Mono", "Liberation Mono", "Courier New", Courier, monospace',padding:o="20px",theme:r=null,mobile:s={}}=n,a=Object.keys(s).length>0?`
|
||||
@media (max-width: 640px) {
|
||||
.overtype-wrapper .overtype-input,
|
||||
.overtype-wrapper .overtype-preview {
|
||||
${Object.entries(s).map(([p,c])=>`${p.replace(/([A-Z])/g,"-$1").toLowerCase()}: ${c} !important;`).join(`
|
||||
`)}
|
||||
}
|
||||
}
|
||||
`:"",l=r&&r.colors?Q(r.colors,r.previewColors):"";return`.overtype-container *{margin: 0 !important;padding: 0 !important;border: 0 !important;float: none !important;clear: none !important;text-decoration: none !important;text-transform: none !important;letter-spacing: normal !important;box-shadow: none !important;text-shadow: none !important;box-sizing: border-box !important}.overtype-container{display: flex !important;flex-direction: column !important;width: 100% !important;height: 100% !important;position: relative !important;overflow: visible !important;font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif !important;text-align: left !important;${l?`
|
||||
/* Theme Variables */
|
||||
${l}`:""}}.overtype-container .overtype-wrapper *{text-align: left !important}.overtype-container.overtype-auto-resize{height: auto !important}.overtype-container.overtype-auto-resize .overtype-wrapper{flex: 0 0 auto !important;height: auto !important;min-height: 60px !important;overflow: visible !important}.overtype-wrapper{position: relative !important;width: 100% !important;flex: 1 1 0 !important;min-height: 60px !important;overflow: hidden !important;background: var(--bg-secondary,#ffffff) !important;z-index: 1}.overtype-wrapper .overtype-input,.overtype-wrapper .overtype-preview{position: absolute !important;top: 0 !important;left: 0 !important;width: 100% !important;height: 100% !important;font-family: var(--instance-font-family,${i}) !important;
|
||||
font-variant-ligatures: none !important; /* keep metrics stable for code */
|
||||
font-size: var(--instance-font-size, ${e}) !important;
|
||||
line-height: var(--instance-line-height, ${t}) !important;
|
||||
font-weight: normal !important;
|
||||
font-style: normal !important;
|
||||
font-variant: normal !important;
|
||||
font-stretch: normal !important;
|
||||
font-kerning: none !important;
|
||||
font-feature-settings: normal !important;
|
||||
|
||||
/* Box model - must match exactly */
|
||||
padding: var(--instance-padding, ${o}) !important;margin: 0 !important;border: none !important;outline: none !important;box-sizing: border-box !important;white-space: pre-wrap !important;word-wrap: break-word !important;word-break: normal !important;overflow-wrap: break-word !important;tab-size: 2 !important;-moz-tab-size: 2 !important;text-align: left !important;text-indent: 0 !important;letter-spacing: normal !important;word-spacing: normal !important;text-transform: none !important;text-rendering: auto !important;-webkit-font-smoothing: auto !important;-webkit-text-size-adjust: 100% !important;direction: ltr !important;writing-mode: horizontal-tb !important;unicode-bidi: normal !important;text-orientation: mixed !important;text-shadow: none !important;filter: none !important;transform: none !important;zoom: 1 !important;vertical-align: baseline !important;min-width: 0 !important;min-height: 0 !important;max-width: none !important;max-height: none !important;overflow-y: auto !important;overflow-x: auto !important;scrollbar-width: auto !important;scrollbar-gutter: auto !important;animation: none !important;transition: none !important}.overtype-wrapper .overtype-input{z-index: 1 !important;color: transparent !important;caret-color: var(--cursor,#f95738) !important;background-color: transparent !important;resize: none !important;appearance: none !important;-webkit-appearance: none !important;-moz-appearance: none !important;touch-action: manipulation !important;autocomplete: off !important;autocorrect: off !important;autocapitalize: off !important}.overtype-wrapper .overtype-input::selection{background-color: var(--selection,rgba(244,211,94,0.4))}.overtype-wrapper .overtype-placeholder{position: absolute !important;top: 0 !important;left: 0 !important;width: 100% !important;z-index: 0 !important;pointer-events: none !important;user-select: none !important;font-family: var(--instance-font-family,${i}) !important;
|
||||
font-size: var(--instance-font-size, ${e}) !important;
|
||||
line-height: var(--instance-line-height, ${t}) !important;
|
||||
padding: var(--instance-padding, ${o}) !important;box-sizing: border-box !important;color: var(--placeholder,#999) !important}.overtype-wrapper .overtype-preview{z-index: 0 !important;pointer-events: none !important;color: var(--text,#0d3b66) !important;background-color: transparent !important;user-select: none !important;-webkit-user-select: none !important;-moz-user-select: none !important;-ms-user-select: none !important}.overtype-wrapper .overtype-preview *{font-family: inherit !important;font-size: inherit !important;line-height: inherit !important}.overtype-wrapper .overtype-preview div{margin: 0 !important;padding: 0 !important;border: none !important;text-align: left !important;text-indent: 0 !important;display: block !important;position: static !important;transform: none !important;min-height: 0 !important;max-height: none !important;line-height: inherit !important;font-size: inherit !important;font-family: inherit !important}.overtype-wrapper .overtype-preview .header{font-weight: bold !important}.overtype-wrapper .overtype-preview .h1{color: var(--h1,#f95738) !important}.overtype-wrapper .overtype-preview .h2{color: var(--h2,#ee964b) !important}.overtype-wrapper .overtype-preview .h3{color: var(--h3,#3d8a51) !important}.overtype-wrapper .overtype-preview h1,.overtype-wrapper .overtype-preview h2,.overtype-wrapper .overtype-preview h3{font-size: inherit !important;font-weight: bold !important;margin: 0 !important;padding: 0 !important;display: inline !important;line-height: inherit !important}.overtype-wrapper .overtype-preview h1{color: var(--h1,#f95738) !important}.overtype-wrapper .overtype-preview h2{color: var(--h2,#ee964b) !important}.overtype-wrapper .overtype-preview h3{color: var(--h3,#3d8a51) !important}.overtype-wrapper .overtype-preview ul,.overtype-wrapper .overtype-preview ol{list-style: none !important;margin: 0 !important;padding: 0 !important;display: block !important}.overtype-wrapper .overtype-preview li{display: block !important;margin: 0 !important;padding: 0 !important}.overtype-wrapper .overtype-preview strong{color: var(--strong,#ee964b) !important;font-weight: bold !important}.overtype-wrapper .overtype-preview em{color: var(--em,#f95738) !important;text-decoration-color: var(--em,#f95738) !important;text-decoration-thickness: 1px !important;font-style: italic !important}.overtype-wrapper .overtype-preview del{color: var(--del,#ee964b) !important;text-decoration: line-through !important;text-decoration-color: var(--del,#ee964b) !important;text-decoration-thickness: 1px !important}.overtype-wrapper .overtype-preview code{background: var(--code-bg,rgba(244,211,94,0.4)) !important;color: var(--code,#0d3b66) !important;padding: 0 !important;border-radius: 2px !important;font-family: inherit !important;font-size: inherit !important;line-height: inherit !important;font-weight: normal !important}.overtype-wrapper .overtype-preview pre{padding: 0 !important;margin: 0 !important;border-radius: 4px !important;overflow-x: auto !important}.overtype-wrapper .overtype-preview pre.code-block{background: var(--code-bg,rgba(244,211,94,0.4)) !important;white-space: break-spaces !important}.overtype-wrapper .overtype-preview pre code{background: transparent !important;color: var(--code,#0d3b66) !important;font-family: var(--instance-font-family,${i}) !important}.overtype-wrapper .overtype-preview .blockquote{color: var(--blockquote,#5a7a9b) !important;padding: 0 !important;margin: 0 !important;border: none !important}.overtype-wrapper .overtype-preview a{color: var(--link,#0d3b66) !important;text-decoration: underline !important;font-weight: normal !important}.overtype-wrapper .overtype-preview a:hover{text-decoration: underline !important;color: var(--link,#0d3b66) !important}.overtype-wrapper .overtype-preview ul,.overtype-wrapper .overtype-preview ol{list-style: none !important;margin: 0 !important;padding: 0 !important}.overtype-wrapper .overtype-preview hr{border: none !important;color: var(--hr,#5a7a9b) !important;margin: 0 !important;padding: 0 !important}.overtype-wrapper .overtype-preview .hr-marker{color: var(--hr,#5a7a9b) !important;opacity: 0.6 !important}.overtype-wrapper .overtype-preview .code-fence{color: var(--code,#0d3b66) !important;background: var(--code-bg,rgba(244,211,94,0.4)) !important}.overtype-wrapper .overtype-preview .code-block-line{background: var(--code-bg,rgba(244,211,94,0.4)) !important}.overtype-wrapper .overtype-preview .code-block-line .code-fence{background: transparent !important}.overtype-wrapper .overtype-preview .raw-line{color: var(--raw-line,#5a7a9b) !important;font-style: normal !important;font-weight: normal !important}.overtype-wrapper .overtype-preview .syntax-marker{color: var(--syntax-marker,rgba(13,59,102,0.52)) !important;opacity: 0.7 !important}.overtype-wrapper .overtype-preview .list-marker{color: var(--list-marker,#ee964b) !important}.overtype-stats{height: 40px !important;padding: 0 20px !important;background: var(--bg-secondary,Line truncated
|
||||
`}var pe={};lt(pe,{applyCustomFormat:()=>Et,default:()=>Fn,expandSelection:()=>_t,getActiveFormats:()=>Ie,getDebugMode:()=>Je,hasFormat:()=>St,insertHeader:()=>ce,insertLink:()=>Te,preserveSelection:()=>xt,setDebugMode:()=>vt,setUndoMethod:()=>wt,toggleBold:()=>Ee,toggleBulletList:()=>He,toggleCode:()=>Ae,toggleH1:()=>Pe,toggleH2:()=>$e,toggleH3:()=>Re,toggleItalic:()=>Ce,toggleNumberedList:()=>Me,toggleQuote:()=>Oe,toggleTaskList:()=>le});var Cn=Object.defineProperty,ht=Object.getOwnPropertySymbols,An=Object.prototype.hasOwnProperty,Tn=Object.prototype.propertyIsEnumerable,ut=(n,e,t)=>e in n?Cn(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t,ft=(n,e)=>{for(var t in e||(e={}))An.call(e,t)&&ut(n,t,e[t]);if(ht)for(var t of ht(e))Tn.call(e,t)&&ut(n,t,e[t]);return n},I={bold:{prefix:"**",suffix:"**",trimFirst:!0},italic:{prefix:"_",suffix:"_",trimFirst:!0},code:{prefix:"`",suffix:"`",blockPrefix:"```",blockSuffix:"```"},link:{prefix:"[",suffix:"](url)",replaceNext:"url",scanFor:"https?://"},bulletList:{prefix:"- ",multiline:!0,unorderedList:!0},numberedList:{prefix:"1. ",multiline:!0,orderedList:!0},quote:{prefix:"> ",multiline:!0,surroundWithNewlines:!0},taskList:{prefix:"- [ ] ",multiline:!0,surroundWithNewlines:!0},header1:{prefix:"# "},header2:{prefix:"## "},header3:{prefix:"### "},header4:{prefix:"#### "},header5:{prefix:"##### "},header6:{prefix:"###### "}};function Hn(){return{prefix:"",suffix:"",blockPrefix:"",blockSuffix:"",multiline:!1,replaceNext:"",prefixSpace:!1,scanFor:"",surroundWithNewlines:!1,orderedList:!1,unorderedList:!1,trimFirst:!1}}function B(n){return ft(ft({},Hn()),n)}var ae=!1;function vt(n){ae=n}function Je(){return ae}function x(n,e,t){ae&&(console.group(`\u{1F50D} ${n}`),console.log(e),t&&console.log("Data:",t),console.groupEnd())}function Se(n,e){if(!ae)return;let t=n.value.slice(n.selectionStart,n.selectionEnd);console.group(`\u{1F4CD} Selection: ${e}`),console.log("Position:",`${n.selectionStart}-${n.selectionEnd}`),console.log("Selected text:",JSON.stringify(t)),console.log("Length:",t.length);let i=n.value.slice(Math.max(0,n.selectionStart-10),n.selectionStart),o=n.value.slice(n.selectionEnd,Math.min(n.value.length,n.selectionEnd+10));console.log("Context:",JSON.stringify(i)+"[SELECTION]"+JSON.stringify(o)),console.groupEnd()}function yt(n){ae&&(console.group("\u{1F4DD} Result"),console.log("Text to insert:",JSON.stringify(n.text)),console.log("New selection:",`${n.selectionStart}-${n.selectionEnd}`),console.groupEnd())}var T=null;function N(n,{text:e,selectionStart:t,selectionEnd:i}){let o=Je();o&&(console.group("\u{1F527} insertText"),console.log("Current selection:",`${n.selectionStart}-${n.selectionEnd}`),console.log("Text to insert:",JSON.stringify(e)),console.log("New selection to set:",t,"-",i)),n.focus();let r=n.selectionStart,s=n.selectionEnd,a=n.value.slice(0,r),l=n.value.slice(s);o&&(console.log("Before text (last 20):",JSON.stringify(a.slice(-20))),console.log("After text (first 20):",JSON.stringify(l.slice(0,20))),console.log("Selected text being replaced:",JSON.stringify(n.value.slice(r,s))));let p=n.value,c=r!==s;if(T===null||T===!0){n.contentEditable="true";try{T=document.execCommand("insertText",!1,e),o&&console.log("execCommand returned:",T,"for text with",e.split(`
|
||||
`).length,"lines")}catch(d){T=!1,o&&console.log("execCommand threw error:",d)}n.contentEditable="false"}if(o&&(console.log("canInsertText before:",T),console.log("execCommand result:",T)),T){let d=a+e+l,h=n.value;o&&(console.log("Expected length:",d.length),console.log("Actual length:",h.length)),h!==d&&o&&(console.log("execCommand changed the value but not as expected"),console.log("Expected:",JSON.stringify(d.slice(0,100))),console.log("Actual:",JSON.stringify(h.slice(0,100))))}if(!T)if(o&&console.log("Using manual insertion"),n.value===p){o&&console.log("Value unchanged, doing manual replacement");try{document.execCommand("ms-beginUndoUnit")}catch(d){}n.value=a+e+l;try{document.execCommand("ms-endUndoUnit")}catch(d){}n.dispatchEvent(new CustomEvent("input",{bubbles:!0,cancelable:!0}))}else o&&console.log("Value was changed by execCommand, skipping manual insertion");o&&console.log("Setting selection range:",t,i),t!=null&&i!=null?n.setSelectionRange(t,i):n.setSelectionRange(r,n.selectionEnd),o&&(console.log("Final value length:",n.value.length),console.groupEnd())}function wt(n){switch(n){case"native":T=!0;break;case"manual":T=!1;break;case"auto":T=null;break}}function Qe(n){return n.trim().split(`
|
||||
`).length>1}function Mn(n,e){let t=e;for(;n[t]&&n[t-1]!=null&&!n[t-1].match(/\s/);)t--;return t}function On(n,e,t){let i=e,o=t?/\n/:/\s/;for(;n[i]&&!n[i].match(o);)i++;return i}function bt(n){let e=n.value.split(`
|
||||
`),t=0;for(let i=0;i<e.length;i++){let o=e[i].length+1;n.selectionStart>=t&&n.selectionStart<t+o&&(n.selectionStart=t),n.selectionEnd>=t&&n.selectionEnd<t+o&&(i===e.length-1?n.selectionEnd=Math.min(t+e[i].length,n.value.length):n.selectionEnd=t+o-1),t+=o}}function Pn(n,e,t,i=!1){if(n.selectionStart===n.selectionEnd)n.selectionStart=Mn(n.value,n.selectionStart),n.selectionEnd=On(n.value,n.selectionEnd,i);else{let o=n.selectionStart-e.length,r=n.selectionEnd+t.length,s=n.value.slice(o,n.selectionStart)===e,a=n.value.slice(n.selectionEnd,r)===t;s&&a&&(n.selectionStart=o,n.selectionEnd=r)}return n.value.slice(n.selectionStart,n.selectionEnd)}function Xe(n){let e=n.value.slice(0,n.selectionStart),t=n.value.slice(n.selectionEnd),i=e.match(/\n*$/),o=t.match(/^\n*/),r=i?i[0].length:0,s=o?o[0].length:0,a="",l="";return e.match(/\S/)&&r<2&&(a=`
|
||||
`.repeat(2-r)),t.match(/\S/)&&s<2&&(l=`
|
||||
`.repeat(2-s)),{newlinesToAppend:a,newlinesToPrepend:l}}function xt(n,e){let t=n.selectionStart,i=n.selectionEnd,o=n.scrollTop;e(),n.selectionStart=t,n.selectionEnd=i,n.scrollTop=o}function _e(n,e,t={}){let i=n.selectionStart,o=n.selectionEnd,r=i===o,s=n.value,a=i;for(;a>0&&s[a-1]!==`
|
||||
`;)a--;if(r){let p=i;for(;p<s.length&&s[p]!==`
|
||||
`;)p++;n.selectionStart=a,n.selectionEnd=p}else bt(n);let l=e(n);if(t.adjustSelection){let c=n.value.slice(n.selectionStart,n.selectionEnd).startsWith(t.prefix),d=t.adjustSelection(c,i,o,a);l.selectionStart=d.start,l.selectionEnd=d.end}else if(t.prefix){let c=n.value.slice(n.selectionStart,n.selectionEnd).startsWith(t.prefix);r?c?(l.selectionStart=Math.max(i-t.prefix.length,a),l.selectionEnd=l.selectionStart):(l.selectionStart=i+t.prefix.length,l.selectionEnd=l.selectionStart):c?(l.selectionStart=Math.max(i-t.prefix.length,a),l.selectionEnd=Math.max(o-t.prefix.length,a)):(l.selectionStart=i+t.prefix.length,l.selectionEnd=o+t.prefix.length)}return l}function te(n,e){let t,i,{prefix:o,suffix:r,blockPrefix:s,blockSuffix:a,replaceNext:l,prefixSpace:p,scanFor:c,surroundWithNewlines:d,trimFirst:h}=e,u=n.selectionStart,f=n.selectionEnd,m=n.value.slice(n.selectionStart,n.selectionEnd),g=Qe(m)&&s&&s.length>0?`${s}
|
||||
`:o,y=Qe(m)&&a&&a.length>0?`
|
||||
${a}`:r;if(p){let k=n.value[n.selectionStart-1];n.selectionStart!==0&&k!=null&&!k.match(/\s/)&&(g=` ${g}`)}m=Pn(n,g,y,e.multiline);let w=n.selectionStart,b=n.selectionEnd,L=l&&l.length>0&&y.indexOf(l)>-1&&m.length>0;if(d){let k=Xe(n);t=k.newlinesToAppend,i=k.newlinesToPrepend,g=t+o,y+=i}if(m.startsWith(g)&&m.endsWith(y)){let k=m.slice(g.length,m.length-y.length);if(u===f){let _=u-g.length;_=Math.max(_,w),_=Math.min(_,w+k.length),w=b=_}else b=w+k.length;return{text:k,selectionStart:w,selectionEnd:b}}else if(L)if(c&&c.length>0&&m.match(c)){y=y.replace(l,m);let k=g+y;return w=b=w+g.length,{text:k,selectionStart:w,selectionEnd:b}}else{let k=g+m+y;return w=w+g.length+m.length+y.indexOf(l),b=w+l.length,{text:k,selectionStart:w,selectionEnd:b}}else{let k=g+m+y;w=u+g.length,b=f+g.length;let _=m.match(/^\s*|\s*$/g);if(h&&_){let be=_[0]||"",Y=_[1]||"";k=be+g+m.trim()+y+Y,w+=be.length,b-=Y.length}return{text:k,selectionStart:w,selectionEnd:b}}}function Ge(n,e){let{prefix:t,suffix:i,surroundWithNewlines:o}=e,r=n.value.slice(n.selectionStart,n.selectionEnd),s=n.selectionStart,a=n.selectionEnd,l=r.split(`
|
||||
`);if(l.every(c=>c.startsWith(t)&&(!i||c.endsWith(i))))r=l.map(c=>{let d=c.slice(t.length);return i&&(d=d.slice(0,d.length-i.length)),d}).join(`
|
||||
`),a=s+r.length;else if(r=l.map(c=>t+c+(i||"")).join(`
|
||||
`),o){let{newlinesToAppend:c,newlinesToPrepend:d}=Xe(n);s+=c.length,a=s+r.length,r=c+r+d}return{text:r,selectionStart:s,selectionEnd:a}}function mt(n){let e=n.split(`
|
||||
`),t=/^\d+\.\s+/,i=e.every(r=>t.test(r)),o=e;return i&&(o=e.map(r=>r.replace(t,""))),{text:o.join(`
|
||||
`),processed:i}}function gt(n){let e=n.split(`
|
||||
`),t="- ",i=e.every(r=>r.startsWith(t)),o=e;return i&&(o=e.map(r=>r.slice(t.length))),{text:o.join(`
|
||||
`),processed:i}}function se(n,e){return e?"- ":`${n+1}. `}function $n(n,e){let t,i,o;return n.orderedList?(t=mt(e),i=gt(t.text),o=i.text):(t=gt(e),i=mt(t.text),o=i.text),[t,i,o]}function Rn(n,e){let t=n.selectionStart===n.selectionEnd,i=n.selectionStart,o=n.selectionEnd;bt(n);let r=n.value.slice(n.selectionStart,n.selectionEnd),[s,a,l]=$n(e,r),p=l.split(`
|
||||
`).map((m,g)=>`${se(g,e.unorderedList)}${m}`),c=p.reduce((m,g,y)=>m+se(y,e.unorderedList).length,0),d=p.reduce((m,g,y)=>m+se(y,!e.unorderedList).length,0);if(s.processed)return t?(i=Math.max(i-se(0,e.unorderedList).length,0),o=i):(i=n.selectionStart,o=n.selectionEnd-c),{text:l,selectionStart:i,selectionEnd:o};let{newlinesToAppend:h,newlinesToPrepend:u}=Xe(n),f=h+p.join(`
|
||||
`)+u;return t?(i=Math.max(i+se(0,e.unorderedList).length+h.length,0),o=i):a.processed?(i=Math.max(n.selectionStart+h.length,0),o=n.selectionEnd+h.length+c-d):(i=Math.max(n.selectionStart+h.length,0),o=n.selectionEnd+h.length+c),{text:f,selectionStart:i,selectionEnd:o}}function kt(n,e){let t=_e(n,i=>Rn(i,e),{adjustSelection:(i,o,r,s)=>{let a=n.value.slice(s,n.selectionEnd),l=/^\d+\.\s+/,p=/^- /,c=l.test(a),d=p.test(a),h=e.orderedList&&c||e.unorderedList&&d;if(o===r)if(h){let u=a.match(e.orderedList?l:p),f=u?u[0].length:0;return{start:Math.max(o-f,s),end:Math.max(o-f,s)}}else if(c||d){let u=a.match(c?l:p),f=u?u[0].length:0,g=(e.unorderedList?2:3)-f;return{start:o+g,end:o+g}}else{let u=e.unorderedList?2:3;return{start:o+u,end:o+u}}else if(h){let u=a.match(e.orderedList?l:p),f=u?u[0].length:0;return{start:Math.max(o-f,s),end:Math.max(r-f,s)}}else if(c||d){let u=a.match(c?l:p),f=u?u[0].length:0,g=(e.unorderedList?2:3)-f;return{start:o+g,end:r+g}}else{let u=e.unorderedList?2:3;return{start:o+u,end:r+u}}}});N(n,t)}function Lt(n){if(!n)return[];let e=[],{selectionStart:t,selectionEnd:i,value:o}=n,r=o.split(`
|
||||
`),s=0,a="";for(let d of r){if(t>=s&&t<=s+d.length){a=d;break}s+=d.length+1}a.startsWith("- ")&&(a.startsWith("- [ ] ")||a.startsWith("- [x] ")?e.push("task-list"):e.push("bullet-list")),/^\d+\.\s/.test(a)&&e.push("numbered-list"),a.startsWith("> ")&&e.push("quote"),a.startsWith("# ")&&e.push("header"),a.startsWith("## ")&&e.push("header-2"),a.startsWith("### ")&&e.push("header-3");let l=Math.max(0,t-10),p=Math.min(o.length,i+10),c=o.slice(l,p);if(c.includes("**")){let d=o.slice(Math.max(0,t-100),t),h=o.slice(i,Math.min(o.length,i+100)),u=d.lastIndexOf("**"),f=h.indexOf("**");u!==-1&&f!==-1&&e.push("bold")}if(c.includes("_")){let d=o.slice(Math.max(0,t-100),t),h=o.slice(i,Math.min(o.length,i+100)),u=d.lastIndexOf("_"),f=h.indexOf("_");u!==-1&&f!==-1&&e.push("italic")}if(c.includes("`")){let d=o.slice(Math.max(0,t-100),t),h=o.slice(i,Math.min(o.length,i+100));d.includes("`")&&h.includes("`")&&e.push("code")}if(c.includes("[")&&c.includes("]")){let d=o.slice(Math.max(0,t-100),t),h=o.slice(i,Math.min(o.length,i+100)),u=d.lastIndexOf("["),f=h.indexOf("]");u!==-1&&f!==-1&&o.slice(i+f+1,i+f+10).startsWith("(")&&e.push("link")}return e}function In(n,e){return Lt(n).includes(e)}function Bn(n,e={}){if(!n)return;let{toWord:t,toLine:i,toFormat:o}=e,{selectionStart:r,selectionEnd:s,value:a}=n;if(i){let l=a.split(`
|
||||
`),p=0,c=0,d=0;for(let h of l){if(r>=d&&r<=d+h.length){p=d,c=d+h.length;break}d+=h.length+1}n.selectionStart=p,n.selectionEnd=c}else if(t&&r===s){let l=r,p=s;for(;l>0&&!/\s/.test(a[l-1]);)l--;for(;p<a.length&&!/\s/.test(a[p]);)p++;n.selectionStart=l,n.selectionEnd=p}}function Ee(n){if(!n||n.disabled||n.readOnly)return;x("toggleBold","Starting"),Se(n,"Before");let e=B(I.bold),t=te(n,e);yt(t),N(n,t),Se(n,"After")}function Ce(n){if(!n||n.disabled||n.readOnly)return;let e=B(I.italic),t=te(n,e);N(n,t)}function Ae(n){if(!n||n.disabled||n.readOnly)return;let e=B(I.code),t=te(n,e);N(n,t)}function Te(n,e={}){if(!n||n.disabled||n.readOnly)return;let t=n.value.slice(n.selectionStart,n.selectionEnd),i=B(I.link);if(t&&t.match(/^https?:\/\//)&&!e.url?(i.suffix=`](${t})`,i.replaceNext=""):e.url&&(i.suffix=`](${e.url})`,i.replaceNext=""),e.text&&!t){let s=n.selectionStart;n.value=n.value.slice(0,s)+e.text+n.value.slice(s),n.selectionStart=s,n.selectionEnd=s+e.text.length}let r=te(n,i);N(n,r)}function He(n){if(!n||n.disabled||n.readOnly)return;let e=B(I.bulletList);kt(n,e)}function Me(n){if(!n||n.disabled||n.readOnly)return;let e=B(I.numberedList);kt(n,e)}function Oe(n){if(!n||n.disabled||n.readOnly)return;x("toggleQuote","Starting"),Se(n,"Initial");let e=B(I.quote),t=_e(n,i=>Ge(i,e),{prefix:e.prefix});yt(t),N(n,t),Se(n,"Final")}function le(n){if(!n||n.disabled||n.readOnly)return;let e=B(I.taskList),t=_e(n,i=>Ge(i,e),{prefix:e.prefix});N(n,t)}function ce(n,e=1,t=!1){if(!n||n.disabled||n.readOnly)return;(e<1||e>6)&&(e=1),x("insertHeader","============ START ============"),x("insertHeader",`Level: ${e}, Toggle: ${t}`),x("insertHeader",`Initial cursor: ${n.selectionStart}-${n.selectionEnd}`);let i=`header${e===1?"1":e}`,o=B(I[i]||I.header1);x("insertHeader",`Style prefix: "${o.prefix}"`);let r=n.value,s=n.selectionStart,a=n.selectionEnd,l=s;for(;l>0&&r[l-1]!==`
|
||||
`;)l--;let p=a;for(;p<r.length&&r[p]!==`
|
||||
`;)p++;let c=r.slice(l,p);x("insertHeader",`Current line (before): "${c}"`);let d=c.match(/^(#{1,6})\s*/),h=d?d[1].length:0,u=d?d[0].length:0;x("insertHeader","Existing header check:"),x("insertHeader",` - Match: ${d?`"${d[0]}"`:"none"}`),x("insertHeader",` - Existing level: ${h}`),x("insertHeader",` - Existing prefix length: ${u}`),x("insertHeader",` - Target level: ${e}`);let f=t&&h===e;x("insertHeader",`Should toggle OFF: ${f} (toggle=${t}, existingLevel=${h}, level=${e})`);let m=_e(n,g=>{let y=g.value.slice(g.selectionStart,g.selectionEnd);x("insertHeader",`Line in operation: "${y}"`);let w=y.replace(/^#{1,6}\s*/,"");x("insertHeader",`Cleaned line: "${w}"`);let b;return f?(x("insertHeader","ACTION: Toggling OFF - removing header"),b=w):h>0?(x("insertHeader",`ACTION: Replacing H${h} with H${e}`),b=o.prefix+w):(x("insertHeader","ACTION: Adding new header"),b=o.prefix+w),x("insertHeader",`New line: "${b}"`),{text:b,selectionStart:g.selectionStart,selectionEnd:g.selectionEnd}},{prefix:o.prefix,adjustSelection:(g,y,w,b)=>{if(x("insertHeader","Adjusting selection:"),x("insertHeader",` - isRemoving param: ${g}`),x("insertHeader",` - shouldToggleOff: ${f}`),x("insertHeader",` - selStart: ${y}, selEnd: ${w}`),x("insertHeader",` - lineStartPos: ${b}`),f){let L=Math.max(y-u,b);return x("insertHeader",` - Removing header, adjusting by -${u}`),{start:L,end:y===w?L:Math.max(w-u,b)}}else if(u>0){let L=o.prefix.length-u;return x("insertHeader",` - Replacing header, adjusting by ${L}`),{start:y+L,end:w+L}}else return x("insertHeader",` - Adding header, adjusting by +${o.prefix.length}`),{start:y+o.prefix.length,end:w+o.prefix.length}}});x("insertHeader",`Final result: text="${m.text}", cursor=${m.selectionStart}-${m.selectionEnd}`),x("insertHeader","============ END ============"),N(n,m)}function Pe(n){ce(n,1,!0)}function $e(n){ce(n,2,!0)}function Re(n){ce(n,3,!0)}function Ie(n){return Lt(n)}function St(n,e){return In(n,e)}function _t(n,e={}){Bn(n,e)}function Et(n,e){if(!n||n.disabled||n.readOnly)return;let t=B(e),i;if(t.multiline){let o=n.value.slice(n.selectionStart,n.selectionEnd);Qe(o)?i=Ge(n,t):i=te(n,t)}else i=te(n,t);N(n,i)}var Fn={toggleBold:Ee,toggleItalic:Ce,toggleCode:Ae,insertLink:Te,toggleBulletList:He,toggleNumberedList:Me,toggleQuote:Oe,toggleTaskList:le,insertHeader:ce,toggleH1:Pe,toggleH2:$e,toggleH3:Re,getActiveFormats:Ie,hasFormat:St,expandSelection:_t,applyCustomFormat:Et,preserveSelection:xt,setUndoMethod:wt,setDebugMode:vt,getDebugMode:Je};var Be=class{constructor(e,t={}){this.editor=e,this.container=null,this.buttons={},this.toolbarButtons=t.toolbarButtons||[]}create(){this.container=document.createElement("div"),this.container.className="overtype-toolbar",this.container.setAttribute("role","toolbar"),this.container.setAttribute("aria-label","Formatting toolbar"),this.toolbarButtons.forEach(e=>{if(e.name==="separator"){let t=this.createSeparator();this.container.appendChild(t)}else{let t=this.createButton(e);this.buttons[e.name]=t,this.container.appendChild(t)}}),this.editor.container.insertBefore(this.container,this.editor.wrapper)}createSeparator(){let e=document.createElement("div");return e.className="overtype-toolbar-separator",e.setAttribute("role","separator"),e}createButton(e){let t=document.createElement("button");return t.className="overtype-toolbar-button",t.type="button",t.setAttribute("data-button",e.name),t.title=e.title||"",t.setAttribute("aria-label",e.title||e.name),t.innerHTML=this.sanitizeSVG(e.icon||""),e.name==="viewMode"?(t.classList.add("has-dropdown"),t.dataset.dropdown="true",t.addEventListener("click",i=>{i.preventDefault(),this.toggleViewModeDropdown(t)}),t):(t._clickHandler=i=>{i.preventDefault();let o=e.actionId||e.name;this.editor.performAction(o,i)},t.addEventListener("click",t._clickHandler),t)}async handleAction(e){if(e&&typeof e=="object"&&typeof e.action=="function"){this.editor.textarea.focus();try{return await e.action({editor:this.editor,getValue:()=>this.editor.getValue(),setValue:t=>this.editor.setValue(t),event:null}),!0}catch(t){return console.error(`Action "${e.name}" error:`,t),this.editor.wrapper.dispatchEvent(new CustomEvent("button-error",{detail:{buttonName:e.name,error:t}})),!1}}return typeof e=="string"?this.editor.performAction(e,null):!1}sanitizeSVG(e){return typeof e!="string"?"":e.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,"").replace(/\son\w+\s*=\s*["'][^"']*["']/gi,"").replace(/\son\w+\s*=\s*[^\s>]*/gi,"")}toggleViewModeDropdown(e){let t=document.querySelector(".overtype-dropdown-menu");if(t){t.remove(),e.classList.remove("dropdown-active");return}e.classList.add("dropdown-active");let i=this.createViewModeDropdown(e),o=e.getBoundingClientRect();i.style.position="absolute",i.style.top=`${o.bottom+5}px`,i.style.left=`${o.left}px`,document.body.appendChild(i),this.handleDocumentClick=r=>{!i.contains(r.target)&&!e.contains(r.target)&&(i.remove(),e.classList.remove("dropdown-active"),document.removeEventListener("cl
Line truncated
|
||||
<span style="display: flex; align-items: center; gap: 6px;">
|
||||
<svg width="12" height="12" viewBox="0 0 20 20" fill="currentColor" style="flex-shrink: 0;">
|
||||
<path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path>
|
||||
<path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"></path>
|
||||
</svg>
|
||||
<span class="overtype-link-tooltip-url"></span>
|
||||
</span>
|
||||
`,this.tooltip.addEventListener("click",e=>{e.preventDefault(),e.stopPropagation(),this.currentLink&&(window.open(this.currentLink.url,"_blank"),this.hide())}),this.editor.container.appendChild(this.tooltip)}checkCursorPosition(){let e=this.editor.textarea.selectionStart,t=this.editor.textarea.value,i=this.findLinkAtPosition(t,e);i?(!this.currentLink||this.currentLink.url!==i.url||this.currentLink.index!==i.index)&&this.show(i):this.scheduleHide()}findLinkAtPosition(e,t){let i=/\[([^\]]+)\]\(([^)]+)\)/g,o,r=0;for(;(o=i.exec(e))!==null;){let s=o.index,a=o.index+o[0].length;if(t>=s&&t<=a)return{text:o[1],url:o[2],index:r,start:s,end:a};r++}return null}async show(e){this.currentLink=e,this.cancelHide();let t=this.tooltip.querySelector(".overtype-link-tooltip-url");t.textContent=e.url,await this.positionTooltip(e),this.currentLink===e&&this.tooltip.classList.add("visible")}async positionTooltip(e){let t=this.findAnchorElement(e.index);if(!t)return;let i=t.getBoundingClientRect();if(!(i.width===0||i.height===0))try{let{x:o,y:r}=await nn(t,this.tooltip,{strategy:"fixed",placement:"bottom",middleware:[Yt(8),en({padding:8}),tn()]});Object.assign(this.tooltip.style,{left:`${o}px`,top:`${r}px`,position:"fixed"})}catch(o){console.warn("Floating UI positioning failed:",o)}}findAnchorElement(e){return this.editor.preview.querySelector(`a[style*="--link-${e}"]`)}hide(){this.tooltip.classList.remove("visible"),this.currentLink=null,this.isTooltipHovered=!1}scheduleHide(){this.cancelHide(),this.hideTimeout=setTimeout(()=>this.hide(),300)}cancelHide(){this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=null)}destroy(){this.cancelHide(),this.visibilityChangeHandler&&(document.removeEventListener("visibilitychange",this.visibilityChangeHandler),this.visibilityChangeHandler=null),this.tooltip&&this.tooltip.parentNode&&this.tooltip.parentNode.removeChild(this.tooltip),this.tooltip=null,this.currentLink=null,this.isTooltipHovered=!1}};var on='<svg viewBox="0 0 18 18"><path stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5,4H9.5A2.5,2.5,0,0,1,12,6.5v0A2.5,2.5,0,0,1,9.5,9H5A0,0,0,0,1,5,9V4A0,0,0,0,1,5,4Z"></path><path stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5,9h5.5A2.5,2.5,0,0,1,13,11.5v0A2.5,2.5,0,0,1,10.5,14H5a0,0,0,0,1,0,0V9A0,0,0,0,1,5,9Z"></path></svg>',rn='<svg viewBox="0 0 18 18"><line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="13" y1="4" y2="4"></line><line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="5" x2="11" y1="14" y2="14"></line><line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="10" y1="14" y2="4"></line></svg>',sn='<svg viewBox="0 0 18 18"><path fill="currentColor" d="M10,4V14a1,1,0,0,1-2,0V10H3v4a1,1,0,0,1-2,0V4A1,1,0,0,1,3,4V8H8V4a1,1,0,0,1,2,0Zm6.06787,9.209H14.98975V7.59863a.54085.54085,0,0,0-.605-.60547h-.62744a1.01119,1.01119,0,0,0-.748.29688L11.645,8.56641a.5435.5435,0,0,0-.022.8584l.28613.30762a.53861.53861,0,0,0,.84717.0332l.09912-.08789a1.2137,1.2137,0,0,0,.2417-.35254h.02246s-.01123.30859-.01123.60547V13.209H12.041a.54085.54085,0,0,0-.605.60547v.43945a.54085.54085,0,0,0,.605.60547h4.02686a.54085.54085,0,0,0,.605-.60547v-.43945A.54085.54085,0,0,0,16.06787,13.209Z"></path></svg>',an='<svg viewBox="0 0 18 18"><path fill="currentColor" d="M16.73975,13.81445v.43945a.54085.54085,0,0,1-.605.60547H11.855a.58392.58392,0,0,1-.64893-.60547V14.0127c0-2.90527,3.39941-3.42187,3.39941-4.55469a.77675.77675,0,0,0-.84717-.78125,1.17684,1.17684,0,0,0-.83594.38477c-.2749.26367-.561.374-.85791.13184l-.4292-.34082c-.30811-.24219-.38525-.51758-.1543-.81445a2.97155,2.97155,0,0,1,2.45361-1.17676,2.45393,2.45393,0,0,1,2.68408,2.40918c0,2.45312-3.1792,2.92676-3.27832,3.93848h2.79443A.54085.54085,0,0,1,16.73975,13.81445ZM9,3A.99974.99974,0,0,0,8,4V8H3V4A1,1,0,0,0,1,4V14a1,1,0,0,0,2,0V10H8v4a1,1,0,0,0,2,0V4A.99974.99974,0,0,0,9,3Z"></path></svg>',ln='<svg viewBox="0 0 18 18"><path fill="currentColor" d="M16.65186,12.30664a2.6742,2.6742,0,0,1-2.915,2.68457,3.96592,3.96592,0,0,1-2.25537-.6709.56007.56007,0,0,1-.13232-.83594L11.64648,13c.209-.34082.48389-.36328.82471-.1543a2.32654,2.32654,0,0,0,1.12256.33008c.71484,0,1.12207-.35156,1.12207-.78125,0-.61523-.61621-.86816-1.46338-.86816H13.2085a.65159.65159,0,0,1-.68213-.41895l-.05518-.10937a.67114.67114,0,0,1,.14307-.78125l.71533-.86914a8.55289,8.55289,0,0,1,.68213-.7373V8.58887a3.93913,3.93913,0,0,1-.748.05469H11.9873a.54085.54085,0,0,1-.605-.60547V7.59863a.54085.54085,0,0,1,.605-.60547h3.75146a.53773.53773,0,0,1,.60547.59375v.17676a1.03723,1.03723,0,0,1-.27539.748L14.74854,10.0293A2.31132,2.31132,0,0,1,16.65186,12.30664ZM9,3A.99974.99974,0,0,0,8,4V8H3V4A1,1,0,0,0,1,4V14a1,1,0,0,0,2,0V10H8v4a1,1,0,0,0,2,0V4A.99974.99974,0,0,0,9,3Z"></path></svg>',cn='<svg viewBox="0 0 18
Line truncated
|
||||
`),this.options.fileUpload.batch){t.push({file:i,placeholder:s});continue}this.options.fileUpload.onInsertFile(i).then(a=>{this.textarea.value=this.textarea.value.replace(s,a),this._trackInsertedUrls(a,i),this.textarea.dispatchEvent(new Event("input",{bubbles:!0}))},a=>{console.error("OverType: File upload failed",a),this.textarea.value=this.textarea.value.replace(s,"[Upload failed]()"),this.textarea.dispatchEvent(new Event("input",{bubbles:!0}))})}this.options.fileUpload.batch&&t.length>0&&this.options.fileUpload.onInsertFile(t.map(i=>i.file)).then(i=>{(Array.isArray(i)?i:[i]).forEach((r,s)=>{this.textarea.value=this.textarea.value.replace(t[s].placeholder,r),this._trackInsertedUrls(r,t[s].file)}),this.textarea.dispatchEvent(new Event("input",{bubbles:!0}))},i=>{console.error("OverType: File upload failed",i),t.forEach(({placeholder:o})=>{this.textarea.value=this.textarea.value.replace(o,"[Upload failed]()")}),this.textarea.dispatchEvent(new Event("input",{bubbles:!0}))})}_handleDragOver(e){e.preventDefault()}_destroyFileUpload(){this.textarea.removeEventListener("paste",this._boundHandleFilePaste),this.textarea.removeEventListener("drop",this._boundHandleFileDrop),this.textarea.removeEventListener("dragover",this._boundHandleDragOver),this._boundHandleFilePaste=null,this._boundHandleFileDrop=null,this._boundHandleDragOver=null,this._uploadedFiles=null,this.fileUploadInitialized=!1}insertAtCursor(e){let t=this.textarea.selectionStart,i=this.textarea.selectionEnd,o=!1;try{o=document.execCommand("insertText",!1,e)}catch(r){}if(!o){let r=this.textarea.value.slice(0,t),s=this.textarea.value.slice(i);this.textarea.value=r+e+s,this.textarea.setSelectionRange(t+e.length,t+e.length)}this.textarea.dispatchEvent(new Event("input",{bubbles:!0}))}updatePreview(){let e=this.textarea.value,t=this.textarea.selectionStart,i=this._getCurrentLine(e,t),o=this.container.dataset.mode==="preview",r=E.parse(e,i,this.options.showActiveLineRaw,this.options.codeHighlighter,o);this.preview.innerHTML=r,this.placeholderEl&&(this.placeholderEl.style.display=e?"none":""),this._applyCodeBlockBackgrounds(),this.options.showStats&&this.statsBar&&this._updateStats(),this.options.onRender&&this.options.onRender(this.preview,o?"preview":"normal",this)}_notifyChange(){this.initialized&&(this._checkForRemovedUploads(),this.options.onChange&&this.options.onChange(this.textarea.value,this))}_applyCodeBlockBackgrounds(){let e=this.preview.querySelectorAll(".code-fence");for(let t=0;t<e.length-1;t+=2){let i=e[t],o=e[t+1],r=i.parentElement,s=o.parentElement;!r||!s||(i.style.display="block",o.style.display="block",r.classList.add("code-block-line"),s.classList.add("code-block-line"))}}_getCurrentLine(e,t){return e.substring(0,t).split(`
|
||||
`).length-1}handleInput(e){this.updatePreview(),this._notifyChange()}handleFocus(e){this.options.onFocus&&this.options.onFocus(e,this)}handleBlur(e){this.options.onBlur&&this.options.onBlur(e,this)}handleKeydown(e){if(e.key==="Tab"){let i=this.textarea.selectionStart,o=this.textarea.selectionEnd,r=this.textarea.value;if(e.shiftKey&&i===o)return;if(e.preventDefault(),i!==o&&e.shiftKey){let s=r.substring(0,i),a=r.substring(i,o),l=r.substring(o),c=a.split(`
|
||||
`).map(d=>d.replace(/^ /,"")).join(`
|
||||
`);document.execCommand?(this.textarea.setSelectionRange(i,o),document.execCommand("insertText",!1,c)):(this.textarea.value=s+c+l,this.textarea.selectionStart=i,this.textarea.selectionEnd=i+c.length)}else if(i!==o){let s=r.substring(0,i),a=r.substring(i,o),l=r.substring(o),c=a.split(`
|
||||
`).map(d=>" "+d).join(`
|
||||
`);document.execCommand?(this.textarea.setSelectionRange(i,o),document.execCommand("insertText",!1,c)):(this.textarea.value=s+c+l,this.textarea.selectionStart=i,this.textarea.selectionEnd=i+c.length)}else document.execCommand?document.execCommand("insertText",!1," "):(this.textarea.value=r.substring(0,i)+" "+r.substring(o),this.textarea.selectionStart=this.textarea.selectionEnd=i+2);this.textarea.dispatchEvent(new Event("input",{bubbles:!0}));return}if(e.key==="Enter"&&!e.shiftKey&&!e.metaKey&&!e.ctrlKey&&this.options.smartLists&&this.handleSmartListContinuation()){e.preventDefault();return}!this.shortcuts.handleKeydown(e)&&this.options.onKeydown&&this.options.onKeydown(e,this)}handleSmartListContinuation(){let e=this.textarea,t=e.selectionStart,i=E.getListContext(e.value,t);return!i||!i.inList?!1:i.content.trim()===""&&t>=i.markerEndPos?(this.deleteListMarker(i),!0):(t>i.markerEndPos&&t<i.lineEnd?this.splitListItem(i,t):this.insertNewListItem(i),i.listType==="numbered"&&this.scheduleNumberedListUpdate(),!0)}deleteListMarker(e){this.textarea.setSelectionRange(e.lineStart,e.markerEndPos),document.execCommand("delete"),this.textarea.dispatchEvent(new Event("input",{bubbles:!0}))}insertNewListItem(e){let t=E.createNewListItem(e);document.execCommand("insertText",!1,`
|
||||
`+t),this.textarea.dispatchEvent(new Event("input",{bubbles:!0}))}splitListItem(e,t){let i=e.content.substring(t-e.markerEndPos);this.textarea.setSelectionRange(t,e.lineEnd),document.execCommand("delete");let o=E.createNewListItem(e);document.execCommand("insertText",!1,`
|
||||
`+o+i);let r=this.textarea.selectionStart-i.length;this.textarea.setSelectionRange(r,r),this.textarea.dispatchEvent(new Event("input",{bubbles:!0}))}scheduleNumberedListUpdate(){this.numberUpdateTimeout&&clearTimeout(this.numberUpdateTimeout),this.numberUpdateTimeout=setTimeout(()=>{this.updateNumberedLists()},10)}updateNumberedLists(){let e=this.textarea.value,t=this.textarea.selectionStart,i=E.renumberLists(e);if(i!==e){let o=0,r=e.split(`
|
||||
`),s=i.split(`
|
||||
`),a=0;for(let p=0;p<r.length&&a<t;p++){if(r[p]!==s[p]){let c=s[p].length-r[p].length;a+r[p].length<t&&(o+=c)}a+=r[p].length+1}this.textarea.value=i;let l=t+o;this.textarea.setSelectionRange(l,l),this.textarea.dispatchEvent(new Event("input",{bubbles:!0}))}}handleScroll(e){this.preview.scrollTop=this.textarea.scrollTop,this.preview.scrollLeft=this.textarea.scrollLeft}getValue(){return this.textarea.value}setValue(e){let t=this.textarea.value!==e;this.textarea.value=e,this.updatePreview(),this.options.autoResize&&this._updateAutoHeight(),t&&this._notifyChange()}async performAction(e,t=null){var r;let i=this.textarea;if(!i)return!1;let o=(r=this.actionsById)==null?void 0:r[e];if(!o)return console.warn(`OverType: Unknown action "${e}"`),!1;i.focus();try{return await o({editor:this,getValue:()=>this.getValue(),setValue:s=>this.setValue(s),event:t}),!0}catch(s){return console.error(`OverType: Action "${e}" error:`,s),this.wrapper.dispatchEvent(new CustomEvent("button-error",{detail:{actionId:e,error:s}})),!1}}getRenderedHTML(e={}){let t=this.getValue(),i=E.parse(t,-1,!1,this.options.codeHighlighter);return e.cleanHTML&&(i=i.replace(/<span class="syntax-marker[^"]*">.*?<\/span>/g,""),i=i.replace(/\sclass="(bullet-list|ordered-list|code-fence|hr-marker|blockquote|url-part)"/g,""),i=i.replace(/\sclass=""/g,"")),i}getPreviewHTML(){return this.preview.innerHTML}getCleanHTML(){return this.getRenderedHTML({cleanHTML:!0})}focus(){this.textarea.focus()}blur(){this.textarea.blur()}isInitialized(){return this.initialized}reinit(e={}){var o;let t=(o=this.options)==null?void 0:o.toolbarButtons;this.options=this._mergeOptions({...this.options,...e});let i=this.toolbar&&this.options.toolbar&&vi(t,this.options.toolbarButtons);this._rebuildActionsMap(),i&&(this._cleanupToolbarListeners(),this.toolbar.destroy(),this.toolbar=null,this._createToolbar()),this.fileUploadInitialized&&this._destroyFileUpload(),this.options.fileUpload&&this._initFileUpload(),this._applyOptions(),this.updatePreview()}showToolbar(){this.toolbar?this.toolbar.show():this._createToolbar()}hideToolbar(){this.toolbar&&this.toolbar.hide()}setTheme(e){if(v._autoInstances.delete(this),this.instanceTheme=e,e==="auto")v._autoInstances.add(this),v._startAutoListener(),this._applyResolvedTheme(Ze("auto"));else{let t=typeof e=="string"?R(e):e,i=typeof t=="string"?t:t.name;if(i&&this.container.setAttribute("data-theme",i),t&&t.colors){let o=Q(t.colors,t.previewColors);this.container.style.cssText+=o}this.updatePreview()}return v._stopAutoListener(),this}_applyResolvedTheme(e){let t=R(e);this.container.setAttribute("data-theme",e),t&&t.colors&&(this.container.style.cssText=Q(t.colors,t.previewColors)),this.updatePreview()}setCodeHighlighter(e){this.options.codeHighlighter=e,this.updatePreview()}_updateStats(){if(!this.statsBar)return;let e=this.textarea.value,t=e.split(`
|
||||
`),i=e.length,o=e.split(/\s+/).filter(c=>c.length>0).length,r=this.textarea.selectionStart,a=e.substring(0,r).split(`
|
||||
`),l=a.length,p=a[a.length-1].length+1;this.options.statsFormatter?this.statsBar.innerHTML=this.options.statsFormatter({chars:i,words:o,lines:t.length,line:l,column:p}):this.statsBar.innerHTML=`
|
||||
<div class="overtype-stat">
|
||||
<span class="live-dot"></span>
|
||||
<span>${i} chars, ${o} words, ${t.length} lines</span>
|
||||
</div>
|
||||
<div class="overtype-stat">Line ${l}, Col ${p}</div>
|
||||
`}_setupAutoResize(){this.container.classList.add("overtype-auto-resize"),this.previousHeight=null,this._updateAutoHeight(),this.textarea.addEventListener("input",()=>this._updateAutoHeight()),window.addEventListener("resize",()=>this._updateAutoHeight())}_updateAutoHeight(){if(!this.options.autoResize)return;let e=this.textarea,t=this.preview,i=this.wrapper;if(this.container.dataset.mode==="preview"){i.style.removeProperty("height"),t.style.removeProperty("height"),t.style.removeProperty("overflow-y"),e.style.removeProperty("height"),e.style.removeProperty("overflow-y");return}let r=e.scrollTop;i.style.setProperty("height","auto","important"),e.style.setProperty("height","auto","important");let s=e.scrollHeight;if(this.options.minHeight){let p=parseInt(this.options.minHeight);s=Math.max(s,p)}let a="hidden";if(this.options.maxHeight){let p=parseInt(this.options.maxHeight);s>p&&(s=p,a="auto")}let l=s+"px";e.style.setProperty("height",l,"important"),e.style.setProperty("overflow-y",a,"important"),t.style.setProperty("height",l,"important"),t.style.setProperty("overflow-y",a,"important"),i.style.setProperty("height",l,"important"),e.scrollTop=r,t.scrollTop=r,this.previousHeight!==s&&(this.previousHeight=s)}showStats(e){this.options.showStats=e,e&&!this.statsBar?(this.statsBar=document.createElement("div"),this.statsBar.className="overtype-stats",this.container.appendChild(this.statsBar),this._updateStats()):e&&this.statsBar?this._updateStats():!e&&this.statsBar&&(this.statsBar.remove(),this.statsBar=null)}showNormalEditMode(){return this.container.dataset.mode="normal",this.updatePreview(),this._updateAutoHeight(),requestAnimationFrame(()=>{this.textarea.scrollTop=this.preview.scrollTop,this.textarea.scrollLeft=this.preview.scrollLeft}),this}showPlainTextarea(){if(this.container.dataset.mode="plain",this._updateAutoHeight(),this.toolbar){let e=this.container.querySelector('[data-action="toggle-plain"]');e&&(e.classList.remove("active"),e.title="Show markdown preview")}return this}showPreviewMode(){return this.container.dataset.mode="preview",this.updatePreview(),this._updateAutoHeight(),this}destroy(){if(v._autoInstances.delete(this),v._stopAutoListener(),this.fileUploadInitialized&&this._destroyFileUpload(),this.element.overTypeInstance=null,v.instances.delete(this.element),this.shortcuts&&this.shortcuts.destroy(),this.wrapper){let e=this.getValue();this.wrapper.remove(),this.element.textContent=e}this.initialized=!1}static init(e,t={}){return new v(e,t)}static initFromData(e,t={}){return v._resolveTargets(e).map(o=>{let r={...t};for(let s of o.attributes)if(s.name.startsWith("data-ot-")){let l=s.name.slice(8).replace(/-([a-z])/g,(p,c)=>c.toUpperCase());r[l]=v._parseDataValue(s.value)}return new v(o,r)[0]})}static _resolveTargets(e){if(e==null)throw new Error("Invalid target: must be selector string, Element, NodeList, or Array");if(typeof e=="string")return Array.from(document.querySelectorAll(e));if(e instanceof Element)return[e];if(e instanceof NodeList)return Array.from(e);if(Array.isArray(e))return e;if(typeof e.length=="number")return Array.from(e);throw new Error("Invalid target: must be selector string, Element, NodeList, or Array")}static _parseDataValue(e){return e==="true"?!0:e==="false"?!1:e==="null"?null:e!==""&&!isNaN(Number(e))?Number(e):e}static getInstance(e){let t;return e instanceof Element?t=e:t=v._resolveTargets(e)[0],t&&(t.overTypeInstance||v.instances.get(t))||null}static destroyAll(){document.querySelectorAll("[data-overtype-instance]").forEach(t=>{let i=v.getInstance(t);i&&i.destroy()})}static injectStyles(e=!1){if(v.stylesInjected&&!e)return;let t=document.querySelector("style.overtype-styles");t&&t.remove();let i=v.currentTheme||z,o=Le({theme:i}),r=document.createElement("style");r.className="overtype-styles",r.textContent=o,document.head.appendChild(r),v.stylesInjected=!0}static setTheme(e,t=null){if(v._globalAutoTheme=!1,v._globalAutoCustomColors=null,e==="auto"){v._globalAutoTheme=!0,v._globalAutoCustomColors=t,v._startAutoListener(),v._applyGlobalTheme(Ze("auto"),t);return}v._stopAutoListener(),v._applyGlobalTheme(e,t)}static _applyGlobalTheme(e,t=null){let i=typeof e=="string"?R(e):e;t&&(i=dt(i,t)),v.currentTheme=i,v.injectStyles(!0);let o=typeof i=="string"?i:i.name;document.querySelectorAll(".overtype-container").forEach(r=>{o&&r.setAttribute("data-theme",o)}),document.querySelectorAll(".overtype-wrapper").forEach(r=>{r.closest(".overtype-container")||o&&r.setAttribute("data-theme",o);let s=r._instance;s&&s.updatePreview()}),document.querySelectorAll("overtype-editor").forEach(r=>{o&&typeof r.setAttribute=="function"&&r.setAttribute("theme",o),typeof r.refreshTheme=="function"&&r.refreshTheme()})}static _startAutoListener(){v._autoMediaQuery||window.matchMedia&&(v._autoMediaQuery=window.matchMedia("(prefers-color-scheme: dark)"),v._autoMediaListener=e=>{let t=e.matches?"cave":"solar";v._globalAutoTheme&&v._applyGlobalTheme(t,v._globalAutoCustomColors),v._autoInstance
Line truncated
|
||||
`).replace(/\\t/g," ")}static get observedAttributes(){return wi}connectedCallback(){this._isConnected=!0,this._initializeEditor()}disconnectedCallback(){this._isConnected=!1,this._cleanup()}attributeChangedCallback(e,t,i){if(t!==i&&!this._silentUpdate){if(!this._initialized){this._pendingOptions[e]=i;return}this._updateOption(e,i)}}_initializeEditor(){if(!(this._initialized||!this._isConnected))try{let e=document.createElement("div");e.className=wn;let t=this.getAttribute("height"),i=this.getAttribute("min-height"),o=this.getAttribute("max-height");t&&(e.style.height=t),i&&(e.style.minHeight=i),o&&(e.style.maxHeight=o),this._injectStyles(),this.shadowRoot.appendChild(e);let r=this._getOptionsFromAttributes(),s=new yn(e,r);this._editor=s[0],this._initialized=!0,this._editor&&this._editor.textarea&&(this._editor.textarea.addEventListener("scroll",()=>{this._editor&&this._editor.preview&&this._editor.textarea&&(this._editor.preview.scrollTop=this._editor.textarea.scrollTop,this._editor.preview.scrollLeft=this._editor.textarea.scrollLeft)}),this._editor.textarea.addEventListener("input",a=>{this._editor&&this._editor.handleInput&&this._editor.handleInput(a)}),this._editor.textarea.addEventListener("keydown",a=>{this._editor&&this._editor.handleKeydown&&this._editor.handleKeydown(a)}),this._selectionChangeHandler=()=>{if(document.activeElement===this){let a=this.shadowRoot.activeElement;a&&a===this._editor.textarea&&(this._editor.options.showStats&&this._editor.statsBar&&this._editor._updateStats(),this._editor.linkTooltip&&this._editor.linkTooltip.checkCursorPosition&&this._editor.linkTooltip.checkCursorPosition())}},document.addEventListener("selectionchange",this._selectionChangeHandler)),this._applyPendingOptions(),this._dispatchEvent("ready",{editor:this._editor})}catch(e){let t=e&&e.message?e.message:String(e);console.warn("OverType Web Component initialization failed:",t),this._dispatchEvent("error",{error:{message:t}})}}_injectStyles(){let e=document.createElement("style"),t=this.getAttribute("theme")||"solar",i=R(t),o=this._getOptionsFromAttributes(),r=Le({...o,theme:i}),s=`
|
||||
/* Web Component Host Styles */
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
contain: layout style;
|
||||
}
|
||||
|
||||
.overtype-webcomponent-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Override container grid layout for web component */
|
||||
.overtype-container {
|
||||
height: 100% !important;
|
||||
}
|
||||
`;this._styleVersion+=1;let a=`
|
||||
/* overtype-webcomponent styles v${this._styleVersion} */
|
||||
`;e.textContent=a+r+s,this._baseStyleElement=e,this.shadowRoot.appendChild(e)}_getOptionsFromAttributes(){let e={value:this.getAttribute("value")!==null?this._decodeValue(this.getAttribute("value")):(this.textContent||"").trim(),placeholder:this.getAttribute("placeholder")||yi,toolbar:this.hasAttribute("toolbar"),autofocus:this.hasAttribute("autofocus"),autoResize:this.hasAttribute("auto-resize"),showStats:this.hasAttribute("show-stats"),smartLists:!this.hasAttribute("smart-lists")||this.getAttribute("smart-lists")!=="false",spellcheck:this.hasAttribute("spellcheck")&&this.getAttribute("spellcheck")!=="false",onChange:this._handleChange,onKeydown:this._handleKeydown,onRender:this._handleRender},t=this.getAttribute("font-size");t&&(e.fontSize=t);let i=this.getAttribute("line-height");i&&(e.lineHeight=parseFloat(i)||1.6);let o=this.getAttribute("padding");o&&(e.padding=o);let r=this.getAttribute("min-height");r&&(e.minHeight=r);let s=this.getAttribute("max-height");return s&&(e.maxHeight=s),e}_applyPendingOptions(){for(let[e,t]of Object.entries(this._pendingOptions))this._updateOption(e,t);this._pendingOptions={}}_updateOption(e,t){if(this._editor)switch(e){case"value":{let i=this._decodeValue(t);this._editor.getValue()!==i&&this._editor.setValue(i||"")}break;case"theme":this._reinjectStyles(),this._editor&&this._editor.setTheme&&this._editor.setTheme(t||"solar");break;case"placeholder":this._editor&&(this._editor.options.placeholder=t||"",this._editor.textarea&&(this._editor.textarea.placeholder=t||""),this._editor.placeholderEl&&(this._editor.placeholderEl.textContent=t||""));break;case"readonly":this._editor.textarea&&(this._editor.textarea.readOnly=this.hasAttribute("readonly"));break;case"height":case"min-height":case"max-height":this._updateContainerHeight();break;case"toolbar":if(!!this.hasAttribute("toolbar")==!!this._editor.options.toolbar)return;this._reinitializeEditor();break;case"auto-resize":if(!!this.hasAttribute("auto-resize")==!!this._editor.options.autoResize)return;this._reinitializeEditor();break;case"show-stats":if(!!this.hasAttribute("show-stats")==!!this._editor.options.showStats)return;this._reinitializeEditor();break;case"font-size":{this._updateFontSize(t)&&this._reinjectStyles();break}case"line-height":{this._updateLineHeight(t)&&this._reinjectStyles();break}case"padding":this._reinjectStyles();break;case"smart-lists":{let i=!this.hasAttribute("smart-lists")||this.getAttribute("smart-lists")!=="false";if(!!this._editor.options.smartLists==!!i)return;this._reinitializeEditor();break}case"spellcheck":if(this._editor){let i=this.hasAttribute("spellcheck")&&this.getAttribute("spellcheck")!=="false";this._editor.options.spellcheck=i,this._editor.textarea&&this._editor.textarea.setAttribute("spellcheck",String(i))}break}}_updateContainerHeight(){let e=this.shadowRoot.querySelector(`.${wn}`);if(!e)return;let t=this.getAttribute("height"),i=this.getAttribute("min-height"),o=this.getAttribute("max-height");e.style.height=t||"",e.style.minHeight=i||"",e.style.maxHeight=o||""}_updateFontSize(e){return this._editor&&this._editor.wrapper?(this._editor.options.fontSize=e||"",this._editor.wrapper.style.setProperty("--instance-font-size",this._editor.options.fontSize),this._editor.updatePreview(),!0):!1}_updateLineHeight(e){if(this._editor&&this._editor.wrapper){let t=parseFloat(e),i=Number.isFinite(t)?t:this._editor.options.lineHeight;return this._editor.options.lineHeight=i,this._editor.wrapper.style.setProperty("--instance-line-height",String(i)),this._editor.updatePreview(),!0}return!1}_reinjectStyles(){this._baseStyleElement&&this._baseStyleElement.parentNode&&this._baseStyleElement.remove(),this._injectStyles()}_reinitializeEditor(){let e=this._editor?this._editor.getValue():"";this._cleanup(),this._initialized=!1,this.shadowRoot.innerHTML="",e&&!this.getAttribute("value")&&this.setAttribute("value",e),this._initializeEditor()}_handleChange(e){this._updateValueAttribute(e),!(!this._initialized||!this._editor)&&this._dispatchEvent("change",{value:e,editor:this._editor})}_handleKeydown(e){this._dispatchEvent("keydown",{event:e,editor:this._editor})}_handleRender(e,t){this._dispatchEvent("render",{preview:e,mode:t,editor:this._editor})}_updateValueAttribute(e){this.getAttribute("value")!==e&&(this._silentUpdate=!0,this.setAttribute("value",e),this._silentUpdate=!1)}_dispatchEvent(e,t={}){let i=new CustomEvent(e,{detail:t,bubbles:!0,composed:!0});this.dispatchEvent(i)}_cleanup(){this._selectionChangeHandler&&(document.removeEventListener("selectionchange",this._selectionChangeHandler),this._selectionChangeHandler=null),this._editor&&typeof this._editor.destroy=="function"&&this._editor.destroy(),this._editor=null,this._initialized=!1,this.shadowRoot&&(this.shadowRoot.innerHTML="")}refreshTheme(){this._initialized&&this._reinjectStyles()}getValue(){return this._editor?this._editor.getValue():this.getAttribute("value")||""}setValue(e){this._editor?this._editor.setValue(e):this.setAttribute("value"
Line truncated
|
||||
`),i=e.length,o=e.split(/\s+/).filter(c=>c.length>0).length,r=this._editor.textarea.selectionStart,a=e.substring(0,r).split(`
|
||||
`),l=a.length,p=a[a.length-1].length+1;return{characters:i,words:o,lines:t.length,line:l,column:p}}isReady(){return this._initialized&&this._editor!==null}getEditor(){return this._editor}showToolbar(){this._editor&&this._editor.showToolbar()}hideToolbar(){this._editor&&this._editor.hideToolbar()}};customElements.get("overtype-editor")||customElements.define("overtype-editor",qe);var bi=qe;return En(xi);})();
|
||||
/**
|
||||
* OverType - A lightweight markdown editor library with perfect WYSIWYG alignment
|
||||
* @version 1.0.0
|
||||
* @license MIT
|
||||
*/
|
||||
/**
|
||||
* OverType Web Component
|
||||
* A custom element wrapper for the OverType markdown editor with Shadow DOM isolation
|
||||
* @version 1.0.0
|
||||
* @license MIT
|
||||
*/
|
||||
@@ -50,6 +50,7 @@ $selectedFormats = isset($_POST['formats']) && is_array($_POST['formats'])
|
||||
: [];
|
||||
|
||||
$adminMode = ($_POST['admin_mode'] ?? '0') === '1';
|
||||
$editMode = ($_POST['edit_mode'] ?? '0') === '1';
|
||||
|
||||
$hasSiteWeb = $siteWebId && in_array($siteWebId, $selectedFormats, true);
|
||||
$hasVideo = $videoId && in_array($videoId, $selectedFormats, true);
|
||||
@@ -70,6 +71,10 @@ $hxPost = $adminMode ? '/admin/fichiers-fragment.php' : '/partage/fichiers-fragm
|
||||
?>
|
||||
<div id="format-fichiers-block">
|
||||
<input type="hidden" name="admin_mode" value="<?= $adminMode ? '1' : '0' ?>">
|
||||
<input type="hidden" name="edit_mode" value="<?= $editMode ? '1' : '0' ?>">
|
||||
<?php if ($editMode && ($_POST['_cover'] ?? null)): ?>
|
||||
<input type="hidden" name="_cover" value="<?= htmlspecialchars($_POST['_cover']) ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ═══════════════════ Format(s) ═══════════════════ -->
|
||||
<fieldset>
|
||||
@@ -81,7 +86,7 @@ $hxPost = $adminMode ? '/admin/fichiers-fragment.php' : '/partage/fichiers-fragm
|
||||
hx-post="<?= htmlspecialchars($hxPost) ?>"
|
||||
hx-target="#format-fichiers-block"
|
||||
hx-trigger="change"
|
||||
hx-include="this, [name='website_url'], [name='website_label'], [name='admin_mode']"
|
||||
hx-include="this, [name='website_url'], [name='website_label'], [name='admin_mode'], [name='edit_mode'], [name='_cover']"
|
||||
hx-swap="outerHTML">
|
||||
<legend class="sr-only">Format(s) du TFE</legend>
|
||||
<ul>
|
||||
@@ -108,13 +113,26 @@ $hxPost = $adminMode ? '/admin/fichiers-fragment.php' : '/partage/fichiers-fragm
|
||||
<!-- ── 1. Couverture (always) ── -->
|
||||
<div>
|
||||
<?php
|
||||
$_cover = $_POST['_cover'] ?? null;
|
||||
if ($editMode && $_cover): ?>
|
||||
<div class="admin-banner-preview">
|
||||
<img src="/media?path=<?= urlencode($_cover) ?>"
|
||||
alt="Couverture actuelle" style="max-height:180px;">
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="remove_cover" value="1"> Supprimer la couverture
|
||||
</label>
|
||||
</div>
|
||||
<?php endif;
|
||||
$name = 'couverture';
|
||||
$label = 'Image de couverture (optionnel) :';
|
||||
$accept = 'image/jpeg,image/png,image/webp';
|
||||
$hint = 'JPG, PNG ou WEBP. Format 4:3 recommandé. Max 20 MB.';
|
||||
$hint = ($editMode && $_cover)
|
||||
? 'Laisser vide pour conserver la couverture actuelle. JPG, PNG ou WEBP. Max 20 MB.'
|
||||
: 'JPG, PNG ou WEBP. Format 4:3 recommandé. Max 20 MB.';
|
||||
$required = false;
|
||||
$id = 'couverture';
|
||||
include APP_ROOT . '/templates/partials/form/file-field.php';
|
||||
unset($_cover);
|
||||
?>
|
||||
</div>
|
||||
|
||||
@@ -162,7 +180,7 @@ $hxPost = $adminMode ? '/admin/fichiers-fragment.php' : '/partage/fichiers-fragm
|
||||
hx-post="<?= htmlspecialchars($hxPost) ?>"
|
||||
hx-target="#format-fichiers-block"
|
||||
hx-trigger="change"
|
||||
hx-include="[name='formats[]'], [name='website_url'], [name='website_label'], [name='admin_mode'], [name='has_annexes']"
|
||||
hx-include="[name='formats[]'], [name='website_url'], [name='website_label'], [name='admin_mode'], [name='edit_mode'], [name='_cover'], [name='has_annexes']"
|
||||
hx-swap="outerHTML">
|
||||
Ce TFE comporte des annexes
|
||||
</label>
|
||||
|
||||
@@ -42,6 +42,13 @@ if ($slug === 'fichiers-fragment' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Special route: /partage/licence-fragment (HTMX fragment — licence section with conditional required)
|
||||
if ($slug === 'licence-fragment' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
App::boot();
|
||||
require_once __DIR__ . '/licence-fragment.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
// Special route: /partage/recapitulatif?id=N
|
||||
if ($slug === 'recapitulatif' || $slug === 'recapitulatif.php') {
|
||||
App::boot();
|
||||
@@ -267,7 +274,7 @@ function renderShareLinkForm(string $slug, array $link): void
|
||||
|
||||
// Load all form help blocks in one query.
|
||||
$helpBlocks = Database::getInstance()->getAllFormHelpBlocks();
|
||||
$helpFn = fn(string $key) => $helpBlocks[$key]['content'] ?? '';
|
||||
$helpFn = fn(string $key) => empty($helpBlocks[$key]['enabled']) ? '' : ($helpBlocks[$key]['content'] ?? '');
|
||||
|
||||
// ── Shared form variables ──────────────────────────────────────────────
|
||||
$mode = 'partage';
|
||||
@@ -356,6 +363,7 @@ function renderShareLinkForm(string $slug, array $link): void
|
||||
<?php if ($isVerified): ?>
|
||||
<span class="share-badge">🔓 Accès partagé</span>
|
||||
<?php endif; ?>
|
||||
<p class="thesis-add-subtitle">Formulaire pour <a href="/">XAMXAM</a></p>
|
||||
</div>
|
||||
|
||||
<?php include APP_ROOT . '/templates/partials/form/form.php'; ?>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* HTMX fragment: renders the licence section with conditional required states.
|
||||
*
|
||||
* POST: access_type_id, license_id, license_custom, cc2r, admin_mode
|
||||
*
|
||||
* When access_type_id=1 (Libre): licence fields are required.
|
||||
* When access_type_id=2|3 (Interne/Interdit): licence fields are optional.
|
||||
*/
|
||||
require_once __DIR__ . '/../../bootstrap.php';
|
||||
|
||||
$accessTypeId = $_POST['access_type_id'] ?? '2';
|
||||
$licenseId = $_POST['license_id'] ?? '';
|
||||
$licenseCustom = $_POST['license_custom'] ?? '';
|
||||
$cc2r = !empty($_POST['cc2r']);
|
||||
$adminMode = ($_POST['admin_mode'] ?? '0') === '1';
|
||||
|
||||
$required = !$adminMode && $accessTypeId === '1';
|
||||
|
||||
require_once APP_ROOT . '/src/Database.php';
|
||||
$db = Database::getInstance();
|
||||
$licenseTypes = $db->getAllLicenseTypes();
|
||||
?>
|
||||
|
||||
<div class="licence-license-choice">
|
||||
<?php
|
||||
$name = 'license_id'; $label = 'Licence :'; $options = $licenseTypes;
|
||||
$selected = $licenseId; $placeholder = '— Sélectionner —';
|
||||
include APP_ROOT . '/templates/partials/form/select-field.php';
|
||||
?>
|
||||
|
||||
<?php
|
||||
$name = 'license_custom'; $label = 'Ou précisez une autre licence :';
|
||||
$value = htmlspecialchars($licenseCustom);
|
||||
$hint = 'Ex: CC BY-NC 4.0, Tous droits réservés...';
|
||||
include APP_ROOT . '/templates/partials/form/text-field.php';
|
||||
?>
|
||||
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="cc2r" value="1"
|
||||
<?= $cc2r ? 'checked' : '' ?>>
|
||||
J'accepte les Conditions Collectives de Réutilisation (CC2r)
|
||||
</label>
|
||||
<small><a href="https://cc2r.net/" target="_blank" rel="noopener">En savoir plus sur la CC2r ↗</a></small>
|
||||
</div>
|
||||
</div>
|
||||
+40
-35
@@ -2209,27 +2209,16 @@ class Database
|
||||
'partage_intro',
|
||||
'fieldset_tfe_info',
|
||||
'fieldset_synopsis',
|
||||
'fieldset_jury',
|
||||
'fieldset_languages',
|
||||
'fieldset_keywords',
|
||||
'fieldset_academic',
|
||||
'fieldset_jury',
|
||||
'fieldset_files',
|
||||
'fieldset_metadata',
|
||||
'fieldset_access',
|
||||
'fieldset_email',
|
||||
];
|
||||
|
||||
/**
|
||||
* Human-readable labels for each block key (used in the admin UI).
|
||||
*/
|
||||
public const FORM_HELP_LABELS = [
|
||||
'partage_intro' => 'Introduction du formulaire',
|
||||
'fieldset_tfe_info' => 'Informations du TFE — note d\'introduction',
|
||||
'fieldset_synopsis' => 'Synopsis — explication',
|
||||
'fieldset_jury' => 'Composition du jury — note',
|
||||
'fieldset_academic' => 'Cadre académique — note',
|
||||
'fieldset_files' => 'Fichiers — note',
|
||||
'fieldset_access' => 'Visibilité / Accès — explication',
|
||||
'fieldset_email' => 'E-mail de confirmation — note',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get a single form help block by key. Returns '' when missing.
|
||||
*/
|
||||
@@ -2244,7 +2233,7 @@ class Database
|
||||
}
|
||||
|
||||
/**
|
||||
* Upsert a form help block.
|
||||
* Upsert a form help block (content only).
|
||||
*/
|
||||
public function setFormHelpBlock(string $key, string $content): void
|
||||
{
|
||||
@@ -2260,41 +2249,57 @@ class Database
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all form help blocks ordered by sort_order, as [ key => ['content' => ..., 'updated_at' => ..., 'sort_order' => ...] ].
|
||||
* Update a form help block's name.
|
||||
*/
|
||||
public function setFormHelpBlockName(string $key, string $name): void
|
||||
{
|
||||
if (!in_array($key, self::FORM_HELP_KEYS, true)) {
|
||||
throw new Exception("Unknown form help block key: $key");
|
||||
}
|
||||
$this->pdo->prepare(
|
||||
'UPDATE form_help_blocks SET name = ?, updated_at = CURRENT_TIMESTAMP WHERE key = ?'
|
||||
)->execute([$name, $key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle a form help block's enabled state. Returns the new state (0 or 1).
|
||||
*/
|
||||
public function toggleFormHelpBlock(string $key): int
|
||||
{
|
||||
if (!in_array($key, self::FORM_HELP_KEYS, true)) {
|
||||
throw new Exception("Unknown form help block key: $key");
|
||||
}
|
||||
$this->pdo->prepare(
|
||||
'UPDATE form_help_blocks SET enabled = CASE enabled WHEN 1 THEN 0 ELSE 1 END,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE key = ?'
|
||||
)->execute([$key]);
|
||||
$stmt = $this->pdo->prepare('SELECT enabled FROM form_help_blocks WHERE key = ?');
|
||||
$stmt->execute([$key]);
|
||||
return (int)$stmt->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all form help blocks, keyed by their key.
|
||||
*/
|
||||
public function getAllFormHelpBlocks(): array
|
||||
{
|
||||
$stmt = $this->pdo->query(
|
||||
'SELECT key, content, updated_at, sort_order FROM form_help_blocks ORDER BY sort_order, key'
|
||||
'SELECT key, name, content, enabled, updated_at FROM form_help_blocks'
|
||||
);
|
||||
$rows = $stmt->fetchAll();
|
||||
$out = [];
|
||||
foreach ($rows as $r) {
|
||||
$out[$r['key']] = [
|
||||
'name' => $r['name'],
|
||||
'content' => $r['content'],
|
||||
'enabled' => (int)$r['enabled'],
|
||||
'updated_at' => $r['updated_at'],
|
||||
'sort_order' => (int)$r['sort_order'],
|
||||
];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist a new sort order for all form help blocks.
|
||||
* $keys must be an ordered array of known block keys.
|
||||
*/
|
||||
public function reorderFormHelpBlocks(array $keys): void
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
'UPDATE form_help_blocks SET sort_order = ? WHERE key = ?'
|
||||
);
|
||||
foreach ($keys as $i => $key) {
|
||||
if (in_array($key, self::FORM_HELP_KEYS, true)) {
|
||||
$stmt->execute([$i, $key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// SINGLETON PATTERN ENFORCEMENT
|
||||
// ========================================================================
|
||||
|
||||
@@ -38,157 +38,97 @@
|
||||
</table>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════════
|
||||
Blocs d'aide du formulaire étudiant·e
|
||||
Structure du formulaire étudiant·e
|
||||
═══════════════════════════════════════════════════════════════════ -->
|
||||
<h2 id="form-help-blocks" style="margin-top:2rem;">Blocs d'aide du formulaire étudiant·e</h2>
|
||||
<p>Ces textes apparaissent dans le formulaire de soumission accessible via les liens de partage.
|
||||
Ils permettent d'expliquer aux étudiant·es comment remplir chaque section. Supporte le Markdown.</p>
|
||||
<h2 id="form-help-blocks" style="margin-top:2rem;">Structure du formulaire étudiant·e</h2>
|
||||
<p class="fhb-hint">
|
||||
<strong>Glissez</strong> les blocs d'aide (cartes violettes) pour les réorganiser dans le formulaire.
|
||||
Cliquez sur <strong>Éditer</strong> pour modifier le contenu d'un bloc.
|
||||
L'ordre est sauvegardé automatiquement après chaque déplacement.
|
||||
Chaque <strong>bloc d'aide</strong> s'affiche au-dessus de sa section dans le formulaire de soumission.
|
||||
Le <strong>bouton rond</strong> active/désactive l'affichage.
|
||||
</p>
|
||||
|
||||
<?php
|
||||
// Build an ordered flat list of all blocks for the sortable form.
|
||||
// $formHelpBlocks is keyed by block key, already sorted by sort_order.
|
||||
$orderedBlocks = [];
|
||||
foreach ($formHelpBlocks as $key => $block) {
|
||||
$orderedBlocks[] = array_merge($block, [
|
||||
'key' => $key,
|
||||
'label' => Database::FORM_HELP_LABELS[$key] ?? $key,
|
||||
]);
|
||||
}
|
||||
$blocks = $formHelpBlocks;
|
||||
|
||||
// Static form structure: each item is either a 'fieldset' (visual container)
|
||||
// or an 'anchor' for a specific block key showing where it sits in the form.
|
||||
// We also need a mapping from block key → where it currently sits in the sorted list.
|
||||
// The entire sorted order is what matters; we render the form structure as a visual
|
||||
// reference alongside the sortable list.
|
||||
$formStructure = [
|
||||
['type' => 'anchor', 'key' => 'partage_intro', 'position' => 'before-form', 'label' => 'Avant le formulaire (introduction)'],
|
||||
['type' => 'fieldset', 'name' => 'Informations du TFE', 'inputs' => ['Titre', 'Sous-titre', 'Auteur·ice(s)', 'Contact', 'Synopsis']],
|
||||
['type' => 'anchor', 'key' => 'fieldset_tfe_info', 'position' => 'intro-fieldset', 'label' => 'Intro du fieldset « Informations du TFE »'],
|
||||
['type' => 'anchor', 'key' => 'fieldset_synopsis', 'position' => 'intro-fieldset', 'label' => 'Note sous le champ Synopsis'],
|
||||
['type' => 'fieldset', 'name' => 'Composition du jury', 'inputs' => ['Président·e', 'Promoteur·ice', 'Lecteur·ices (×4)']],
|
||||
['type' => 'anchor', 'key' => 'fieldset_jury', 'position' => 'intro-fieldset', 'label' => 'Intro du fieldset « Jury »'],
|
||||
['type' => 'fieldset', 'name' => 'Cadre académique', 'inputs' => ['Année', 'Orientation', 'AP', 'Finalité', 'Langues', 'Formats', 'Mots-clés']],
|
||||
['type' => 'anchor', 'key' => 'fieldset_academic', 'position' => 'intro-fieldset', 'label' => 'Intro du fieldset « Cadre académique »'],
|
||||
['type' => 'fieldset', 'name' => 'Fichiers', 'inputs' => ['Fichier principal (PDF)', 'Annexes']],
|
||||
['type' => 'anchor', 'key' => 'fieldset_files', 'position' => 'intro-fieldset', 'label' => 'Intro du fieldset « Fichiers »'],
|
||||
['type' => 'fieldset', 'name' => 'Visibilité / Accès', 'inputs' => ["Type d'accès", 'Licence']],
|
||||
['type' => 'anchor', 'key' => 'fieldset_access', 'position' => 'intro-fieldset', 'label' => 'Intro du fieldset « Visibilité »'],
|
||||
['type' => 'fieldset', 'name' => 'E-mail de confirmation', 'inputs' => ['Adresse e-mail']],
|
||||
['type' => 'anchor', 'key' => 'fieldset_email', 'position' => 'intro-fieldset', 'label' => 'Intro du fieldset « E-mail »'],
|
||||
// ── Student form structure — each help block above its fieldset ───────────
|
||||
// Pairs: [help_key, fieldset_name, fieldset_inputs]
|
||||
$pairs = [
|
||||
// Top of form
|
||||
['partage_intro', null, null],
|
||||
|
||||
// Informations du TFE
|
||||
['fieldset_tfe_info', 'Informations du TFE',
|
||||
['Titre', 'Sous-titre', 'Auteur·ice(s)', 'Contact visible', 'Synopsis']],
|
||||
|
||||
// Langue(s)
|
||||
['fieldset_languages', 'Langue(s)',
|
||||
['Langues du TFE (cases à cocher)', 'Autre(s) langue(s)']],
|
||||
|
||||
// Mots-clés
|
||||
['fieldset_keywords', 'Mots-clés',
|
||||
['Mots-clés (max 10), séparés par des virgules']],
|
||||
|
||||
// Cadre académique
|
||||
['fieldset_academic', 'Cadre académique',
|
||||
['Année', 'Orientation', 'AP', 'Finalité']],
|
||||
|
||||
// Composition du jury
|
||||
['fieldset_jury', 'Composition du jury',
|
||||
['Président·e', 'Promoteur·ice(s)', 'Lecteur·ices']],
|
||||
|
||||
// Format(s) + Fichiers
|
||||
['fieldset_files', 'Format(s) + Fichiers',
|
||||
['Formats (PDF, vidéo, audio, site web…)', 'Couverture', 'Note d\'intention', 'Fichier principal', 'Annexes']],
|
||||
|
||||
// Métadonnées complémentaires
|
||||
['fieldset_metadata', 'Métadonnées complémentaires',
|
||||
['Nombre de pages', 'Durée (minutes)']],
|
||||
|
||||
// Degrés d'ouverture et licences
|
||||
['fieldset_access', 'Degrés d\'ouverture et licences',
|
||||
['Généralités', 'Degré (libre/interne/interdit)', 'Licence', 'CC2r']],
|
||||
|
||||
// E-mail de confirmation
|
||||
['fieldset_email', 'E-mail de confirmation',
|
||||
['Adresse e-mail']],
|
||||
];
|
||||
?>
|
||||
|
||||
<div class="fhb-layout">
|
||||
|
||||
<!-- Left: sortable ordered list of help blocks -->
|
||||
<div class="fhb-sortable-panel">
|
||||
<h3 class="fhb-panel-title">Ordre des blocs</h3>
|
||||
<p class="fhb-panel-desc">Glissez pour réorganiser</p>
|
||||
|
||||
<form class="fhb-sortable sortable"
|
||||
hx-post="/admin/actions/form-help-reorder.php"
|
||||
hx-trigger="end"
|
||||
hx-include="this"
|
||||
hx-swap="none">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
<div class="htmx-indicator fhb-saving">Sauvegarde…</div>
|
||||
|
||||
<?php foreach ($orderedBlocks as $b): ?>
|
||||
<div class="fhb-block-card" data-key="<?= htmlspecialchars($b['key']) ?>">
|
||||
<input type="hidden" name="block[]" value="<?= htmlspecialchars($b['key']) ?>">
|
||||
<span class="fhb-drag-handle" aria-hidden="true">⠿</span>
|
||||
<div class="fhb-block-info">
|
||||
<span class="fhb-block-label"><?= htmlspecialchars($b['label']) ?></span>
|
||||
<?php if (trim($b['content']) !== ''): ?>
|
||||
<span class="fhb-block-preview"><?= htmlspecialchars(strlen($preview = trim($b['content'])) > 60 ? substr($preview, 0, 60) . '…' : $preview) ?></span>
|
||||
<?php else: ?>
|
||||
<span class="fhb-block-empty">— vide —</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<a href="/admin/contenus-edit.php?form_block=<?= urlencode($b['key']) ?>"
|
||||
class="btn btn--primary btn--sm fhb-edit-btn">Éditer</a>
|
||||
<div class="fhb-structure">
|
||||
<?php foreach ($pairs as [$helpKey, $fieldsetName, $inputs]):
|
||||
// Help block
|
||||
$b = $blocks[$helpKey] ?? ['content' => '', 'name' => '', 'enabled' => 0];
|
||||
$title = $b['name'] ?: ($fieldsetName ?? $helpKey);
|
||||
?>
|
||||
<div class="fhb-block-wrapper" data-key="<?= htmlspecialchars($helpKey) ?>">
|
||||
<div class="fhb-inline"
|
||||
hx-get="/admin/form-help-inline-fragment.php?key=<?= urlencode($helpKey) ?>"
|
||||
hx-trigger="load"
|
||||
hx-swap="outerHTML">
|
||||
<div class="fhb-inline-name"><?= htmlspecialchars($title) ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Right: static form structure reference -->
|
||||
<div class="fhb-form-preview-panel">
|
||||
<h3 class="fhb-panel-title">Structure du formulaire</h3>
|
||||
<p class="fhb-panel-desc">Référence visuelle — non modifiable</p>
|
||||
|
||||
<div class="fhb-form-preview">
|
||||
<?php foreach ($formStructure as $item): ?>
|
||||
<?php if ($item['type'] === 'anchor'): ?>
|
||||
<?php
|
||||
$bData = $formHelpBlocks[$item['key']] ?? ['content' => '', 'sort_order' => 99];
|
||||
$hasContent = trim($bData['content'] ?? '') !== '';
|
||||
?>
|
||||
<div class="fhb-anchor <?= $hasContent ? 'fhb-anchor--filled' : 'fhb-anchor--empty' ?>">
|
||||
<span class="fhb-anchor-icon"><?= $hasContent ? '✎' : '○' ?></span>
|
||||
<span class="fhb-anchor-label"><?= htmlspecialchars(Database::FORM_HELP_LABELS[$item['key']] ?? $item['key']) ?></span>
|
||||
<?php if ($hasContent): ?>
|
||||
<span class="fhb-anchor-pos">#<?= (int)$bData['sort_order'] + 1 ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php elseif ($item['type'] === 'fieldset'): ?>
|
||||
<div class="fhb-fieldset-preview">
|
||||
<div class="fhb-fieldset-legend"><?= htmlspecialchars($item['name']) ?></div>
|
||||
<ul class="fhb-fieldset-inputs">
|
||||
<?php foreach ($item['inputs'] as $inp): ?>
|
||||
<li><?= htmlspecialchars($inp) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /.fhb-layout -->
|
||||
<?php if ($fieldsetName !== null): ?>
|
||||
<div class="fhb-fieldset-card">
|
||||
<div class="fhb-fieldset-card-legend"><?= htmlspecialchars($fieldsetName) ?></div>
|
||||
<?php if ($inputs): ?>
|
||||
<ul class="fhb-fieldset-card-inputs">
|
||||
<?php foreach ($inputs as $inp): ?>
|
||||
<li><?= htmlspecialchars($inp) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var script = document.createElement('script');
|
||||
script.src = '<?= App::assetV('/assets/js/sortable.min.js') ?>';
|
||||
script.onload = initSortable;
|
||||
document.head.appendChild(script);
|
||||
|
||||
function initSortable() {
|
||||
htmx.onLoad(function (content) {
|
||||
var sortables = content.querySelectorAll('.sortable');
|
||||
for (var i = 0; i < sortables.length; i++) {
|
||||
(function (sortable) {
|
||||
var sortableInstance = new Sortable(sortable, {
|
||||
animation: 150,
|
||||
handle: '.fhb-drag-handle',
|
||||
ghostClass: 'fhb-ghost',
|
||||
chosenClass: 'fhb-chosen',
|
||||
dragClass: 'fhb-dragging',
|
||||
|
||||
filter: '.htmx-indicator',
|
||||
onMove: function (evt) {
|
||||
return evt.related.className.indexOf('htmx-indicator') === -1;
|
||||
},
|
||||
|
||||
onEnd: function () {
|
||||
this.option('disabled', true);
|
||||
}
|
||||
});
|
||||
|
||||
sortable.addEventListener('htmx:afterRequest', function () {
|
||||
sortableInstance.option('disabled', false);
|
||||
});
|
||||
})(sortables[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
var otScript = document.createElement('script');
|
||||
otScript.src = '<?= App::assetV('/assets/js/overtype.min.js') ?>';
|
||||
document.head.appendChild(otScript);
|
||||
})();
|
||||
</script>
|
||||
+11
-13
@@ -12,11 +12,11 @@ $_thesisId = $_GET['id'] ?? null;
|
||||
<?php if ($_isAdmin): ?>
|
||||
|
||||
<nav aria-label="Navigation admin">
|
||||
<div class="nav-left">
|
||||
<a href="/" target="_blank" rel="noopener noreferrer" class="nav-logo">
|
||||
<ul class="nav-left-links">
|
||||
<li><a href="/" target="_blank" rel="noopener noreferrer" class="nav-logo">
|
||||
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 256 256"><path d="M208,72H128V32a8,8,0,0,0-13.66-5.66l-96,96a8,8,0,0,0,0,11.32l96,96A8,8,0,0,0,128,224V184h80a16,16,0,0,0,16-16V88A16,16,0,0,0,208,72Zm0,96H120a8,8,0,0,0-8,8v28.69L35.31,128,112,51.31V80a8,8,0,0,0,8,8h88Z"></path></svg>XAMXAM<span class="sr-only"> (site public, nouvel onglet)</span>
|
||||
</a>
|
||||
</div>
|
||||
</a></li>
|
||||
</ul>
|
||||
<ul class="nav-right-links">
|
||||
<li><a href="/admin/" <?= $_currentPage === 'index.php' ? 'aria-current="page"' : '' ?>>Liste des TFE</a></li>
|
||||
<li><a href="/admin/contenus.php" <?= in_array($_currentPage, ['contenus.php', 'contenus-edit.php']) ? 'aria-current="page"' : '' ?>>Contenus</a></li>
|
||||
@@ -41,15 +41,13 @@ $_thesisId = $_GET['id'] ?? null;
|
||||
<input class="menu-btn" type="checkbox" id="menu-btn" name="menu-btn" />
|
||||
<nav aria-label="Navigation principale">
|
||||
<div class="nav-top-row">
|
||||
<div class="nav-left">
|
||||
<a href="/" class="nav-logo">Xamxam</a>
|
||||
<ul class="nav-left-links">
|
||||
<li>
|
||||
<a href="/repertoire"
|
||||
<?= ($_navCurrent === 'repertoire') ? 'aria-current="page"' : '' ?>>Répertoire</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="nav-left-links">
|
||||
<li><a href="/" class="nav-logo">Xamxam</a></li>
|
||||
<li>
|
||||
<a href="/repertoire"
|
||||
<?= ($_navCurrent === 'repertoire') ? 'aria-current="page"' : '' ?>>Répertoire</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav-right-links">
|
||||
<li>
|
||||
<a href="/licence"
|
||||
|
||||
@@ -30,32 +30,19 @@ $adminMode = $adminMode ?? false;
|
||||
<fieldset class="licence-explanation">
|
||||
<legend>Degrés d'ouverture et licences</legend>
|
||||
|
||||
<!-- Généralités -->
|
||||
<div class="licence-generalites">
|
||||
<h3>Généralités</h3>
|
||||
<?php if ($generalitiesHtml): ?>
|
||||
<div class="form-help-editable"><?= $generalitiesHtml ?></div>
|
||||
<?php else: ?>
|
||||
<ul>
|
||||
<li>L'auteur·ice peut décider entre trois degrés de partage de son travail : <strong>libre</strong>, <strong>interne</strong>, <strong>interdit</strong>.</li>
|
||||
<li>L'auteur·ice peut, à tout moment, décider de <strong>restreindre</strong> le degré d'accès à son travail. Il ne peut néanmoins pas l'ouvrir davantage.</li>
|
||||
<li>Le choix effectué dans ce formulaire sera d'application <strong>une semaine après la soutenance orale</strong> de l'auteur·ice. Celui-ci peut donc décider de restreindre ce choix avant sa publication (mais pas l'ouvrir).</li>
|
||||
<li>L'erg se réserve le droit de restreindre le degré d'ouverture du TFE – ce en accord avec le règlement.</li>
|
||||
<li>Dans tous les cas, l'auteur·ice garde les droits d'auteurs, de diffusion, d'utilisation, etc. de son travail – sauf si la licence choisie restreindrait ses droits.</li>
|
||||
<li>La diffusion « xamxam » est indépendante de la diffusion à la BAIU.</li>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Degré d'ouverture -->
|
||||
<div class="licence-choice">
|
||||
<h3>J'autorise l'erg à archiver mon TFE de la manière suivante :</h3>
|
||||
<p class="licence-prompt">J'autorise l'erg à archiver mon TFE de la manière suivante :</p>
|
||||
<?php $selectedAccess = $formData['access_type_id'] ?? (string)$defaultAccessTypeId; ?>
|
||||
|
||||
<?php if ($libreEnabled): ?>
|
||||
<div class="licence-degree">
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="radio" name="access_type_id" value="1"
|
||||
hx-post="<?= $adminMode ? '/admin/licence-fragment.php' : '/partage/licence-fragment' ?>"
|
||||
hx-target=".licence-license-choice"
|
||||
hx-swap="outerHTML"
|
||||
hx-include="closest fieldset"
|
||||
<?= $selectedAccess === '1' ? 'checked' : '' ?> <?= $adminMode ? '' : 'required' ?>>
|
||||
<strong>🔓 Libre</strong> — Mon TFE est en libre accès à tout le monde sur la plateforme des TFE ainsi que dans la bibliothèque de l'erg.
|
||||
</label>
|
||||
@@ -66,6 +53,10 @@ $adminMode = $adminMode ?? false;
|
||||
<div class="licence-degree">
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="radio" name="access_type_id" value="2"
|
||||
hx-post="<?= $adminMode ? '/admin/licence-fragment.php' : '/partage/licence-fragment' ?>"
|
||||
hx-target=".licence-license-choice"
|
||||
hx-swap="outerHTML"
|
||||
hx-include="closest fieldset"
|
||||
<?= $selectedAccess === '2' ? 'checked' : '' ?> <?= $adminMode ? '' : 'required' ?>>
|
||||
<strong>🔒 Interne</strong> — Mon TFE n'est accessible que sur place en physique. Une note descriptive est disponible sur le site.
|
||||
</label>
|
||||
@@ -76,6 +67,10 @@ $adminMode = $adminMode ?? false;
|
||||
<div class="licence-degree">
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="radio" name="access_type_id" value="3"
|
||||
hx-post="<?= $adminMode ? '/admin/licence-fragment.php' : '/partage/licence-fragment' ?>"
|
||||
hx-target=".licence-license-choice"
|
||||
hx-swap="outerHTML"
|
||||
hx-include="closest fieldset"
|
||||
<?= $selectedAccess === '3' ? 'checked' : '' ?> <?= $adminMode ? '' : 'required' ?>>
|
||||
<strong>🚫 Interdit</strong> — Mon TFE n'est pas disponible en physique ni sur le site. Une note descriptive est disponible sur le site.
|
||||
</label>
|
||||
@@ -84,29 +79,11 @@ $adminMode = $adminMode ?? false;
|
||||
<p class="licence-note"><em>L'auteur·ice peut, à tout moment, décider de restreindre son propre choix. Iel ne peut par contre pas l'ouvrir.</em></p>
|
||||
</div>
|
||||
|
||||
<!-- Licence -->
|
||||
<div class="licence-license-choice">
|
||||
<h3>Licence du TFE</h3>
|
||||
<?php
|
||||
$name = 'license_id'; $label = 'Licence :'; $options = $licenseTypes;
|
||||
$selected = $formData['license_id'] ?? ''; $placeholder = '— Sélectionner —'; $required = !$adminMode;
|
||||
include APP_ROOT . '/templates/partials/form/select-field.php';
|
||||
?>
|
||||
|
||||
<?php
|
||||
$name = 'license_custom'; $label = 'Ou précisez une autre licence :';
|
||||
$value = htmlspecialchars($formData['license_custom'] ?? '');
|
||||
$hint = 'Ex: CC BY-NC 4.0, Tous droits réservés...';
|
||||
include APP_ROOT . '/templates/partials/form/text-field.php';
|
||||
?>
|
||||
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="cc2r" value="1"
|
||||
<?= !empty($formData['cc2r']) ? 'checked' : '' ?>>
|
||||
J'accepte les Conditions Collectives de Réutilisation (CC2r)
|
||||
</label>
|
||||
<small><a href="https://cc2r.net/" target="_blank" rel="noopener">En savoir plus sur la CC2r ↗</a></small>
|
||||
</div>
|
||||
<!-- Licence — swapped via htmx when radio changes -->
|
||||
<div class="licence-license-choice"
|
||||
hx-post="<?= $adminMode ? '/admin/licence-fragment.php' : '/partage/licence-fragment' ?>"
|
||||
hx-trigger="load"
|
||||
hx-include="closest fieldset"
|
||||
hx-swap="outerHTML">
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -4,6 +4,7 @@
|
||||
*
|
||||
* Variables consumed:
|
||||
* string $helpContent — raw Markdown string from the DB (may be empty).
|
||||
* string $helpKey — block key, used as element id.
|
||||
*
|
||||
* Outputs nothing when $helpContent is empty or whitespace-only.
|
||||
* Parsedown must already be autoloaded (it is, via bootstrap → APP_ROOT/src/).
|
||||
@@ -19,7 +20,7 @@ $pd = new Parsedown();
|
||||
$pd->setSafeMode(true);
|
||||
$html = $pd->text($helpContent);
|
||||
?>
|
||||
<div class="form-help-block">
|
||||
<div class="student-help-block" id="help-<?= htmlspecialchars($helpKey ?? 'unknown') ?>">
|
||||
<?= $html ?>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
@@ -102,6 +102,7 @@ $checkedFormatsForSiteWeb = $checkedFormatsForSiteWeb ?? [];
|
||||
<?php if ($showIntroHelp && isset($helpFn)): ?>
|
||||
<?php
|
||||
$helpContent = $helpFn("partage_intro");
|
||||
$helpKey = 'partage_intro';
|
||||
include APP_ROOT . "/templates/partials/form/form-help-block.php";
|
||||
?>
|
||||
<?php endif; ?>
|
||||
@@ -146,6 +147,7 @@ $checkedFormatsForSiteWeb = $checkedFormatsForSiteWeb ?? [];
|
||||
<?php
|
||||
if ($mode === "partage" && isset($helpFn)) {
|
||||
$helpContent = $helpFn("fieldset_tfe_info");
|
||||
$helpKey = 'fieldset_tfe_info';
|
||||
include APP_ROOT . "/templates/partials/form/form-help-block.php";
|
||||
}
|
||||
include APP_ROOT . "/templates/partials/form/fieldset-tfe-info.php";
|
||||
@@ -170,6 +172,13 @@ $checkedFormatsForSiteWeb = $checkedFormatsForSiteWeb ?? [];
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ═══════════════════ Langue(s) ═══════════════════ -->
|
||||
<?php
|
||||
if ($mode === "partage" && isset($helpFn)) {
|
||||
$helpContent = $helpFn("fieldset_languages");
|
||||
$helpKey = 'fieldset_languages';
|
||||
include APP_ROOT . "/templates/partials/form/form-help-block.php";
|
||||
}
|
||||
?>
|
||||
<fieldset id="languages-fieldset">
|
||||
<legend>Langue(s)</legend>
|
||||
<?php
|
||||
@@ -205,6 +214,13 @@ $checkedFormatsForSiteWeb = $checkedFormatsForSiteWeb ?? [];
|
||||
</fieldset>
|
||||
|
||||
<!-- ═══════════════════ Mots-clés ═══════════════════ -->
|
||||
<?php
|
||||
if ($mode === "partage" && isset($helpFn)) {
|
||||
$helpContent = $helpFn("fieldset_keywords");
|
||||
$helpKey = 'fieldset_keywords';
|
||||
include APP_ROOT . "/templates/partials/form/form-help-block.php";
|
||||
}
|
||||
?>
|
||||
<fieldset>
|
||||
<legend>Mots-clés</legend>
|
||||
<?php
|
||||
@@ -219,13 +235,20 @@ $checkedFormatsForSiteWeb = $checkedFormatsForSiteWeb ?? [];
|
||||
</fieldset>
|
||||
|
||||
<!-- ═══════════════════ Cadre académique ═══════════════════ -->
|
||||
<?php include APP_ROOT .
|
||||
<?php
|
||||
if ($mode === "partage" && isset($helpFn)) {
|
||||
$helpContent = $helpFn("fieldset_academic");
|
||||
$helpKey = 'fieldset_academic';
|
||||
include APP_ROOT . "/templates/partials/form/form-help-block.php";
|
||||
}
|
||||
include APP_ROOT .
|
||||
"/templates/partials/form/fieldset-academic.php"; ?>
|
||||
|
||||
<!-- ═══════════════════ Composition du jury ═══════════════════ -->
|
||||
<?php
|
||||
if ($mode === "partage" && isset($helpFn)) {
|
||||
$helpContent = $helpFn("fieldset_jury");
|
||||
$helpKey = 'fieldset_jury';
|
||||
include APP_ROOT . "/templates/partials/form/form-help-block.php";
|
||||
}
|
||||
require APP_ROOT . "/templates/partials/form/jury-fieldset.php";
|
||||
@@ -238,6 +261,7 @@ $checkedFormatsForSiteWeb = $checkedFormatsForSiteWeb ?? [];
|
||||
// Synthesise POST-like data so fichiers-fragment.php can render the initial state.
|
||||
if ($mode === "partage" && isset($helpFn)) {
|
||||
$helpContent = $helpFn("fieldset_files");
|
||||
$helpKey = 'fieldset_files';
|
||||
include APP_ROOT . "/templates/partials/form/form-help-block.php";
|
||||
}
|
||||
// Temporarily populate $_POST so the fragment can read formats/website/annexes values.
|
||||
@@ -260,6 +284,8 @@ $checkedFormatsForSiteWeb = $checkedFormatsForSiteWeb ?? [];
|
||||
$_POST['website_url'] = $existingWebsiteUrl;
|
||||
$_POST['website_label'] = $existingWebsiteLabel;
|
||||
$_POST['admin_mode'] = $adminMode ? '1' : '0';
|
||||
$_POST['edit_mode'] = '1';
|
||||
$_POST['_cover'] = $currentCover['file_path'] ?? null;
|
||||
$_POST['has_annexes'] = $formData['has_annexes'] ?? null;
|
||||
include APP_ROOT . '/public/partage/fichiers-fragment.php';
|
||||
$_POST = $_savedPost;
|
||||
@@ -268,29 +294,6 @@ $checkedFormatsForSiteWeb = $checkedFormatsForSiteWeb ?? [];
|
||||
|
||||
<!-- Edit-only: existing files management -->
|
||||
<div id="edit-existing-files-block">
|
||||
<!-- Cover image -->
|
||||
<div class="admin-form-group">
|
||||
<label>Image de couverture :</label>
|
||||
<div class="admin-file-input">
|
||||
<?php if (!empty($currentCover)): ?>
|
||||
<div class="admin-banner-preview">
|
||||
<img src="/media?path=<?= urlencode(
|
||||
$currentCover["file_path"],
|
||||
) ?>"
|
||||
alt="Couverture actuelle" style="max-height:180px;">
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="remove_cover" value="1"> Supprimer la couverture
|
||||
</label>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<input type="file" id="couverture" name="couverture" accept="image/jpeg,image/png,image/webp" data-preview="fp-couverture">
|
||||
<div id="fp-couverture" class="file-preview-list" aria-live="polite"></div>
|
||||
<small><?= empty($currentCover)
|
||||
? "JPG, PNG ou WEBP. Format 4:3 recommandé. Max 20 MB."
|
||||
: "Laisser vide pour conserver la couverture actuelle. JPG, PNG ou WEBP. Max 20 MB." ?></small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Existing thesis files — sortable, with labels -->
|
||||
<?php $thesisFilesList = array_values(
|
||||
array_filter(
|
||||
@@ -398,13 +401,20 @@ $checkedFormatsForSiteWeb = $checkedFormatsForSiteWeb ?? [];
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ═══════════════════ Métadonnées complémentaires ═══════════════════ -->
|
||||
<?php include APP_ROOT .
|
||||
<?php
|
||||
if ($mode === "partage" && isset($helpFn)) {
|
||||
$helpContent = $helpFn("fieldset_metadata");
|
||||
$helpKey = 'fieldset_metadata';
|
||||
include APP_ROOT . "/templates/partials/form/form-help-block.php";
|
||||
}
|
||||
include APP_ROOT .
|
||||
"/templates/partials/form/fieldset-metadata.php"; ?>
|
||||
|
||||
<!-- ═══════════════════ Degrés d'ouverture et licences ═══════════════════ -->
|
||||
<?php
|
||||
if ($mode === "partage" && isset($helpFn)) {
|
||||
$helpContent = $helpFn("fieldset_access");
|
||||
$helpKey = 'fieldset_access';
|
||||
include APP_ROOT . "/templates/partials/form/form-help-block.php";
|
||||
}
|
||||
include APP_ROOT .
|
||||
@@ -509,6 +519,7 @@ $checkedFormatsForSiteWeb = $checkedFormatsForSiteWeb ?? [];
|
||||
<?php if ($mode === "partage" && isset($helpFn)): ?>
|
||||
<?php
|
||||
$helpContent = $helpFn("fieldset_email");
|
||||
$helpKey = 'fieldset_email';
|
||||
include APP_ROOT . "/templates/partials/form/form-help-block.php";
|
||||
?>
|
||||
<?php endif; ?>
|
||||
|
||||
Reference in new issue
Block a user