Tiroir Comments - Section « Reactions » au-dessus des fils : émoji, compte (si >1) et texte ancré lu dans le DOM (reactionList() / _anchorText(), offsets source hors pastilles) ; l'état vide ne s'affiche plus s'il reste des réactions. - Suppression : ✕ sur la ligne du tiroir (mes réactions) ET croix ::after au survol de la pastille sur l'émoji que j'ai posé (aucun nœud ajouté → offsets intacts). Deux entrées → removeReaction() → POST toggle → loadReactions(). Menu de sélection (maquette ASCII) - Ligne 4 = rangée scannée : 💬 Comment (libellé) à gauche, 🙂+ réaction à droite (.ft-row-split) ; la réaction sort de la ligne 3. - Entête Skills avec ≡ à droite (::after), liste Skills scrollable. - ✨ Ask AI retiré de la toolbar : doublon exact de « Edit with AI » du pied. - Écart volontaire à la maquette : le barré (T) de la ligne 2 est conservé. Fix racine (flake de gate) - _is_public_host attrape OSError (le garde SSRF renvoie un booléen, il ne lève plus) : un socket.timeout DNS ne bascule plus fetch_og_metadata dans son except Exception → repli silencieux → AssertionError. - fixture stub_dns sur TestOGParser (même motif que test_agent_web_tools) : zéro vrai DNS dans les tests OG. Tests: pytest tests/ -q -n auto → 1390 passed / 0 failed (flake corrigé) ; ruff check app tests → OK ; e2e v764 (réactions dans le tiroir + suppression A/B), v762 (nouvelle ligne 4), v766, v7641, v763 → 6 passed sur :8081 (image flowdeck:v7.67.0 rebuild + redéployée).
933 lines
55 KiB
HTML
933 lines
55 KiB
HTML
<div
|
||
class="page-editor-wrapper"
|
||
x-data="editorState()"
|
||
x-init="loadCoverIcon()"
|
||
>
|
||
<!-- Share/More dialogs (in content area, triggered by header buttons) -->
|
||
|
||
<!-- ═══════════ Share / Publish Dialog ═══════════ -->
|
||
<div
|
||
class="share-dialog"
|
||
x-show="shareOpen"
|
||
@click.outside="!_justOpened && (shareOpen = false)"
|
||
x-transition.opacity.scale.origin.top.right
|
||
>
|
||
<!-- Header with tabs -->
|
||
<div class="sd-header">
|
||
<button
|
||
class="sd-tab"
|
||
:class="{ active: shareTab === 'share' }"
|
||
@click="shareTab = 'share'"
|
||
>
|
||
Share
|
||
</button>
|
||
<button
|
||
class="sd-tab"
|
||
:class="{ active: shareTab === 'publish' }"
|
||
@click="shareTab = 'publish'"
|
||
>
|
||
Publish
|
||
<span class="sd-badge" x-show="pagePublished"></span>
|
||
</button>
|
||
<button class="sd-close" @click="shareOpen = false">
|
||
✕
|
||
</button>
|
||
</div>
|
||
|
||
<!-- ═══════ SHARE TAB ═══════ -->
|
||
<div class="sd-body" x-show="shareTab === 'share'" x-transition>
|
||
<!-- Invite input -->
|
||
<div class="sd-invite-wrap">
|
||
<div class="sd-invite-row">
|
||
<input
|
||
type="text"
|
||
class="sd-input"
|
||
x-ref="inviteInput"
|
||
placeholder="Add people, emails or type a name..."
|
||
x-model="inviteEmail"
|
||
@input="inviteInput()"
|
||
@focus="inviteInput()"
|
||
@keydown.enter.prevent="inviteEnter()"
|
||
@keydown.down.prevent="inviteMove(1)"
|
||
@keydown.up.prevent="inviteMove(-1)"
|
||
@keydown.escape="closeInviteSuggest()"
|
||
autocomplete="off"
|
||
/>
|
||
<button class="sd-btn-primary" @click="shareInvite()">
|
||
Invite
|
||
</button>
|
||
</div>
|
||
<!-- Existing users autocomplete -->
|
||
<div
|
||
class="sd-user-suggest"
|
||
x-ref="inviteSuggest"
|
||
x-show="inviteSuggestOpen"
|
||
@click.outside="inviteSuggestOpen = false"
|
||
x-transition.opacity
|
||
>
|
||
<template x-for="(u, i) in inviteUsers" :key="u.id">
|
||
<div
|
||
class="sd-user-item"
|
||
:class="{ active: inviteSel === i }"
|
||
@mouseenter="inviteSel = i"
|
||
@click="pickInviteUser(u)"
|
||
>
|
||
<div
|
||
class="sd-user-avatar"
|
||
:style="{ background: u.avatar_color || '#3A3A3A' }"
|
||
>
|
||
<img
|
||
x-show="u.avatar_url"
|
||
:src="u.avatar_url"
|
||
:alt="u.login"
|
||
style="width:100%;height:100%;border-radius:50%;object-fit:cover;"
|
||
/>
|
||
<span
|
||
x-show="!u.avatar_url"
|
||
x-text="((u.full_name || u.login || '?').charAt(0).toUpperCase())"
|
||
></span>
|
||
</div>
|
||
<div class="sd-user-meta">
|
||
<div class="sd-user-login" x-text="u.login"></div>
|
||
<div class="sd-user-name" x-text="u.full_name"></div>
|
||
</div>
|
||
<div class="sd-user-add">Invite</div>
|
||
</div>
|
||
</template>
|
||
<template x-if="inviteGroups && inviteGroups.length">
|
||
<div>
|
||
<div class="sd-user-empty" style="font-weight:600;">Groups</div>
|
||
<template x-for="g in inviteGroups" :key="'g'+g.id">
|
||
<div
|
||
class="sd-user-item"
|
||
@click="pickInviteGroup(g)"
|
||
>
|
||
<div class="sd-user-avatar" style="background:#5b5bd6;color:#fff;">
|
||
<span x-text="(g.name || '?').charAt(0).toUpperCase()"></span>
|
||
</div>
|
||
<div class="sd-user-meta">
|
||
<div class="sd-user-login" x-text="g.name"></div>
|
||
<div class="sd-user-name" x-text="'Group · ' + (g.member_count || 0) + ' members'"></div>
|
||
</div>
|
||
<div class="sd-user-add">Invite</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</template>
|
||
<div class="sd-user-empty" x-show="!inviteUsers.length && !(inviteGroups && inviteGroups.length)">No matching users</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Context card (shown when independent permissions) -->
|
||
<div class="sd-context-card" x-show="false">
|
||
Share settings on this page are unlinked from parent
|
||
page
|
||
</div>
|
||
|
||
<!-- Participants list -->
|
||
<div class="sd-participants" x-show="accessList.length">
|
||
<template x-for="a in accessList" :key="a.id">
|
||
{% raw %}
|
||
<div class="sd-participant">
|
||
<div class="sd-participant-info">
|
||
<div class="sd-avatar" x-show="a.user_avatar_url" style="overflow:hidden">
|
||
<img :src="a.user_avatar_url" style="width:100%;height:100%;border-radius:50%;object-fit:cover;" />
|
||
</div>
|
||
<!-- Group avatar -->
|
||
<div class="sd-avatar" x-show="(a.kind === 'group' || a.shared_with_group_id) && !a.user_avatar_url"
|
||
style="background:#5b5bd6;color:#fff;"
|
||
x-text="(a.group_name || '?')[0].toUpperCase()"
|
||
></div>
|
||
<div class="sd-avatar" x-show="!(a.kind === 'group' || a.shared_with_group_id) && !a.user_avatar_url"
|
||
x-text="(a.user_full_name || a.user_login || a.shared_with_email || '?')[0].toUpperCase()"
|
||
></div>
|
||
<div>
|
||
<div
|
||
class="sd-participant-name"
|
||
x-text="(a.kind === 'group' || a.shared_with_group_id) ? (a.group_name || 'Group') : (a.user_full_name || a.user_login || a.shared_with_email)"
|
||
></div>
|
||
<div
|
||
class="sd-participant-email"
|
||
x-text="(a.kind === 'group' || a.shared_with_group_id) ? 'Group' : (a.shared_with_email || a.user_login || '')"
|
||
></div>
|
||
</div>
|
||
</div>
|
||
<button
|
||
class="sd-perm-btn"
|
||
@click="a.permOpen = !a.permOpen"
|
||
>
|
||
<span
|
||
x-text="a.permission === 'edit' ? 'Full access' : a.permission === 'comment' ? 'Can comment' : 'Can view'"
|
||
></span>
|
||
▾
|
||
</button>
|
||
<div
|
||
class="sd-perm-menu"
|
||
x-show="a.permOpen"
|
||
@click.outside="a.permOpen = false"
|
||
>
|
||
<div
|
||
class="sd-perm-option"
|
||
:class="{ active: a.permission === 'edit' }"
|
||
@click="setSharePerm(a, 'edit')"
|
||
>
|
||
<span>Can edit</span>
|
||
<span class="sd-perm-desc"
|
||
>Edit, suggest and comment</span
|
||
>
|
||
</div>
|
||
<div
|
||
class="sd-perm-option"
|
||
:class="{ active: a.permission === 'comment' }"
|
||
@click="setSharePerm(a, 'comment')"
|
||
>
|
||
<span>Can comment</span>
|
||
<span class="sd-perm-desc"
|
||
>Suggest and comment only</span
|
||
>
|
||
</div>
|
||
<div
|
||
class="sd-perm-option"
|
||
:class="{ active: a.permission === 'view' }"
|
||
@click="setSharePerm(a, 'view')"
|
||
>
|
||
<span>Can view</span>
|
||
<span class="sd-perm-desc"
|
||
>Read only</span
|
||
>
|
||
</div>
|
||
<div class="sd-perm-sep"></div>
|
||
<div
|
||
class="sd-perm-option danger"
|
||
@click="removeShareAndClose(a)"
|
||
>
|
||
Remove
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endraw %}
|
||
</template>
|
||
</div>
|
||
|
||
<div class="sd-sep"></div>
|
||
|
||
<!-- General Access -->
|
||
<div class="sd-section">
|
||
<div class="sd-section-title">General access</div>
|
||
<div style="position: relative">
|
||
<button
|
||
class="sd-access-btn"
|
||
@click="accessMenuOpen = !accessMenuOpen"
|
||
>
|
||
<span
|
||
x-text="generalAccess === 'invited' ? 'Only people invited' : 'Anyone with the link'"
|
||
></span>
|
||
<span class="chevron-down">▾</span>
|
||
</button>
|
||
<div
|
||
class="sd-access-menu"
|
||
x-show="accessMenuOpen"
|
||
@click.outside="accessMenuOpen = false"
|
||
x-transition
|
||
>
|
||
<div
|
||
class="sd-perm-option"
|
||
:class="{ active: generalAccess === 'invited' }"
|
||
@click="pickGeneralAccess('invited')"
|
||
>
|
||
<span>{{ fd_icon('lock',14) }} Only people invited</span>
|
||
<span class="sd-perm-desc"
|
||
>People need to be invited</span
|
||
>
|
||
</div>
|
||
<div
|
||
class="sd-perm-option"
|
||
:class="{ active: generalAccess === 'anyone' }"
|
||
@click="pickGeneralAccess('anyone')"
|
||
>
|
||
<span>{{ fd_icon('globe',14) }} Anyone with the link</span>
|
||
<span class="sd-perm-desc"
|
||
>Can view the page</span
|
||
>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Public permission when link is open -->
|
||
<div
|
||
x-show="generalAccess === 'anyone'"
|
||
style="
|
||
margin-top: 6px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
"
|
||
>
|
||
<span
|
||
style="
|
||
font-size: 13px;
|
||
color: var(--text-primary);
|
||
"
|
||
>Anyone with the link</span
|
||
>
|
||
<select class="sd-select-sm" x-model="publicPerm">
|
||
<option value="viewer">Can view</option>
|
||
<option value="commenter">Can comment</option>
|
||
<option value="editor">Can edit</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sd-sep"></div>
|
||
|
||
<!-- v5.5.0: Cover & Icon -->
|
||
<div class="sd-section">
|
||
<div class="sd-section-title">{{ fd_icon('image',14) }} Cover & Icon</div>
|
||
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
||
<button class="sd-btn-secondary" @click="toggleCover()">
|
||
<span x-show="!coverOpen">{{ fd_icon('image',14) }} Cover</span>
|
||
<span x-show="coverOpen">{{ fd_icon('x',14) }} Hide</span>
|
||
</button>
|
||
<button class="sd-btn-secondary" @click="toggleIcon()">
|
||
<span x-show="!iconOpen">{{ fd_icon('sparkles',14) }} Icon</span>
|
||
<span x-show="iconOpen">{{ fd_icon('x',14) }} Hide</span>
|
||
</button>
|
||
</div>
|
||
<div x-show="coverOpen" style="margin-top:12px;" x-transition>
|
||
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:8px;">
|
||
<input type="text" class="sd-input" x-model="coverUrl" placeholder="Cover image URL" style="flex:1;min-width:200px;">
|
||
<button class="sd-btn-primary" @click="setCoverUrl()" :disabled="coverLoading" x-show="!coverLoading">Set</button>
|
||
<button class="sd-btn-primary" :disabled="coverLoading" x-show="coverLoading">Saving…</button>
|
||
<label class="sd-btn-secondary" style="display:flex;align-items:center;gap:6px;">
|
||
{{ fd_icon('upload',14) }} Upload
|
||
<input type="file" accept="image/*" @change="uploadCover($event)" style="display:none;">
|
||
</label>
|
||
<button class="sd-btn-danger" @click="removeCover()" x-show="coverUrl">Remove</button>
|
||
</div>
|
||
<div x-show="coverUrl" style="max-width:320px;border-radius:8px;overflow:hidden;border:1px solid var(--border);">
|
||
<img :src="coverUrl" style="width:100%;height:auto;display:block;">
|
||
</div>
|
||
</div>
|
||
<div x-show="iconOpen" style="margin-top:12px;" x-transition>
|
||
<div style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;">
|
||
<template x-for="(ic, ici) in ['📄','📝','📋','📊','🗂️','📁','📂','🗒️','🗓️','📌','🔖','⭐','🚀','💡','🎯','🔑','📚','🧪','🌍','💰','📝','🧩','🎨','🔧','⚙️','🗃️','📦','🏷️','📍','🏠','👤']" :key="ici">
|
||
<button type="button" class="sd-icon-btn" @click="setIcon(ic)" :class="{ active: iconValue === ic }" style="width:36px;height:36px;border-radius:6px;border:1px solid var(--border);background:var(--bg-secondary);font-size:18px;display:flex;align-items:center;justify-content:center;cursor:pointer;" x-text="ic"></button>
|
||
</template>
|
||
<input type="text" class="sd-input" x-model="iconValue" placeholder="Custom emoji" style="max-width:80px;" @keydown.enter.prevent="applyIcon()">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sd-sep"></div>
|
||
|
||
<!-- Footer -->
|
||
<div class="sd-footer">
|
||
<a href="#" class="sd-footer-link" @click.prevent
|
||
>? Learn about sharing</a
|
||
>
|
||
<button class="sd-footer-action" @click="copyPageLink">
|
||
{{ fd_icon("link",14) }} Copy link
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══════ PUBLISH TAB ═══════ -->
|
||
<div
|
||
class="sd-body"
|
||
x-show="shareTab === 'publish'"
|
||
x-transition
|
||
>
|
||
<!-- Before publishing -->
|
||
<div x-show="!pagePublished" class="sd-publish-hero">
|
||
<div class="sd-publish-preview">
|
||
<div class="sd-preview-bar"></div>
|
||
<div class="sd-preview-title">
|
||
{{ fd_icon('globe',14) }} {{ page.title }}
|
||
</div>
|
||
</div>
|
||
<h3 class="sd-publish-heading">Publish to web</h3>
|
||
<p class="sd-publish-desc">
|
||
Make this page visible to anyone on the internet.
|
||
You can share the link with anyone.
|
||
</p>
|
||
<button
|
||
class="sd-btn-primary sd-btn-full"
|
||
@click="publishPage()"
|
||
>
|
||
Publish
|
||
</button>
|
||
<p class="sd-publish-note">
|
||
Your page will be visible to anyone with the link.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- After publishing -->
|
||
<div x-show="pagePublished">
|
||
<div class="sd-url-bar">
|
||
<span
|
||
style="font-size: 13px; color: var(--text-dim)"
|
||
>{{ workspace_key or 'flowdeck' }}</span
|
||
>
|
||
<input
|
||
type="text"
|
||
class="sd-url-input"
|
||
:value="publishedUrl || pageUrl"
|
||
readonly
|
||
@click="$event.target.select()"
|
||
/>
|
||
<button
|
||
class="sd-btn-primary"
|
||
style="
|
||
height: 32px;
|
||
padding: 0 12px;
|
||
font-size: 12px;
|
||
"
|
||
@click="copyPageLink"
|
||
>
|
||
Copy link
|
||
</button>
|
||
</div>
|
||
|
||
<div class="sd-sep"></div>
|
||
|
||
<!-- Settings list -->
|
||
<div class="sd-settings">
|
||
<div class="sd-setting-row">
|
||
<span>{{ fd_icon("link",14) }} Link expires</span>
|
||
<span style="color: var(--text-dim)"
|
||
>Never ▾</span
|
||
>
|
||
</div>
|
||
<div class="sd-setting-row">
|
||
<span>{{ fd_icon('search',14) }} Search engine indexing</span>
|
||
<span style="color: var(--text-dim)"
|
||
>Off ▾</span
|
||
>
|
||
</div>
|
||
<div class="sd-setting-row">
|
||
<span>{{ fd_icon("file",14) }} Allow duplicate as template</span>
|
||
<label class="toggle-switch sm">
|
||
<input type="checkbox" checked />
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="sd-setting-row">
|
||
<span>{{ fd_icon("edit",14) }} Allow editing</span>
|
||
<label class="toggle-switch sm">
|
||
<input
|
||
type="checkbox"
|
||
x-model="pubAllowEdit"
|
||
/>
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="sd-setting-row">
|
||
<span>{{ fd_icon("message-square",14) }} Allow comments</span>
|
||
<label class="toggle-switch sm">
|
||
<input
|
||
type="checkbox"
|
||
x-model="pubAllowComments"
|
||
checked
|
||
/>
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sd-sep"></div>
|
||
|
||
<button
|
||
class="sd-danger-btn"
|
||
@click="unpublishPage()"
|
||
>
|
||
Unpublish
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══════════ Activity Popover ═══════════ -->
|
||
<div class="activity-popover"
|
||
x-show="activityOpen"
|
||
@click.outside="!_justOpened && (activityOpen = false)"
|
||
x-transition.opacity.scale.origin.top.right
|
||
style="position:absolute;top:100%;right:0;margin-top:4px;background:var(--bg-modal);border:1px solid var(--border);border-radius:var(--radius-lg);box-shadow:var(--shadow-modal);z-index:1000;width:280px;padding:12px;">
|
||
<div style="font-size:12px;color:var(--text-secondary);margin-bottom:4px;">Edited <span x-text="timeAgo"></span></div>
|
||
<template x-if="pageCreated">
|
||
<div style="font-size:12px;color:var(--text-tertiary);">Created <span x-text="formatDate(pageCreated)"></span></div>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- More menu dropdown -->
|
||
<div
|
||
class="more-menu"
|
||
x-show="moreOpen"
|
||
@click.outside="!_justOpened && (moreOpen = false)"
|
||
x-transition
|
||
>
|
||
<div class="more-menu-item" @click="toggleBacklinks">
|
||
{{ fd_icon("link-2",14) }} Linked from
|
||
</div>
|
||
<div class="more-menu-item" @click="toggleVersions">
|
||
{{ fd_icon("history",14) }} Version history
|
||
</div>
|
||
<div class="more-menu-item" @click="toggleImport">
|
||
{{ fd_icon("download",14) }} Import
|
||
</div>
|
||
<div class="more-menu-item" @click="exportOpen = !exportOpen">↗ Export <span style="margin-left:auto;font-size:10px;opacity:.6">▾</span></div>
|
||
<template x-if="exportOpen">
|
||
<div class="more-menu-sub">
|
||
<div class="more-menu-item" @click="exportPage('markdown')"> Markdown (.md)</div>
|
||
<div class="more-menu-item" @click="exportPage('html')"> HTML (.html)</div>
|
||
<div class="more-menu-item" @click="exportPage('pdf')"> PDF (.pdf)</div>
|
||
<div class="more-menu-item" @click="exportPage('site')"> Site statique (.zip)</div>
|
||
</div>
|
||
</template>
|
||
<template x-if="contentFormat === 'file' && fileUrl">
|
||
<div>
|
||
<div class="more-menu-sep"></div>
|
||
<div class="more-menu-item" @click="downloadFile()">
|
||
{{ fd_icon("download",14) }} Download
|
||
</div>
|
||
<div class="more-menu-item" @click="copyFileContent()">
|
||
{{ fd_icon("copy",14) }} Copy content
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<div class="more-menu-item" @click="duplicatePage">
|
||
{{ fd_icon("copy",14) }} Duplicate
|
||
</div>
|
||
<div class="more-menu-item" @click="movePage">↗ Move to</div>
|
||
<div class="more-menu-sep"></div>
|
||
<div class="more-menu-item" @click="moreToggleLock()">
|
||
<span x-text="isLocked ? '🔓 Unlock page' : '🔒 Lock page'"></span>
|
||
</div>
|
||
<div class="more-menu-item" @click="moreFullWidth()">
|
||
↔ Full width <span x-show="fullWidth" style="margin-left:auto;font-size:11px">✓</span>
|
||
</div>
|
||
<div class="more-menu-item" @click="moreFontSmall()">
|
||
Aa Small text <span x-show="fontSmall" style="margin-left:auto;font-size:11px">✓</span>
|
||
</div>
|
||
<div class="more-menu-item" @click="moreSaveTemplate()">
|
||
📑 Save as template
|
||
</div>
|
||
<div class="more-menu-sep"></div>
|
||
<div class="more-menu-item danger" @click="deletePage">
|
||
{{ fd_icon("trash",14) }} Move to Trash
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══════════ Move to Dialog ═══════════ -->
|
||
<div class="move-dialog"
|
||
x-show="moveOpen"
|
||
@click.outside="moveOpen = false"
|
||
x-transition.opacity.scale
|
||
style="position:absolute;top:100%;right:0;margin-top:4px;background:var(--bg-modal);border:1px solid var(--border);border-radius:var(--radius-lg);box-shadow:var(--shadow-modal);z-index:1000;width:320px;padding:16px;">
|
||
<h3 style="font-size:14px;margin-bottom:12px;">Move to workspace</h3>
|
||
<div x-show="moveLoading" style="color:var(--text-tertiary);text-align:center;padding:20px;">Loading...</div>
|
||
<div x-show="!moveLoading" style="max-height:240px;overflow-y:auto;">
|
||
<template x-for="ws in moveWorkspaces" :key="ws.id">
|
||
<div class="move-ws-item"
|
||
@click="doMove(ws.id)"
|
||
style="display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:6px;cursor:pointer;font-size:13px;">
|
||
<span>{{ fd_icon("folder",14) }}</span>
|
||
<span x-text="ws.name" style="flex:1;"></span>
|
||
<span style="font-size:11px;color:var(--text-tertiary);" x-text="ws.page_count + ' pages'"></span>
|
||
</div>
|
||
</template>
|
||
<div x-show="moveWorkspaces.length === 0" style="color:var(--text-tertiary);text-align:center;padding:12px;">
|
||
No workspaces available
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══════════ Page Content ═══════════ -->
|
||
<div class="page-cover-area" x-show="coverUrl" style="position:relative;height:240px;overflow:hidden;">
|
||
<img :src="coverUrl" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0;">
|
||
<div style="position:absolute;inset:0;background:linear-gradient(180deg,rgba(0,0,0,0) 50%,rgba(0,0,0,.6) 100%);z-index:1;"></div>
|
||
</div>
|
||
<div class="page-title-block">
|
||
<span class="page-icon-emoji" @click="openIconPicker()" title="Change icon" x-init="bindIconHtml($el)"></span>
|
||
<div
|
||
class="page-title-input"
|
||
contenteditable="true"
|
||
id="_titleEl"
|
||
data-placeholder="New Page"
|
||
@input="markAndSave()"
|
||
@keydown.enter.prevent="focusBlock()"
|
||
@paste.prevent="pastePlain($event)"
|
||
spellcheck="false"
|
||
>
|
||
{{ page.title }}
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
class="blocks-container"
|
||
id="_blocksCt"
|
||
@click="onCtClick($event)"
|
||
></div>
|
||
<div
|
||
id="_slashMenu"
|
||
class="slash-menu"
|
||
style="display: none; position: fixed; z-index: 999"
|
||
></div>
|
||
|
||
<!-- ═══════════ @mention autocomplete (v4.9.0) ═══════════ -->
|
||
<div id="_mentionMenu" class="mention-menu"
|
||
style="display:none;position:fixed;z-index:1200;min-width:240px;max-height:280px;overflow-y:auto;
|
||
background:var(--bg-modal,#1f1f1f);border:1px solid var(--border,#333);border-radius:10px;
|
||
box-shadow:var(--shadow-modal);padding:6px;"></div>
|
||
|
||
<!-- ═══════════ Comments drawer (v4.9.0 · refonte v7.64 façon Notion) ═══════════ -->
|
||
<div class="comments-drawer" x-show="commentsOpen" x-cloak x-transition
|
||
style="position:fixed;top:var(--topbar-height,44px);right:0;bottom:0;width:340px;max-width:92vw;
|
||
background:var(--bg-primary,#1c1c1c);border-left:1px solid var(--border,#333);
|
||
box-shadow:-8px 0 30px rgba(0,0,0,.25);z-index:900;display:flex;flex-direction:column;">
|
||
<div class="comments-drawer-header"
|
||
style="display:flex;align-items:center;justify-content:space-between;padding:12px 16px;
|
||
border-bottom:1px solid var(--border,#333);font-weight:600;font-size:14px;">
|
||
<span>Comments <span style="color:var(--text-dim);font-weight:400;" x-text="'(' + commentCount + ')'"></span></span>
|
||
<button type="button" class="topbar-btn" @click="toggleComments()" title="Close">✕</button>
|
||
</div>
|
||
<div class="comments-drawer-list" style="flex:1;overflow-y:auto;padding:12px 16px;">
|
||
<!-- v7.67 : les réactions emoji du texte vivent aussi ici (✕ = retirer) -->
|
||
<div class="ct-reactions" x-show="reactionList().length" x-cloak style="margin-bottom:14px;">
|
||
<div class="ct-section-title">Reactions</div>
|
||
<template x-for="r in reactionList()" :key="r.key">
|
||
<div class="ct-reaction">
|
||
<span class="ct-re-emoji" x-text="r.emoji"></span>
|
||
<span class="ct-re-count" x-show="r.count > 1" x-text="r.count"></span>
|
||
<span class="ct-re-text" x-text="r.text"></span>
|
||
<button type="button" class="ct-act" title="Remove reaction"
|
||
x-show="r.mine" @click="removeReaction(r)">✕</button>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
<div x-show="comments.length === 0 && !reactionList().length" style="text-align:center;color:var(--text-dim,#999);padding:32px 0;">
|
||
No comments yet. Select some text and click 💬 Comment.
|
||
</div>
|
||
<template x-for="c in comments" :key="c.id">
|
||
<div class="comment-thread" :data-thread="c.id"
|
||
:class="{ 'is-resolved': c.resolved, 'has-anchor': !!c.anchor_block_id }">
|
||
<div class="ct-head">
|
||
<div class="ct-avatar"
|
||
:style="{ background: c.author && c.author.avatar_color ? c.author.avatar_color : '#3A3A3A' }">
|
||
<span x-text="(c.author ? (c.author.full_name || c.author.login || '?') : '?').charAt(0).toUpperCase()"></span>
|
||
</div>
|
||
<span class="ct-name" x-text="c.author ? (c.author.full_name || c.author.login) : 'Unknown'"></span>
|
||
<span class="ct-time" x-text="fmtTime(c.created_at)"></span>
|
||
<div class="ct-actions">
|
||
<button type="button" class="ct-act" :title="c.resolved ? 'Reopen' : 'Resolve'"
|
||
@click="resolveComment(c)"
|
||
:class="{ 'is-on': c.resolved }">✓</button>
|
||
<button type="button" class="ct-act" title="More"
|
||
x-show="c.user_id === currentUserId"
|
||
@click.stop="commentMenu = commentMenu === c.id ? null : c.id">⋯</button>
|
||
</div>
|
||
<div class="ct-menu" x-cloak x-show="commentMenu === c.id" @click.outside="commentMenu = null">
|
||
<button type="button" class="ct-menu-item" @click="copyCommentLink(c.id)">🔗 Copy link</button>
|
||
<button type="button" class="ct-menu-item is-danger" @click="deleteComment(c.id)">🗑 Delete</button>
|
||
</div>
|
||
</div>
|
||
<div class="ct-body" x-text="c.body"></div>
|
||
<div class="ct-anchor-label" x-show="!!c.anchor_block_id && !c.resolved">💬 on selected text</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
<div class="comments-drawer-input">
|
||
<div class="ct-input-row">
|
||
<textarea x-model="commentDraft" rows="1"
|
||
placeholder="Add a comment..."
|
||
@keydown.enter.exact.prevent="addPageComment()"></textarea>
|
||
<div class="ct-input-actions">
|
||
<button type="button" class="ct-act" title="Mention" @click="commentAt()">@</button>
|
||
<button type="button" class="ct-send" title="Send"
|
||
:disabled="!(commentDraft || '').trim()" @click="addPageComment()">↑</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══════════ Menu contextuel au highlight de texte (v7.62.0, façon Notion) ═══════════ -->
|
||
<div
|
||
class="format-toolbar"
|
||
x-show="fmt.open"
|
||
x-cloak
|
||
:style="{top:fmt.top+'px',left:fmt.left+'px'}"
|
||
@mousedown.prevent
|
||
>
|
||
<!-- Ligne 1 : style de texte -->
|
||
<div class="ft-row">
|
||
<button type="button" class="ft-item ft-style"
|
||
@click="fmtStyle($event)"
|
||
data-tooltip-html="<em>To be the foremost driver…</em><br>Just start writing with plain text">
|
||
<span class="ft-style-ico">T</span>
|
||
<span class="ft-style-name" x-text="fmtStyleName()"></span>
|
||
<span class="ft-caret">›</span>
|
||
</button>
|
||
</div>
|
||
<!-- Ligne 2 : formatage de base -->
|
||
<div class="ft-row">
|
||
<button type="button" class="ft-item ft-a" @click="fmtColor($event)" data-tooltip="Color — text and background colors"><span class="ft-a-letter">A</span></button>
|
||
<button type="button" class="ft-item" @click="fmtApply('bold')" data-tooltip="Bold — Ctrl+B"><strong>B</strong></button>
|
||
<button type="button" class="ft-item" @click="fmtApply('italic')" data-tooltip="Italic — Ctrl+I"><em>I</em></button>
|
||
<button type="button" class="ft-item" @click="fmtApply('underline')" data-tooltip="Underline"><u>U</u></button>
|
||
<button type="button" class="ft-item" @click="fmtApply('strikeThrough')" data-tooltip="Strikethrough"><s>T</s></button>
|
||
<button type="button" class="ft-item ft-tx" @click="fmtCode()" data-tooltip="Inline code">Tx</button>
|
||
</div>
|
||
<!-- Ligne 3 : outils avancés -->
|
||
<div class="ft-row">
|
||
<button type="button" class="ft-item" @click="fmtLink()" data-tooltip="Link">{{ fd_icon("link",15) }}</button>
|
||
<button type="button" class="ft-item" @click="fmtHighlight()" data-tooltip="Highlight"><span class="ft-hl">S</span></button>
|
||
<button type="button" class="ft-item ft-code-ico" @click="fmtCodeBlock()" data-tooltip="Code block"></></button>
|
||
<button type="button" class="ft-item" @click="fmtEquation()" data-tooltip="Equation">√x</button>
|
||
<button type="button" class="ft-item ft-more" @click="fmtMore($event)" data-tooltip="More actions">…</button>
|
||
</div>
|
||
<!-- Ligne 4 : Comment à gauche, réaction à droite (le ✨ Ask AI sauté :
|
||
doublon exact du bouton du pied, même action) -->
|
||
<div class="ft-row ft-row-split">
|
||
<button type="button" class="ft-item ft-comment" @click="fmtComment()" data-tooltip="Comment — Ctrl+⇧+M">{{ fd_icon("message-square",15) }}<span>Comment</span></button>
|
||
<button type="button" class="ft-item ft-react" @click="fmtReact()" data-tooltip="React to selected text"><span class="ft-react-ico">🙂<span class="ft-react-plus">+</span></span></button>
|
||
</div>
|
||
<!-- Section « Skills » -->
|
||
<div class="ft-sep"></div>
|
||
<div class="ft-group ft-skills-head">Skills</div>
|
||
<div class="ft-skills">
|
||
<button type="button" class="ft-item ft-skill" @click="fmtSkill('Improve the writing of this text (wording, clarity, style). Keep the meaning, language and format. Reply ONLY with the improved text.')" data-tooltip="Run on this block">Improve writing</button>
|
||
<button type="button" class="ft-item ft-skill" @click="fmtSkill('Proofread this text: fix spelling and grammar mistakes only. Reply ONLY with the corrected text.')" data-tooltip="Run on this block">Proofread</button>
|
||
<button type="button" class="ft-item ft-skill" @click="fmtSkill('Explain this text in simpler terms, in the same language. Reply ONLY with the explanation.')" data-tooltip="Run on this block">Explain</button>
|
||
<button type="button" class="ft-item ft-skill" @click="fmtSkill('Reformat this text for readability (structure, spacing, lists where useful) without changing its meaning. Reply ONLY with the reformatted text.')" data-tooltip="Run on this block">Reformat</button>
|
||
</div>
|
||
<!-- Pied de page -->
|
||
<div class="ft-sep"></div>
|
||
<button type="button" class="ft-item ft-foot" @click="fmtEditAI()" data-tooltip="Edit with AI">
|
||
<span>Edit with AI</span>
|
||
<kbd>Alt+⇧+E</kbd>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- ═══════════ Copy Link Toast ═══════════ -->
|
||
<div
|
||
class="sd-toast"
|
||
x-show="toastVisible"
|
||
x-transition
|
||
x-text="toastMsg"
|
||
></div>
|
||
|
||
<!-- ═══════════ Get Started Toolbar (bottom) ═══════════ -->
|
||
<div
|
||
class="get-started-toolbar"
|
||
x-show="blocks.length === 1 && blocks[0].type === 'paragraph' && !blocks[0].content.trim()"
|
||
>
|
||
<span class="gs-label">Get started with</span>
|
||
<button
|
||
class="gs-pill"
|
||
@click="askAI()"
|
||
>
|
||
<span class="gs-icon">{{ fd_icon("sparkles",14) }}</span> Ask AI
|
||
</button>
|
||
<button
|
||
class="gs-pill"
|
||
@click="meetingNote()"
|
||
>
|
||
<span class="gs-icon">{{ fd_icon('edit',14) }}</span> AI meeting note
|
||
</button>
|
||
<button
|
||
class="gs-pill"
|
||
@click="createDatabase()"
|
||
>
|
||
<span class="gs-icon">{{ fd_icon("folder",14) }}</span> Database
|
||
</button>
|
||
<button
|
||
class="gs-pill"
|
||
@click="createForm()"
|
||
>
|
||
<span class="gs-icon">{{ fd_icon("copy",14) }}</span> Form
|
||
</button>
|
||
<button
|
||
class="gs-pill"
|
||
@click="gsMoreOpen = !gsMoreOpen"
|
||
>
|
||
<span class="gs-icon">{{ fd_icon("file",14) }}</span> Templates
|
||
</button>
|
||
<div style="position: relative">
|
||
<button class="gs-pill" @click="gsMoreOpen = !gsMoreOpen">
|
||
<span>⋯</span>
|
||
</button>
|
||
<div
|
||
class="gs-more-dropdown"
|
||
x-show="gsMoreOpen"
|
||
@click.outside="gsMoreOpen = false"
|
||
x-transition
|
||
>
|
||
<div class="gs-more-item" @click="gsMoreOpen=false">
|
||
{{ fd_icon("bar-chart",14) }} Table
|
||
</div>
|
||
<div class="gs-more-item" @click="gsMoreOpen=false">
|
||
{{ fd_icon("copy",14) }} Board
|
||
</div>
|
||
<div class="gs-more-item" @click="gsMoreOpen=false">
|
||
{{ fd_icon('list',14) }} List
|
||
</div>
|
||
<div class="gs-more-item" @click="gsMoreOpen=false">
|
||
{{ fd_icon("calendar",14) }} Timeline
|
||
</div>
|
||
<div class="gs-more-item" @click="gsMoreOpen=false">
|
||
{{ fd_icon("calendar",14) }} Calendar
|
||
</div>
|
||
<div class="gs-more-item" @click="gsMoreOpen=false">
|
||
{{ fd_icon("image",14) }} Gallery
|
||
</div>
|
||
<div class="gs-more-sep"></div>
|
||
<div class="gs-more-item" @click="gsMoreOpen=false">
|
||
{{ fd_icon("download",14) }} Import
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="editor-statusbar">
|
||
<span x-show="saving">Saving...</span>
|
||
<span x-show="!saving&&dirty">Unsaved</span>
|
||
<span x-show="!saving&&!dirty&&lastSaved"
|
||
>Saved <span x-text="lastSaved"></span
|
||
></span>
|
||
<span class="statusbar-right" x-text="blocks.length+' blocks'"></span>
|
||
</div>
|
||
|
||
<!-- ═══════════ Database template picker (v5.3.0) ═══════════ -->
|
||
<div class="fd-dbtpl-overlay" id="fd-dbtpl" x-show="dbTplOpen"
|
||
style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:2100;
|
||
align-items:flex-start;justify-content:center;padding-top:10vh;opacity:0;transition:opacity .15s ease;"
|
||
@click.self="closeDbTemplatePicker()">
|
||
<div class="fd-dbtpl" style="width:620px;max-width:94vw;max-height:76vh;display:flex;flex-direction:column;
|
||
background:var(--bg-secondary,#232323);border:1px solid var(--border,rgba(255,255,255,.08));border-radius:12px;
|
||
box-shadow:0 24px 70px rgba(0,0,0,.6);overflow:hidden;">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--border);">
|
||
<h3 style="margin:0;font-size:16px;color:var(--text,#fff);">Create a database</h3>
|
||
<button class="flowdeck-modal-close" @click="closeDbTemplatePicker()">×</button>
|
||
</div>
|
||
<div style="padding:12px 20px;overflow-y:auto;">
|
||
<div x-show="dbTplLoading" style="color:var(--text-dim,#999);font-size:13px;padding:20px 0;text-align:center;">Loading templates…</div>
|
||
<div x-show="!dbTplLoading">
|
||
<!-- Blank -->
|
||
<div class="fd-dbtpl-card" @click="createDbFromTemplate(null)"
|
||
style="display:flex;align-items:center;gap:12px;padding:12px 14px;border:1px solid var(--border);border-radius:10px;margin-bottom:10px;cursor:pointer;">
|
||
<span style="font-size:22px;">📋</span>
|
||
<div><div style="font-weight:600;color:var(--text,#fff);font-size:14px;">Blank database</div>
|
||
<div style="font-size:12px;color:var(--text-dim,#999);">Start from an empty database</div></div>
|
||
</div>
|
||
<template x-for="t in dbTemplates" :key="t.id">
|
||
<div class="fd-dbtpl-card" @click="createDbFromTemplate(t.name)"
|
||
style="display:flex;align-items:center;gap:12px;padding:12px 14px;border:1px solid var(--border);border-radius:10px;margin-bottom:10px;cursor:pointer;">
|
||
<span style="font-size:22px;" x-text="t.icon||'📋'"></span>
|
||
<div style="flex:1;"><div style="font-weight:600;color:var(--text,#fff);font-size:14px;" x-text="t.name"></div>
|
||
<div style="font-size:12px;color:var(--text-dim,#999);" x-text="t.description||''"></div></div>
|
||
</div>
|
||
</template>
|
||
<div x-show="!dbTplLoading && !dbTemplates.length" style="color:var(--text-dim,#999);font-size:13px;padding:16px 0;text-align:center;">
|
||
No templates available
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══════════ v5.4.0: Version History Popover ═══════════ -->
|
||
<div class="versions-popover"
|
||
x-show="versionsOpen"
|
||
@click.outside="versionsOpen = false"
|
||
x-transition.opacity.scale.origin.top.right
|
||
style="position:absolute;top:100%;right:0;margin-top:4px;background:var(--bg-modal);border:1px solid var(--border);border-radius:var(--radius-lg);box-shadow:var(--shadow-modal);z-index:1000;width:380px;max-height:500px;overflow:hidden;">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);font-weight:600;font-size:14px;">
|
||
<span>{{ fd_icon('history',14) }} Version history</span>
|
||
<button class="topbar-btn" @click="versionsOpen = false">✕</button>
|
||
</div>
|
||
<div style="max-height:420px;overflow-y:auto;">
|
||
<div x-show="versionsLoading" style="padding:32px;text-align:center;color:var(--text-dim);">Loading…</div>
|
||
<template x-if="!versionsLoading && versionsList.length === 0">
|
||
<div style="padding:24px;text-align:center;color:var(--text-dim);">No versions yet</div>
|
||
</template>
|
||
<template x-for="v in versionsList" :key="v.id">
|
||
<div class="version-item"
|
||
style="padding:12px 16px;border-bottom:1px solid var(--border);cursor:pointer;"
|
||
@click="restoreVersion(v.id)">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;">
|
||
<div style="font-weight:500;color:var(--text-primary);" x-text="v.note || 'Edited'"></div>
|
||
<span style="font-size:12px;color:var(--text-dim);" x-text="v.author ? v.author + ' · ' : ''"></span>
|
||
</div>
|
||
<div style="font-size:12px;color:var(--text-dim);margin-top:4px;" x-text="formatDate(v.created_at)"></div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══════════ v5.4.0: Backlinks Popover ═══════════ -->
|
||
<div class="backlinks-popover"
|
||
x-show="backlinksOpen"
|
||
@click.outside="backlinksOpen = false"
|
||
x-transition.opacity.scale.origin.top.right
|
||
style="position:absolute;top:100%;right:0;margin-top:4px;background:var(--bg-modal);border:1px solid var(--border);border-radius:var(--radius-lg);box-shadow:var(--shadow-modal);z-index:1000;width:360px;max-height:500px;overflow:hidden;">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);font-weight:600;font-size:14px;">
|
||
<span>{{ fd_icon('link-2',14) }} Linked from</span>
|
||
<button class="topbar-btn" @click="backlinksOpen = false">✕</button>
|
||
</div>
|
||
<div style="max-height:420px;overflow-y:auto;">
|
||
<div x-show="backlinksLoading" style="padding:32px;text-align:center;color:var(--text-dim);">Loading…</div>
|
||
<template x-if="!backlinksLoading && backlinksList.length === 0">
|
||
<div style="padding:24px;text-align:center;color:var(--text-dim);">No pages link here</div>
|
||
</template>
|
||
<template x-for="b in backlinksList" :key="b.id">
|
||
<a :href="'/pages/' + b.id" style="display:block;padding:12px 16px;border-bottom:1px solid var(--border);text-decoration:none;color:inherit;" @click.prevent="openBacklink(b)">
|
||
<div style="font-weight:500;color:var(--text-primary);" x-text="b.title"></div>
|
||
<div style="font-size:12px;color:var(--text-dim);margin-top:2px;" x-text="b.workspace || ''"></div>
|
||
</a>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══════════ v5.4.0: Import Modal ═══════════ -->
|
||
<div class="import-modal"
|
||
x-show="importOpen"
|
||
@click.self="importOpen = false"
|
||
x-transition.opacity
|
||
style="position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:2100;display:flex;align-items:center;justify-content:center;padding:20px;">
|
||
<div class="import-box" style="width:560px;max-width:94vw;max-height:76vh;background:var(--bg-secondary);border:1px solid var(--border);border-radius:12px;box-shadow:0 24px 70px rgba(0,0,0,.6);overflow:hidden;display:flex;flex-direction:column;">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--border);">
|
||
<h3 style="margin:0;font-size:16px;color:var(--text);">{{ fd_icon('download',14) }} Import page</h3>
|
||
<button class="flowdeck-modal-close" @click="importOpen = false">×</button>
|
||
</div>
|
||
<div style="padding:20px;overflow-y:auto;flex:1;">
|
||
<div style="display:flex;flex-direction:column;gap:16px;">
|
||
<!-- Markdown text import -->
|
||
<div>
|
||
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:8px;color:var(--text);">Markdown text</label>
|
||
<textarea x-model="importText" rows="10" placeholder="Paste Markdown here…" style="width:100%;background:var(--bg-primary);border:1px solid var(--border);border-radius:8px;padding:12px;color:var(--text);font-family:var(--font-mono);font-size:13px;resize:vertical;outline:none;box-sizing:border-box;"></textarea>
|
||
<div style="display:flex;gap:8px;margin-top:10px;">
|
||
<button class="sd-btn-primary" @click="doImport()" :disabled="!importText.trim() && !importFile">{{ fd_icon('download',14) }} Import as new page</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="border-top:1px solid var(--border);padding-top:16px;">
|
||
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:8px;color:var(--text);">{{ fd_icon('file',14) }} File (.md, .txt, .zip)</label>
|
||
<input type="file" accept=".md,.markdown,.txt,.zip" @change="handleImportFile($event)" style="width:100%;padding:12px;background:var(--bg-primary);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:13px;box-sizing:border-box;">
|
||
<template x-if="importFile">
|
||
<div style="margin-top:8px;display:flex;align-items:center;gap:12px;color:var(--text-dim);font-size:13px;">
|
||
<span x-text="importFile.name"></span>
|
||
<span x-text="fmtImportSize(importFile)"></span>
|
||
</div>
|
||
</template>
|
||
<div style="display:flex;gap:8px;margin-top:10px;">
|
||
<button class="sd-btn-primary" @click="doImport()" :disabled="!importText.trim() && !importFile">{{ fd_icon('download',14) }} Import file</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div style="padding:12px 20px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;">
|
||
<a href="/import" style="font-size:13px;color:var(--accent);text-decoration:none;">Assistant d'import avancé (Obsidian, Notion, CSV, Excel…) →</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|