From 3a5d6a114bd90a2062701f33c912963334fa2d2b Mon Sep 17 00:00:00 2001 From: Mike Ralphson <mike.ralphson@gmail.com> Date: Mon, 19 Jul 2021 15:35:54 +0100 Subject: [PATCH 1/2] perf: switch to tempura for templating --- .eslintrc.json | 10 ++- src/_includes/card.html | 59 ++++++------- src/_layouts/apis-page.liquid | 2 - src/assets/javascript/apis.js | 13 ++- src/assets/javascript/main.js | 2 - src/assets/javascript/tempura.js | 138 +++++++++++++++++++++++++++++++ src/index.md | 10 ++- 7 files changed, 186 insertions(+), 48 deletions(-) create mode 100644 src/assets/javascript/tempura.js diff --git a/.eslintrc.json b/.eslintrc.json index f588728..f246ece 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,7 +6,8 @@ "jquery": true }, "parserOptions": { - "ecmaVersion": 2017 + "ecmaVersion": 2017, + "sourceType": "module" }, "extends": "eslint:recommended", "rules": { @@ -37,7 +38,7 @@ "callback-return": "off", "camelcase": "off", "class-methods-use-this": "error", - "comma-dangle": "error", + "comma-dangle": "warn", "comma-spacing": "off", "comma-style": [ "error", @@ -131,6 +132,7 @@ "no-extra-parens": "off", "no-floating-decimal": "error", "no-global-assign": "error", + "no-cond-assign": "warn", "no-implicit-globals": "error", "no-implied-eval": "error", "no-inline-comments": "off", @@ -151,7 +153,7 @@ "no-multi-spaces": "off", "no-multi-str": "error", "no-multiple-empty-lines": "error", - "no-negated-condition": "error", + "no-negated-condition": "warn", "no-nested-ternary": "off", "no-new": "error", "no-new-func": "error", @@ -252,7 +254,7 @@ "spaced-comment": "off", "strict": "error", "symbol-description": "error", - "template-curly-spacing": "error", + "template-curly-spacing": "warn", "unicode-bom": [ "error", "never" diff --git a/src/_includes/card.html b/src/_includes/card.html index f2dcbd8..793c7c2 100644 --- a/src/_includes/card.html +++ b/src/_includes/card.html @@ -1,56 +1,57 @@ -<script type="text/dot-template"> +<script type="text/tempura"> {% raw %} + {{#expect it}} <div class="card"> - {{? it.classes}} - <span class="{{=it.classes}}" title="{{=it.flashTitle}}"><strong>{{=it.flashText}}</strong></span> - {{??}} + {{#if it.classes}} + <span class="{{{it.classes}}}" title="{{{it.flashTitle}}}"><strong>{{{it.flashText}}}</strong></span> + {{#else}} <span class="spacer"></span> - {{?}} + {{/if}} <header> - <h2 title="{{=it.info.title }}"> - {{? it.externalUrl }} - <a href="{{=it.externalUrl }}" target="_blank">{{=it.info.title }}</a> - {{??}} - {{=it.info.title }} - {{?}} + <h2 title="{{{it.info.title}}}"> + {{#if it.externalUrl }} + <a href="{{{it.externalUrl}}}" target="_blank">{{it.info.title}}</a> + {{#else}} + {{it.info.title}} + {{/if}} </h2> </header> <section class="api-body"> - <img src="{{=it.logo.url || 'assets/images/no-logo.svg'}}" alt="{{=it.info.title }} API logo" style="background-color: {{=it.logo.backgroundColor || 'transparent'}}" class="api-logo"> - <p>{{=it.cardDescription}}</p> + <img src="{{it.logo.url || 'assets/images/no-logo.svg'}}" alt="{{it.info.title }} API logo" style="background-color: {{it.logo.backgroundColor || 'transparent'}}" class="api-logo"> + <p>{{it.cardDescription}}</p> </section> <footer> <h3> OpenAPI: </h3> - <h4>Preferred Version - {{=it.preferred}}</h4> + <h4>Preferred Version - {{it.preferred}}</h4> <ul class="preferred-api"> - <li><a href="{{=it.api.swaggerUrl}}" target="_blank" >JSON</a></li> - <li><a href='{{=it.api.swaggerYamlUrl}}' target="_blank" >YAML</a></li> - <li><a href="{{=it.origUrl}}" target='_blank'>Orig</a></li> - <li><a href='https://redocly.github.io/redoc/?url={{=it.api.swaggerUrl}}' target="_blank" >Docs</a></li> + <li><a href="{{it.api.swaggerUrl}}" target="_blank" >JSON</a></li> + <li><a href='{{it.api.swaggerYamlUrl}}' target="_blank" >YAML</a></li> + <li><a href="{{it.origUrl}}" target='_blank'>Orig</a></li> + <li><a href='https://redocly.github.io/redoc/?url={{it.api.swaggerUrl}}' target="_blank" >Docs</a></li> </ul> - {{? it.versions }} + {{#if it.versions }} <details> <summary><h4>All Versions</h4></summary> <ul class="other-versions"> - {{~it.versions :version:index}} + {{#each it.versions as version}} <li> - <span>{{=version.version}}</span> + <span>{{version.version}}</span> <ul> - <li><a href="{{=version.swaggerUrl}}" target="_blank" >JSON</a></li> - <li><a href="{{=version.swaggerYamlUrl}}" target='_blank'>YAML</a></li> - <li><a href='https://redocly.github.io/redoc/?url={{=version.swaggerUrl}}' target="_blank" >Docs</a></li> + <li><a href="{{version.swaggerUrl}}" target="_blank" >JSON</a></li> + <li><a href="{{version.swaggerYamlUrl}}" target='_blank'>YAML</a></li> + <li><a href='https://redocly.github.io/redoc/?url={{version.swaggerUrl}}' target="_blank" >Docs</a></li> </ul> </li> - {{~}} + {{/each}} </ul> </details> - {{?}} + {{/if}} <details> <summary><h4>Tools</h4></summary> <ul class="tools"> - {{~it.integrations :integration:index}} - <li><a href="{{=integration.template}}" target="_blank" >{{=integration.text}}</a></li> - {{~}} + {{#each it.integrations as integration}} + <li><a href="{{integration.template}}" target="_blank" >{{integration.text}}</a></li> + {{/each}} </ul> </details> </footer> diff --git a/src/_layouts/apis-page.liquid b/src/_layouts/apis-page.liquid index 9a4dcef..41fdec3 100644 --- a/src/_layouts/apis-page.liquid +++ b/src/_layouts/apis-page.liquid @@ -18,10 +18,8 @@ <!-- JS --> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.7.0/marked.js"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/dot/1.0.3/doT.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/slideout/0.1.12/slideout.js"></script> <!-- inject:js --> - <script src="/assets/javascript/apis.js"></script> <script src="/assets/javascript/main.js"></script> <!-- endinject --> diff --git a/src/assets/javascript/apis.js b/src/assets/javascript/apis.js index f969df5..9dab00f 100644 --- a/src/assets/javascript/apis.js +++ b/src/assets/javascript/apis.js @@ -1,4 +1,4 @@ -'use strict'; +import * as tempura from "./tempura.js"; const dummy = { loading: { @@ -128,16 +128,15 @@ CardModel.prototype.fromAPIs = function(name, apis) { return this; }; -if (window.$) { - $(document).ready(function () { - var cardTemplateSrc = document.querySelector('script[type="text/dot-template"]').innerText; - var cardTemplate = window.doT.compile(cardTemplateSrc); +export function loadAPIs() { + var cardTemplateSrc = document.querySelector('script[type="text/tempura"]').innerText; + var cardTemplate = tempura.compile(cardTemplateSrc); var updateCards = function(data) { var fragment = $(document.createDocumentFragment()); $.each(data, function (name, apis) { var model = new CardModel().fromAPIs(name, apis); - var view = cardTemplate(model); + var view = cardTemplate({it:model}); fragment.append($(view)); }); @@ -232,5 +231,5 @@ if (window.$) { $('#search-input').focus(); }); - }); } + diff --git a/src/assets/javascript/main.js b/src/assets/javascript/main.js index a53e87a..ab5d4d7 100644 --- a/src/assets/javascript/main.js +++ b/src/assets/javascript/main.js @@ -1,5 +1,3 @@ -'use strict'; - function domReady(cb) { document.addEventListener("DOMContentLoaded", cb, false); } diff --git a/src/assets/javascript/tempura.js b/src/assets/javascript/tempura.js new file mode 100644 index 0000000..93dcd79 --- /dev/null +++ b/src/assets/javascript/tempura.js @@ -0,0 +1,138 @@ +const ESCAPE = /[&"<]/g, CHARS = { + '"': '"', + '&': '&', + '<': '<', +}; + +const ENDLINES = /[\r\n]+$/g; +const CURLY = /{{{?\s*([\s\S]*?)\s*}}}?/g; +const ARGS = /([a-zA-Z$_][^\s=]*)\s*=\s*((["`'])(?:(?=(\\?))\4.)*?\3|{[^}]*}|\[[^\]]*]|\S+)/g; + +// $$1 = escape() +// $$2 = extra blocks +// $$3 = template values +function gen(input, options) { + options = options || {}; + + let char, num, action, tmp; + let last = CURLY.lastIndex = 0; + let wip='', txt='', match, inner; + + let extra=options.blocks||{}, stack=[]; + let initials = new Set(options.props||[]); + + function close() { + if (wip.length > 0) { + txt += (txt ? 'x+=' : '=') + '`' + wip + '`;'; + } else if (txt.length === 0) { + txt = '="";' + } + wip = ''; + } + + while (match = CURLY.exec(input)) { + wip += input.substring(last, match.index).replace(ENDLINES, ''); + last = match.index + match[0].length; + + inner = match[1].trim(); + char = inner.charAt(0); + + if (char === '!') { + // comment, continue + } else if (char === '#') { + close(); + [, action, inner] = /^#\s*(\w[\w\d]+)\s*([^]*)/.exec(inner); + + if (action === 'expect') { + inner.split(/[\n\r\s\t]*,[\n\r\s\t]*/g).forEach(key => { + initials.add(key); + }); + } else if (action === 'var') { + num = inner.indexOf('='); + tmp = inner.substring(0, num++).trim(); + inner = inner.substring(num).trim().replace(/[;]$/, ''); + txt += `var ${tmp}=${inner};`; + } else if (action === 'each') { + num = inner.indexOf(' as '); + stack.push(action); + if (!~num) { + txt += `for(var i=0,$$a=${inner};i<$$a.length;i++){`; + } else { + tmp = inner.substring(0, num).trim(); + inner = inner.substring(num + 4).trim(); + let [item, idx='i'] = inner.replace(/[()\s]/g, '').split(','); // (item, idx?) + txt += `for(var ${idx}=0,${item},$$a=${tmp};${idx}<$$a.length;${idx}++){${item}=$$a[${idx}];`; + } + } else if (action === 'if') { + txt += `if(${inner}){`; + stack.push(action); + } else if (action === 'elif') { + txt += `}else if(${inner}){`; + } else if (action === 'else') { + txt += `}else{`; + } else if (action in extra) { + if (inner) { + tmp = []; + // parse arguments, `defer=true` -> `{ defer: true }` + while (match = ARGS.exec(inner)) tmp.push(match[1] + ':' + match[2]); + inner = tmp.length ? '{' + tmp.join() + '}' : ''; + } + inner = inner || '{}'; + tmp = options.async ? 'await ' : ''; + wip += '${' + tmp + '$$2.' + action + '(' + inner + ',$$2)}'; + } else { + throw new Error(`Unknown "${action}" block`); + } + } else if (char === '/') { + action = inner.substring(1); + inner = stack.pop(); + close(); + if (action === inner) txt += '}'; + else throw new Error(`Expected to close "${inner}" block; closed "${action}" instead`); + } else if (match[0].charAt(2) === '{') { + wip += '${' + inner + '}'; // {{{ raw }}} + } else { + wip += '${$$1(' + inner + ')}'; + } + } + + if (stack.length > 0) { + throw new Error(`Unterminated "${stack.pop()}" block`); + } + + if (last < input.length) { + wip += input.substring(last).replace(ENDLINES, ''); + } + + close(); + + tmp = initials.size ? `{${ [...initials].join() }}=$$3,x` : ' x'; + return `var${tmp + txt}return x`; +} + +export function esc(value) { + if (typeof value !== 'string') return value; + let last=ESCAPE.lastIndex=0, tmp=0, out=''; + while (ESCAPE.test(value)) { + tmp = ESCAPE.lastIndex - 1; + out += value.substring(last, tmp) + CHARS[value[tmp]]; + last = tmp + 1; + } + return out + value.substring(last); +} + +export function compile(input, options={}) { + return new (options.async ? (async()=>{}).constructor : Function)( + '$$1', '$$2', '$$3', gen(input, options) + ).bind(0, options.escape || esc, options.blocks); +} + +export function transform(input, options={}) { + return ( + options.format === 'cjs' + ? 'var $$1=require("tempura").esc;module.exports=' + : 'import{esc as $$1}from"tempura";export default ' + ) + ( + options.async ? 'async ' : '' + ) + 'function($$3,$$2){'+gen(input, options)+'}'; +} diff --git a/src/index.md b/src/index.md index 7aead80..c65d784 100644 --- a/src/index.md +++ b/src/index.md @@ -28,18 +28,20 @@ support: true {% include 'card.html' %} <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script> -<script> +<script type="module"> + + import * as apis from "./assets/javascript/apis.js"; + $(document).ready(function(){ - var newData = false; - if (window.location.href.indexOf('nd=')>=0) newData = true; $.ajax({ type: "GET", - url: (newData ? "https://raw.githubusercontent.com/APIs-guru/openapi-directory/gh-pages/v2/metrics.json" : "https://api.apis.guru/v2/metrics.json"), + url: "https://api.apis.guru/v2/metrics.json", dataType: 'json', cache: true, success: function (data) { $('#numAPIs').text(data.numAPIs.toLocaleString()); } }); + apis.loadAPIs(); }); </script> From 3e90a9623ebebbf04c950f630bfb69746fbbe4ae Mon Sep 17 00:00:00 2001 From: Mike Ralphson <mike.ralphson@gmail.com> Date: Tue, 28 Mar 2023 22:28:30 +0100 Subject: [PATCH 2/2] feat: add ChatGPT plugin manifest --- .eleventy.js | 1 + src/.well-known/ai-plugin.json | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/.well-known/ai-plugin.json diff --git a/.eleventy.js b/.eleventy.js index e343c5b..b66e38a 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -7,6 +7,7 @@ module.exports = function(eleventyConfig) { eleventyConfig.addPassthroughCopy("src/assets"); eleventyConfig.addPassthroughCopy("src/.nojekyll"); eleventyConfig.addPassthroughCopy("src/robots.txt"); + eleventyConfig.addPassthroughCopy("src/.well-known/ai-plugin.json"); return { dir: { // ⚠️ These values are both relative to your input directory. diff --git a/src/.well-known/ai-plugin.json b/src/.well-known/ai-plugin.json new file mode 100644 index 0000000..1f3f82f --- /dev/null +++ b/src/.well-known/ai-plugin.json @@ -0,0 +1,18 @@ +{ + "schema_version": "v1", + "name_for_human": "APIs.guru Plugin", + "name_for_model": "apis.giru", + "description_for_human": "Plugin for accessing APIs.guru OpenAPI Directory.", + "description_for_model": "Plugin for accessing APIs.guru OpenAPI Directory.", + "auth": { + "type": "none" + }, + "api": { + "type": "openapi", + "url": "https://api.apis.guru/v2/openapi.yaml", + "is_user_authenticated": false + }, + "logo_url": "https://api.apis.guru/logo.svg", + "contact_email": "mike.ralphson@gmail.com", + "legal_info_url": "https://apis.guru" +}