Reformat HTML in page editor template
This commit is contained in:
+345
-97
@@ -1,51 +1,84 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %} {% block page_icon %}📄{% endblock %} {% block
|
||||||
{% block page_icon %}📄{% endblock %}
|
page_title %}{{ page.title }}{% endblock %} {% block topbar %}
|
||||||
{% block page_title %}{{ page.title }}{% endblock %}
|
|
||||||
|
|
||||||
{% block topbar %}
|
|
||||||
<!-- Page editor uses its own topbar -->
|
<!-- Page editor uses its own topbar -->
|
||||||
{% endblock %}
|
{% endblock %} {% block content %}
|
||||||
|
<div class="page-editor-wrapper" x-data="editorState()">
|
||||||
{% block content %}
|
|
||||||
<div class="page-editor-wrapper" x-data="editorState()" x-init="loadPage()">
|
|
||||||
|
|
||||||
<!-- ═══════════ Top Action Bar ═══════════ -->
|
<!-- ═══════════ Top Action Bar ═══════════ -->
|
||||||
<div class="page-topbar">
|
<div class="page-topbar">
|
||||||
<div class="page-topbar-right" style="position:relative;">
|
<div class="page-topbar-right" style="position: relative">
|
||||||
<span class="topbar-edited">Edited just now</span>
|
<span class="topbar-edited">Edited just now</span>
|
||||||
<button class="page-action-btn share-btn" @click="shareOpen = !shareOpen">
|
<button
|
||||||
|
class="page-action-btn share-btn"
|
||||||
|
@click="shareOpen = !shareOpen"
|
||||||
|
>
|
||||||
🔒 Share <span class="chevron-down">▾</span>
|
🔒 Share <span class="chevron-down">▾</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="page-action-btn" @click="copyPageLink" title="Copy link">🔗</button>
|
<button
|
||||||
|
class="page-action-btn"
|
||||||
|
@click="copyPageLink"
|
||||||
|
title="Copy link"
|
||||||
|
>
|
||||||
|
🔗
|
||||||
|
</button>
|
||||||
<button class="page-action-btn star-btn" @click="toggleFavorite">
|
<button class="page-action-btn star-btn" @click="toggleFavorite">
|
||||||
<span x-text="favorited ? '⭐' : '☆'" :class="{ starred: favorited }"></span>
|
<span
|
||||||
|
x-text="favorited ? '⭐' : '☆'"
|
||||||
|
:class="{ starred: favorited }"
|
||||||
|
></span>
|
||||||
|
</button>
|
||||||
|
<button class="page-action-btn" @click="moreOpen = !moreOpen">
|
||||||
|
⋮
|
||||||
</button>
|
</button>
|
||||||
<button class="page-action-btn" @click="moreOpen = !moreOpen">⋮</button>
|
|
||||||
|
|
||||||
<!-- ═══════════ Share / Publish Dialog ═══════════ -->
|
<!-- ═══════════ Share / Publish Dialog ═══════════ -->
|
||||||
<div class="share-dialog" x-show="shareOpen" @click.away="shareOpen = false" x-transition.opacity.scale.origin.top.right>
|
<div
|
||||||
|
class="share-dialog"
|
||||||
|
x-show="shareOpen"
|
||||||
|
@click.away="shareOpen = false"
|
||||||
|
x-transition.opacity.scale.origin.top.right
|
||||||
|
>
|
||||||
<!-- Header with tabs -->
|
<!-- Header with tabs -->
|
||||||
<div class="sd-header">
|
<div class="sd-header">
|
||||||
<button class="sd-tab" :class="{ active: shareTab === 'share' }" @click="shareTab = 'share'">Share</button>
|
<button
|
||||||
<button class="sd-tab" :class="{ active: shareTab === 'publish' }" @click="shareTab = 'publish'">
|
class="sd-tab"
|
||||||
|
:class="{ active: shareTab === 'share' }"
|
||||||
|
@click="shareTab = 'share'"
|
||||||
|
>
|
||||||
|
Share
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="sd-tab"
|
||||||
|
:class="{ active: shareTab === 'publish' }"
|
||||||
|
@click="shareTab = 'publish'"
|
||||||
|
>
|
||||||
Publish
|
Publish
|
||||||
<span class="sd-badge" x-show="pagePublished"></span>
|
<span class="sd-badge" x-show="pagePublished"></span>
|
||||||
</button>
|
</button>
|
||||||
<button class="sd-close" @click="shareOpen = false">✕</button>
|
<button class="sd-close" @click="shareOpen = false">
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ═══════ SHARE TAB ═══════ -->
|
<!-- ═══════ SHARE TAB ═══════ -->
|
||||||
<div class="sd-body" x-show="shareTab === 'share'" x-transition>
|
<div class="sd-body" x-show="shareTab === 'share'" x-transition>
|
||||||
|
|
||||||
<!-- Invite input -->
|
<!-- Invite input -->
|
||||||
<div class="sd-invite-row">
|
<div class="sd-invite-row">
|
||||||
<input type="email" class="sd-input" placeholder="Add people, groups or emails..." x-model="inviteEmail" @keydown.enter="invitePerson">
|
<input
|
||||||
<button class="sd-btn-primary" @click="invitePerson">Invite</button>
|
type="email"
|
||||||
|
class="sd-input"
|
||||||
|
placeholder="Add people, groups or emails..."
|
||||||
|
x-model="inviteEmail"
|
||||||
|
@keydown.enter="invitePerson"
|
||||||
|
/>
|
||||||
|
<button class="sd-btn-primary" @click="invitePerson">
|
||||||
|
Invite
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Context card (shown when independent permissions) -->
|
<!-- Context card (shown when independent permissions) -->
|
||||||
<div class="sd-context-card" x-show="false">
|
<div class="sd-context-card" x-show="false">
|
||||||
Share settings on this page are unlinked from parent page
|
Share settings on this page are unlinked from parent
|
||||||
|
page
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Participants list -->
|
<!-- Participants list -->
|
||||||
@@ -54,30 +87,72 @@
|
|||||||
{% raw %}
|
{% raw %}
|
||||||
<div class="sd-participant">
|
<div class="sd-participant">
|
||||||
<div class="sd-participant-info">
|
<div class="sd-participant-info">
|
||||||
<div class="sd-avatar" x-text="a.email[0].toUpperCase()"></div>
|
<div
|
||||||
|
class="sd-avatar"
|
||||||
|
x-text="a.email[0].toUpperCase()"
|
||||||
|
></div>
|
||||||
<div>
|
<div>
|
||||||
<div class="sd-participant-name" x-text="a.email"></div>
|
<div
|
||||||
<div class="sd-participant-email" x-text="a.email"></div>
|
class="sd-participant-name"
|
||||||
|
x-text="a.email"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="sd-participant-email"
|
||||||
|
x-text="a.email"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="sd-perm-btn" @click="a.permOpen = !a.permOpen">
|
<button
|
||||||
<span x-text="a.permission === 'editor' ? 'Full access' : a.permission === 'commenter' ? 'Can comment' : 'Can view'"></span> ▾
|
class="sd-perm-btn"
|
||||||
|
@click="a.permOpen = !a.permOpen"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
x-text="a.permission === 'editor' ? 'Full access' : a.permission === 'commenter' ? 'Can comment' : 'Can view'"
|
||||||
|
></span>
|
||||||
|
▾
|
||||||
</button>
|
</button>
|
||||||
<div class="sd-perm-menu" x-show="a.permOpen" @click.away="a.permOpen = false">
|
<div
|
||||||
<div class="sd-perm-option" :class="{ active: a.permission === 'editor' }" @click="a.permission='editor'; a.permOpen=false">
|
class="sd-perm-menu"
|
||||||
|
x-show="a.permOpen"
|
||||||
|
@click.away="a.permOpen = false"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="sd-perm-option"
|
||||||
|
:class="{ active: a.permission === 'editor' }"
|
||||||
|
@click="a.permission='editor'; a.permOpen=false"
|
||||||
|
>
|
||||||
<span>Can edit</span>
|
<span>Can edit</span>
|
||||||
<span class="sd-perm-desc">Edit, suggest and comment</span>
|
<span class="sd-perm-desc"
|
||||||
|
>Edit, suggest and comment</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="sd-perm-option" :class="{ active: a.permission === 'commenter' }" @click="a.permission='commenter'; a.permOpen=false">
|
<div
|
||||||
|
class="sd-perm-option"
|
||||||
|
:class="{ active: a.permission === 'commenter' }"
|
||||||
|
@click="a.permission='commenter'; a.permOpen=false"
|
||||||
|
>
|
||||||
<span>Can comment</span>
|
<span>Can comment</span>
|
||||||
<span class="sd-perm-desc">Suggest and comment only</span>
|
<span class="sd-perm-desc"
|
||||||
|
>Suggest and comment only</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="sd-perm-option" :class="{ active: a.permission === 'viewer' }" @click="a.permission='viewer'; a.permOpen=false">
|
<div
|
||||||
|
class="sd-perm-option"
|
||||||
|
:class="{ active: a.permission === 'viewer' }"
|
||||||
|
@click="a.permission='viewer'; a.permOpen=false"
|
||||||
|
>
|
||||||
<span>Can view</span>
|
<span>Can view</span>
|
||||||
<span class="sd-perm-desc">Read only</span>
|
<span class="sd-perm-desc"
|
||||||
|
>Read only</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="sd-perm-sep"></div>
|
<div class="sd-perm-sep"></div>
|
||||||
<div class="sd-perm-option danger" @click="removeAccess(a.email); a.permOpen=false">Remove</div>
|
<div
|
||||||
|
class="sd-perm-option danger"
|
||||||
|
@click="removeAccess(a.email); a.permOpen=false"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endraw %}
|
{% endraw %}
|
||||||
@@ -89,25 +164,61 @@
|
|||||||
<!-- General Access -->
|
<!-- General Access -->
|
||||||
<div class="sd-section">
|
<div class="sd-section">
|
||||||
<div class="sd-section-title">General access</div>
|
<div class="sd-section-title">General access</div>
|
||||||
<div style="position:relative;">
|
<div style="position: relative">
|
||||||
<button class="sd-access-btn" @click="accessMenuOpen = !accessMenuOpen">
|
<button
|
||||||
<span x-text="generalAccess === 'invited' ? '🔒 Only people invited' : '🌐 Anyone with the link'"></span>
|
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>
|
<span class="chevron-down">▾</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="sd-access-menu" x-show="accessMenuOpen" @click.away="accessMenuOpen = false" x-transition>
|
<div
|
||||||
<div class="sd-perm-option" :class="{ active: generalAccess === 'invited' }" @click="generalAccess='invited'; accessMenuOpen=false">
|
class="sd-access-menu"
|
||||||
|
x-show="accessMenuOpen"
|
||||||
|
@click.away="accessMenuOpen = false"
|
||||||
|
x-transition
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="sd-perm-option"
|
||||||
|
:class="{ active: generalAccess === 'invited' }"
|
||||||
|
@click="generalAccess='invited'; accessMenuOpen=false"
|
||||||
|
>
|
||||||
<span>🔒 Only people invited</span>
|
<span>🔒 Only people invited</span>
|
||||||
<span class="sd-perm-desc">People need to be invited</span>
|
<span class="sd-perm-desc"
|
||||||
|
>People need to be invited</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="sd-perm-option" :class="{ active: generalAccess === 'anyone' }" @click="generalAccess='anyone'; accessMenuOpen=false">
|
<div
|
||||||
|
class="sd-perm-option"
|
||||||
|
:class="{ active: generalAccess === 'anyone' }"
|
||||||
|
@click="generalAccess='anyone'; accessMenuOpen=false"
|
||||||
|
>
|
||||||
<span>🌐 Anyone with the link</span>
|
<span>🌐 Anyone with the link</span>
|
||||||
<span class="sd-perm-desc">Can view the page</span>
|
<span class="sd-perm-desc"
|
||||||
|
>Can view the page</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Public permission when link is open -->
|
<!-- Public permission when link is open -->
|
||||||
<div x-show="generalAccess === 'anyone'" style="margin-top:6px;display:flex;align-items:center;gap:8px;">
|
<div
|
||||||
<span style="font-size:13px;color:var(--text-primary);">Anyone with the link</span>
|
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">
|
<select class="sd-select-sm" x-model="publicPerm">
|
||||||
<option value="viewer">Can view</option>
|
<option value="viewer">Can view</option>
|
||||||
<option value="commenter">Can comment</option>
|
<option value="commenter">Can comment</option>
|
||||||
@@ -120,32 +231,70 @@
|
|||||||
|
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<div class="sd-footer">
|
<div class="sd-footer">
|
||||||
<a href="#" class="sd-footer-link" @click.prevent>? Learn about sharing</a>
|
<a href="#" class="sd-footer-link" @click.prevent
|
||||||
<button class="sd-footer-action" @click="copyPageLink">🔗 Copy link</button>
|
>? Learn about sharing</a
|
||||||
|
>
|
||||||
|
<button class="sd-footer-action" @click="copyPageLink">
|
||||||
|
🔗 Copy link
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ═══════ PUBLISH TAB ═══════ -->
|
<!-- ═══════ PUBLISH TAB ═══════ -->
|
||||||
<div class="sd-body" x-show="shareTab === 'publish'" x-transition>
|
<div
|
||||||
|
class="sd-body"
|
||||||
|
x-show="shareTab === 'publish'"
|
||||||
|
x-transition
|
||||||
|
>
|
||||||
<!-- Before publishing -->
|
<!-- Before publishing -->
|
||||||
<div x-show="!pagePublished" class="sd-publish-hero">
|
<div x-show="!pagePublished" class="sd-publish-hero">
|
||||||
<div class="sd-publish-preview">
|
<div class="sd-publish-preview">
|
||||||
<div class="sd-preview-bar"></div>
|
<div class="sd-preview-bar"></div>
|
||||||
<div class="sd-preview-title">🌐 {{ page.title }}</div>
|
<div class="sd-preview-title">
|
||||||
|
🌐 {{ page.title }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="sd-publish-heading">Publish to web</h3>
|
<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>
|
<p class="sd-publish-desc">
|
||||||
<button class="sd-btn-primary sd-btn-full" @click="pagePublished = true">Publish</button>
|
Make this page visible to anyone on the internet.
|
||||||
<p class="sd-publish-note">Your page will be visible to anyone with the link.</p>
|
You can share the link with anyone.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
class="sd-btn-primary sd-btn-full"
|
||||||
|
@click="pagePublished = true"
|
||||||
|
>
|
||||||
|
Publish
|
||||||
|
</button>
|
||||||
|
<p class="sd-publish-note">
|
||||||
|
Your page will be visible to anyone with the link.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- After publishing -->
|
<!-- After publishing -->
|
||||||
<div x-show="pagePublished">
|
<div x-show="pagePublished">
|
||||||
<div class="sd-url-bar">
|
<div class="sd-url-bar">
|
||||||
<span style="font-size:13px;color:var(--text-dim);">{{ workspace_key or 'flowdeck' }}</span>
|
<span
|
||||||
<input type="text" class="sd-url-input" :value="publishedUrl || pageUrl" readonly @click="$event.target.select()">
|
style="font-size: 13px; color: var(--text-dim)"
|
||||||
<button class="sd-btn-primary" style="height:32px;padding:0 12px;font-size:12px;" @click="copyPageLink">Copy link</button>
|
>{{ 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>
|
||||||
|
|
||||||
<div class="sd-sep"></div>
|
<div class="sd-sep"></div>
|
||||||
@@ -154,30 +303,41 @@
|
|||||||
<div class="sd-settings">
|
<div class="sd-settings">
|
||||||
<div class="sd-setting-row">
|
<div class="sd-setting-row">
|
||||||
<span>🔗 Link expires</span>
|
<span>🔗 Link expires</span>
|
||||||
<span style="color:var(--text-dim);">Never ▾</span>
|
<span style="color: var(--text-dim)"
|
||||||
|
>Never ▾</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="sd-setting-row">
|
<div class="sd-setting-row">
|
||||||
<span>🔍 Search engine indexing</span>
|
<span>🔍 Search engine indexing</span>
|
||||||
<span style="color:var(--text-dim);">Off ▾</span>
|
<span style="color: var(--text-dim)"
|
||||||
|
>Off ▾</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="sd-setting-row">
|
<div class="sd-setting-row">
|
||||||
<span>📄 Allow duplicate as template</span>
|
<span>📄 Allow duplicate as template</span>
|
||||||
<label class="toggle-switch sm">
|
<label class="toggle-switch sm">
|
||||||
<input type="checkbox" checked>
|
<input type="checkbox" checked />
|
||||||
<span class="toggle-slider"></span>
|
<span class="toggle-slider"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="sd-setting-row">
|
<div class="sd-setting-row">
|
||||||
<span>✏️ Allow editing</span>
|
<span>✏️ Allow editing</span>
|
||||||
<label class="toggle-switch sm">
|
<label class="toggle-switch sm">
|
||||||
<input type="checkbox" x-model="pubAllowEdit">
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
x-model="pubAllowEdit"
|
||||||
|
/>
|
||||||
<span class="toggle-slider"></span>
|
<span class="toggle-slider"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="sd-setting-row">
|
<div class="sd-setting-row">
|
||||||
<span>💬 Allow comments</span>
|
<span>💬 Allow comments</span>
|
||||||
<label class="toggle-switch sm">
|
<label class="toggle-switch sm">
|
||||||
<input type="checkbox" x-model="pubAllowComments" checked>
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
x-model="pubAllowComments"
|
||||||
|
checked
|
||||||
|
/>
|
||||||
<span class="toggle-slider"></span>
|
<span class="toggle-slider"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -185,18 +345,32 @@
|
|||||||
|
|
||||||
<div class="sd-sep"></div>
|
<div class="sd-sep"></div>
|
||||||
|
|
||||||
<button class="sd-danger-btn" @click="pagePublished = false">Unpublish</button>
|
<button
|
||||||
|
class="sd-danger-btn"
|
||||||
|
@click="pagePublished = false"
|
||||||
|
>
|
||||||
|
Unpublish
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- More menu dropdown -->
|
<!-- More menu dropdown -->
|
||||||
<div class="more-menu" x-show="moreOpen" @click.away="moreOpen = false" x-transition>
|
<div
|
||||||
|
class="more-menu"
|
||||||
|
x-show="moreOpen"
|
||||||
|
@click.away="moreOpen = false"
|
||||||
|
x-transition
|
||||||
|
>
|
||||||
<div class="more-menu-item" @click="exportPage">↗ Export</div>
|
<div class="more-menu-item" @click="exportPage">↗ Export</div>
|
||||||
<div class="more-menu-item" @click="duplicatePage">📋 Duplicate</div>
|
<div class="more-menu-item" @click="duplicatePage">
|
||||||
|
📋 Duplicate
|
||||||
|
</div>
|
||||||
<div class="more-menu-item" @click="movePage">↗ Move to</div>
|
<div class="more-menu-item" @click="movePage">↗ Move to</div>
|
||||||
<div class="more-menu-sep"></div>
|
<div class="more-menu-sep"></div>
|
||||||
<div class="more-menu-item danger" @click="deletePage">🗑️ Move to Trash</div>
|
<div class="more-menu-item danger" @click="deletePage">
|
||||||
|
🗑️ Move to Trash
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -205,18 +379,37 @@
|
|||||||
<div class="page-cover-area">
|
<div class="page-cover-area">
|
||||||
<div class="page-title-block">
|
<div class="page-title-block">
|
||||||
<span class="page-icon-emoji">📄</span>
|
<span class="page-icon-emoji">📄</span>
|
||||||
<div class="page-title-input" contenteditable="true" id="_titleEl"
|
<div
|
||||||
|
class="page-title-input"
|
||||||
|
contenteditable="true"
|
||||||
|
id="_titleEl"
|
||||||
@input="dirty=true;save()"
|
@input="dirty=true;save()"
|
||||||
@keydown.enter.prevent="focusBlock()"
|
@keydown.enter.prevent="focusBlock()"
|
||||||
@paste.prevent="pastePlain($event)"
|
@paste.prevent="pastePlain($event)"
|
||||||
spellcheck="false">{{ page.title }}</div>
|
spellcheck="false"
|
||||||
|
>
|
||||||
|
{{ page.title }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="blocks-container" id="_blocksCt" @click="onCtClick($event)"></div>
|
<div
|
||||||
<div id="_slashMenu" class="slash-menu" style="display:none;position:fixed;z-index:999"></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>
|
||||||
|
|
||||||
<div class="format-toolbar" x-show="fmt.open" :style="{top:fmt.top+'px',left:fmt.left+'px'}" @mousedown.prevent>
|
<div
|
||||||
|
class="format-toolbar"
|
||||||
|
x-show="fmt.open"
|
||||||
|
:style="{top:fmt.top+'px',left:fmt.left+'px'}"
|
||||||
|
@mousedown.prevent
|
||||||
|
>
|
||||||
<button @click="fmtApply('bold')"><strong>B</strong></button>
|
<button @click="fmtApply('bold')"><strong>B</strong></button>
|
||||||
<button @click="fmtApply('italic')"><em>I</em></button>
|
<button @click="fmtApply('italic')"><em>I</em></button>
|
||||||
<button @click="fmtApply('underline')"><u>U</u></button>
|
<button @click="fmtApply('underline')"><u>U</u></button>
|
||||||
@@ -225,27 +418,81 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ═══════════ Copy Link Toast ═══════════ -->
|
<!-- ═══════════ Copy Link Toast ═══════════ -->
|
||||||
<div class="sd-toast" x-show="toastVisible" x-transition x-text="toastMsg"></div>
|
<div
|
||||||
|
class="sd-toast"
|
||||||
|
x-show="toastVisible"
|
||||||
|
x-transition
|
||||||
|
x-text="toastMsg"
|
||||||
|
></div>
|
||||||
|
|
||||||
<!-- ═══════════ Get Started Toolbar (bottom) ═══════════ -->
|
<!-- ═══════════ Get Started Toolbar (bottom) ═══════════ -->
|
||||||
<div class="get-started-toolbar" x-show="blocks.length === 1 && blocks[0].type === 'paragraph' && !blocks[0].content.trim()">
|
<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>
|
<span class="gs-label">Get started with</span>
|
||||||
<button class="gs-pill" @click="replaceBlock(0, 'paragraph');blocks[0].content='Ask AI to write...';render()"><span class="gs-icon">✨</span> Ask AI</button>
|
<button
|
||||||
<button class="gs-pill" @click="replaceBlock(0, 'heading_1');blocks[0].content='AI Meeting Note';render()"><span class="gs-icon">📝</span> AI meeting note</button>
|
class="gs-pill"
|
||||||
<button class="gs-pill" @click="replaceBlock(0, 'paragraph');blocks[0].content='Database';render()"><span class="gs-icon">🗄️</span> Database</button>
|
@click="replaceBlock(0, 'paragraph');blocks[0].content='Ask AI to write...';render()"
|
||||||
<button class="gs-pill" @click="replaceBlock(0, 'paragraph');blocks[0].content='Form';render()"><span class="gs-icon">📋</span> Form</button>
|
>
|
||||||
<button class="gs-pill" @click="replaceBlock(0, 'paragraph');blocks[0].content='Templates';render()"><span class="gs-icon">📑</span> Templates</button>
|
<span class="gs-icon">✨</span> Ask AI
|
||||||
<div style="position:relative;">
|
</button>
|
||||||
<button class="gs-pill" @click="gsMoreOpen = !gsMoreOpen"><span>⋯</span></button>
|
<button
|
||||||
<div class="gs-more-dropdown" x-show="gsMoreOpen" @click.away="gsMoreOpen = false" x-transition>
|
class="gs-pill"
|
||||||
<div class="gs-more-item" @click="gsMoreOpen=false">📊 Table</div>
|
@click="replaceBlock(0, 'heading_1');blocks[0].content='AI Meeting Note';render()"
|
||||||
<div class="gs-more-item" @click="gsMoreOpen=false">📋 Board</div>
|
>
|
||||||
<div class="gs-more-item" @click="gsMoreOpen=false">📝 List</div>
|
<span class="gs-icon">📝</span> AI meeting note
|
||||||
<div class="gs-more-item" @click="gsMoreOpen=false">📅 Timeline</div>
|
</button>
|
||||||
<div class="gs-more-item" @click="gsMoreOpen=false">🗓️ Calendar</div>
|
<button
|
||||||
<div class="gs-more-item" @click="gsMoreOpen=false">🖼️ Gallery</div>
|
class="gs-pill"
|
||||||
|
@click="replaceBlock(0, 'paragraph');blocks[0].content='Database';render()"
|
||||||
|
>
|
||||||
|
<span class="gs-icon">🗄️</span> Database
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="gs-pill"
|
||||||
|
@click="replaceBlock(0, 'paragraph');blocks[0].content='Form';render()"
|
||||||
|
>
|
||||||
|
<span class="gs-icon">📋</span> Form
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="gs-pill"
|
||||||
|
@click="replaceBlock(0, 'paragraph');blocks[0].content='Templates';render()"
|
||||||
|
>
|
||||||
|
<span class="gs-icon">📑</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.away="gsMoreOpen = false"
|
||||||
|
x-transition
|
||||||
|
>
|
||||||
|
<div class="gs-more-item" @click="gsMoreOpen=false">
|
||||||
|
📊 Table
|
||||||
|
</div>
|
||||||
|
<div class="gs-more-item" @click="gsMoreOpen=false">
|
||||||
|
📋 Board
|
||||||
|
</div>
|
||||||
|
<div class="gs-more-item" @click="gsMoreOpen=false">
|
||||||
|
📝 List
|
||||||
|
</div>
|
||||||
|
<div class="gs-more-item" @click="gsMoreOpen=false">
|
||||||
|
📅 Timeline
|
||||||
|
</div>
|
||||||
|
<div class="gs-more-item" @click="gsMoreOpen=false">
|
||||||
|
🗓️ Calendar
|
||||||
|
</div>
|
||||||
|
<div class="gs-more-item" @click="gsMoreOpen=false">
|
||||||
|
🖼️ Gallery
|
||||||
|
</div>
|
||||||
<div class="gs-more-sep"></div>
|
<div class="gs-more-sep"></div>
|
||||||
<div class="gs-more-item" @click="gsMoreOpen=false">📥 Import</div>
|
<div class="gs-more-item" @click="gsMoreOpen=false">
|
||||||
|
📥 Import
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -253,15 +500,16 @@
|
|||||||
<div class="editor-statusbar">
|
<div class="editor-statusbar">
|
||||||
<span x-show="saving">Saving...</span>
|
<span x-show="saving">Saving...</span>
|
||||||
<span x-show="!saving&&dirty">Unsaved</span>
|
<span x-show="!saving&&dirty">Unsaved</span>
|
||||||
<span x-show="!saving&&!dirty&&lastSaved">Saved <span x-text="lastSaved"></span></span>
|
<span x-show="!saving&&!dirty&&lastSaved"
|
||||||
|
>Saved <span x-text="lastSaved"></span
|
||||||
|
></span>
|
||||||
<span class="statusbar-right" x-text="blocks.length+' blocks'"></span>
|
<span class="statusbar-right" x-text="blocks.length+' blocks'"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %} {% block scripts %}
|
||||||
|
<script type="application/json" id="page-data">
|
||||||
{% block scripts %}
|
{{ page_data | tojson }}
|
||||||
<script type="application/json" id="page-data">{{ page_data | tojson }}</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
const CMDS=[{name:'BASIC',items:[
|
const CMDS=[{name:'BASIC',items:[
|
||||||
{id:'paragraph',name:'Text',icon:'¶'},{id:'heading_1',name:'Heading 1',icon:'H1'},{id:'heading_2',name:'Heading 2',icon:'H2'},
|
{id:'paragraph',name:'Text',icon:'¶'},{id:'heading_1',name:'Heading 1',icon:'H1'},{id:'heading_2',name:'Heading 2',icon:'H2'},
|
||||||
@@ -342,11 +590,12 @@ function editorState(){
|
|||||||
pageUrl:window.location.href,publishedUrl:'',
|
pageUrl:window.location.href,publishedUrl:'',
|
||||||
inviteEmail:'',invitePermission:'editor',accessList:[],
|
inviteEmail:'',invitePermission:'editor',accessList:[],
|
||||||
toastVisible:false,toastMsg:'',
|
toastVisible:false,toastMsg:'',
|
||||||
|
|
||||||
init(){
|
init(){
|
||||||
_bid=Math.floor(Math.random()*10000);
|
_bid=Math.floor(Math.random()*10000);
|
||||||
|
const dataEl=document.getElementById('page-data');
|
||||||
|
if(dataEl){try{const data=JSON.parse(dataEl.textContent);this.pid=data.id;this.pageTitle=data.title||'';this.favorited=data.favorited||false;const fmt=data.content_format||'blocks';const raw=data.content||'';
|
||||||
if(fmt==='blocks'&&raw){try{this.blocks=JSON.parse(raw);this.blocks.forEach(b=>{if(!b.id)b.id=genId()});}catch(e){this.blocks=[];}}
|
if(fmt==='blocks'&&raw){try{this.blocks=JSON.parse(raw);this.blocks.forEach(b=>{if(!b.id)b.id=genId()});}catch(e){this.blocks=[];}}
|
||||||
else if(raw&&raw.trim())this.blocks=this.md2b(raw);
|
else if(raw&&raw.trim())this.blocks=this.md2b(raw);}catch(e){}}
|
||||||
if(!this.blocks.length)this.blocks=[this.mkB('paragraph','')];
|
if(!this.blocks.length)this.blocks=[this.mkB('paragraph','')];
|
||||||
window.E=this;SM.init();this.render();
|
window.E=this;SM.init();this.render();
|
||||||
setTimeout(()=>{this.focusBlock();const ct=document.getElementById('_blocksCt');if(!ct)return;
|
setTimeout(()=>{this.focusBlock();const ct=document.getElementById('_blocksCt');if(!ct)return;
|
||||||
@@ -478,7 +727,6 @@ function editorState(){
|
|||||||
},
|
},
|
||||||
md2b(md){const bl=[];let cb=null;for(const line of md.split('\n')){if(cb){if(line.trim()==='```'){bl.push(cb);cb=null;}else cb.content+=(cb.content?'\n':'')+line;continue;}const t=line.trim();if(!t)continue;if(t.startsWith('```')){cb=this.mkB('code','',{language:t.substring(3).trim()||'Plain Text'});continue;}if(t.startsWith('# ')&&!t.startsWith('## '))bl.push(this.mkB('heading_1',t.substring(2)));else if(t.startsWith('## ')&&!t.startsWith('### '))bl.push(this.mkB('heading_2',t.substring(3)));else if(t.startsWith('### ')&&!t.startsWith('#### '))bl.push(this.mkB('heading_3',t.substring(4)));else if(t.startsWith('#### '))bl.push(this.mkB('heading_4',t.substring(5)));else if(t==='---'||t==='***')bl.push(this.mkB('divider',''));else if(/^[-*+] /.test(t))bl.push(this.mkB('bulleted_list',t.substring(2)));else if(/^\d+\. /.test(t))bl.push(this.mkB('numbered_list',t.replace(/^\d+\. /,'')));else if(t.startsWith('- [ ] ')||t.startsWith('* [ ] '))bl.push(this.mkB('to_do',t.substring(6)));else if(t.startsWith('- [x] ')||t.startsWith('* [x] '))bl.push(this.mkB('to_do',t.substring(6),{checked:true}));else if(t.startsWith('> '))bl.push(this.mkB('quote',t.substring(2)));else bl.push(this.mkB('paragraph',t));}if(cb)bl.push(cb);return bl;},
|
md2b(md){const bl=[];let cb=null;for(const line of md.split('\n')){if(cb){if(line.trim()==='```'){bl.push(cb);cb=null;}else cb.content+=(cb.content?'\n':'')+line;continue;}const t=line.trim();if(!t)continue;if(t.startsWith('```')){cb=this.mkB('code','',{language:t.substring(3).trim()||'Plain Text'});continue;}if(t.startsWith('# ')&&!t.startsWith('## '))bl.push(this.mkB('heading_1',t.substring(2)));else if(t.startsWith('## ')&&!t.startsWith('### '))bl.push(this.mkB('heading_2',t.substring(3)));else if(t.startsWith('### ')&&!t.startsWith('#### '))bl.push(this.mkB('heading_3',t.substring(4)));else if(t.startsWith('#### '))bl.push(this.mkB('heading_4',t.substring(5)));else if(t==='---'||t==='***')bl.push(this.mkB('divider',''));else if(/^[-*+] /.test(t))bl.push(this.mkB('bulleted_list',t.substring(2)));else if(/^\d+\. /.test(t))bl.push(this.mkB('numbered_list',t.replace(/^\d+\. /,'')));else if(t.startsWith('- [ ] ')||t.startsWith('* [ ] '))bl.push(this.mkB('to_do',t.substring(6)));else if(t.startsWith('- [x] ')||t.startsWith('* [x] '))bl.push(this.mkB('to_do',t.substring(6),{checked:true}));else if(t.startsWith('> '))bl.push(this.mkB('quote',t.substring(2)));else bl.push(this.mkB('paragraph',t));}if(cb)bl.push(cb);return bl;},
|
||||||
};
|
};
|
||||||
return self;
|
|
||||||
}
|
}
|
||||||
document.addEventListener('mouseup',()=>{setTimeout(()=>{const s=window.getSelection();if(!s||s.isCollapsed)return;const ed=document.querySelector('.page-editor-wrapper');if(!ed?.__x)return;const d=ed.__x.$data;const ct=document.getElementById('_blocksCt');if(!ct?.contains(s.anchorNode))return;const bel=s.anchorNode.parentElement?.closest('[data-bid]');if(bel){const idx=d.getIdx(bel.dataset.bid);if(idx>=0)d.showFmt(idx);}},80);});
|
document.addEventListener('mouseup',()=>{setTimeout(()=>{const s=window.getSelection();if(!s||s.isCollapsed)return;const ed=document.querySelector('.page-editor-wrapper');if(!ed?.__x)return;const d=ed.__x.$data;const ct=document.getElementById('_blocksCt');if(!ct?.contains(s.anchorNode))return;const bel=s.anchorNode.parentElement?.closest('[data-bid]');if(bel){const idx=d.getIdx(bel.dataset.bid);if(idx>=0)d.showFmt(idx);}},80);});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user