Hive Markdown Post Editor
<div class="flex flex-col lg:flex-row h-[calc(100vh-73px)]">
<!-- Preview Pane -->
<div class="lg:w-1/2 border-r border-zinc-800 bg-white text-zinc-900 flex flex-col">
<div class="px-6 py-3 border-b bg-zinc-50 flex justify-between items-center">
<h2 class="font-semibold text-zinc-700">Preview</h2>
<button onclick="refreshPreview()" class="text-xs px-4 py-1 border border-zinc-300 rounded-xl hover:bg-zinc-100">↻ Refresh</button>
</div>
<div id="preview" class="preview p-8 overflow-auto flex-1 prose prose-zinc max-w-none"></div>
</div>
<!-- Editor Pane -->
<div class="lg:w-1/2 flex flex-col bg-zinc-900">
<div class="flex border-b border-zinc-700">
<button onclick="switchTab(0)" id="tab0" class="tab-active flex-1 py-4 font-medium">Body</button>
<button onclick="switchTab(1)" id="tab1" class="flex-1 py-4 font-medium">Metadata</button>
</div>
<div id="tabContent0" class="flex-1 p-6 space-y-6 overflow-auto">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-xs text-zinc-400 mb-1">Author</label>
<input id="author" class="w-full bg-zinc-950 border border-zinc-700 rounded-xl px-4 py-3 focus:outline-none focus:border-emerald-400">
</div>
<div>
<label class="block text-xs text-zinc-400 mb-1">Permlink</label>
<input id="permlink" class="w-full bg-zinc-950 border border-zinc-700 rounded-xl px-4 py-3 focus:outline-none focus:border-emerald-400">
</div>
</div>
<div>
<label class="block text-xs text-zinc-400 mb-1">Title</label>
<input id="title" class="w-full bg-zinc-950 border border-zinc-700 rounded-xl px-4 py-3 focus:outline-none focus:border-emerald-400">
</div>
<div>
<label class="block text-xs text-zinc-400 mb-1">Body (Markdown)</label>
<textarea id="body" class="w-full bg-zinc-950 border border-zinc-700 rounded-3xl px-5 py-4 h-96 font-mono text-sm focus:outline-none focus:border-emerald-400"></textarea>
</div>
</div>
<div id="tabContent1" class="flex-1 p-6 overflow-auto hidden">
<label class="block text-xs text-zinc-400 mb-1">json_metadata</label>
<textarea id="jsonMetadata" class="w-full bg-zinc-950 border border-zinc-700 rounded-3xl px-5 py-4 h-96 font-mono text-sm focus:outline-none focus:border-emerald-400"></textarea>
<button onclick="validateAndFormatJson()" class="mt-4 px-5 py-2 bg-zinc-800 hover:bg-zinc-700 rounded-2xl text-sm">Format & Validate JSON</button>
</div>
<div class="p-6 border-t border-zinc-700 bg-zinc-950 flex flex-wrap gap-3">
<button onclick="saveWithKeychain()" class="flex-1 bg-emerald-500 hover:bg-emerald-600 text-black py-4 rounded-3xl font-medium transition">🚀 Save to Hive (Keychain)</button>
<button onclick="copyFullOperation()" class="px-8 bg-zinc-800 hover:bg-zinc-700 rounded-3xl text-sm font-medium">📋 Copy Operation</button>
</div>
</div>
</div>
// ====================== RECEIVE FROM SHIM ======================
let currentPost = null;
window.addEventListener("message", (event) => {
if (event.data && event.data.type === "hive-shim-context" && event.data.post) {
currentPost = event.data.post;
populateEditor(currentPost);
}
});
// ====================== FALLBACK FETCH ======================
async function fallbackFetch() {
const path = window.location.pathname.replace(/^/shim//, '/');
const match = path.match(//@([/]+)/([/?#]+)/);
if (!match) return;
const author = match[1];
const permlink = match[2];
document.getElementById("author").value = author;
document.getElementById("permlink").value = permlink;
try {
const res = await fetch(HIVE_NODE, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
method: "condenser_api.get_content",
params: [author, permlink],
id: 1
})
});
const data = await res.json();
const post = data.result;
if (post && post.body) {
populateEditor(post);
}
} catch (e) {
console.error(e);
}
}
function populateEditor(post) {
if (!post) return;
document.getElementById("author").value = post.author || "";
document.getElementById("permlink").value = post.permlink || "";
document.getElementById("title").value = post.title || "";
document.getElementById("body").value = post.body || "";
let meta = {};
try { meta = JSON.parse(post.json_metadata || "{}"); } catch(e) {}
document.getElementById("jsonMetadata").value = JSON.stringify(meta, null, 2);
renderPreview(post.body);
logStatus("Post loaded ✓", "success");
}
// ====================== UTILITIES ======================
function logStatus(msg, type = "info") {
const el = document.getElementById("status");
el.textContent = msg;
if (type === "success") el.className = "px-4 py-1.5 rounded-2xl text-sm font-medium bg-emerald-400 text-black";
else if (type === "error") el.className = "px-4 py-1.5 rounded-2xl text-sm font-medium bg-red-500 text-white";
setTimeout(() => el.className = "px-4 py-1.5 rounded-2xl text-sm font-medium bg-zinc-800 text-emerald-400", 4000);
}
function renderPreview(body) {
const el = document.getElementById("preview");
try {
el.innerHTML = marked.parse(body || "", { gfm: true, breaks: true, tables: true });
} catch (e) {
el.innerHTML = <p class="text-red-500">Preview error</p>;
}
}
function refreshPreview() {
renderPreview(document.getElementById("body").value);
}
function refreshAll() {
location.reload();
}
function switchTab(n) {
document.getElementById("tabContent0").classList.toggle("hidden", n !== 0);
document.getElementById("tabContent1").classList.toggle("hidden", n !== 1);
document.getElementById("tab0").classList.toggle("tab-active", n === 0);
document.getElementById("tab1").classList.toggle("tab-active", n === 1);
}
function validateAndFormatJson() {
const ta = document.getElementById("jsonMetadata");
try {
ta.value = JSON.stringify(JSON.parse(ta.value), null, 2);
logStatus("JSON formatted", "success");
} catch (e) {
logStatus("Invalid JSON", "error");
}
}
async function saveWithKeychain() {
if (!window.hive_keychain) return alert("Hive Keychain not detected");
const author = document.getElementById("author").value.trim();
const permlink = document.getElementById("permlink").value.trim();
const title = document.getElementById("title").value.trim();
const body = document.getElementById("body").value.trim();
const jsonMetadata = document.getElementById("jsonMetadata").value.trim();
if (!author || !permlink || !body) {
logStatus("Author, permlink and body are required", "error");
return;
}
const operation = ["comment", {
parent_author: "",
parent_permlink: "hive",
author: author,
permlink: permlink,
title: title,
body: body,
json_metadata: jsonMetadata
}];
window.hive_keychain.requestBroadcast(author, [operation], "Posting", (res) => {
if (res.success) logStatus("✅ Saved to Hive!", "success");
else logStatus("❌ Save failed", "error");
});
}
function copyFullOperation() {
const op = {
parent_author: "",
parent_permlink: "hive",
author: document.getElementById("author").value.trim(),
permlink: document.getElementById("permlink").value.trim(),
title: document.getElementById("title").value.trim(),
body: document.getElementById("body").value.trim(),
json_metadata: document.getElementById("jsonMetadata").value.trim()
};
navigator.clipboard.writeText(JSON.stringify(op, null, 2)).then(() => {
logStatus("✅ Operation copied to clipboard", "success");
});
}
// ====================== INIT ======================
window.addEventListener('load', () => {
tailwind.config = { content: [] };
// Try to get data from shim first, fallback to direct fetch if needed
setTimeout(fallbackFetch, 800);
// Live preview
document.getElementById("body").addEventListener("input", () => renderPreview(document.getElementById("body").value));
});