diff --git a/README.md b/README.md index ae8b1eea..4c0a98d6 100644 --- a/README.md +++ b/README.md @@ -408,24 +408,7 @@ const document = await FileAttachment("index.html").html(); *Note: this function is not part of the Observable standard library (in notebooks), but is provided by this module as a means for defining custom file attachment implementations when working directly with the Observable runtime.* -Returns a [*FileAttachment*](#FileAttachment) function given the specified *resolve* function. The *resolve* function is an async function that takes a *name* and returns a URL at which the file of that name can be loaded. For example: - -```js -const FileAttachment = FileAttachments((name) => - `https://my.server/notebooks/demo/${name}` -); -``` - -Or, with a more complex example, calling an API to produce temporary URLs: - -```js -const FileAttachment = FileAttachments(async (name) => - if (cachedUrls.has(name)) return cachedUrls.get(name); - const url = await fetchSignedFileUrl(notebookId, name); - cachedUrls.set(name, url); - return url; -); -``` +Returns a [*FileAttachment*](#FileAttachment) function given the specified *resolve* function. The *resolve* function is a function that takes a *name* and returns either an object {url, mimeType} representing the requested file if it exists, or null if the file does not exist. The url field (though not the object itself!) may be represented as a Promise if the URL is not yet known, such as for a file that is currently being uploaded. The mimeType must be a string, or undefined if the mime type is not known. For backwards compatibility, the *resolve* function may instead return just a URL, either a string or a promise. ### Generators diff --git a/src/fileAttachment.js b/src/fileAttachment.js index efe2df43..9cb3f88a 100644 --- a/src/fileAttachment.js +++ b/src/fileAttachment.js @@ -18,8 +18,9 @@ async function dsv(file, delimiter, {array = false, typed = false} = {}) { } export class AbstractFile { - constructor(name) { + constructor(name, mimeType) { Object.defineProperty(this, "name", {value: name, enumerable: true}); + if (mimeType !== undefined) Object.defineProperty(this, "mimeType", {value: mimeType + "", enumerable: true}); } async blob() { return (await remote_fetch(this)).blob(); @@ -79,8 +80,8 @@ export class AbstractFile { } class FileAttachment extends AbstractFile { - constructor(url, name) { - super(name); + constructor(url, name, mimeType) { + super(name, mimeType); Object.defineProperty(this, "_url", {value: url}); } async url() { @@ -95,9 +96,13 @@ export function NoFileAttachments(name) { export default function FileAttachments(resolve) { return Object.assign( name => { - const url = resolve(name += ""); // Returns a Promise, string, or null. - if (url == null) throw new Error(`File not found: ${name}`); - return new FileAttachment(url, name); + const result = resolve(name += ""); + if (result == null) throw new Error(`File not found: ${name}`); + if (typeof result === "object" && "url" in result) { + const {url, mimeType} = result; + return new FileAttachment(url, name, mimeType); + } + return new FileAttachment(result, name); }, {prototype: FileAttachment.prototype} // instanceof );