Skip to content

Commit 42a5e57

Browse files
loading media data works too
1 parent 9d3937c commit 42a5e57

1 file changed

Lines changed: 160 additions & 1 deletion

File tree

src/views/Export/Nostr.vue

Lines changed: 160 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,22 @@
154154
</div>
155155
</div>
156156

157+
<div class="form-check mb-3 mt-3">
158+
<input
159+
class="form-check-input"
160+
type="checkbox"
161+
id="embedMedia"
162+
v-model="embedMedia"
163+
/>
164+
<label class="form-check-label" for="embedMedia">
165+
Embed media files as data URIs
166+
</label>
167+
<div class="form-text text-muted">
168+
Converts images, audio, and video files to embedded data URIs for better
169+
portability.
170+
</div>
171+
</div>
172+
157173
<div class="nostr-actions">
158174
<button class="btn btn-outline-primary me-2" @click="step = 'customize'">
159175
<i class="bi bi-pencil-square me-1"></i> Customize Post
@@ -310,6 +326,9 @@ export default {
310326
// Relay management
311327
relays: ["wss://relay.damus.io", "wss://relay.nostr.band", "wss://nos.lol"],
312328
storeRelays: true,
329+
330+
// Embed media option
331+
embedMedia: true, // Default to true for better portability
313332
};
314333
},
315334
@@ -431,6 +450,141 @@ export default {
431450
this.tags.splice(index, 1);
432451
},
433452
453+
async embedMediaAsDataURIs(content) {
454+
try {
455+
// Get all blob references from the editor
456+
const blobs = this.$parent.$refs.editor.getAllBlobs();
457+
458+
if (!blobs || blobs.length === 0) {
459+
return content; // No media to embed
460+
}
461+
462+
let modifiedContent = content;
463+
464+
// Process each blob and replace references in the content
465+
for (const blob of blobs) {
466+
const fileName = blob.name;
467+
const fileData = blob.data;
468+
469+
// Only process media files
470+
if (!this.isMediaFile(fileName)) {
471+
continue;
472+
}
473+
474+
try {
475+
// Determine the MIME type based on file extension
476+
const mimeType = this.getMimeType(fileName);
477+
478+
// Convert the blob data to a base64 data URI
479+
const dataUri = await this.blobToDataUri(fileData, mimeType);
480+
481+
// Replace only the file references with data URIs
482+
// Escape special characters in the file name for regex
483+
const fileNameEscaped = fileName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
484+
485+
// Match URLs in markdown patterns
486+
// Image syntax: ![alt](fileName)
487+
const imgRegex = new RegExp(
488+
`(\\!\\[[^\\]]*\\]\\()${fileNameEscaped}(\\))`,
489+
"g"
490+
);
491+
modifiedContent = modifiedContent.replace(imgRegex, `$1${dataUri}$2`);
492+
493+
// Link syntax: [text](fileName)
494+
const linkRegex = new RegExp(
495+
`(\\[[^\\]]*\\]\\()${fileNameEscaped}(\\))`,
496+
"g"
497+
);
498+
modifiedContent = modifiedContent.replace(linkRegex, `$1${dataUri}$2`);
499+
500+
// URL references
501+
const urlRegex = new RegExp(
502+
`(src="|href="|url\\(|@import ['"]?)${fileNameEscaped}(['"]?\\)|["'])`,
503+
"g"
504+
);
505+
modifiedContent = modifiedContent.replace(urlRegex, `$1${dataUri}$2`);
506+
} catch (err) {
507+
console.error(`Error converting ${fileName} to data URI:`, err);
508+
}
509+
}
510+
511+
return modifiedContent;
512+
} catch (error) {
513+
console.error("Error embedding media:", error);
514+
return content; // Return original content if there was an error
515+
}
516+
},
517+
518+
isMediaFile(fileName) {
519+
const mediaExtensions = [
520+
// Images
521+
".jpg",
522+
".jpeg",
523+
".png",
524+
".gif",
525+
".svg",
526+
".webp",
527+
".bmp",
528+
// Audio
529+
".mp3",
530+
".wav",
531+
".ogg",
532+
".m4a",
533+
// Video
534+
".mp4",
535+
".webm",
536+
".ogv",
537+
".mov",
538+
];
539+
540+
const extension = "." + fileName.split(".").pop().toLowerCase();
541+
return mediaExtensions.includes(extension);
542+
},
543+
544+
getMimeType(fileName) {
545+
const extension = fileName.split(".").pop().toLowerCase();
546+
547+
const mimeTypes = {
548+
// Images
549+
jpg: "image/jpeg",
550+
jpeg: "image/jpeg",
551+
png: "image/png",
552+
gif: "image/gif",
553+
svg: "image/svg+xml",
554+
webp: "image/webp",
555+
bmp: "image/bmp",
556+
// Audio
557+
mp3: "audio/mpeg",
558+
wav: "audio/wav",
559+
ogg: "audio/ogg",
560+
m4a: "audio/mp4",
561+
// Video
562+
mp4: "video/mp4",
563+
webm: "video/webm",
564+
ogv: "video/ogg",
565+
mov: "video/quicktime",
566+
};
567+
568+
return mimeTypes[extension] || "application/octet-stream";
569+
},
570+
571+
blobToDataUri(blob, mimeType) {
572+
return new Promise((resolve, reject) => {
573+
// Use FileReader's built-in base64 encoding
574+
const reader = new FileReader();
575+
576+
reader.onload = function () {
577+
resolve(reader.result);
578+
};
579+
580+
reader.onerror = function () {
581+
reject(new Error("Failed to convert file to data URI"));
582+
};
583+
584+
reader.readAsDataURL(new Blob([blob], { type: mimeType }));
585+
});
586+
},
587+
434588
publishToNostr() {
435589
if (!this.publicKey || !this.privateKey) {
436590
alert("Please enter both your public and private keys before publishing.");
@@ -457,7 +611,12 @@ export default {
457611
provider.on("synced", async (_: any) => {
458612
try {
459613
const metaData = await meta;
460-
const contentData = yDoc.getText(this.storageId).toJSON();
614+
let contentData = yDoc.getText(this.storageId).toJSON();
615+
616+
// Embed media if option is checked
617+
if (this.embedMedia) {
618+
contentData = await this.embedMediaAsDataURIs(contentData);
619+
}
461620
462621
const pool = new SimplePool();
463622
const relays = [...this.relays];

0 commit comments

Comments
 (0)