@ -13,7 +13,7 @@
@@ -13,7 +13,7 @@
import AdvancedEditor from './AdvancedEditor.svelte';
import { shouldIncludeClientTag } from '../../services/client-tag-preference.js';
import { goto } from '$app/navigation';
import { KIND , KIND _LOOKUP } from '../../types/kind-lookup.js';
import { KIND_LOOKUP } from '../../types/kind-lookup.js';
import { getKindMetadata , getWritableKinds } from '../../types/kind-metadata.js';
import type { NostrEvent } from '../../types/nostr.js';
import { autoExtractTags , ensureDTagForParameterizedReplaceable } from '../../services/auto-tagging.js';
@ -26,27 +26,84 @@
@@ -26,27 +26,84 @@
const SUPPORTED_KINDS = getWritableKinds();
interface Props {
initialKind?: number | null;
initialContent?: string | null;
initialTags?: string[][] | null;
initialEvent?: NostrEvent | null;
}
let { initialKind = null , initialContent : propInitialContent = null , initialTags : propInitialTags = null } : Props = $props();
let { initialEvent = null } : Props = $props();
const DRAFT_ID = 'write';
let selectedKind = $state< number > (1);
let customKindId = $state< string > ('');
let content = $state('');
let tags = $state< string [ ] [ ] > ([]);
// Extract initial values from event
const getInitialKind = (): number => {
if (initialEvent?.kind !== undefined) {
const isSupported = SUPPORTED_KINDS.some(k => k.value === initialEvent.kind);
return isSupported ? initialEvent.kind : -1; // -1 means "unknown kind"
}
return 1; // Default to kind 1
};
const getInitialCustomKindId = (): string => {
if (initialEvent?.kind !== undefined) {
const isSupported = SUPPORTED_KINDS.some(k => k.value === initialEvent.kind);
return isSupported ? '' : String(initialEvent.kind);
}
return '';
};
const getInitialContent = (): string => {
return initialEvent?.content || '';
};
const getInitialTags = (): string[][] => {
return initialEvent?.tags ? [...initialEvent.tags] : [];
};
// Initialize state
let selectedKind = $state< number > (getInitialKind());
let customKindId = $state< string > (getInitialCustomKindId());
let content = $state< string > (getInitialContent());
let tags = $state< string [ ] [ ] > (getInitialTags());
let publishing = $state(false);
let showAdvancedEditor = $state(false);
let richTextEditorRef: { clearUploadedFiles : () => void ; getUploadedFiles : () => Array < { url : string ; imetaTag : string [] } > } | null = $state(null);
let uploadedFiles: Array< { url : string ; imetaTag : string [] } > = $state([]);
// Modal states
let publicationModalOpen = $state(false);
let publicationResults = $state< { success : string []; failed : Array < { relay : string ; error : string } > } | null>(null);
let showJsonModal = $state(false);
let showPreviewModal = $state(false);
let showExampleModal = $state(false);
let mediaViewerOpen = $state(false);
let mediaViewerUrl = $state< string | null > (null);
// Restore draft from IndexedDB on mount (only if no initial props)
// Preview/JSON refs
let jsonPreviewRef: HTMLElement | null = $state(null);
let exampleJsonPreviewRef: HTMLElement | null = $state(null);
let eventJson = $state('{} ');
let previewContent = $state< string > ('');
let previewEvent = $state< NostrEvent | null > (null);
// Sync when initialEvent changes
$effect(() => {
if (typeof window === 'undefined') return;
if (initialEvent) {
const isSupported = SUPPORTED_KINDS.some(k => k.value === initialEvent.kind);
if (isSupported) {
selectedKind = initialEvent.kind;
customKindId = '';
} else {
selectedKind = -1;
customKindId = String(initialEvent.kind);
}
content = initialEvent.content || '';
tags = initialEvent.tags ? [...initialEvent.tags] : [];
}
});
// Restore draft from IndexedDB if no initial event
$effect(() => {
if (typeof window === 'undefined' || initialEvent) return;
// Only restore if no initial content/tags were provided (from highlight feature)
if (propInitialContent === null && propInitialTags === null) {
(async () => {
try {
const draft = await getDraft(DRAFT_ID);
@ -57,7 +114,7 @@
@@ -57,7 +114,7 @@
if (draft.tags && draft.tags.length > 0 && tags.length === 0) {
tags = draft.tags;
}
if (draft.selectedKind !== undefined && initialKind === null ) {
if (draft.selectedKind !== undefined) {
selectedKind = draft.selectedKind;
}
}
@ -65,18 +122,15 @@
@@ -65,18 +122,15 @@
console.error('Error restoring draft:', error);
}
})();
}
});
// Save draft to IndexedDB when content or tags change
// Save draft to IndexedDB
$effect(() => {
if (typeof window === 'undefined') return;
if (publishing) return; // Don't save while publishing
if (publishing || initialEvent ) return; // Don't save while publishing or when editing an event
// Debounce saves to avoid excessive IndexedDB writes
const timeoutId = setTimeout(async () => {
try {
// Only save if there's actual content
if (content.trim() || tags.length > 0) {
await saveDraft(DRAFT_ID, {
content,
@ -84,7 +138,6 @@
@@ -84,7 +138,6 @@
selectedKind
});
} else {
// Clear if empty
await deleteDraft(DRAFT_ID);
}
} catch (error) {
@ -94,35 +147,6 @@
@@ -94,35 +147,6 @@
return () => clearTimeout(timeoutId);
});
let publicationModalOpen = $state(false);
let publicationResults = $state< { success : string []; failed : Array < { relay : string ; error : string } > } | null>(null);
let showJsonModal = $state(false);
let showPreviewModal = $state(false);
let exampleJsonPreviewRef: HTMLElement | null = $state(null);
let previewContent = $state< string > ('');
let previewEvent = $state< NostrEvent | null > (null);
let showExampleModal = $state(false);
// Media viewer state for preview
let mediaViewerOpen = $state(false);
let mediaViewerUrl = $state< string | null > (null);
function handleMediaUrlClick(url: string, e: MouseEvent) {
e.stopPropagation();
e.preventDefault();
mediaViewerUrl = url;
mediaViewerOpen = true;
}
function closeMediaViewer() {
mediaViewerOpen = false;
mediaViewerUrl = null;
}
let showAdvancedEditor = $state(false);
let richTextEditorRef: { clearUploadedFiles : () => void ; getUploadedFiles : () => Array < { url : string ; imetaTag : string [] } > } | null = $state(null);
let uploadedFiles: Array< { url : string ; imetaTag : string [] } > = $state([]);
let eventJson = $state('{} ');
let jsonPreviewRef: HTMLElement | null = $state(null);
// Highlight JSON when it changes
$effect(() => {
@ -132,7 +156,6 @@
@@ -132,7 +156,6 @@
jsonPreviewRef.innerHTML = highlighted;
jsonPreviewRef.className = 'hljs language-json';
} catch (err) {
// Fallback to plain text if highlighting fails
jsonPreviewRef.textContent = eventJson;
jsonPreviewRef.className = 'language-json';
}
@ -141,45 +164,20 @@
@@ -141,45 +164,20 @@
const isUnknownKind = $derived(selectedKind === -1);
const effectiveKind = $derived(isUnknownKind ? (parseInt(customKindId) || 1) : selectedKind);
// Determine editor mode based on selected kind
const editorMode = $derived(
effectiveKind === 30818 || effectiveKind === 30041 ? 'asciidoc' : 'markdown'
);
// Show advanced editor button when editing (has initial content) or for AsciiDoc kinds
const showAdvancedEditorButton = $derived(
(propInitialContent !== null & & propInitialContent !== undefined) ||
initialEvent !== null ||
effectiveKind === 30818 ||
effectiveKind === 30041 ||
effectiveKind === 30023 || // Long-form note (markdown)
effectiveKind === 1 // Short text note (markdown)
effectiveKind === 30023 ||
effectiveKind === 1
);
// Sync selectedKind when initialKind prop changes
$effect(() => {
if (initialKind !== null && initialKind !== undefined) {
selectedKind = initialKind;
}
});
// Track if we've already applied initial props to prevent re-applying after clear
let initialPropsApplied = $state(false);
let formCleared = $state(false); // Track if form was explicitly cleared
// Sync content and tags when initial props change (only if form is empty and not yet applied)
$effect(() => {
if (initialPropsApplied || formCleared) return; // Don't re-apply after they've been used or after clear
if (propInitialContent !== null && propInitialContent !== undefined && content === '') {
content = propInitialContent;
initialPropsApplied = true;
}
if (propInitialTags !== null && propInitialTags !== undefined && propInitialTags.length > 0 && tags.length === 0) {
tags = [...propInitialTags];
initialPropsApplied = true;
}
});
const kindMetadata = $derived(getKindMetadata(effectiveKind));
const helpText = $derived(kindMetadata.helpText);
const isKind30040 = $derived(selectedKind === 30040);
const isKind10895 = $derived(selectedKind === 10895);
// Clear content for metadata-only kinds
$effect(() => {
@ -189,9 +187,6 @@
@@ -189,9 +187,6 @@
}
});
const kindMetadata = $derived(getKindMetadata(effectiveKind));
const helpText = $derived(kindMetadata.helpText);
function getExampleJSON(): string {
const examplePubkey = '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d';
const exampleEventId = '67b48a14fb66c60c8f9070bdeb37afdfcc3d08ad01989460448e4081eddda446';
@ -202,7 +197,7 @@
@@ -202,7 +197,7 @@
const exampleJSON = $derived(getExampleJSON());
// Highlight example JSON when it changes
// Highlight example JSON
$effect(() => {
if (exampleJsonPreviewRef && exampleJSON && exampleJsonPreviewRef instanceof HTMLElement) {
try {
@ -210,16 +205,12 @@
@@ -210,16 +205,12 @@
exampleJsonPreviewRef.innerHTML = highlighted;
exampleJsonPreviewRef.className = 'hljs language-json';
} catch (err) {
// Fallback to plain text if highlighting fails
exampleJsonPreviewRef.textContent = exampleJSON;
exampleJsonPreviewRef.className = 'language-json';
}
}
});
const isKind30040 = $derived(selectedKind === KIND.PUBLICATION_INDEX);
const isKind10895 = $derived(selectedKind === KIND.RSS_FEED);
function addTag() {
tags = [...tags, ['', '']];
}
@ -245,16 +236,11 @@
@@ -245,16 +236,11 @@
const session = sessionManager.getSession();
if (!session) return '{} ';
// Add file attachments as imeta tags (like jumble)
let contentWithUrls = content.trim();
const allTags = [...tags.filter(t => t[0] & & t[1])];
for (const file of uploadedFiles) {
// Use imeta tag from upload response (like jumble)
allTags.push(file.imetaTag);
// Add URL to content field only if it's not already there
// (to avoid duplicates if URL was already inserted into textarea)
if (!contentWithUrls.includes(file.url)) {
if (contentWithUrls && !contentWithUrls.endsWith('\n')) {
contentWithUrls += '\n';
@ -263,7 +249,6 @@
@@ -263,7 +249,6 @@
}
}
// Auto-extract tags from content (hashtags, mentions, nostr: links)
const autoTagsResult = await autoExtractTags({
content: contentWithUrls,
existingTags: allTags,
@ -271,7 +256,6 @@
@@ -271,7 +256,6 @@
});
allTags.push(...autoTagsResult.tags);
// For parameterized replaceable events, ensure d-tag exists (for preview)
if (isParameterizedReplaceableKind(effectiveKind)) {
const dTagResult = ensureDTagForParameterizedReplaceable(allTags, effectiveKind);
if (dTagResult) {
@ -283,7 +267,6 @@
@@ -283,7 +267,6 @@
allTags.push(['client', 'aitherboard']);
}
// Process content to add "nostr:" prefix to valid Nostr addresses
const { processNostrLinks } = await import('../../utils/nostr-link-processor.js');
const processedContent = processNostrLinks(contentWithUrls.trim());
@ -298,12 +281,10 @@
@@ -298,12 +281,10 @@
return JSON.stringify(event, null, 2);
}
function handleFilesUploaded(files: Array< { url : string ; imetaTag : string [] } >) {
uploadedFiles = files;
}
async function publish() {
const session = sessionManager.getSession();
if (!session) {
@ -319,21 +300,15 @@
@@ -319,21 +300,15 @@
publishing = true;
try {
// Add file attachments as imeta tags (like jumble)
let contentWithUrls = content.trim();
// Create a plain array (not a Proxy) by mapping and filtering
const allTags: string[][] = tags
.filter(t => t[0] & & t[1])
.map(tag => [...tag]); // Create new array for each tag to avoid Proxy
.map(tag => [...tag]);
for (const file of uploadedFiles) {
// Use imeta tag from upload response (like jumble)
// Ensure imetaTag is also a plain array
const imetaTag = Array.isArray(file.imetaTag) ? [...file.imetaTag] : file.imetaTag;
allTags.push(imetaTag);
// Add URL to content field only if it's not already there
// (to avoid duplicates if URL was already inserted into textarea)
if (!contentWithUrls.includes(file.url)) {
if (contentWithUrls && !contentWithUrls.endsWith('\n')) {
contentWithUrls += '\n';
@ -342,7 +317,6 @@
@@ -342,7 +317,6 @@
}
}
// Auto-extract tags from content (hashtags, mentions, nostr: links)
const autoTagsResult = await autoExtractTags({
content: contentWithUrls,
existingTags: allTags,
@ -350,16 +324,13 @@
@@ -350,16 +324,13 @@
});
allTags.push(...autoTagsResult.tags);
// For parameterized replaceable events (30000-39999), ensure d-tag exists
if (isParameterizedReplaceableKind(effectiveKind)) {
const dTagResult = ensureDTagForParameterizedReplaceable(allTags, effectiveKind);
if (dTagResult) {
allTags.push(['d', dTagResult.dTag]);
} else {
// Check if d-tag exists (it should after ensureDTagForParameterizedReplaceable if title exists)
const existingDTag = allTags.find(t => t[0] === 'd' & & t[1]);
if (!existingDTag) {
// No d-tag and no title tag - alert user
alert(`Parameterized replaceable events (kind ${ effectiveKind } ) require a d-tag. Please add a d-tag or a title tag that can be normalized to a d-tag.`);
publishing = false;
return;
@ -371,11 +342,9 @@
@@ -371,11 +342,9 @@
allTags.push(['client', 'aitherboard']);
}
// Process content to add "nostr:" prefix to valid Nostr addresses
const { processNostrLinks } = await import('../../utils/nostr-link-processor.js');
const processedContent = processNostrLinks(contentWithUrls.trim());
// Create a plain object (not a Proxy) to avoid cloning issues
const eventTemplate: Omit< NostrEvent , ' sig ' | ' id ' > = {
kind: effectiveKind,
pubkey: session.pubkey,
@ -399,11 +368,10 @@
@@ -399,11 +368,10 @@
if (results.success.length > 0) {
content = '';
tags = [];
uploadedFiles = []; // Clear uploaded files after successful publish
uploadedFiles = [];
if (richTextEditorRef) {
richTextEditorRef.clearUploadedFiles();
}
// Clear draft from IndexedDB after successful publish
await deleteDraft(DRAFT_ID);
setTimeout(() => {
goto(`/event/${ signedEvent . id } `);
@ -424,27 +392,15 @@
@@ -424,27 +392,15 @@
async function clearForm() {
if (confirm('Are you sure you want to clear the form? This will delete all unsaved content.')) {
try {
// Mark form as cleared to prevent initial props from re-applying
formCleared = true;
// Clear state synchronously
content = '';
tags = [];
uploadedFiles = [];
customKindId = '';
selectedKind = 1; // Reset to default kind
// Reset the initial props applied flag
initialPropsApplied = false;
// Clear draft from IndexedDB after clearing state
// This prevents the save effect from running with old data
selectedKind = 1;
if (richTextEditorRef) {
richTextEditorRef.clearUploadedFiles();
}
await deleteDraft(DRAFT_ID);
// Reset formCleared flag after a brief delay to allow effects to settle
setTimeout(() => {
formCleared = false;
}, 100);
} catch (error) {
console.error('Error clearing form:', error);
alert('Failed to clear form. Please try again.');
@ -465,12 +421,10 @@
@@ -465,12 +421,10 @@
const session = sessionManager.getSession();
if (!session) return;
// Create plain arrays/objects to avoid Proxy cloning issues
const plainTags: string[][] = tags
.filter(t => t[0] & & t[1])
.map(tag => [...tag]); // Create new array for each tag to avoid Proxy
.map(tag => [...tag]);
// Process content to add "nostr:" prefix to valid Nostr addresses
const { processNostrLinks } = await import('../../utils/nostr-link-processor.js');
const processedContent = processNostrLinks(content);
@ -490,6 +444,73 @@
@@ -490,6 +444,73 @@
publishing = false;
}
}
function handleMediaUrlClick(url: string, e: MouseEvent) {
e.stopPropagation();
e.preventDefault();
mediaViewerUrl = url;
mediaViewerOpen = true;
}
function closeMediaViewer() {
mediaViewerOpen = false;
mediaViewerUrl = null;
}
async function showPreview() {
let contentWithUrls = content.trim();
for (const file of uploadedFiles) {
if (!contentWithUrls.includes(file.url)) {
if (contentWithUrls && !contentWithUrls.endsWith('\n')) {
contentWithUrls += '\n';
}
contentWithUrls += `${ file . url } \n`;
}
}
const { processNostrLinks } = await import('../../utils/nostr-link-processor.js');
previewContent = processNostrLinks(contentWithUrls.trim());
const previewTags: string[][] = [];
for (const tag of tags) {
if (tag[0] && tag[1]) {
previewTags.push([...tag]);
}
}
for (const file of uploadedFiles) {
previewTags.push(file.imetaTag);
}
const autoTagsResult = await autoExtractTags({
content: contentWithUrls,
existingTags: previewTags,
kind: effectiveKind
});
previewTags.push(...autoTagsResult.tags);
if (isParameterizedReplaceableKind(effectiveKind)) {
const dTagResult = ensureDTagForParameterizedReplaceable(previewTags, effectiveKind);
if (dTagResult) {
previewTags.push(['d', dTagResult.dTag]);
}
}
if (shouldIncludeClientTag()) {
previewTags.push(['client', 'aitherboard']);
}
previewEvent = {
kind: effectiveKind,
pubkey: sessionManager.getCurrentPubkey() || '',
created_at: Math.floor(Date.now() / 1000),
tags: previewTags,
content: previewContent,
id: '',
sig: ''
} as NostrEvent;
showPreviewModal = true;
}
< / script >
< div class = "create-form-container" >
@ -527,11 +548,12 @@
@@ -527,11 +548,12 @@
{ #each SUPPORTED_KINDS as kind }
< option value = { kind . value } > { kind . label } </option >
{ /each }
{ #if selectedKind !== - 1 && ! SUPPORTED_KINDS . find ( k => k . value === selectedKind )}
{ #if selectedKind !== - 1 && selectedKind !== 1 && ! SUPPORTED_KINDS . find ( k => k . value === selectedKind )}
{ @const kindInfo = getKindMetadata ( selectedKind )}
{ @const kindDescription = kindInfo ? . description || KIND_LOOKUP [ selectedKind ] ? . description || 'Unknown' }
< option value = { selectedKind } > { selectedKind } - { kindDescription } </ option >
{ /if }
< option value = { - 1 } > Unknown Kind </ option >
< / select >
{ #if isUnknownKind }
< div class = "custom-kind-input" >
@ -592,66 +614,7 @@
@@ -592,66 +614,7 @@
< / button >
< button
type="button"
onclick={ async () => {
// Generate preview content with all processing applied
let contentWithUrls = content.trim();
for (const file of uploadedFiles) {
if (!contentWithUrls.includes(file.url)) {
if (contentWithUrls && !contentWithUrls.endsWith('\n')) {
contentWithUrls += '\n';
}
contentWithUrls += `${ file . url } \n`;
}
}
// Process content to add "nostr:" prefix
const { processNostrLinks } = await import('../../utils/nostr-link-processor.js');
previewContent = processNostrLinks(contentWithUrls.trim());
// Build preview event with all tags
const previewTags: string[][] = [];
for (const tag of tags) {
if (tag[0] && tag[1]) {
previewTags.push([...tag]);
}
}
for (const file of uploadedFiles) {
previewTags.push(file.imetaTag);
}
// Auto-extract tags
const autoTagsResult = await autoExtractTags({
content: contentWithUrls,
existingTags: previewTags,
kind: effectiveKind
});
previewTags.push(...autoTagsResult.tags);
// For parameterized replaceable events, ensure d-tag exists
if (isParameterizedReplaceableKind(effectiveKind)) {
const dTagResult = ensureDTagForParameterizedReplaceable(previewTags, effectiveKind);
if (dTagResult) {
previewTags.push(['d', dTagResult.dTag]);
}
}
// Include client tag if selected
if (shouldIncludeClientTag()) {
previewTags.push(['client', 'aitherboard']);
}
previewEvent = {
kind: effectiveKind,
pubkey: sessionManager.getCurrentPubkey() || '',
created_at: Math.floor(Date.now() / 1000),
tags: previewTags,
content: previewContent,
id: '',
sig: ''
} as NostrEvent;
showPreviewModal = true;
}}
onclick={ showPreview }
class="content-button"
disabled={ publishing }
title="Preview"
@ -733,8 +696,8 @@
@@ -733,8 +696,8 @@
/>
{ /if }
<!-- JSON View Modal -->
{ #if showJsonModal }
<!-- JSON View Modal -->
{ #if showJsonModal }
< div
class="modal-overlay"
onclick={() => showJsonModal = false }
@ -778,10 +741,10 @@
@@ -778,10 +741,10 @@
< / div >
< / div >
< / div >
{ /if }
{ /if }
<!-- Preview Modal -->
{ #if showPreviewModal }
<!-- Preview Modal -->
{ #if showPreviewModal }
< div
class="modal-overlay"
onclick={() => showPreviewModal = false }
@ -809,7 +772,6 @@
@@ -809,7 +772,6 @@
< / div >
< div class = "modal-body preview-body" >
{ #if previewEvent && previewContent }
<!-- Essential Metadata Display -->
{ @const titleTag = previewEvent . tags . find ( t => ( t [ 0 ] === 'title' || t [ 0 ] === 'T' ) && t [ 1 ])}
{ @const authorTag = previewEvent . tags . find ( t => t [ 0 ] === 'author' && t [ 1 ])}
{ @const summaryTag = previewEvent . tags . find ( t => t [ 0 ] === 'summary' && t [ 1 ])}
@ -875,14 +837,14 @@
@@ -875,14 +837,14 @@
< / div >
< / div >
< / div >
{ /if }
{ /if }
{ #if mediaViewerUrl && mediaViewerOpen }
< MediaViewer url = { mediaViewerUrl } isOpen= { mediaViewerOpen } onClose = { closeMediaViewer } / >
{ /if }
<!-- Example JSON Modal -->
{ #if showExampleModal }
<!-- Example JSON Modal -->
{ #if showExampleModal }
< div
class="modal-overlay"
onclick={() => showExampleModal = false }
@ -926,7 +888,7 @@
@@ -926,7 +888,7 @@
< / div >
< / div >
< / div >
{ /if }
{ /if }
{ #if publicationResults && publicationResults . success . length === 0 && publicationResults . failed . length > 0 }
< div class = "republish-section" >
@ -1059,13 +1021,13 @@
@@ -1059,13 +1021,13 @@
.example-button:hover {
background: var(--fog-accent, #64748b);
color: #ffffff; /* White text on accent background for good contrast */
color: #ffffff;
border-color: var(--fog-accent, #64748b);
}
:global(.dark) .example-button:hover {
background: var(--fog-dark-accent, #94a3b8);
color: #ffffff; /* White text on dark accent for good contrast */
color: #ffffff;
border-color: var(--fog-dark-accent, #94a3b8);
}
@ -1087,7 +1049,7 @@
@@ -1087,7 +1049,7 @@
}
.example-modal .json-preview {
background: #1e1e1e !important; /* VS Code dark background, same as code blocks */
background: #1e1e1e !important;
border: 1px solid #3e3e3e;
border-radius: 4px;
padding: 1rem;
@ -1101,7 +1063,7 @@
@@ -1101,7 +1063,7 @@
overflow-x: auto;
padding: 0;
background: transparent !important;
color: #d4d4d4; /* VS Code text color */
color: #d4d4d4;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Droid Sans Mono', 'Source Code Pro', monospace;
font-size: 0.875rem;
line-height: 1.5;
@ -1192,13 +1154,13 @@
@@ -1192,13 +1154,13 @@
.advanced-editor-button:hover:not(:disabled) {
background: var(--fog-accent, #64748b);
color: #ffffff; /* White text on accent background for good contrast */
color: #ffffff;
border-color: var(--fog-accent, #64748b);
}
:global(.dark) .advanced-editor-button:hover:not(:disabled) {
background: var(--fog-dark-accent, #94a3b8);
color: #ffffff; /* White text on dark accent for good contrast */
color: #ffffff;
border-color: var(--fog-dark-accent, #94a3b8);
}
@ -1265,7 +1227,6 @@
@@ -1265,7 +1227,6 @@
color: var(--fog-dark-text, #cbd5e1);
}
.tags-list {
display: flex;
flex-direction: column;
@ -1384,7 +1345,7 @@
@@ -1384,7 +1345,7 @@
.publish-button {
padding: 0.75rem 1.5rem;
background: var(--fog-accent, #64748b);
color: #ffffff; /* White text on accent background for good contrast */
color: #ffffff;
border: none;
border-radius: 0.25rem;
cursor: pointer;
@ -1395,7 +1356,7 @@
@@ -1395,7 +1356,7 @@
:global(.dark) .publish-button {
background: var(--fog-dark-accent, #94a3b8);
color: #ffffff; /* White text on dark accent for good contrast */
color: #ffffff;
}
.publish-button:hover:not(:disabled) {
@ -1433,7 +1394,7 @@
@@ -1433,7 +1394,7 @@
.republish-button {
padding: 0.5rem 1rem;
background: var(--fog-accent, #64748b);
color: #ffffff; /* White text on accent background for good contrast */
color: #ffffff;
border: none;
border-radius: 0.25rem;
cursor: pointer;
@ -1444,7 +1405,7 @@
@@ -1444,7 +1405,7 @@
:global(.dark) .republish-button {
background: var(--fog-dark-accent, #94a3b8);
color: #ffffff; /* White text on dark accent for good contrast */
color: #ffffff;
}
.republish-button:hover:not(:disabled) {
@ -1456,7 +1417,6 @@
@@ -1456,7 +1417,6 @@
cursor: not-allowed;
}
.content-buttons {
display: flex;
gap: 0.5rem;
@ -1500,7 +1460,7 @@
@@ -1500,7 +1460,7 @@
:global(.dark) .content-button:hover:not(:disabled) {
background: var(--fog-dark-border, #475569);
border-color: var(--fog-dark-accent, #64748b );
border-color: var(--fog-dark-accent, #94a3b8 );
}
.content-button:disabled {
@ -1508,7 +1468,6 @@
@@ -1508,7 +1468,6 @@
cursor: not-allowed;
}
/* Modal styles */
.modal-overlay {
position: fixed;
@ -1651,7 +1610,7 @@
@@ -1651,7 +1610,7 @@
}
.json-preview {
background: #1e1e1e !important; /* VS Code dark background, same as code blocks */
background: #1e1e1e !important;
border: 1px solid #3e3e3e;
border-radius: 0.5rem;
padding: 1rem;
@ -1664,7 +1623,7 @@
@@ -1664,7 +1623,7 @@
overflow-x: auto;
padding: 0;
background: transparent !important;
color: #d4d4d4; /* VS Code text color */
color: #d4d4d4;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Droid Sans Mono', 'Source Code Pro', monospace;
font-size: 0.875rem;
line-height: 1.5;
@ -1797,19 +1756,6 @@
@@ -1797,19 +1756,6 @@
color: var(--fog-dark-text-light, #94a3b8);
}
.d-tag-preview code {
background: #e2e8f0;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
font-size: 0.8125rem;
}
:global(.dark) .d-tag-preview code {
background: #334155;
color: #f1f5f9;
}
.modal-footer {
display: flex;
justify-content: flex-end;
@ -1863,6 +1809,6 @@
@@ -1863,6 +1809,6 @@
:global(.dark) .modal-footer button:hover {
background: var(--fog-dark-border, #475569);
border-color: var(--fog-dark-accent, #64748b );
border-color: var(--fog-dark-accent, #94a3b8 );
}
< / style >