Skip to content

Commit

Permalink
feat: adds fallback image
Browse files Browse the repository at this point in the history
  • Loading branch information
akoenig committed May 31, 2024
1 parent fb3c306 commit 306cb64
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 19 deletions.
9 changes: 9 additions & 0 deletions __snapshots__/mod_test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,12 @@ snapshot[`should archive a markdown file with all images 1`] = `
<img src="data:image/gif;base64,R0lGODlhFAFuAPcAAPf39//7/+fn59bT1u/r787Lzq0UAN7b3hhFrRhJtRA0hBA8lMYYALWytffz94wQAMa+vb26vRhNxufj5+/v78bDxvfz772+vcbHxghRCM7PzggkYyFZ1tYkCNbX1hhRzpyenO+6AN7f3gBlANauAGOW7zFl1kp95wg8pbW2tZyanHOi797f5zlx3msMAAB9CP/PAL22va2mraWipedJMSlRtf91Y72WAFqK76WmpRBFta2qrcaeAK2urfdpUuc8If39/e9ZQmPTY94wGFrLWrUkEISq9/n5+ZR5ABCWGLWOAIRpAGNRAClJlK2GAL0sGGssITG2OaWCAK3H9848KaW+762qpcbX/73jvWN5pGvTc/eGc//vCFppfIwgEJRBMYSGlPeWhJSSjHN5jMaSjKWmrb3P787V1ilBa4R9c9nXy+rq6ntNQu/y95Su3svO1kphlISa1t7b1vf7/3uGraWGexdAmoySlDm6QrWyraWqrRiiIWtpa0LDStrl/1JtrcbHzpxlWntlUklVcztZk3vbhISStfffWufr786upVmB1ta2taW21tLS0ve6rd7j5zlhtfemnJSetefn78LCw7W2vdnZ2bVFMf/3//f3/8Z1a9bb59bb3eHh4a2WjM7HxoR1OSGqKbW6xvT09JyGQrXD3v/r5+fk3s7rzufb3q2ytffXKbWiY6inp1p1WrWmjP/LxnOOzsC/v+/n56qSMffXzr2eEP/3hPHx8f//987T57m4ubVZShk5f97LjO/HKQ03ks7DpdZlWv/vMd7LxqWuvefr/86uKd66QgwyhcnJyf/ztbKxsrWqpe/37//z7///75zTpf/397Szsvfn1ozLjO/jlN7v3qWko62OEO/r5//73tbX3vf37wotd1qqYxlNvYyujN7X1qKgoHuWe5KUmkKOQhZKvildMRh5IZqZmcXEwZ2amaajoNfU0QMaTAsiV5STlSo+bZibpSti2puZl5u28pGs5pqbn9Hd8/fr94WFi7i1sBguYfP09v///ywAAAAAFAFuAAAI/wD/CRxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzatzIsaPHjyBDihxJsqTJkyhTqlzJsqXLlzBjypxJs6bNmzhz6tzJs6fPn0CDCh1KtKjRo0iDYinEtJAWLUSIfJv6DVFShnOMXTEzpYrX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" alt="Google Logo 2006" />
'
`;

