From bfdb699694ed08baafa15410731cc25f7bac66d5 Mon Sep 17 00:00:00 2001 From: jashkenas Date: Fri, 18 Oct 2019 10:47:57 -0700 Subject: [PATCH 01/23] Adding a basic implementation of remote file attachments, with resolve function --- src/fileAttachment.js | 40 ++++++++++++++++++++++++++++++++++++++++ src/library.js | 2 ++ test/index-test.js | 1 + 3 files changed, 43 insertions(+) create mode 100644 src/fileAttachment.js diff --git a/src/fileAttachment.js b/src/fileAttachment.js new file mode 100644 index 00000000..1c3ee267 --- /dev/null +++ b/src/fileAttachment.js @@ -0,0 +1,40 @@ +async function remote_fetch(url) { + const response = await fetch(url); + if (!response.ok) throw new Error("Unable to load file"); + return response; +} + +export default function FileAttachment(resolve) { + return class FileAttachment { + constructor(name) { + Object.defineProperties(this, { + name: {value: name, enumerable: true} + }); + } + async url() { + return resolve(this.name); + } + async blob() { + return (await remote_fetch(await this.url())).blob(); + } + async arrayBuffer() { + return (await remote_fetch(await this.url())).arrayBuffer(); + } + async text() { + return (await remote_fetch(await this.url())).text(); + } + async json() { + return (await remote_fetch(await this.url())).json(); + } + async image() { + return new Promise((resolve, reject) => { + const img = document.createElement("img"); + img.onload = () => resolve(img); + img.onerror = () => reject(new Error("Unable to load image")); + this.url().then(url => { + img.src = url; + }); + }); + } + }; +} diff --git a/src/library.js b/src/library.js index 1555a278..3b67efdf 100644 --- a/src/library.js +++ b/src/library.js @@ -1,5 +1,6 @@ import constant from "./constant"; import DOM from "./dom/index"; +import FileAttachment from "./fileAttachment"; import Files from "./files/index"; import Generators from "./generators/index"; import html from "./html"; @@ -17,6 +18,7 @@ export default function Library(resolver) { const require = requirer(resolver); Object.defineProperties(this, { DOM: {value: DOM, writable: true, enumerable: true}, + FileAttachment: {value: constant(FileAttachment), writable: true, enumerable: true}, Files: {value: Files, writable: true, enumerable: true}, Generators: {value: Generators, writable: true, enumerable: true}, html: {value: constant(html), writable: true, enumerable: true}, diff --git a/test/index-test.js b/test/index-test.js index 3c19fa2c..c8002d5a 100644 --- a/test/index-test.js +++ b/test/index-test.js @@ -4,6 +4,7 @@ import Library from "../src/library"; test("new Library returns a library with the expected keys", async t => { t.deepEqual(Object.keys(new Library()).sort(), [ "DOM", + "FileAttachment", "Files", "Generators", "Mutable", From d0c10dc4c987cedddfa5eea31d7df7367ad4aa00 Mon Sep 17 00:00:00 2001 From: jashkenas Date: Mon, 21 Oct 2019 11:07:42 -0700 Subject: [PATCH 02/23] Don't expose raw FileAttachment from the standard library --- src/index.js | 1 + src/library.js | 2 -- test/index-test.js | 9 +++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/index.js b/src/index.js index 2897b3e8..1cfe8209 100644 --- a/src/index.js +++ b/src/index.js @@ -1 +1,2 @@ +export {default as FileAttachment} from "./fileAttachment"; export {default as Library} from "./library"; diff --git a/src/library.js b/src/library.js index 3b67efdf..1555a278 100644 --- a/src/library.js +++ b/src/library.js @@ -1,6 +1,5 @@ import constant from "./constant"; import DOM from "./dom/index"; -import FileAttachment from "./fileAttachment"; import Files from "./files/index"; import Generators from "./generators/index"; import html from "./html"; @@ -18,7 +17,6 @@ export default function Library(resolver) { const require = requirer(resolver); Object.defineProperties(this, { DOM: {value: DOM, writable: true, enumerable: true}, - FileAttachment: {value: constant(FileAttachment), writable: true, enumerable: true}, Files: {value: Files, writable: true, enumerable: true}, Generators: {value: Generators, writable: true, enumerable: true}, html: {value: constant(html), writable: true, enumerable: true}, diff --git a/test/index-test.js b/test/index-test.js index c8002d5a..d416d084 100644 --- a/test/index-test.js +++ b/test/index-test.js @@ -1,10 +1,9 @@ import { test } from "tap"; -import Library from "../src/library"; +import {Library, FileAttachment} from "../src"; test("new Library returns a library with the expected keys", async t => { t.deepEqual(Object.keys(new Library()).sort(), [ "DOM", - "FileAttachment", "Files", "Generators", "Mutable", @@ -20,3 +19,9 @@ test("new Library returns a library with the expected keys", async t => { ]); t.end(); }); + +test("FileAttachment is exported by stdlib/index", t => { + t.equal(typeof FileAttachment, "function"); + t.equal(FileAttachment.name, "FileAttachment"); + t.end(); +}); From 811d89ba1722bfb5c02cfb1fcfd5d2b8c683b55d Mon Sep 17 00:00:00 2001 From: jashkenas Date: Mon, 21 Oct 2019 14:38:12 -0700 Subject: [PATCH 03/23] Bump d3-require to latest --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 72021df1..c4f258d0 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "dist/**/*.js" ], "dependencies": { - "d3-require": "^1.2.0", + "d3-require": "^1.2.4", "marked": "https://github.com/observablehq/marked.git#94c6b946f462fd25db4465d71a6859183f86c57f" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index afb2cab6..d6211a74 100644 --- a/yarn.lock +++ b/yarn.lock @@ -818,10 +818,10 @@ csstype@^2.2.0: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.6.tgz#c34f8226a94bbb10c32cc0d714afdf942291fc41" integrity sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg== -d3-require@^1.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/d3-require/-/d3-require-1.2.3.tgz#65a1c2137e69c5c442f1ca2a04f72a3965124c16" - integrity sha512-zfat3WTZRZmh7jCrTIhg4zZvivA0DZvez8lZq0JwYG9aW8LcSUFO4eiPnL5F2MolHcLE8CLfEt06sPP8N7y3AQ== +d3-require@^1.2.4: + version "1.2.4" + resolved "https://registry.npmjs.org/d3-require/-/d3-require-1.2.4.tgz#59afc591d5089f99fecd8c45ef7539e1fee112b3" + integrity sha512-8UseEGCkBkBxIMouLMPONUBmU8DUPC1q12LARV1Lk/2Jwa32SVgmRfX8GdIeR06ZP+CG85YD3N13K2s14qCNyA== dashdash@^1.12.0: version "1.14.1" From 590fffdb0f6f480360005d1d01d59eb41fd2d32a Mon Sep 17 00:00:00 2001 From: jashkenas Date: Tue, 22 Oct 2019 14:49:08 -0700 Subject: [PATCH 04/23] Tweak ResolveFileAttachment Implementation Strategy --- src/fileAttachment.js | 67 ++++++++++++++++++++++--------------------- src/index.js | 2 +- test/index-test.js | 8 +++--- 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/src/fileAttachment.js b/src/fileAttachment.js index 1c3ee267..70a5df39 100644 --- a/src/fileAttachment.js +++ b/src/fileAttachment.js @@ -4,37 +4,40 @@ async function remote_fetch(url) { return response; } -export default function FileAttachment(resolve) { - return class FileAttachment { - constructor(name) { - Object.defineProperties(this, { - name: {value: name, enumerable: true} +class FileAttachment { + constructor(resolve, name) { + Object.defineProperties(this, { + _resolve: {value: resolve}, + name: {value: name, enumerable: true} + }); + } + async url() { + return this._resolve(this.name); + } + async blob() { + return (await remote_fetch(await this.url())).blob(); + } + async arrayBuffer() { + return (await remote_fetch(await this.url())).arrayBuffer(); + } + async text() { + return (await remote_fetch(await this.url())).text(); + } + async json() { + return (await remote_fetch(await this.url())).json(); + } + async image() { + return new Promise((resolve, reject) => { + const img = document.createElement("img"); + img.onload = () => resolve(img); + img.onerror = () => reject(new Error("Unable to load image")); + this.url().then(url => { + img.src = url; }); - } - async url() { - return resolve(this.name); - } - async blob() { - return (await remote_fetch(await this.url())).blob(); - } - async arrayBuffer() { - return (await remote_fetch(await this.url())).arrayBuffer(); - } - async text() { - return (await remote_fetch(await this.url())).text(); - } - async json() { - return (await remote_fetch(await this.url())).json(); - } - async image() { - return new Promise((resolve, reject) => { - const img = document.createElement("img"); - img.onload = () => resolve(img); - img.onerror = () => reject(new Error("Unable to load image")); - this.url().then(url => { - img.src = url; - }); - }); - } - }; + }); + } +} + +export default function ResolveFileAttachment(resolve) { + return (name) => new FileAttachment(resolve, name); } diff --git a/src/index.js b/src/index.js index 1cfe8209..ae23ad91 100644 --- a/src/index.js +++ b/src/index.js @@ -1,2 +1,2 @@ -export {default as FileAttachment} from "./fileAttachment"; +export {default as ResolveFileAttachment} from "./fileAttachment"; export {default as Library} from "./library"; diff --git a/test/index-test.js b/test/index-test.js index d416d084..9ba54751 100644 --- a/test/index-test.js +++ b/test/index-test.js @@ -1,5 +1,5 @@ import { test } from "tap"; -import {Library, FileAttachment} from "../src"; +import {Library, ResolveFileAttachment} from "../src"; test("new Library returns a library with the expected keys", async t => { t.deepEqual(Object.keys(new Library()).sort(), [ @@ -20,8 +20,8 @@ test("new Library returns a library with the expected keys", async t => { t.end(); }); -test("FileAttachment is exported by stdlib/index", t => { - t.equal(typeof FileAttachment, "function"); - t.equal(FileAttachment.name, "FileAttachment"); +test("ResolveFileAttachment is exported by stdlib/index", t => { + t.equal(typeof ResolveFileAttachment, "function"); + t.equal(ResolveFileAttachment.name, "ResolveFileAttachment"); t.end(); }); From 793fd80711d028bceb5f2d9b058561e84321517a Mon Sep 17 00:00:00 2001 From: jashkenas Date: Wed, 23 Oct 2019 09:53:29 -0700 Subject: [PATCH 05/23] Throw an error when resolve() fails to find a file by name. --- src/fileAttachment.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/fileAttachment.js b/src/fileAttachment.js index 70a5df39..3cbf20a2 100644 --- a/src/fileAttachment.js +++ b/src/fileAttachment.js @@ -12,7 +12,9 @@ class FileAttachment { }); } async url() { - return this._resolve(this.name); + const url = await this._resolve(this.name); + if (url == null) throw new Error(`Unknown file: ${this.name}`); + return url; } async blob() { return (await remote_fetch(await this.url())).blob(); From e06f7882546784fed0019cba5d85e23917c3baa7 Mon Sep 17 00:00:00 2001 From: jashkenas Date: Wed, 23 Oct 2019 10:02:45 -0700 Subject: [PATCH 06/23] Rename ResolveFileAttachment to FileAttachments --- src/fileAttachment.js | 2 +- src/index.js | 2 +- test/index-test.js | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/fileAttachment.js b/src/fileAttachment.js index 3cbf20a2..29a2c18f 100644 --- a/src/fileAttachment.js +++ b/src/fileAttachment.js @@ -40,6 +40,6 @@ class FileAttachment { } } -export default function ResolveFileAttachment(resolve) { +export default function FileAttachments(resolve) { return (name) => new FileAttachment(resolve, name); } diff --git a/src/index.js b/src/index.js index ae23ad91..fa926c0f 100644 --- a/src/index.js +++ b/src/index.js @@ -1,2 +1,2 @@ -export {default as ResolveFileAttachment} from "./fileAttachment"; +export {default as FileAttachments} from "./fileAttachment"; export {default as Library} from "./library"; diff --git a/test/index-test.js b/test/index-test.js index 9ba54751..fe10264d 100644 --- a/test/index-test.js +++ b/test/index-test.js @@ -1,5 +1,5 @@ import { test } from "tap"; -import {Library, ResolveFileAttachment} from "../src"; +import {Library, FileAttachments} from "../src"; test("new Library returns a library with the expected keys", async t => { t.deepEqual(Object.keys(new Library()).sort(), [ @@ -20,8 +20,8 @@ test("new Library returns a library with the expected keys", async t => { t.end(); }); -test("ResolveFileAttachment is exported by stdlib/index", t => { - t.equal(typeof ResolveFileAttachment, "function"); - t.equal(ResolveFileAttachment.name, "ResolveFileAttachment"); +test("FileAttachments is exported by stdlib/index", t => { + t.equal(typeof FileAttachments, "function"); + t.equal(FileAttachments.name, "FileAttachments"); t.end(); }); From 6e897e9ced14faa86477b6e83b56fc7bb38bab02 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Wed, 23 Oct 2019 14:31:36 -0700 Subject: [PATCH 07/23] Shorten. --- src/fileAttachment.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/fileAttachment.js b/src/fileAttachment.js index 29a2c18f..efc71ca1 100644 --- a/src/fileAttachment.js +++ b/src/fileAttachment.js @@ -1,5 +1,5 @@ -async function remote_fetch(url) { - const response = await fetch(url); +async function remote_fetch(file) { + const response = await fetch(await file.url()); if (!response.ok) throw new Error("Unable to load file"); return response; } @@ -17,16 +17,16 @@ class FileAttachment { return url; } async blob() { - return (await remote_fetch(await this.url())).blob(); + return (await remote_fetch(this)).blob(); } async arrayBuffer() { - return (await remote_fetch(await this.url())).arrayBuffer(); + return (await remote_fetch(this)).arrayBuffer(); } async text() { - return (await remote_fetch(await this.url())).text(); + return (await remote_fetch(this)).text(); } async json() { - return (await remote_fetch(await this.url())).json(); + return (await remote_fetch(this)).json(); } async image() { return new Promise((resolve, reject) => { From 942218a1096534da205d427da23d521827546eb3 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Wed, 23 Oct 2019 14:32:26 -0700 Subject: [PATCH 08/23] Update fileAttachment.js --- src/fileAttachment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fileAttachment.js b/src/fileAttachment.js index efc71ca1..059c1303 100644 --- a/src/fileAttachment.js +++ b/src/fileAttachment.js @@ -1,6 +1,6 @@ async function remote_fetch(file) { const response = await fetch(await file.url()); - if (!response.ok) throw new Error("Unable to load file"); + if (!response.ok) throw new Error(`Unable to load file: ${file.name}`); return response; } From e6907a503ab5f90268d29aeb2db4cfd96507040c Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Wed, 23 Oct 2019 14:33:46 -0700 Subject: [PATCH 09/23] Update fileAttachment.js --- src/fileAttachment.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/fileAttachment.js b/src/fileAttachment.js index 059c1303..5e725b6a 100644 --- a/src/fileAttachment.js +++ b/src/fileAttachment.js @@ -29,13 +29,11 @@ class FileAttachment { return (await remote_fetch(this)).json(); } async image() { - return new Promise((resolve, reject) => { - const img = document.createElement("img"); + return new Promise(async (resolve, reject) => { + const img = new Image; img.onload = () => resolve(img); - img.onerror = () => reject(new Error("Unable to load image")); - this.url().then(url => { - img.src = url; - }); + img.onerror = () => reject(new Error(`Unable to load file: ${this.name}`)); + img.src = await this.url(); }); } } From 83a82ce7505eaeab8faddeb8beace5cf45da2b27 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Wed, 23 Oct 2019 14:36:41 -0700 Subject: [PATCH 10/23] Update fileAttachment.js --- src/fileAttachment.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fileAttachment.js b/src/fileAttachment.js index 5e725b6a..200a78b7 100644 --- a/src/fileAttachment.js +++ b/src/fileAttachment.js @@ -29,11 +29,12 @@ class FileAttachment { return (await remote_fetch(this)).json(); } async image() { + const url = await this.url(); return new Promise(async (resolve, reject) => { const img = new Image; img.onload = () => resolve(img); img.onerror = () => reject(new Error(`Unable to load file: ${this.name}`)); - img.src = await this.url(); + img.src = url; }); } } From e44ff24d82245e05bac9a197495e2e5cf9a848e1 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Wed, 23 Oct 2019 14:36:50 -0700 Subject: [PATCH 11/23] Update fileAttachment.js --- src/fileAttachment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fileAttachment.js b/src/fileAttachment.js index 200a78b7..9701f688 100644 --- a/src/fileAttachment.js +++ b/src/fileAttachment.js @@ -30,7 +30,7 @@ class FileAttachment { } async image() { const url = await this.url(); - return new Promise(async (resolve, reject) => { + return new Promise((resolve, reject) => { const img = new Image; img.onload = () => resolve(img); img.onerror = () => reject(new Error(`Unable to load file: ${this.name}`)); From 5565a33e48895c201e1232ea141eacc538983586 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Fri, 25 Oct 2019 14:15:24 -0700 Subject: [PATCH 12/23] 3.2.0-rc.1 --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index c4f258d0..30fbdd94 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,9 @@ { "name": "@observablehq/stdlib", - "version": "3.1.1", + "version": "3.2.0-rc.1", + "publishConfig": { + "tag": "next" + }, "license": "ISC", "main": "dist/stdlib.js", "module": "src/index.js", From 6c35a627f7c99af2d2df438a41114dfa1be517e4 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Mon, 28 Oct 2019 12:47:09 -0700 Subject: [PATCH 13/23] Set crossOrigin automatically. --- src/fileAttachment.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/fileAttachment.js b/src/fileAttachment.js index 9701f688..98cb7d85 100644 --- a/src/fileAttachment.js +++ b/src/fileAttachment.js @@ -31,10 +31,13 @@ class FileAttachment { async image() { const url = await this.url(); return new Promise((resolve, reject) => { - const img = new Image; - img.onload = () => resolve(img); - img.onerror = () => reject(new Error(`Unable to load file: ${this.name}`)); - img.src = url; + const i = new Image; + if (new URL(url).origin !== new URL(location).origin) { + i.crossOrigin = "anonymous"; + } + i.onload = () => resolve(i); + i.onerror = () => reject(new Error(`Unable to load file: ${this.name}`)); + i.src = url; }); } } From 66d99a01d80709f1c85d18c78c9f593ea4d521a8 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Mon, 28 Oct 2019 13:12:22 -0700 Subject: [PATCH 14/23] 3.2.0-rc.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 30fbdd94..2f982fa2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@observablehq/stdlib", - "version": "3.2.0-rc.1", + "version": "3.2.0-rc.2", "publishConfig": { "tag": "next" }, From 603e75e25c71d3cb78f382475721c7939bbb3eb4 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Mon, 28 Oct 2019 13:34:53 -0700 Subject: [PATCH 15/23] Add FileAttachment.prototype.stream. --- src/fileAttachment.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/fileAttachment.js b/src/fileAttachment.js index 98cb7d85..4f3bda96 100644 --- a/src/fileAttachment.js +++ b/src/fileAttachment.js @@ -28,6 +28,9 @@ class FileAttachment { async json() { return (await remote_fetch(this)).json(); } + async stream() { + return (await remote_fetch(this)).body; + } async image() { const url = await this.url(); return new Promise((resolve, reject) => { From 300ed53106333e2fcc7e20e298a6b44080592ee8 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Mon, 28 Oct 2019 14:31:33 -0700 Subject: [PATCH 16/23] 3.2.0-rc.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2f982fa2..eb01023b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@observablehq/stdlib", - "version": "3.2.0-rc.2", + "version": "3.2.0-rc.3", "publishConfig": { "tag": "next" }, From f14ae5849124a77b1e1c9f05d34e513dbb89558c Mon Sep 17 00:00:00 2001 From: jashkenas Date: Mon, 28 Oct 2019 19:56:51 -0700 Subject: [PATCH 17/23] Fix FileAttachment().image() with relative paths --- src/fileAttachment.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/fileAttachment.js b/src/fileAttachment.js index 4f3bda96..2f5b6624 100644 --- a/src/fileAttachment.js +++ b/src/fileAttachment.js @@ -35,8 +35,12 @@ class FileAttachment { const url = await this.url(); return new Promise((resolve, reject) => { const i = new Image; - if (new URL(url).origin !== new URL(location).origin) { - i.crossOrigin = "anonymous"; + try { + if (new URL(url).origin !== new URL(location).origin) { + i.crossOrigin = "anonymous"; + } + } catch (e) { + // Relative paths, not urls. } i.onload = () => resolve(i); i.onerror = () => reject(new Error(`Unable to load file: ${this.name}`)); From aeeba14f5121be081f9bd86198458f96e4bddaa6 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Mon, 28 Oct 2019 21:48:58 -0700 Subject: [PATCH 18/23] Fix relative path. --- src/fileAttachment.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/fileAttachment.js b/src/fileAttachment.js index 2f5b6624..4962b773 100644 --- a/src/fileAttachment.js +++ b/src/fileAttachment.js @@ -35,12 +35,8 @@ class FileAttachment { const url = await this.url(); return new Promise((resolve, reject) => { const i = new Image; - try { - if (new URL(url).origin !== new URL(location).origin) { - i.crossOrigin = "anonymous"; - } - } catch (e) { - // Relative paths, not urls. + if (new URL(url, document.baseURI).origin !== new URL(location).origin) { + i.crossOrigin = "anonymous"; } i.onload = () => resolve(i); i.onerror = () => reject(new Error(`Unable to load file: ${this.name}`)); From d5e65c73d91c3521b6f2f4dd0cec06f3a4c0102a Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Mon, 28 Oct 2019 21:50:05 -0700 Subject: [PATCH 19/23] 3.2.0-rc.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eb01023b..46aeec3a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@observablehq/stdlib", - "version": "3.2.0-rc.3", + "version": "3.2.0-rc.4", "publishConfig": { "tag": "next" }, From d134b72656615446b66105086e3f12cdf816705d Mon Sep 17 00:00:00 2001 From: Ricky Reusser Date: Wed, 28 Aug 2019 08:21:22 -0700 Subject: [PATCH 20/23] Remove fragment identifier from DOM.uid Concatenating `#` + id to `window.location` results in UID hrefs with _two_ fragment identifiers. This commit removes the existing fragment identifier before concatenating. --- src/dom/uid.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dom/uid.js b/src/dom/uid.js index 4f8c14f8..cf78c136 100644 --- a/src/dom/uid.js +++ b/src/dom/uid.js @@ -6,7 +6,7 @@ export default function(name) { function Id(id) { this.id = id; - this.href = window.location.href + "#" + id; + this.href = window.location.href.replace(window.location.hash, "") + "#" + id; } Id.prototype.toString = function() { From 034f1155d7bbf5ea3df25afe0969ee8a19b34cac Mon Sep 17 00:00:00 2001 From: Ricky Reusser Date: Mon, 2 Sep 2019 20:55:11 -0700 Subject: [PATCH 21/23] Improve href creation in DOM.uid --- src/dom/uid.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dom/uid.js b/src/dom/uid.js index cf78c136..639bc612 100644 --- a/src/dom/uid.js +++ b/src/dom/uid.js @@ -6,7 +6,7 @@ export default function(name) { function Id(id) { this.id = id; - this.href = window.location.href.replace(window.location.hash, "") + "#" + id; + this.href = new URL(`#${id}`, location) + ""; } Id.prototype.toString = function() { From c0bdb06538b469e8fe1f04f048c1134b53105c78 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Mon, 28 Oct 2019 21:51:57 -0700 Subject: [PATCH 22/23] 3.2.0-rc.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 46aeec3a..82eabc81 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@observablehq/stdlib", - "version": "3.2.0-rc.4", + "version": "3.2.0-rc.5", "publishConfig": { "tag": "next" }, From 4fd1f08466286ac53a6dec2d710684fec26645b5 Mon Sep 17 00:00:00 2001 From: jashkenas Date: Tue, 29 Oct 2019 11:46:22 -0700 Subject: [PATCH 23/23] Adding FileAttachment to the README --- README.md | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/README.md b/README.md index cb6715f1..3617db0b 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ For examples, see https://observablehq.com/@observablehq/standard-library. * [DOM](#dom) - create HTML and SVG elements. * [Files](#files) - read local files into memory. +* [FileAttachments](#file_attachments) - read remote files. * [Generators](#generators) - utilities for generators and iterators. * [Promises](#promises) - utilities for promises. * [require](#require) - load third-party libraries. @@ -273,6 +274,100 @@ A data URL may be significantly less efficient than [URL.createObjectURL](https: } ``` +### File Attachments + +See [File Attachments](https://observablehq.com/@observablehq/file-attachments) on Observable for examples. + +#
FileAttachments(resolve) [<>](https://github.com/observablehq/stdlib/blob/master/src/fileAttachment.js "Source") + +The **FileAttachments** function exported by the standard library is an abstract class that can be used to fetch files by name from remote URLs. To make it concrete, you call it with a *resolve* function, which is an async function that takes a *name* and returns a URL at which the file of that name may 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; +); +``` + +Once you have your **FileAttachment** function defined, you can call it from notebook code: + +```js +photo = FileAttachment("sunset.jpg") +``` + +FileAttachments work similarly to the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch), providing methods that return promises to the file’s contents in a handful of convenient forms. + +# FileAttachment(name).url() [<>](https://github.com/observablehq/stdlib/blob/master/src/fileAttachment.js "Source") + +Returns a promise to the URL at which the file may be retrieved. + +```js +const url = await FileAttachment("file.txt").url(); +``` + +# FileAttachment(name).text() [<>](https://github.com/observablehq/stdlib/blob/master/src/fileAttachment.js "Source") + +Returns a promise to the file’s contents as a JavaScript string. + +```js +const data = d3.csvParse(await FileAttachment("cars.csv").text()); +``` + +# FileAttachment(name).json() [<>](https://github.com/observablehq/stdlib/blob/master/src/fileAttachment.js "Source") + +Returns a promise to the file’s contents, parsed as JSON into JavaScript values. + +```js +const logs = await FileAttachment("weekend-logs.json").json(); +``` + +# FileAttachment(name).image() [<>](https://github.com/observablehq/stdlib/blob/master/src/fileAttachment.js "Source") + +Returns a promise to a file loaded as an [HTML Image](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image). Note that the promise won't resolve until the image has finished loading — making this a useful value to pass to other cells that need to process the image, or paint it into a ``. + +```js +const image = await FileAttachment("sunset.jpg").image(); +``` + +# FileAttachment(name).arrayBuffer() [<>](https://github.com/observablehq/stdlib/blob/master/src/fileAttachment.js "Source") + +Returns a promise to the file’s contents as an [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer). + +```js +const city = shapefile.read(await FileAttachment("sf.shp").arrayBuffer()); +``` + +# FileAttachment(name).stream() [<>](https://github.com/observablehq/stdlib/blob/master/src/fileAttachment.js "Source") + +Returns a promise to a [Stream](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) of the file’s contents. + +```js +const stream = await FileAttachment("metrics.csv").stream(); +const reader = stream.getReader(); +let done, value; +while (({done, value} = await reader.read()), !done) { + yield value; +} +``` + +# FileAttachment(name).blob() [<>](https://github.com/observablehq/stdlib/blob/master/src/fileAttachment.js "Source") + +Returns a promise to a [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) containing the raw contents of the file. + +```js +const blob = await FileAttachment("binary-data.dat").blob(); +``` + ### Generators # Generators.disposable(value, dispose) [<>](https://github.com/observablehq/stdlib/blob/master/src/generators/disposable.js "Source")