Browse Source

Editor: reshuffle

imwald
Nuša Pukšič 2 weeks ago
parent
commit
5ae23365c6
  1. 35
      assets/controllers/editor/header_controller.js
  2. 35
      assets/controllers/editor/json-panel_controller.js
  3. 50
      assets/controllers/editor/layout_controller.js
  4. 124
      assets/controllers/nostr/nostr_publish_controller.js
  5. 3
      assets/styles/03-components/form.css
  6. 2
      assets/styles/05-utilities/utilities.css
  7. 103
      templates/editor/layout.html.twig
  8. 90
      templates/editor/panels/_articlelist.html.twig
  9. 35
      templates/editor/panels/_json.html.twig
  10. 2
      templates/editor/panels/_metadata.html.twig

35
assets/controllers/editor/header_controller.js

@ -2,19 +2,42 @@ import { Controller } from "@hotwired/stimulus"; @@ -2,19 +2,42 @@ import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
connect() {
// For debug
// console.log("Header controller connected");
console.log("Header controller connected");
}
saveDraft(event) {
event.preventDefault();
const btn = document.querySelector('[data-editor--layout-target="saveDraftSubmit"]');
if (btn) btn.click();
// Set isDraft to true
const draftCheckbox = document.querySelector('input[name*="[isDraft]"]');
if (draftCheckbox) {
draftCheckbox.checked = true;
} else {
console.warn('[Header] Draft checkbox not found');
}
// Trigger click on the hidden Nostr publish button
const publishButton = document.querySelector('[data-nostr--nostr-publish-target="publishButton"]');
if (publishButton) {
publishButton.click();
} else {
console.error('[Header] Hidden publish button not found');
}
}
publish(event) {
event.preventDefault();
const btn = document.querySelector('[data-editor--layout-target="publishSubmit"]');
if (btn) btn.click();
// Set isDraft to false
const draftCheckbox = document.querySelector('input[name*="[isDraft]"]');
if (draftCheckbox) {
draftCheckbox.checked = false;
} else {
console.warn('[Header] Draft checkbox not found');
}
// Trigger click on the hidden Nostr publish button
const publishButton = document.querySelector('[data-nostr--nostr-publish-target="publishButton"]');
if (publishButton) {
publishButton.click();
} else {
console.error('[Header] Hidden publish button not found');
}
}
}

35
assets/controllers/editor/json-panel_controller.js

