{"html_url": "https://github.com/simonw/datasette/issues/987#issuecomment-752696499", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/987", "id": 752696499, "node_id": "MDEyOklzc3VlQ29tbWVudDc1MjY5NjQ5OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T17:21:08Z", "updated_at": "2020-12-30T17:21:08Z", "author_association": "OWNER", "body": "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.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 712984738, "label": "Documented HTML hooks for JavaScript plugin authors"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/987#issuecomment-752697279", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/987", "id": 752697279, "node_id": "MDEyOklzc3VlQ29tbWVudDc1MjY5NzI3OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T17:23:27Z", "updated_at": "2020-12-30T17:23:32Z", "author_association": "OWNER", "body": "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.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 712984738, "label": "Documented HTML hooks for JavaScript plugin authors"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/983#issuecomment-752742669", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/983", "id": 752742669, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc0MjY2OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T20:07:05Z", "updated_at": "2020-12-30T20:07:18Z", "author_association": "OWNER", "body": "Initial prototype:\r\n```javascript\r\nwindow.datasette = {};\r\nwindow.datasette.plugins = (function() {\r\n var registry = {};\r\n\r\n function extractParameters(fn) {\r\n var match = /\\((.*)\\)/.exec(fn.toString());\r\n if (match && match[1].trim()) {\r\n return match[1].split(',').map(s => s.trim());\r\n } else {\r\n return [];\r\n }\r\n }\r\n\r\n function register(hook, fn, parameters) {\r\n parameters = parameters || extractParameters(fn);\r\n if (!registry[hook]) {\r\n registry[hook] = [];\r\n }\r\n registry[hook].push([fn, parameters]);\r\n }\r\n\r\n function call(hook, args) {\r\n args = args || {};\r\n var implementations = registry[hook] || [];\r\n var results = [];\r\n implementations.forEach(([fn, parameters]) => {\r\n /* Call with the correct arguments */\r\n var callWith = parameters.map(parameter => args[parameter]);\r\n var result = fn.apply(fn, callWith);\r\n if (result) {\r\n results.push(result);\r\n }\r\n });\r\n return results;\r\n }\r\n return {\r\n register: register,\r\n _registry: registry,\r\n call: call\r\n };\r\n})();\r\n```\r\nUsage example:\r\n```javascript\r\ndatasette.plugins.register('numbers', (a, b) => a + b)\r\ndatasette.plugins.register('numbers', (a, b) => a * b)\r\ndatasette.plugins.call('numbers', {a: 4, b: 6})\r\n/* Returns [10, 24] */\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 712260429, "label": "JavaScript plugin hooks mechanism similar to pluggy"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/983#issuecomment-752744195", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/983", "id": 752744195, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc0NDE5NQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T20:12:26Z", "updated_at": "2020-12-30T20:12:26Z", "author_association": "OWNER", "body": "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.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 712260429, "label": "JavaScript plugin hooks mechanism similar to pluggy"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/983#issuecomment-752744311", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/983", "id": 752744311, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc0NDMxMQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T20:12:50Z", "updated_at": "2020-12-30T20:13:02Z", "author_association": "OWNER", "body": "This could work to define a plugin hook:\r\n```javascript\r\ndatasette.plugins.define('numbers', ['a' ,'b'])\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 712260429, "label": "JavaScript plugin hooks mechanism similar to pluggy"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/983#issuecomment-752747169", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/983", "id": 752747169, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc0NzE2OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T20:24:07Z", "updated_at": "2020-12-30T20:24:07Z", "author_association": "OWNER", "body": "This version adds `datasette.plugins.define()` plus extra validation of both `.register()` and `.call()`:\r\n```javascript\r\nwindow.datasette = {};\r\nwindow.datasette.plugins = (function() {\r\n var registry = {};\r\n var definitions = {};\r\n\r\n function extractParameters(fn) {\r\n var match = /\\((.*)\\)/.exec(fn.toString());\r\n if (match && match[1].trim()) {\r\n return match[1].split(',').map(s => s.trim());\r\n } else {\r\n return [];\r\n }\r\n }\r\n\r\n function define(hook, parameters) {\r\n definitions[hook] = parameters || [];\r\n }\r\n\r\n function isSubSet(a, b) {\r\n return a.every(parameter => b.includes(parameter))\r\n }\r\n\r\n function register(hook, fn, parameters) {\r\n parameters = parameters || extractParameters(fn);\r\n if (!definitions[hook]) {\r\n throw new Error('\"' + hook + '\" is not a defined plugin hook');\r\n }\r\n if (!definitions[hook]) {\r\n throw new Error('\"' + hook + '\" is not a defined plugin hook');\r\n }\r\n /* Check parameters is a subset of definitions[hook] */\r\n var validParameters = definitions[hook];\r\n if (!isSubSet(parameters, validParameters)) {\r\n throw new Error('\"' + hook + '\" valid parameters are ' + JSON.stringify(validParameters));\r\n }\r\n if (!registry[hook]) {\r\n registry[hook] = [];\r\n }\r\n registry[hook].push([fn, parameters]);\r\n }\r\n\r\n function call(hook, args) {\r\n args = args || {};\r\n if (!definitions[hook]) {\r\n throw new Error('\"' + hook + '\" hook has not been defined');\r\n }\r\n if (!isSubSet(Object.keys(args), definitions[hook])) {\r\n throw new Error('\"' + hook + '\" valid arguments are ' + JSON.stringify(definitions[hook]));\r\n }\r\n \r\n var implementations = registry[hook] || [];\r\n var results = [];\r\n implementations.forEach(([fn, parameters]) => {\r\n /* Call with the correct arguments */\r\n var callWith = parameters.map(parameter => args[parameter]);\r\n var result = fn.apply(fn, callWith);\r\n if (result) {\r\n results.push(result);\r\n }\r\n });\r\n return results;\r\n }\r\n return {\r\n define: define,\r\n register: register,\r\n _registry: registry,\r\n call: call\r\n };\r\n})();\r\n```\r\nUsage:\r\n```javascript\r\ndatasette.plugins.define('numbers', ['a', 'b'])\r\ndatasette.plugins.register('numbers', (a, b) => a + b)\r\ndatasette.plugins.register('numbers', (a, b) => a * b)\r\ndatasette.plugins.call('numbers', {a: 4, b: 6})\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 712260429, "label": "JavaScript plugin hooks mechanism similar to pluggy"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/983#issuecomment-752747999", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/983", "id": 752747999, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc0Nzk5OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T20:27:00Z", "updated_at": "2020-12-30T20:27:00Z", "author_association": "OWNER", "body": "I need to decide how this code is going to be loaded. Putting it in a blocking `