{"html_url": "https://github.com/simonw/datasette/pull/2052#issuecomment-1509461324", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2052", "id": 1509461324, "node_id": "IC_kwDOBm6k_c5Z-I1M", "user": {"value": 9020979, "label": "hydrosquall"}, "created_at": "2023-04-15T01:57:06Z", "updated_at": "2023-04-15T01:57:06Z", "author_association": "CONTRIBUTOR", "body": "Notes from 1:1 - it _is_ possible to pass in URL params into a ObservableHQ notebook: https://observablehq.com/@bherbertlc/pass-values-as-url-parameters", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1651082214, "label": "feat: Javascript Plugin API (Custom panels, column menu items with JS actions)"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/2052#issuecomment-1510423051", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2052", "id": 1510423051, "node_id": "IC_kwDOBm6k_c5aBzoL", "user": {"value": 9020979, "label": "hydrosquall"}, "created_at": "2023-04-16T16:12:14Z", "updated_at": "2023-04-20T05:14:39Z", "author_association": "CONTRIBUTOR", "body": "# Javascript Plugin Docs (alpha)\r\n\r\n## Motivation\r\n\r\nThe Datasette JS Plugin API allows developers to add interactive features to the UI, without having to modify the Python source code. \r\n\r\n## Setup\r\n\r\nNo external/NPM dependencies are needed.\r\n\r\nPlugin behavior is coordinated by the Datasette `manager`. Every page has 1 `manager`.\r\n\r\nThere are 2 ways to add your plugin to the `manager`.\r\n\r\n1. Read `window.__DATASETTE__` if the manager was already loaded.\r\n\r\n```js\r\nconst manager = window.__DATASETTE__;\r\n```\r\n\r\n2. Wait for the `datasette_init` event to fire if your code was loaded before the manager is ready. \r\n\r\n```js\r\ndocument.addEventListener(\"datasette_init\", function (evt) {\r\n const { detail: manager } = evt;\r\n \r\n // register plugin here\r\n});\r\n```\r\n\r\n3. Add plugin to the manager by calling `manager.registerPlugin` in a JS file. Each plugin will supply 1 or more hooks with\r\n\r\n- unique name (`YOUR_PLUGIN_NAME`)\r\n- a numeric version (starting at `0.1`), \r\n- configuration value, the details vary by hook. (In this example, `getColumnActions` takes a function)\r\n\r\n```js\r\nmanager.registerPlugin(\"YOUR_PLUGIN_NAME\", {\r\n version: 0.1,\r\n makeColumnActions: (columnMeta) => {\r\n return [\r\n {\r\n label: \"Copy name to clipboard\",\r\n // evt = native click event\r\n onClick: (evt) => copyToClipboard(columnMeta.column),\r\n }\r\n ];\r\n },\r\n });\r\n```\r\n\r\nThere are 2 plugin hooks available to `manager.registerPlugin`:\r\n\r\n- `makeColumnActions` - Add items to the cog menu for headers on datasette table pages\r\n- `makeAboveTablePanelConfigs` - Add items to \"tabbed\" panel above the `` on pages that use the Datasette table template.\r\n\r\nWhile there are additional properties on the `manager`, but it's not advised to depend on them directly as the shape is subject to change.\r\n\r\n4. To make your JS file available as a Datasette plugin from the Python side, you can add a python file resembling [this](https://github.com/simonw/datasette/pull/2052/files#diff-c5ecf3d22075a60d04a4e95da2e15c612cf1bc84e38d777b67ba60dbd156e293) to your plugins directory. Note that you could host your JS file anywhere, it doesn't have to be served from the Datasette statics folder.\r\n\r\nI welcome ideas for more hooks, or feedback on the current design!\r\n\r\n## Examples\r\n\r\nSee the [example plugins file](https://github.com/simonw/datasette/blob/2d92b9328022d86505261bcdac419b6ed9cb2236/datasette/static/table-example-plugins.js) for additional examples.\r\n\r\n## Hooks API Guide\r\n\r\n### `makeAboveTablePanelConfigs`\r\n\r\nProvide a function with a list of panel objects. Each panel object should contain\r\n\r\n1. A unique string `id`\r\n2. A string `label` for the tab\r\n3. A `render` function. The first argument is reference to an HTML [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element). \r\n\r\nExample:\r\n\r\n```js\r\n manager.registerPlugin(\"panel-plugin-graphs\", {\r\n version: 0.1,\r\n makeAboveTablePanelConfigs: () => {\r\n return [\r\n {\r\n id: 'first-panel',\r\n label: \"My new panel\",\r\n render: node => {\r\n const description = document.createElement('p');\r\n description.innerText = 'Hello world';\r\n node.appendChild(description);\r\n }\r\n }\r\n ];\r\n },\r\n });\r\n```\r\n\r\n### `makeColumnActions`\r\n\r\nProvide a function that returns a list of action objects. Each action object has\r\n\r\n1. A string `label` for the menu dropdown label\r\n2. An onClick `render` function.\r\n\r\nExample:\r\n\r\n```js\r\n manager.registerPlugin(\"column-name-plugin\", {\r\n version: 0.1,\r\n getColumnActions: (columnMeta) => {\r\n \r\n // Info about selected column. \r\n const { columnName, columnNotNull, columnType, isPk } = columnMeta;\r\n\r\n return [\r\n {\r\n label: \"Copy name to clipboard\",\r\n onClick: (evt) => copyToClipboard(column),\r\n }\r\n ];\r\n },\r\n });\r\n```\r\n\r\nThe getColumnActions callback has access to an object with metadata about the clicked column. These fields include:\r\n\r\n- columnName: string (name of the column)\r\n- columnNotNull: boolean\r\n- columnType: sqlite datatype enum (text, number, etc)\r\n- isPk: Whether this is the primary key: boolean\r\n\r\nYou can use this column metadata to customize the action config objects (for example, handling different summaries for text vs number columns).\r\n\r\n\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1651082214, "label": "feat: Javascript Plugin API (Custom panels, column menu items with JS actions)"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/2052#issuecomment-1510423215", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2052", "id": 1510423215, "node_id": "IC_kwDOBm6k_c5aBzqv", "user": {"value": 9020979, "label": "hydrosquall"}, "created_at": "2023-04-16T16:12:59Z", "updated_at": "2023-04-16T16:12:59Z", "author_association": "CONTRIBUTOR", "body": "## Research notes\r\n\r\n- I stuck to the \"minimal dependencies\" ethos of datasette (no React, Typescript, JS linting, etc).\r\n- Main threads on JS plugin development\r\n - Main: sketch of pluggy-inspired system: https://github.com/simonw/datasette/issues/983\r\n - Main: provide locations in Datasette HTML that are designed for multiple plugins to safely cooperate with each other (starting with the panel, but eventually could extend to \"search boxes\"): https://github.com/simonw/datasette/issues/1191\r\n - Main: HTML hooks for JS plugin authors: https://github.com/simonw/datasette/issues/987\r\n- Prior threads on JS plugins in Datasette for future design directions\r\n - Idea: pass useful strings to JS plugins: https://github.com/simonw/datasette/issues/1565\r\n - Idea: help with plugin dependency loading: https://github.com/simonw/datasette/issues/1542 . (IMO - the plugin providing the dependency can emit an event once it's done. Other plugins can listen for it, or ask the manager to inform them when the dependency is available). \r\n - Idea: help plugins to manage state in shareable URLs (plugins shouldn't have to interact with the URL directly, should have some basic insulation from clobbering each others' keys): https://github.com/simonw/datasette/issues/1144\r\n- Articles on plugins reviewed\r\n - https://css-tricks.com/designing-a-javascript-plugin-system/\r\n- Plugin/Extension systems reviewed (mostly JS).\r\n - Yarn: https://yarnpkg.com/advanced/plugin-tutorial\r\n - Tappable https://github.com/webpack/tapable (used by Auto, webpack)\r\n - Pluggy: https://pluggy.readthedocs.io/en/stable/\r\n - VSCode: https://code.visualstudio.com/api/get-started/your-first-extension\r\n - Chrome: https://developer.chrome.com/docs/extensions/reference/\r\n - Figma/Figjam Widget: https://www.figma.com/widget-docs/\r\n - Datadog Apps: [Programming Model](https://github.com/DataDog/apps/blob/master/docs/en/programming-model.md)\r\n - Storybook: https://storybook.js.org/docs/react/addons/addons-api", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1651082214, "label": "feat: Javascript Plugin API (Custom panels, column menu items with JS actions)"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/2052#issuecomment-1515694393", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2052", "id": 1515694393, "node_id": "IC_kwDOBm6k_c5aV6k5", "user": {"value": 9020979, "label": "hydrosquall"}, "created_at": "2023-04-20T04:25:55Z", "updated_at": "2023-04-20T04:25:55Z", "author_association": "CONTRIBUTOR", "body": "Thanks for the thoughtful review and generous examples @asg017 ! I'll make the changes you suggested soon. Bonus thoughts inlined below.\r\n\r\n> comments\r\n\r\nThese were very much appreciated, it's important to a plugin system that details like this feel right! I'll address them in batch later in the week. \r\n\r\n> I know TypeScript can be a little controversial\r\n\r\nFWIW I am in favor of doing Typescript - I just wanted to keep the initial set of files in this PR as simple as possible to review. Really appreciate you scaffolding this initial set of types + I think it would be a welcome addition to maintain a set of types.d.ts files. \r\n\r\nI'm entertaining the idea of writing the actual source code in Typescript as long as the compiled output is readable b/c it can be tricky to keep the types and plain JS files in sync. Curious if you have encountered projects that are good at preventing drift.\r\n\r\n> Maybe they should have more \"action-y\" names\r\n\r\nThis is a great observation. I'm inclined towards something like `make*` or `build*` since to me `add*` make me think the thing the method is attached to is being mutated, but I agree that any of these may be clearer than the current `get*` setup. I'll go through and update these. \r\n\r\n> Maybe we can make it easier to do pure-js datasette plugins?\r\n\r\nI really like this idea! It'll be easier to get contributors if they don't have to touch the python side at _all_. \r\n\r\n> And then do the PERMITTED_VIEWS filtering in JS rather than Python.\r\n\r\nOne cost of doing this is that pages that won't use the JS would still have to load the unused code (given that I'm not sending up anything complex like lazy loading). But hopefully the manager core size is close to negligible, and it won't be a big deal. ", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1651082214, "label": "feat: Javascript Plugin API (Custom panels, column menu items with JS actions)"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/2052#issuecomment-1530817667", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2052", "id": 1530817667, "node_id": "IC_kwDOBm6k_c5bPmyD", "user": {"value": 193185, "label": "cldellow"}, "created_at": "2023-05-02T03:24:53Z", "updated_at": "2023-05-02T03:24:53Z", "author_association": "CONTRIBUTOR", "body": "Thanks for putting this together! I've been slammed with work/personal stuff so haven't been able to actually prototype anything with this. :(\r\n\r\ntl;dr: I think this would be useful immediately as is. It might also be nice if the plugins could return `Promise`s.\r\n\r\nThe long version: I read the design notes and example plugin. I think I'd be able to use this in [datasette-ui-extras](https://github.com/cldellow/datasette-ui-extras) for my lazy-facets feature.\r\n\r\nThe lazy-facets feature tries to provide a snappier user experience. It does this by altering how suggested facets work.\r\n\r\nFirst, at page render time:\r\n(A) it lies to Datasette and claims that no columns support facets, this avoids the lengthy delays/timeouts that can happen if the dataset is large.\r\n(B) there's a python plugin that implements the [extra_body_script](https://docs.datasette.io/en/stable/plugin_hooks.html#extra-body-script-template-database-table-columns-view-name-request-datasette) hook, to write out the list of column names for future use by JavaScript\r\n\r\nSecond, at page load time: there is some JavaScript that:\r\n(C) makes AJAX requests to suggest facets for each column - it makes 1 request per column, using the data from (B)\r\n(D) wires up the column menus to add Facet-by-this options for each facet\r\n\r\nWith the currently proposed plugin scheme, I think (D) could be moved into the plugin. I'd do the ajax requests, then register the plugin.\r\n\r\nIf the plugin scheme also supported promises, I think (B) and (C) could also be moved into the plugin.\r\n\r\nDoes that make sense? Sorry for the wall of text!", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1651082214, "label": "feat: Javascript Plugin API (Custom panels, column menu items with JS actions)"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/2052#issuecomment-1530822437", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2052", "id": 1530822437, "node_id": "IC_kwDOBm6k_c5bPn8l", "user": {"value": 193185, "label": "cldellow"}, "created_at": "2023-05-02T03:35:30Z", "updated_at": "2023-05-02T16:02:38Z", "author_association": "CONTRIBUTOR", "body": "Also, just checking - is this how I'd write bulletproof plugin registration code that is robust against the order in which the script tags load (eg if both my code and the Datasette code are loaded via a `` in `base.html`\r\n\r\nI'll let @simonw decide which one to work with. I kindof like the idea of having an \"unstable\" opt-in process to enable JS plugins, to give us time to try it out with a wide variety of plugins until we feel its ready.\r\n\r\nI'm also curious to see how \"plugins for a plugin' would work, like #1542. For example, if the leaflet plugin showed default markers, but also included its own hook for other plugins to add more markers/styling. I'm imagine that the individual plugin would re-create their own plugin system compared to this, since handling \"plugins of plugins\" at the top with Datasette seems really convoluted. \r\n\r\nAlso for posterity, here's a list of Simon's Datasette plugins that use \"extra_js_urls()\", which probably means they can be ported/re-written to use this new plugin system:\r\n\r\n- [`datasette-vega`](https://github.com/simonw/datasette-vega/blob/00de059ab1ef77394ba9f9547abfacf966c479c4/datasette_vega/__init__.py#L25)\r\n- [`datasette-cluster-map`](https://github.com/simonw/datasette-cluster-map/blob/795d25ad9ff6cba0307191f44fecc8f8070bef5c/datasette_cluster_map/__init__.py#L14)\r\n- [`datasette-leaflet-geojson`](https://github.com/simonw/datasette-leaflet-geojson/blob/64713aa497750400b9ac2c12e8bb6ffab8eb77f3/datasette_leaflet_geojson/__init__.py#L47)\r\n- [`datasette-pretty-traces`](https://github.com/simonw/datasette-pretty-traces/blob/5219d65eca3d7d7a73bb9d3120df42fe046a1315/datasette_pretty_traces/__init__.py#L5)\r\n- [`datasette-youtube-embed`](https://github.com/simonw/datasette-youtube-embed/blob/4b4a0d7e58ebe15f47e9baf68beb9908c1d899da/datasette_youtube_embed/__init__.py#L55)\r\n- [`datasette-leaflet-freedraw`](https://github.com/simonw/datasette-leaflet-freedraw/blob/8f28c2c2080ec9d29f18386cc6a2573a1c8fbde7/datasette_leaflet_freedraw/__init__.py#L66)\r\n- [`datasette-hovercards`](https://github.com/simonw/datasette-hovercards/blob/9439ba46b7140fb03223faff0d21aeba5615a287/datasette_hovercards/__init__.py#L5)\r\n- [`datasette-mp3-audio`](https://github.com/simonw/datasette-mp3-audio/blob/4402168792f452a46ab7b488e40ec49cd4b12185/datasette_mp3_audio/__init__.py#L6)\r\n- [`datasette-geojson-map`](https://github.com/simonw/datasette-geojson-map/blob/32af5f1fd1a07278bbf8071fbb20a61e0f613246/datasette_geojson_map/__init__.py#L30)", "reactions": "{\"total_count\": 1, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 1}", "issue": {"value": 1651082214, "label": "feat: Javascript Plugin API (Custom panels, column menu items with JS actions)"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/2052#issuecomment-1616095810", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2052", "id": 1616095810, "node_id": "IC_kwDOBm6k_c5gU6pC", "user": {"value": 15178711, "label": "asg017"}, "created_at": "2023-07-01T20:31:31Z", "updated_at": "2023-07-01T20:31:31Z", "author_association": "CONTRIBUTOR", "body": "> Just curious, is there a query that can be used to compile this programmatically, or did you identify these through memory?\r\n\r\nI just did a github search for `user:simonw \"def extra_js_urls(\"` ! Though I'm sure other plugins made by people other than Simon also exist out there https://github.com/search?q=user%3Asimonw+%22def+extra_js_urls%28%22&type=code", "reactions": "{\"total_count\": 1, \"+1\": 1, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1651082214, "label": "feat: Javascript Plugin API (Custom panels, column menu items with JS actions)"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/2052#issuecomment-1615997736", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2052", "id": 1615997736, "node_id": "IC_kwDOBm6k_c5gUiso", "user": {"value": 9020979, "label": "hydrosquall"}, "created_at": "2023-07-01T16:55:24Z", "updated_at": "2023-07-01T16:55:24Z", "author_association": "CONTRIBUTOR", "body": "> Ok @hydrosquall a couple things before this PR should be good to go:\r\n\r\nThank you @asg017 ! I've pushed both suggested changes onto this branch.\r\n\r\n> Not sure how difficult it'll be to inject it server-side\r\n\r\nIf we are OK with having a build system, it would free me up to do do many things! We could make datasette-manager.js a server-side rendered file as a \"template\" instead of having it as a static JS file, but I'm not sure it's worth the extra jump in complexity / loss of syntax highlighting in the JS file.\r\n\r\nIn the short-term, I could see an intermediary solution where a unit test in the preferred language was able to read both `version.py` and `datasette-manager.js`, and make sure that the strings versions are in sync. (This assumes that we want the manager and datasette's versions to be synced, and not decoupled). Since the version is not changing very often, a \"manual sync\" might be good enough. \r\n\r\n> In terms of how to integrate this into Datasette, a few options I can see working:\r\n\r\nThis sounds good to me. I'm not sure how to add a settings flag, but will be interested to see the PR that adds support for it.\r\n\r\n> I'm also curious to see how \"plugins for a plugin' would work\r\n\r\nI'm comfortable to wait until we have a realistic usecase for this. In the short term, I think we could give plugins a way to grant access to a \"public API of other plugins\", and also ask to be notified when plugins with other names have loaded, but don't picture the datasette manager getting more involved than that. \r\n\r\n> here's a list of Simon's Datasette plugins that use \"extra_js_urls()\"\r\n\r\nNeat, thanks for compiling this list! Just curious, is there a query that can be used to compile this programmatically, or did you identify these through memory?\r\n\r\n> I want to make a javascript plugin on top of the code-mirror editor to make a few things nicer (function auto-complete, table/column descriptions, etc.)\r\n\r\nI look forward to trying this out \ud83d\udc4d \r\n\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1651082214, "label": "feat: Javascript Plugin API (Custom panels, column menu items with JS actions)"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/2052#issuecomment-1630776144", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2052", "id": 1630776144, "node_id": "IC_kwDOBm6k_c5hM6tQ", "user": {"value": 9020979, "label": "hydrosquall"}, "created_at": "2023-07-11T12:54:03Z", "updated_at": "2023-07-11T12:54:03Z", "author_association": "CONTRIBUTOR", "body": "Thanks for the review and the code pointers @simonw - I've made the suggested edits, fixed the renamed variable, and confirmed that the panels still render on the `table` and `database` views. ", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1651082214, "label": "feat: Javascript Plugin API (Custom panels, column menu items with JS actions)"}, "performed_via_github_app": null}