snapshot[`should use a fallback image if downloading the image failed 1`] = `
'# Testing
![](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAADN0lEQVR4nO2Y3U9SYRzH+T/qstq6sYv+gmKkKaAmaLalazJNjdNoikAo0PSyteWFF3mRlk0Kp64ZDGsElq/LeLtpxYbrwoZ4lq04cG5+7XnwIfDEi43ToTy/7bs9nPNs5/N5Xsb2k0jEEkssscQqdzVq9cebKNMzldb0TUWZQNBoMcPMJcp8snR4rXFXcHAqN4hJrTMdKyqAV54ygW10HCLRLaBpWtBEoltgHX1AROxFBcixqQR4OksC7wJl+lpcYH/LhIamD4RwHV2B0y8SfzW0KED/Zzug8CVA4WPgvIeBKtfvP4qeo/dortzHgLTAXJTrKwH4EhuG7Z1h6FwJ8itQv8RkQuCQkNzLwDkPA2fdaejseWRulZMLf8aVgFh8GGBPjxOLj+TM41XgsKnzpkUOysazBHbiI+kF8aV3sKIE8sXmD2IJBI/G2e94FVB6PoNsSgOyJxo85kOO5k3Al4AL9j6QPqzGaXDcAksoCcYAC+1ryRyIlmUGdO9TMBRiYSiUAt1mCprfCiRgCLBgCbPQ7p7OwJNoXk2DLcyCNcxiyMFQCsxBBM3i59lBIh3rSRjwp8X0gRS0rf4Sb1tl8LuyC6CP6zc+gWxSwRGQTcqhf/0DB/YwsYRZvEjkd9kFLMEfoLR3ceBJFHYNDIW+c8Bu+7dBvdALqoUbeFyqEF1ugTbnWF54knbnWC5IKAVXXGaQzzbgtLqMYA0nhRGQTtQUFUBzdMsbGYjuJUcGnqT7jYN7LwIx6PXYoMdzB4/5ESgGv5/aqVYY9O+CYTMCynk1R0A5p4aBdx9zdknrvQediz04Wu9dvEuCCaA0z1tBvXCTA0+C7oNl/770r7ky8CR9ay5hBVBqniryCqBoXo+D2R+FrpcURwA9E1wA3Ye6mfr8EnON0LE4wIEnEV4A/T88vlhwF+rnmkDj7q5cAXyUpuUFJVTPL1e2gHSiGmodyoISV53XKlgAHaVHNSCfzX8flOg+uLv4FRA6qiMj0EQZ9/711uIMmowaqpUgEYlugeV+urmLGs9FBVp6DadUlCnOd7v8z9rrhhOSUgr14VErmxwnQcEp4x5a+ZLhxRJLLLHEkhyifgJu5rlgZTGQ8gAAAABJRU5ErkJggg==)
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAADN0lEQVR4nO2Y3U9SYRzH+T/qstq6sYv+gmKkKaAmaLalazJNjdNoikAo0PSyteWFF3mRlk0Kp64ZDGsElq/LeLtpxYbrwoZ4lq04cG5+7XnwIfDEi43ToTy/7bs9nPNs5/N5Xsb2k0jEEkssscQqdzVq9cebKNMzldb0TUWZQNBoMcPMJcp8snR4rXFXcHAqN4hJrTMdKyqAV54ygW10HCLRLaBpWtBEoltgHX1AROxFBcixqQR4OksC7wJl+lpcYH/LhIamD4RwHV2B0y8SfzW0KED/Zzug8CVA4WPgvIeBKtfvP4qeo/dortzHgLTAXJTrKwH4EhuG7Z1h6FwJ8itQv8RkQuCQkNzLwDkPA2fdaejseWRulZMLf8aVgFh8GGBPjxOLj+TM41XgsKnzpkUOysazBHbiI+kF8aV3sKIE8sXmD2IJBI/G2e94FVB6PoNsSgOyJxo85kOO5k3Al4AL9j6QPqzGaXDcAksoCcYAC+1ryRyIlmUGdO9TMBRiYSiUAt1mCprfCiRgCLBgCbPQ7p7OwJNoXk2DLcyCNcxiyMFQCsxBBM3i59lBIh3rSRjwp8X0gRS0rf4Sb1tl8LuyC6CP6zc+gWxSwRGQTcqhf/0DB/YwsYRZvEjkd9kFLMEfoLR3ceBJFHYNDIW+c8Bu+7dBvdALqoUbeFyqEF1ugTbnWF54knbnWC5IKAVXXGaQzzbgtLqMYA0nhRGQTtQUFUBzdMsbGYjuJUcGnqT7jYN7LwIx6PXYoMdzB4/5ESgGv5/aqVYY9O+CYTMCynk1R0A5p4aBdx9zdknrvQediz04Wu9dvEuCCaA0z1tBvXCTA0+C7oNl/770r7ky8CR9ay5hBVBqniryCqBoXo+D2R+FrpcURwA9E1wA3Ye6mfr8EnON0LE4wIEnEV4A/T88vlhwF+rnmkDj7q5cAXyUpuUFJVTPL1e2gHSiGmodyoISV53XKlgAHaVHNSCfzX8flOg+uLv4FRA6qiMj0EQZ9/711uIMmowaqpUgEYlugeV+urmLGs9FBVp6DadUlCnOd7v8z9rrhhOSUgr14VErmxwnQcEp4x5a+ZLhxRJLLLHEkhyifgJu5rlgZTGQ8gAAAABJRU5ErkJggg==" />
'
`;
4 changes: 2 additions & 2 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@openformation/markdown-archiver",
"description": "Library for creating self-contained markdown files by embedding images.",
"version": "1.0.3",
"version": "1.0.4",
"exports": "./mod.ts",
"imports": {
"@std/encoding": "jsr:@std/encoding@^0.224.2",
Expand All @@ -21,4 +21,4 @@
"__snapshots__"
]
}
}
}
21 changes: 18 additions & 3 deletions lib/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,18 @@ const ImageDataUri = Brand.refined<ImageDataUri>(
export function fetchImage(url: string) {
return Effect.gen(function* () {
const response = yield* Effect.tryPromise({
try: () => {
return fetch(url);
},
try: () => fetch(url),
catch: (cause: unknown) =>
new FetchImageError(`Failed to fetch image at ${url}: ${cause}`, {
cause,
}),
});

if (!response.ok) {
if (response.body) {
yield* Effect.promise(response.body.cancel.bind(response.body));
}

return yield* Effect.fail(
new FetchImageError(
`Failed to fetch image at ${url} as the server responded with a status code of ${response.status}.`,
Expand Down Expand Up @@ -76,10 +78,22 @@ export class ImageService extends Context.Tag("ImageService")<
readonly fetch: (
url: string,
) => Effect.Effect<ImageDataUri, FetchImageError>;
readonly getFallbackImageDataUri: () => Effect.Effect<
ImageDataUri
>;
}
>() {}

function getFallbackImageDataUri() {
return Effect.succeed(
ImageDataUri(
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAADN0lEQVR4nO2Y3U9SYRzH+T/qstq6sYv+gmKkKaAmaLalazJNjdNoikAo0PSyteWFF3mRlk0Kp64ZDGsElq/LeLtpxYbrwoZ4lq04cG5+7XnwIfDEi43ToTy/7bs9nPNs5/N5Xsb2k0jEEkssscQqdzVq9cebKNMzldb0TUWZQNBoMcPMJcp8snR4rXFXcHAqN4hJrTMdKyqAV54ygW10HCLRLaBpWtBEoltgHX1AROxFBcixqQR4OksC7wJl+lpcYH/LhIamD4RwHV2B0y8SfzW0KED/Zzug8CVA4WPgvIeBKtfvP4qeo/dortzHgLTAXJTrKwH4EhuG7Z1h6FwJ8itQv8RkQuCQkNzLwDkPA2fdaejseWRulZMLf8aVgFh8GGBPjxOLj+TM41XgsKnzpkUOysazBHbiI+kF8aV3sKIE8sXmD2IJBI/G2e94FVB6PoNsSgOyJxo85kOO5k3Al4AL9j6QPqzGaXDcAksoCcYAC+1ryRyIlmUGdO9TMBRiYSiUAt1mCprfCiRgCLBgCbPQ7p7OwJNoXk2DLcyCNcxiyMFQCsxBBM3i59lBIh3rSRjwp8X0gRS0rf4Sb1tl8LuyC6CP6zc+gWxSwRGQTcqhf/0DB/YwsYRZvEjkd9kFLMEfoLR3ceBJFHYNDIW+c8Bu+7dBvdALqoUbeFyqEF1ugTbnWF54knbnWC5IKAVXXGaQzzbgtLqMYA0nhRGQTtQUFUBzdMsbGYjuJUcGnqT7jYN7LwIx6PXYoMdzB4/5ESgGv5/aqVYY9O+CYTMCynk1R0A5p4aBdx9zdknrvQediz04Wu9dvEuCCaA0z1tBvXCTA0+C7oNl/770r7ky8CR9ay5hBVBqniryCqBoXo+D2R+FrpcURwA9E1wA3Ye6mfr8EnON0LE4wIEnEV4A/T88vlhwF+rnmkDj7q5cAXyUpuUFJVTPL1e2gHSiGmodyoISV53XKlgAHaVHNSCfzX8flOg+uLv4FRA6qiMj0EQZ9/711uIMmowaqpUgEYlugeV+urmLGs9FBVp6DadUlCnOd7v8z9rrhhOSUgr14VErmxwnQcEp4x5a+ZLhxRJLLLHEkhyifgJu5rlgZTGQ8gAAAABJRU5ErkJggg==",
),
);
}

const ImageServiceServer = ImageService.of({
getFallbackImageDataUri,
fetch(url: string) {
return Effect.gen(function* () {
const { buffer, mimeType } = yield* fetchImage(url);
Expand All @@ -92,6 +106,7 @@ const ImageServiceServer = ImageService.of({
});

const ImageServiceBrowser = ImageService.of({
getFallbackImageDataUri,
fetch(url: string) {
return Effect.gen(function* () {
const { buffer } = yield* fetchImage(url);
Expand Down
10 changes: 3 additions & 7 deletions lib/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,15 @@ function makeServices() {
return Layer.mergeAll(imageService, markdownService, transformerService);
}

/**
* The archiver result type. The effect contains either a `string` with
* the archived markdown or a `FetchImageError` if fetching an image failed.
*/
export type Archiver = Effect.Effect<string, FetchImageError, never>;

/**
* Takes source markdown contents and makes it self-contained by embedding images.
*
* @param markdown {string} The markdown to archive.
* @returns {string} The markdown with embedded images.
*/
export function archive(markdown: string): Archiver {
export function archive(
markdown: string,
): Effect.Effect<string> {
const run = Effect.gen(function* () {
const markdownService = yield* MarkdownService;

Expand Down
13 changes: 9 additions & 4 deletions lib/transformer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Html, Image, Root } from "mdast";
import type { FetchImageError } from "./image.ts";

import { Context, Effect, Layer } from "effect";
import { visit } from "unist-util-visit";
Expand All @@ -11,15 +10,18 @@ export class TransformerService extends Context.Tag("TransformerService")<
{
readonly embedImages: (
ast: Root,
) => Effect.Effect<void, FetchImageError, ImageService>;
) => Effect.Effect<void, never, ImageService>;
}
>() {}

function processImage(image: Image) {
return Effect.gen(function* () {
const imageService = yield* ImageService;

const dataUri = yield* imageService.fetch(image.url);
const dataUri = yield* Effect.orElse(
imageService.fetch(image.url),
() => imageService.getFallbackImageDataUri(),
);

image.url = dataUri;
});
Expand All @@ -37,7 +39,10 @@ function processHtmlImage({
if (match) {
const url = match[1];

const dataUri = yield* imageService.fetch(url);
const dataUri = yield* Effect.orElse(
imageService.fetch(url),
() => imageService.getFallbackImageDataUri(),
);

node.value = node.value.replace(url, dataUri);
}
Expand Down
3 changes: 0 additions & 3 deletions mod.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
export type { FetchImageError } from "./lib/image.ts";
export type { Archiver } from "./lib/mod.ts";

export { archive } from "./lib/mod.ts";
14 changes: 14 additions & 0 deletions mod_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,17 @@ Deno.test("should archive a markdown file with all images", async (t) => {

await assertSnapshot(t, result);
});

Deno.test("should use a fallback image if downloading the image failed", async (t) => {
const program = archive(`
# Testing
![](https://example.com/not-found.jpg)
<img src="https://example.com/not-found.jpg" />
`);

const result = await Effect.runPromise(program);

await assertSnapshot(t, result);
});

0 comments on commit 306cb64

Please sign in to comment.