Upload markdown blobs as text/plain so browsers render them inline; keep the kind 1063 m tag as text/markdown via a mimeType override
This commit is contained in:
@@ -204,6 +204,8 @@ Blossom is a protocol for storing blobs (files) on media servers using your Nost
|
||||
2. Click **Publish to Blossom (with header)** (or run **Nostr: Publish to Blossom (with header)**).
|
||||
3. The front-matter block is stripped, the markdown body is uploaded as a blob, and a kind 1063 event is built from the descriptor + front-matter (title, author, language, year, cover, tags) and published to your relays.
|
||||
|
||||
> **Markdown MIME handling:** markdown blobs are uploaded as `text/plain` so browsers render them inline (no mainstream browser displays `text/markdown`, and servers send `X-Content-Type-Options: nosniff` which forbids guessing). The kind 1063 event's `m` tag still advertises `text/markdown`, so Nostr clients that render markdown can detect the file's true type.
|
||||
|
||||
**Blossom servers**:
|
||||
|
||||
- The **Blossom Servers** section shows your servers with checkboxes — exactly like the Relays section. Every checked server receives the upload.
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "nostr-publish",
|
||||
"displayName": "Nostr Publish",
|
||||
"description": "Publish files to Nostr from Codium/VSCode. Phase 0: raw unsigned event JSON. Phase 1: long-form notes (.md). Phase 2: signer (Rust n_signer port) signing backend.",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"publisher": "laantungir",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
+19
-2
@@ -1869,6 +1869,12 @@ function resolveBlossomServers(): string[] {
|
||||
* 3. Uploads to all checked Blossom servers concurrently.
|
||||
* 4. Builds a kind 1063 NIP-94 event from the first successful descriptor.
|
||||
* 5. Signs and publishes the 1063 event to the enabled relays.
|
||||
*
|
||||
* Markdown bodies are uploaded as `text/plain` (browsers render text/plain
|
||||
* inline; no mainstream browser displays text/markdown, and servers send
|
||||
* `X-Content-Type-Options: nosniff` which forbids guessing). The kind 1063
|
||||
* event's `m` tag still advertises `text/markdown` so Nostr clients that
|
||||
* render markdown can detect the file's true semantic type.
|
||||
*/
|
||||
async function uploadAndPublishMetadata(
|
||||
state: StateService,
|
||||
@@ -1877,6 +1883,12 @@ async function uploadAndPublishMetadata(
|
||||
body: Buffer,
|
||||
mime: string,
|
||||
): Promise<void> {
|
||||
// Hybrid MIME handling: markdown uploads go out as text/plain for
|
||||
// browser-renderability; the semantic type is preserved for the m tag.
|
||||
const isMarkdown = mime === "text/markdown";
|
||||
const uploadMime = isMarkdown ? "text/plain" : mime;
|
||||
const semanticMime = isMarkdown ? "text/markdown" : undefined;
|
||||
|
||||
// Require sign-in.
|
||||
let signer = getSigner(state, config);
|
||||
if (!signer) {
|
||||
@@ -1938,7 +1950,7 @@ async function uploadAndPublishMetadata(
|
||||
results = await uploadBlobToMany(
|
||||
{
|
||||
body,
|
||||
contentType: mime,
|
||||
contentType: uploadMime,
|
||||
sha256,
|
||||
signer,
|
||||
authTokenTtlSec: config.blossomAuthTokenTtlSec,
|
||||
@@ -1966,7 +1978,12 @@ async function uploadAndPublishMetadata(
|
||||
|
||||
// Build the kind 1063 event from the first successful descriptor.
|
||||
const descriptor: BlobDescriptor = ok[0].descriptor!;
|
||||
const template = buildFileMetadataEvent({ descriptor, meta, publishedAt });
|
||||
const template = buildFileMetadataEvent({
|
||||
descriptor,
|
||||
meta,
|
||||
publishedAt,
|
||||
mimeType: semanticMime,
|
||||
});
|
||||
|
||||
// Validate.
|
||||
const validation = validateEventTemplate(template);
|
||||
|
||||
+23
-1
@@ -13,6 +13,17 @@ export interface FileMetadataInput {
|
||||
meta: BlossomFrontMatter;
|
||||
/** First-publish timestamp (unix seconds). Reused on updates. */
|
||||
publishedAt: number;
|
||||
/**
|
||||
* Override for the `m` tag (the file's semantic MIME type).
|
||||
*
|
||||
* Markdown blobs are uploaded as `text/plain` so browsers render them
|
||||
* inline (no mainstream browser displays `text/markdown`, and
|
||||
* `X-Content-Type-Options: nosniff` forbids guessing). The server then
|
||||
* reports `type: text/plain` in the descriptor. This override keeps the
|
||||
* Nostr `m` tag advertising the true semantic type (`text/markdown`) so
|
||||
* clients that render markdown can still detect it.
|
||||
*/
|
||||
mimeType?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,10 +58,21 @@ export function buildFileMetadataEvent(input: FileMetadataInput): EventTemplate
|
||||
|
||||
// Ensure the core tags exist even if nip94 omitted them.
|
||||
ensureTag(tags, "url", descriptor.url);
|
||||
ensureTag(tags, "m", descriptor.type);
|
||||
ensureTag(tags, "m", input.mimeType ?? descriptor.type);
|
||||
ensureTag(tags, "x", descriptor.sha256);
|
||||
ensureTag(tags, "size", String(descriptor.size));
|
||||
|
||||
// When an explicit mimeType override is given, replace any server-provided
|
||||
// `m` tag (BUD-08 nip94 tags carry the upload Content-Type, which for
|
||||
// markdown is deliberately text/plain — see FileMetadataInput.mimeType).
|
||||
if (input.mimeType) {
|
||||
for (const t of tags) {
|
||||
if (t[0] === "m") {
|
||||
t[1] = input.mimeType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Descriptive metadata from front-matter.
|
||||
tags.push(["title", meta.title]);
|
||||
if (meta.summary) {
|
||||
|
||||
Reference in New Issue
Block a user