Skip to content

Instantly share code, notes, and snippets.

@derekmcloughlin
Created June 21, 2024 11:14
Show Gist options
  • Save derekmcloughlin/8b6bc96c109de5ca098edd23e0646715 to your computer and use it in GitHub Desktop.
Save derekmcloughlin/8b6bc96c109de5ca098edd23e0646715 to your computer and use it in GitHub Desktop.
{"name":"Active Search","method":"url","files":[{"location":"./playgrounds/activesearch/server.js","filename":"server.js","builtin":true,"contents":"import { faker } from 'https://cdn.skypack.dev/@faker-js/faker@v7.4.0';\nimport Fuse from 'https://cdn.jsdelivr.net/npm/fuse.js@7.0.0/dist/fuse.mjs'\n\nfaker.seed(42)\n\nlet contacts = []\n\nfor (let i = 0; i < 300; i++) {\n contacts.push({\n firstName: faker.name.firstName(),\n lastName: faker.name.lastName(),\n })\n}\n\nconst fuse = new Fuse(contacts, {\n keys: ['firstName', 'lastName'],\n includeScore: true\n})\n\non.get(\"/\", (request) => {\n return render(request, 'index.html', {\n results: []\n })\n})\n\non.post(\"/search\", async (request) => {\n let formData = await request.formData()\n let query = formData.get('search')\n\n let results = fuse.search(query)\n\n await sleep(500);\n\n return render(request, 'results.html', {\n results: results.slice(0, 8)\n })\n})\n"},{"location":"./playgrounds/activesearch/.playground.json","filename":".playground.json","builtin":true,"contents":"{\n \"name\": \"Active Search\",\n \"method\": \"url\",\n \"files\": [\n {\n \"location\": \"./playgrounds/activesearch/server.js\",\n \"filename\": \"server.js\",\n \"builtin\": true\n },\n {\n \"location\": \"./playgrounds/activesearch/.playground.json\",\n \"filename\": \".playground.json\",\n \"builtin\": true\n },\n {\n \"location\": \"./playgrounds/.loader.html\",\n \"filename\": \".loader.html\",\n \"builtin\": true\n },\n {\n \"location\": \"./playgrounds/activesearch/index.html\",\n \"filename\": \"index.html\"\n },\n {\n \"location\": \"./playgrounds/activesearch/results.html\",\n \"filename\": \"results.html\"\n }\n ]\n}"},{"location":"./playgrounds/.loader.html","filename":".loader.html","builtin":true,"contents":"<script src=\"./js/nunjucks.js\"></script>\n<script src=\"./js/pollyjs-core.js\"></script>\n<script src=\"./js/pollyjs-adapter-fetch.js\"></script>\n<script src=\"./js/pollyjs-adapter-xhr.js\"></script>\n<script>\n\n document.addEventListener('htmx:configRequest', function(evt) {\n evt.detail.path = window.location.href + evt.detail.path;\n });\n\n const fileContents = {};\n files.forEach(file => fileContents[file.filename] = file.contents);\n\n function readFile(filename) {\n return fileContents[filename]\n }\n\n let TemplateLoader = {\n getSource: (name) => ({\n src: readFile(name),\n path: name\n })\n }\n var templates = new nunjucks.Environment(TemplateLoader, {\n autoescape: false\n })\n\n function render(request, template, context) {\n let out = templates.render(template, {...context, request});\n \n return new Response(out, {\n headers: {\n 'content-type': 'text/html'\n }\n })\n }\n\n const { Polly } = window['@pollyjs/core'];\n\n Polly.register(window['@pollyjs/adapter-fetch']);\n Polly.register(window['@pollyjs/adapter-xhr']);\n\n const polly = new Polly('sandbox-server', {\n adapters: ['fetch', 'xhr'],\n mode: \"passthrough\",\n logging: true,\n });\n\n const server = polly.server;\n \n const on = {};\n \n ['get','put','post','patch','delete','merge','head','options'].forEach((method) => {\n on[method] = (route, handler) => {\n \n let parsedRoute = window.location.href + route.replaceAll(/\\/<([^>]+)>/gm, (_, m) => \"/:\"+m)\n\n server[method](parsedRoute).intercept(async (req, res) => {\n\n var requestHeaders = Object.assign({}, req.headers)\n var request = new Request(req.url, {\n body: req.body,\n headers: requestHeaders,\n method: req.method,\n })\n\n var response = await handler(request, ...Object.values(req.params), ...new URL(req.url).searchParams.values())\n\n if (!response) throw new Error(\"Handler returned no response!\") // Return 404?\n\n let responseHeaders = {}\n\n for (const [k,v] of response.headers.entries()) {\n res.setHeader(k,v);\n responseHeaders[k] = v\n }\n\n let text = await response.text()\n\n \n res.status(response.status).send(text);\n \n window.parent.postMessage({\n type: \"network_log\",\n request: {\n url: req.url.replace(window.location.href, ''),\n body: req.body,\n method: req.method,\n headers: requestHeaders,\n },\n response: {\n headers: responseHeaders,\n status: response.status,\n body: text,\n },\n }, \"*\")\n });\n }\n })\n \n const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay))\n</script>\n<script type=\"module\">\n ///server.js\n</script>"},{"location":"./playgrounds/activesearch/index.html","filename":"index.html","contents":"<html>\n<head>\n <link href=\"https://unpkg.com/water.css@2/out/dark.css\" rel=\"stylesheet\">\n <script src=\"https://unpkg.com/htmx.org@<2/dist/htmx.js\" defer></script>\n</head>\n<body>\n \n <h1>Active Search <span class=\"htmx-indicator\">Searching...</span></h1>\n\n <input type=\"search\" \n name=\"search\" placeholder=\"Type to search...\" \n hx-post=\"/search\" \n hx-trigger=\"input changed delay:500ms, search\" \n hx-target=\"#search-results\"\n hx-indicator=\".htmx-indicator\">\n \n <table>\n <thead>\n <tr>\n <th>Distance</th>\n <th>First Name</th>\n <th>Last Name</th>\n </tr>\n </thead>\n <tbody id=\"search-results\"></tbody>\n </table>\n\n</body>\n</html>"},{"location":"./playgrounds/activesearch/results.html","filename":"results.html","contents":"{% for result in results %}\n <tr>\n <td>{{ result.score|round(2) }}</td>\n <td>{{ result.item.firstName }}</td>\n <td>{{ result.item.lastName }}</td>\n </tr>\n{% endfor %}"}]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment