html_url,issue_url,id,node_id,user,user_label,created_at,updated_at,author_association,body,reactions,issue,issue_label,performed_via_github_app https://github.com/simonw/datasette/issues/987#issuecomment-752696499,https://api.github.com/repos/simonw/datasette/issues/987,752696499,MDEyOklzc3VlQ29tbWVudDc1MjY5NjQ5OQ==,9599,simonw,2020-12-30T17:21:08Z,2020-12-30T17:21:08Z,OWNER,"More generally, I need to document certain areas of the page that JavaScript plugins are invited to append their content to - such that plugin authors can use them and feel confident that future changes to the Datasette templates won't break their plugins.","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712984738,Documented HTML hooks for JavaScript plugin authors, https://github.com/simonw/datasette/issues/987#issuecomment-752697279,https://api.github.com/repos/simonw/datasette/issues/987,752697279,MDEyOklzc3VlQ29tbWVudDc1MjY5NzI3OQ==,9599,simonw,2020-12-30T17:23:27Z,2020-12-30T17:23:32Z,OWNER,Related problem: right now if you're writing custom template it's not at all obvious how to write them such that visualization plugins like `datasette-cluster-map` will continue to work.,"{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712984738,Documented HTML hooks for JavaScript plugin authors, https://github.com/simonw/datasette/issues/983#issuecomment-752715236,https://api.github.com/repos/simonw/datasette/issues/983,752715236,MDEyOklzc3VlQ29tbWVudDc1MjcxNTIzNg==,9599,simonw,2020-12-30T18:24:54Z,2020-12-30T18:24:54Z,OWNER,"I think I'm going to try building a very lightweight clone of the core API design of Pluggy - not the advanced features, just the idea that plugins can register and a call to `plugin.nameOfHook()` will return the concatenated results of all of the registered hooks.","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429,JavaScript plugin hooks mechanism similar to pluggy, https://github.com/simonw/datasette/issues/983#issuecomment-752715412,https://api.github.com/repos/simonw/datasette/issues/983,752715412,MDEyOklzc3VlQ29tbWVudDc1MjcxNTQxMg==,9599,simonw,2020-12-30T18:25:31Z,2020-12-30T18:25:31Z,OWNER,I'm going to introduce a global `datasette` object which holds all the documented JavaScript API for plugin authors.,"{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429,JavaScript plugin hooks mechanism similar to pluggy, https://github.com/simonw/datasette/issues/983#issuecomment-752721069,https://api.github.com/repos/simonw/datasette/issues/983,752721069,MDEyOklzc3VlQ29tbWVudDc1MjcyMTA2OQ==,9599,simonw,2020-12-30T18:46:10Z,2020-12-30T18:46:10Z,OWNER,"Pluggy does dependency injection by introspecting the named arguments to the Python function, which I really like. That's tricker in JavaScript. It looks like the only way to introspect a function is to look at the `.toString()` representation of it and parse the `(parameter, list)` using a regular expression. Even more challenging: JavaScript developers love minifying their code, and minification can shorten the function parameter names. From https://code-maven.com/dependency-injection-in-angularjs it looks like Angular.js does dependency injection and solves this by letting you optionally provide a separate list of the arguments your function uses: ```javascript angular.module('DemoApp', []) .controller('DemoController', ['$scope', '$log', function($scope, $log) { $scope.message = ""Hello World""; $log.debug('logging hello'); }]); ``` I can copy that approach: I'll introspect by default, but provide a documented mechanism for explicitly listing your parameter names so that if you know your plugin code will be minified you can use that instead.","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429,JavaScript plugin hooks mechanism similar to pluggy, https://github.com/simonw/datasette/issues/983#issuecomment-752721840,https://api.github.com/repos/simonw/datasette/issues/983,752721840,MDEyOklzc3VlQ29tbWVudDc1MjcyMTg0MA==,9599,simonw,2020-12-30T18:48:53Z,2020-12-30T18:51:51Z,OWNER,"Potential design: ```javascript datasette.plugins.register('column_actions', function(database, table, column, actor) { /* ... *l }) ``` Or if you want to be explicit to survive minification: ```javascript datasette.plugins.register('column_actions', function(database, table, column, actor) { /* ... *l }, ['database', 'table', 'column', 'actor']) ``` I'm making that list of parameter names an optional third argument to the `register()` function. If that argument isn't passed, introspection will be used to figure out the parameter names. ","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429,JavaScript plugin hooks mechanism similar to pluggy, https://github.com/simonw/datasette/issues/983#issuecomment-752722863,https://api.github.com/repos/simonw/datasette/issues/983,752722863,MDEyOklzc3VlQ29tbWVudDc1MjcyMjg2Mw==,9599,simonw,2020-12-30T18:52:39Z,2020-12-30T18:52:39Z,OWNER,"Then to call the plugins: ```javascript datasette.plugins.call('column_actions', {database: 'database', table: 'table'}) ```","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429,JavaScript plugin hooks mechanism similar to pluggy, https://github.com/simonw/datasette/issues/983#issuecomment-752729035,https://api.github.com/repos/simonw/datasette/issues/983,752729035,MDEyOklzc3VlQ29tbWVudDc1MjcyOTAzNQ==,9599,simonw,2020-12-30T19:15:56Z,2020-12-30T19:16:44Z,OWNER,"The `column_actions` hook is the obvious first place to try this out. What are some demo plugins I could build for it? - Word cloud for this column - Count values (essentially linking to the SQL query for that column, as an extended version of the facet counts) - would be great if this could include pagination somehow, via #856. - Extract this column into a separate table - Add an index to this column","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429,JavaScript plugin hooks mechanism similar to pluggy, https://github.com/simonw/datasette/issues/983#issuecomment-752742669,https://api.github.com/repos/simonw/datasette/issues/983,752742669,MDEyOklzc3VlQ29tbWVudDc1Mjc0MjY2OQ==,9599,simonw,2020-12-30T20:07:05Z,2020-12-30T20:07:18Z,OWNER,"Initial prototype: ```javascript window.datasette = {}; window.datasette.plugins = (function() { var registry = {}; function extractParameters(fn) { var match = /\((.*)\)/.exec(fn.toString()); if (match && match[1].trim()) { return match[1].split(',').map(s => s.trim()); } else { return []; } } function register(hook, fn, parameters) { parameters = parameters || extractParameters(fn); if (!registry[hook]) { registry[hook] = []; } registry[hook].push([fn, parameters]); } function call(hook, args) { args = args || {}; var implementations = registry[hook] || []; var results = []; implementations.forEach(([fn, parameters]) => { /* Call with the correct arguments */ var callWith = parameters.map(parameter => args[parameter]); var result = fn.apply(fn, callWith); if (result) { results.push(result); } }); return results; } return { register: register, _registry: registry, call: call }; })(); ``` Usage example: ```javascript datasette.plugins.register('numbers', (a, b) => a + b) datasette.plugins.register('numbers', (a, b) => a * b) datasette.plugins.call('numbers', {a: 4, b: 6}) /* Returns [10, 24] */ ```","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429,JavaScript plugin hooks mechanism similar to pluggy, https://github.com/simonw/datasette/issues/983#issuecomment-752744195,https://api.github.com/repos/simonw/datasette/issues/983,752744195,MDEyOklzc3VlQ29tbWVudDc1Mjc0NDE5NQ==,9599,simonw,2020-12-30T20:12:26Z,2020-12-30T20:12:26Z,OWNER,"This implementation doesn't have an equivalent of ""hookspecs"" which can identify if a registered plugin implementation matches a known signature. I should add that, it will provide a better developer experience if someone has a typo.","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429,JavaScript plugin hooks mechanism similar to pluggy, https://github.com/simonw/datasette/issues/983#issuecomment-752744311,https://api.github.com/repos/simonw/datasette/issues/983,752744311,MDEyOklzc3VlQ29tbWVudDc1Mjc0NDMxMQ==,9599,simonw,2020-12-30T20:12:50Z,2020-12-30T20:13:02Z,OWNER,"This could work to define a plugin hook: ```javascript datasette.plugins.define('numbers', ['a' ,'b']) ```","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429,JavaScript plugin hooks mechanism similar to pluggy, https://github.com/simonw/datasette/issues/983#issuecomment-752747169,https://api.github.com/repos/simonw/datasette/issues/983,752747169,MDEyOklzc3VlQ29tbWVudDc1Mjc0NzE2OQ==,9599,simonw,2020-12-30T20:24:07Z,2020-12-30T20:24:07Z,OWNER,"This version adds `datasette.plugins.define()` plus extra validation of both `.register()` and `.call()`: ```javascript window.datasette = {}; window.datasette.plugins = (function() { var registry = {}; var definitions = {}; function extractParameters(fn) { var match = /\((.*)\)/.exec(fn.toString()); if (match && match[1].trim()) { return match[1].split(',').map(s => s.trim()); } else { return []; } } function define(hook, parameters) { definitions[hook] = parameters || []; } function isSubSet(a, b) { return a.every(parameter => b.includes(parameter)) } function register(hook, fn, parameters) { parameters = parameters || extractParameters(fn); if (!definitions[hook]) { throw new Error('""' + hook + '"" is not a defined plugin hook'); } if (!definitions[hook]) { throw new Error('""' + hook + '"" is not a defined plugin hook'); } /* Check parameters is a subset of definitions[hook] */ var validParameters = definitions[hook]; if (!isSubSet(parameters, validParameters)) { throw new Error('""' + hook + '"" valid parameters are ' + JSON.stringify(validParameters)); } if (!registry[hook]) { registry[hook] = []; } registry[hook].push([fn, parameters]); } function call(hook, args) { args = args || {}; if (!definitions[hook]) { throw new Error('""' + hook + '"" hook has not been defined'); } if (!isSubSet(Object.keys(args), definitions[hook])) { throw new Error('""' + hook + '"" valid arguments are ' + JSON.stringify(definitions[hook])); } var implementations = registry[hook] || []; var results = []; implementations.forEach(([fn, parameters]) => { /* Call with the correct arguments */ var callWith = parameters.map(parameter => args[parameter]); var result = fn.apply(fn, callWith); if (result) { results.push(result); } }); return results; } return { define: define, register: register, _registry: registry, call: call }; })(); ``` Usage: ```javascript datasette.plugins.define('numbers', ['a', 'b']) datasette.plugins.register('numbers', (a, b) => a + b) datasette.plugins.register('numbers', (a, b) => a * b) datasette.plugins.call('numbers', {a: 4, b: 6}) ```","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429,JavaScript plugin hooks mechanism similar to pluggy, https://github.com/simonw/datasette/issues/983#issuecomment-752747999,https://api.github.com/repos/simonw/datasette/issues/983,752747999,MDEyOklzc3VlQ29tbWVudDc1Mjc0Nzk5OQ==,9599,simonw,2020-12-30T20:27:00Z,2020-12-30T20:27:00Z,OWNER,"I need to decide how this code is going to be loaded. Putting it in a blocking `