@ -45,6 +45,35 @@ export default class extends Controller { @@ -45,6 +45,35 @@ export default class extends Controller {
} else {
this.cmView = this.textarea._codemirror;
}
// Restore from localStorage if available
const savedJson = localStorage.getItem('editorState');
if (savedJson) {
try {
JSON.parse(savedJson); // Validate JSON
this.jsonTextareaTarget.value = savedJson;
if (this.cmView) {
this.cmView.dispatch({
changes: {from: 0, to: this.cmView.state.doc.length, insert: savedJson}
});
}
} catch (e) {
// Ignore corrupt JSON
localStorage.removeItem('editorState');
}
}
// Periodic save every 10 seconds
this._saveInterval = setInterval(() => {
if (this.hasJsonTextareaTarget) {
const value = this.jsonTextareaTarget.value;
try {
JSON.parse(value); // Only save valid JSON
localStorage.setItem('editorState', value);
} catch (e) {
// Do not save invalid JSON
}
}
}, 10000);
}
disconnect() {
@ -60,6 +89,12 @@ export default class extends Controller { @@ -60,6 +89,12 @@ export default class extends Controller {
}
this.textarea.style.display = '';
this.textarea._codemirror = null;
// Clear periodic save interval
if (this._saveInterval) {
clearInterval(this._saveInterval);
this._saveInterval = null;
}
}
handleMarkdownInput() {

50
assets/controllers/editor/layout_controller.js

@ -7,7 +7,7 @@ export default class extends Controller { @@ -7,7 +7,7 @@ export default class extends Controller {
'previewBody', 'previewTitle',
'previewSummary', 'previewImage', 'previewImagePlaceholder', 'previewAuthor', 'previewDate',
'markdownEditor', 'markdownTitle', 'markdownCode', 'status',
'saveDraftSubmit', 'publishSubmit'
'saveDraftSubmit', 'publishSubmit', 'jsonCode'
];
connect() {
@ -26,26 +26,10 @@ export default class extends Controller { @@ -26,26 +26,10 @@ export default class extends Controller {
imageInput.addEventListener('change', () => this.updatePreview());
}
// If editing an existing article, load JSON event by default
if (this.element.dataset.articleId && this.hasJsonPaneTarget) {
// Find the JSON textarea in the pane and load the event
const jsonTextarea = this.jsonPaneTarget.querySelector('[data-editor--json-panel-target="jsonTextarea"]');
if (jsonTextarea && !jsonTextarea.value.trim()) {
// Try to get the Nostr publish controller's JSON
const nostrController = this.application.getControllerForElementAndIdentifier(
this.element.querySelector('[data-controller*="nostr--nostr-publish"]'),
'nostr--nostr-publish'
);
if (nostrController && nostrController.hasJsonTextareaTarget) {
jsonTextarea.value = nostrController.jsonTextareaTarget.value;
// Optionally, trigger formatting/validation if needed
}
}
}
// Listen for content changes from Quill or Markdown
this.element.addEventListener('content:changed', () => {
this.updatePreview();
this.updateJsonCode();
// Update Quill pane live
const markdownInput = this.element.querySelector('textarea[name="editor[content]"]');
if (markdownInput && window.appQuill) {
@ -61,13 +45,6 @@ export default class extends Controller { @@ -61,13 +45,6 @@ export default class extends Controller {
.then(data => { window.appQuill.root.innerHTML = data.html || ''; });
}
}
// If JSON pane is present, update it as well
if (this.hasJsonPaneTarget) {
const jsonTextarea = this.jsonPaneTarget.querySelector('[data-editor--json-panel-target="jsonTextarea"]');
if (jsonTextarea && window.nostrPublishController && typeof window.nostrPublishController.regenerateJsonPreview === 'function') {
window.nostrPublishController.regenerateJsonPreview();
}
}
});
}
@ -108,8 +85,27 @@ export default class extends Controller { @@ -108,8 +85,27 @@ export default class extends Controller {
} else if (mode === 'preview') {
this.updatePreview();
} else if (mode === 'json') {
// Optionally, trigger JSON formatting/validation
this.updateJsonCode();
}
}
updateJsonCode() {
// Fill the JSON code block with the latest JSON event and highlight
if (!this.hasJsonCodeTarget) return;
let json = '';
const nostrController = this.application.getControllerForElementAndIdentifier(
this.element.querySelector('[data-controller*="nostr--nostr-publish"]'),
'nostr--nostr-publish'
);
if (nostrController && nostrController.hasJsonTextareaTarget) {
json = nostrController.jsonTextareaTarget.value;
}
try {
json = JSON.stringify(JSON.parse(json), null, 2);
} catch (e) {
// If not valid JSON, show as-is
}
this.jsonCodeTarget.textContent = json || 'No JSON event available.';
}
updateMarkdown() {
@ -255,7 +251,7 @@ export default class extends Controller { @@ -255,7 +251,7 @@ export default class extends Controller {
'nostr--nostr-publish'
);
if (nostrController) {
if (nostrController && typeof nostrController.publish === 'function') {
console.log('[Editor] Nostr publish controller found, calling publish()');
nostrController.publish();
} else {

124
assets/controllers/nostr/nostr_publish_controller.js

@ -211,16 +211,21 @@ export default class extends Controller { @@ -211,16 +211,21 @@ export default class extends Controller {
}
}
// If a user provided a partial or custom event, make sure required keys exist
applyEventDefaults(event, formData) {
// If a user provided a partial or custom event, make sure required keys exist and supplement from form
applyEventDefaults(event, formData, options = {}) {
const now = Math.floor(Date.now() / 1000);
const corrected = { ...event };
// Ensure tags/content/kind/created_at/pubkey exist; tags default includes d/title/summary/image/topics
if (!Array.isArray(corrected.tags)) corrected.tags = [];
// Supplement missing core fields from form
if (typeof corrected.kind !== 'number') corrected.kind = formData.isDraft ? 30024 : 30023;
// Supplement missing core fields from form or options
// Kind: explicit option > formData.isDraft > event.kind
if (typeof options.kind === 'number') {
corrected.kind = options.kind;
} else if (typeof corrected.kind !== 'number') {
corrected.kind = formData.isDraft ? 30024 : 30023;
}
if (typeof corrected.created_at !== 'number') corrected.created_at = now;
if (typeof corrected.content !== 'string') corrected.content = formData.content || '';
@ -228,31 +233,33 @@ export default class extends Controller { @@ -228,31 +233,33 @@ export default class extends Controller {
if (!corrected.pubkey) corrected.pubkey = undefined; // will be filled by createNostrEvent path if used
// Guarantee a d tag (slug)
const hasD = corrected.tags.some(t => Array.isArray(t) && t[0] === 'd');
if (!hasD && formData.slug) corrected.tags.push(['d', formData.slug]);
// Ensure title/summary/image/topics exist if absent
const ensureTag = (name, value) => {
if (!value) return;
const exists = corrected.tags.some(t => Array.isArray(t) && t[0] === name);
if (!exists) corrected.tags.push([name, value]);
};
ensureTag('title', formData.title);
ensureTag('summary', formData.summary);
ensureTag('image', formData.image);
for (const t of formData.topics || []) {
const exists = corrected.tags.some(tag => Array.isArray(tag) && tag[0] === 't' && tag[1] === t.replace('#', ''));
if (!exists) corrected.tags.push(['t', t.replace('#', '')]);
const tagsMap = new Map();
for (const t of corrected.tags) {
if (Array.isArray(t) && t.length > 0) tagsMap.set(t[0], t);
}
if (formData.slug) tagsMap.set('d', ['d', formData.slug]);
if (formData.title) tagsMap.set('title', ['title', formData.title]);
if (formData.summary) tagsMap.set('summary', ['summary', formData.summary]);
if (formData.image) tagsMap.set('image', ['image', formData.image]);
// Topics: allow multiple t tags
if (formData.topics && Array.isArray(formData.topics)) {
// Remove all existing t tags
for (const key of Array.from(tagsMap.keys())) {
if (key === 't') tagsMap.delete(key);
}
for (const topic of formData.topics) {
tagsMap.set(`t:${topic.replace('#','')}`, ['t', topic.replace('#','')]);
}
}
// Advanced tags from form, but don't duplicate existing tags by name
if (formData.advancedMetadata) {
const adv = buildAdvancedTags(formData.advancedMetadata);
for (const tag of adv) {
const exists = corrected.tags.some(t => Array.isArray(t) && t[0] === tag[0]);
if (!exists) corrected.tags.push(tag);
if (!tagsMap.has(tag[0])) tagsMap.set(tag[0], tag);
}
}
// Rebuild tags array
corrected.tags = Array.from(tagsMap.values());
return corrected;
}
@ -271,14 +278,8 @@ export default class extends Controller { @@ -271,14 +278,8 @@ export default class extends Controller {
const fd = new FormData(form);
// Prefer the Markdown field populated by the Quill controller
const md = fd.get('editor[content]');
let html = fd.get('editor[content]') || fd.get('content') || '';
// Final content: use MD if present, otherwise convert HTML -> MD
const content = (typeof md === 'string' && md.length > 0)
? md
: this.htmlToMarkdown(String(html));
// Only use the Markdown field
const content = fd.get('editor[content]') || '';
const title = fd.get('editor[title]') || '';
const summary = fd.get('editor[summary]') || '';
@ -439,69 +440,6 @@ export default class extends Controller { @@ -439,69 +440,6 @@ export default class extends Controller {
return await response.json();
}
htmlToMarkdown(html) {
// Basic HTML to Markdown conversion
let markdown = html;
// Convert headers
markdown = markdown.replace(/<h1[^>]*>(.*?)<\/h1>/gi, '# $1\n\n');
markdown = markdown.replace(/<h2[^>]*>(.*?)<\/h2>/gi, '## $1\n\n');
markdown = markdown.replace(/<h3[^>]*>(.*?)<\/h3>/gi, '### $1\n\n');
// Convert formatting
markdown = markdown.replace(/<strong[^>]*>(.*?)<\/strong>/gi, '**$1**');
markdown = markdown.replace(/<b[^>]*>(.*?)<\/b>/gi, '**$1**');
markdown = markdown.replace(/<em[^>]*>(.*?)<\/em>/gi, '*$1*');
markdown = markdown.replace(/<i[^>]*>(.*?)<\/i>/gi, '*$1*');
// Convert links
markdown = markdown.replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi, '[$2]($1)');
// Convert images
markdown = markdown.replace(/<img\b[^>]*>/gi, (imgTag) => {
const srcMatch = imgTag.match(/src=["']([^"']+)["']/i);
const altMatch = imgTag.match(/alt=["']([^"']*)["']/i);
const src = srcMatch ? srcMatch[1] : '';
const alt = altMatch ? altMatch[1] : '';
return src ? `![${alt}](${src})` : '';
});
// Convert lists
markdown = markdown.replace(/<ul[^>]*>(.*?)<\/ul>/gis, '$1\n');
markdown = markdown.replace(/<ol[^>]*>(.*?)<\/ol>/gis, '$1\n');
markdown = markdown.replace(/<li[^>]*>(.*?)<\/li>/gi, '- $1\n');
// Convert paragraphs
markdown = markdown.replace(/<p[^>]*>(.*?)<\/p>/gi, '$1\n\n');
// Convert line breaks
markdown = markdown.replace(/<br[^>]*>/gi, '\n');
// Convert blockquotes
markdown = markdown.replace(/<blockquote[^>]*>(.*?)<\/blockquote>/gis, '> $1\n\n');
// Convert code blocks
markdown = markdown.replace(/<pre[^>]*><code[^>]*>(.*?)<\/code><\/pre>/gis, '```\n$1\n```\n\n');
markdown = markdown.replace(/<code[^>]*>(.*?)<\/code>/gi, '`$1`');
// Escape "_" inside display math $$...$$ and inline math $...$
markdown = markdown.replace(/\$\$([\s\S]*?)\$\$/g, (m, g1) => `$$${g1.replace(/_/g, (u, i, s) => (i>0 && s[i-1]==='\\') ? '\\_' : '\\_')}$$`);
markdown = markdown.replace(/\$([^$]*?)\$/g, (m, g1) => `$${g1.replace(/_/g, (u, i, s) => (i>0 && s[i-1]==='\\') ? '\\_' : '\\_')}$`);
// Clean up HTML entities and remaining tags
markdown = markdown.replace(/&nbsp;/g, ' ');
markdown = markdown.replace(/&amp;/g, '&');
markdown = markdown.replace(/&lt;/g, '<');
markdown = markdown.replace(/&gt;/g, '>');
markdown = markdown.replace(/&quot;/g, '"');
markdown = markdown.replace(/<[^>]*>/g, '');
// Clean up extra whitespace
markdown = markdown.replace(/\n{3,}/g, '\n\n').trim();
return markdown;
}
generateSlug(title) {
// add a random seed at the end of the title to avoid collisions
const randomSeed = Math.random().toString(36).substring(2, 8);

3
assets/styles/03-components/form.css

@ -21,7 +21,8 @@ label { @@ -21,7 +21,8 @@ label {
clear: both;
}
input, textarea, select {
input:not([type="checkbox"]):not([type="radio"]),
textarea, select {
display: block;
clear: both;
width: 100%;

2
assets/styles/05-utilities/utilities.css

@ -28,7 +28,7 @@ @@ -28,7 +28,7 @@
.d-block{display:block!important}
.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}
.flex-row{flex-direction:row}
.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}
.flex-wrap{flex-wrap: wrap}
.justify-content-between{justify-content:space-between!important}
.justify-content-center{justify-content:center!important}

103
templates/editor/layout.html.twig

@ -1,4 +1,3 @@ @@ -1,4 +1,3 @@
{# templates/editor/layout.html.twig #}
{% extends 'base.html.twig' %}
{% form_theme form _self 'pages/_advanced_metadata.html.twig' %}
@ -16,6 +15,12 @@ @@ -16,6 +15,12 @@
<header class="editor-header" data-controller="editor--header" data-editor--header-target="header">
<div class="editor-header-left">
<a href="{{ path('home') }}" class="btn">← Back</a>
{# If not create new, show btn for 'Create new' #}
{% if article.id %}
<a href="{{ path('editor-create') }}" class="btn btn-secondary">
Create new
</a>
{% endif %}
<div class="editor-title">
{{ article.title|default('New article') }}
</div>
@ -40,95 +45,11 @@ @@ -40,95 +45,11 @@
<main data-controller="editor--layout" data-article-id="{{ article.id|default('') }}">
<div class="editor-main">
{# Insert the article list sidebar as the first grid column #}
<aside class="editor-articlelist-sidebar" data-controller="editor--articlelist-panels">
<div class="editor-sidebar-tabs">
<button
type="button"
class="editor-sidebar-tab is-active"
data-editor--articlelist-panels-target="tab"
data-panel="articles"
data-action="editor--articlelist-panels#switch"
>
Articles
</button>
</div>
<section class="editor-sidebar-panels">
<div
class="editor-panel"
data-editor--articlelist-panels-target="panel"
data-panel="articles"
>
<div class="articlelist-content" data-articlelist-target="list">
{% if is_granted('ROLE_USER') %}
{% if readingLists is defined and readingLists|length > 0 %}
{% for list in readingLists %}
<details class="mb-2">
<summary>
{{ list.title|default('Untitled List') }}
{% if list.summary is defined and list.summary %}
<span class="readinglist-summary">&mdash; {{ list.summary }}</span>
{% endif %}
</summary>
<ul class="list-unstyled">
{% if list.articles|length > 0 %}
{% for articleObj in list.articles %}
{% set article = articleObj.article %}
<li class="readinglist-article">
<span class="article-icon" title="{{ article.kind == 30024 ? 'Draft' : 'Published' }}">
{% if article.kind == 30024 %}
<span style="color: orange; font-weight: bold;">●</span><span class="article-kind">D</span>
{% else %}
<span style="color: #22c55e; font-weight: bold;">●</span><span class="article-kind">A</span>
{% endif %}
</span>
<a href="{{ path('editor-preview-npub-slug', {npub: article.pubkey|toNpub , slug: article.slug}) }}">
{{ article.title|default(article.slug) }}
</a>
<span>by {{ articleObj.author.name }}</span>
</li>
{% endfor %}
{% else %}
<li class="readinglist-empty">No articles in this list.</li>
{% endif %}
</ul>
</details>
{% endfor %}
{% endif %}
<ul class="list-unstyled">
{% for recent in recentArticles %}
<li class="mb-2">
<span class="article-icon" title="Published">
<span style="color: #22c55e; font-weight: bold;">●</span><span class="article-kind">A</span>
</span>
<a href="{{ path('editor-edit-slug', {slug: recent.slug}) }}">
{{ recent.title }} ({{ recent.publishedAt|date('Y-m-d') }})
</a>
</li>
{% else %}
<li>No recent articles found.</li>
{% endfor %}
</ul>
<ul class="list-unstyled">
{% for draft in drafts %}
<li>
<span class="article-icon" title="Draft">
<span style="color: orange; font-weight: bold;">●</span><span class="article-kind">D</span>
</span>
<a href="{{ path('editor-edit-slug', {slug: draft.slug}) }}">
{{ draft.title }} ({{ draft.updatedAt|date('Y-m-d') }})
</a>
</li>
{% else %}
<li>No drafts found.</li>
{% endfor %}
</ul>
{% else %}
<div class="articlelist-placeholder">Sign in to see your articles.</div>
{% endif %}
</div>
</div>
</section>
</aside>
{% include 'editor/panels/_articlelist.html.twig' with {
readingLists: readingLists is defined ? readingLists : [],
recentArticles: recentArticles is defined ? recentArticles : [],
drafts: drafts is defined ? drafts : []
} %}
{# Center editor area (middle grid column) #}
<div class="editor-center">
<div class="editor-center-tabs">
@ -331,7 +252,7 @@ @@ -331,7 +252,7 @@
</div>
<div
class="editor-panel"
class="editor-panel is-hidden"
data-editor--panels-target="panel"
data-panel="relays"
>

90
templates/editor/panels/_articlelist.html.twig

@ -0,0 +1,90 @@ @@ -0,0 +1,90 @@
<aside class="editor-articlelist-sidebar" data-controller="editor--articlelist-panels">
<div class="editor-sidebar-tabs">
<button
type="button"
class="editor-sidebar-tab is-active"
data-editor--articlelist-panels-target="tab"
data-panel="articles"
data-action="editor--articlelist-panels#switch"
>
Articles
</button>
</div>
<section class="editor-sidebar-panels">
<div
class="editor-panel"
data-editor--articlelist-panels-target="panel"
data-panel="articles"
>
<div class="articlelist-content" data-articlelist-target="list">
{% if is_granted('ROLE_USER') %}
{% if readingLists is defined and readingLists|length > 0 %}
{% for list in readingLists %}
<details class="mb-2">
<summary>
{{ list.title|default('Untitled List') }}
{% if list.summary is defined and list.summary %}
<span class="readinglist-summary">&mdash; {{ list.summary }}</span>
{% endif %}
</summary>
<ul class="list-unstyled">
{% if list.articles|length > 0 %}
{% for articleObj in list.articles %}
{% set article = articleObj.article %}
<li class="readinglist-article">
<span class="article-icon" title="{{ article.kind == 30024 ? 'Draft' : 'Published' }}">
{% if article.kind == 30024 %}
<span style="color: orange; font-weight: bold;"></span><span class="article-kind">D</span>
{% else %}
<span style="color: #22c55e; font-weight: bold;"></span><span class="article-kind">A</span>
{% endif %}
</span>
<a href="{{ path('editor-preview-npub-slug', {npub: article.pubkey|toNpub , slug: article.slug}) }}">
{{ article.title|default(article.slug) }}
</a>
<span>by {{ articleObj.author.name }}</span>
</li>
{% endfor %}
{% else %}
<li class="readinglist-empty">No articles in this list.</li>
{% endif %}
</ul>
</details>
{% endfor %}
{% endif %}
<ul class="list-unstyled">
{% for recent in recentArticles %}
<li class="mb-2">
<span class="article-icon" title="Published">
<span style="color: #22c55e; font-weight: bold;"></span><span class="article-kind">A</span>
</span>
<a href="{{ path('editor-edit-slug', {slug: recent.slug}) }}">
{{ recent.title }} ({{ recent.publishedAt|date('Y-m-d') }})
</a>
</li>
{% else %}
<li>No recent articles found.</li>
{% endfor %}
</ul>
<ul class="list-unstyled">
{% for draft in drafts %}
<li>
<span class="article-icon" title="Draft">
<span style="color: orange; font-weight: bold;"></span><span class="article-kind">D</span>
</span>
<a href="{{ path('editor-edit-slug', {slug: draft.slug}) }}">
{{ draft.title }} ({{ draft.updatedAt|date('Y-m-d') }})
</a>
</li>
{% else %}
<li>No drafts found.</li>
{% endfor %}
</ul>
{% else %}
<div class="articlelist-placeholder">Sign in to see your articles.</div>
{% endif %}
</div>
</div>
</section>
</aside>

35
templates/editor/panels/_json.html.twig

@ -1,13 +1,13 @@ @@ -1,13 +1,13 @@
<div class="panel-section" data-controller="editor--json-panel">
<div class="mb-3">
<button
type="button"
class="btn btn-sm btn-primary w-100"
data-action="click->editor--json-panel#regenerateJson"
>
Rebuild from form
</button>
</div>
{# <div class="mb-3">#}
{# <button#}
{# type="button"#}
{# class="btn btn-sm btn-primary w-100"#}
{# data-action="click->editor--json-panel#regenerateJson"#}
{# >#}
{# Rebuild from form#}
{# </button>#}
{# </div>#}
<div class="json-editor-container">
<textarea
@ -15,19 +15,18 @@ @@ -15,19 +15,18 @@
class="json-textarea"
data-editor--json-panel-target="jsonTextarea"
data-nostr--nostr-publish-target="jsonTextarea"
data-action="input->nostr--nostr-publish#onJsonInput input->editor--json-panel#onJsonInput"
rows="20"
spellcheck="false"
>{{ article.raw is defined and article.raw ? article.raw|json_encode(constant('JSON_PRETTY_PRINT')) : '' }}</textarea>
<div class="json-status" data-editor--json-panel-target="status"></div>
</div>
<div class="panel-help mt-2">
<small>
<strong>Required fields:</strong> kind, created_at, tags, content, pubkey<br>
<span class="text-warning" data-editor--json-panel-target="dirtyHint" style="display:none">
JSON modified - will override form values
</span>
</small>
</div>
{# <div class="panel-help mt-2">#}
{# <small>#}
{# <strong>Required fields:</strong> kind, created_at, tags, content, pubkey<br>#}
{# <span class="text-warning" data-editor--json-panel-target="dirtyHint" style="display:none">#}
{# JSON modified - will override form values#}
{# </span>#}
{# </small>#}
{# </div>#}
</div>

2
templates/editor/panels/_metadata.html.twig

@ -61,7 +61,7 @@ @@ -61,7 +61,7 @@
{{ form_row(form.clientTag, {
'label': 'Add client tag (Decent Newsroom)',
'row_attr': {'class': 'form-check d-flex flex-row'},
'row_attr': {'class': 'form-check mt-2 d-flex flex-row-reverse justify-content-between'},
'label_attr': {'class': 'form-check-label'},
'attr': {'class': 'form-check-input'}
}) }}

Loading…
Cancel
Save