katalyst-koi 5.2.0 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dfcdf49c4632945c3b3a9377d1059b48c369735936e14f2d5e8aa8ebfe2cba26
4
- data.tar.gz: 1e065fb94849acb2a26cfd4d2e94fe9ef48e78dacf60e4b08237dce29d12df2d
3
+ metadata.gz: 6937b0a5bf4cdef293b89e7c161bd5d93f04ad21a36f49cbd4076fc2852f18de
4
+ data.tar.gz: 1972770915bc76a57277111d7f89f7765b318f658ce8e038f81f62bb4ada74f5
5
5
  SHA512:
6
- metadata.gz: de5af9e249dceeedbc1ee44ea191544ee8c601386098b6c2b605da4876a153f8884d18bee17c1da19b086ce9d43df5c67f255f93cf6f1a26fd49a9411348814a
7
- data.tar.gz: 533c94541efccfe3b806b20fc1c4e7d39b0888b0fade694d366f82a90c296f97a65c1c7e95f4b7bfbc8e4b14129128313398a2d328c8d234fb57ca15224a7215
6
+ metadata.gz: 9413dc2f0a998a20ea1e7b82aad89a00f184a2bb7ba092bcb05107da11e20ed62bf4542e9142139ae66c6f004187ffbce38c051c58664c20811ab98161a5f291
7
+ data.tar.gz: e11c4ca35de970aee07600c460d17c6c11a6a77e74a0925d2ca05fc7ca9eb8f86844417d3c24f67af5c679edee7fd803ae920ed80d32a736298bbd9dbfdae708
@@ -6,7 +6,6 @@ import { Application, Controller } from '@hotwired/stimulus';
6
6
  import content from '@katalyst/content';
7
7
  import navigation from '@katalyst/navigation';
8
8
  import tables from '@katalyst/tables';
9
- import { get, parseRequestOptionsFromJSON, create, parseCreationOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
10
9
 
11
10
  const application = Application.start();
12
11
 
@@ -385,25 +384,31 @@ function parameterize(input) {
385
384
 
386
385
  class WebauthnAuthenticationController extends Controller {
387
386
  static targets = ["response"];
388
- static values = { options: Object };
387
+ static values = {
388
+ options: Object,
389
+ };
389
390
 
390
- authenticate() {
391
- get(this.options).then((response) => {
392
- this.responseTarget.value = JSON.stringify(response);
391
+ async authenticate() {
392
+ const credential = await navigator.credentials.get(this.options);
393
393
 
394
- this.element.requestSubmit();
395
- });
394
+ this.responseTarget.value = JSON.stringify(credential.toJSON());
395
+
396
+ this.element.requestSubmit();
396
397
  }
397
398
 
398
399
  get options() {
399
- return parseRequestOptionsFromJSON(this.optionsValue);
400
+ return {
401
+ publicKey: PublicKeyCredential.parseRequestOptionsFromJSON(
402
+ this.optionsValue.publicKey,
403
+ ),
404
+ };
400
405
  }
401
406
  }
402
407
 
403
408
  class WebauthnRegistrationController extends Controller {
404
409
  static values = {
405
410
  options: Object,
406
- response: String,
411
+ response: Object,
407
412
  };
408
413
  static targets = ["intro", "nickname", "response"];
409
414
 
@@ -413,15 +418,15 @@ class WebauthnRegistrationController extends Controller {
413
418
  e.submitter.formMethod !== "dialog"
414
419
  ) {
415
420
  e.preventDefault();
416
- this.createCredential();
421
+ this.createCredential().then();
417
422
  }
418
423
  }
419
424
 
420
425
  async createCredential() {
421
- const response = await create(this.options);
426
+ const credential = await navigator.credentials.create(this.options);
422
427
 
423
- this.responseValue = JSON.stringify(response);
424
- this.responseTarget.value = JSON.stringify(response);
428
+ this.responseValue = credential.toJSON();
429
+ this.responseTarget.value = JSON.stringify(credential.toJSON());
425
430
  }
426
431
 
427
432
  responseValueChanged(response) {
@@ -431,7 +436,11 @@ class WebauthnRegistrationController extends Controller {
431
436
  }
432
437
 
433
438
  get options() {
434
- return parseCreationOptionsFromJSON(this.optionsValue);
439
+ return {
440
+ publicKey: PublicKeyCredential.parseCreationOptionsFromJSON(
441
+ this.optionsValue.publicKey,
442
+ ),
443
+ };
435
444
  }
436
445
  }
437
446
 
@@ -6,7 +6,6 @@ import { Application, Controller } from '@hotwired/stimulus';
6
6
  import content from '@katalyst/content';
7
7
  import navigation from '@katalyst/navigation';
8
8
  import tables from '@katalyst/tables';
9
- import { get, parseRequestOptionsFromJSON, create, parseCreationOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
10
9
 
11
10
  const application = Application.start();
12
11
 
@@ -385,25 +384,31 @@ function parameterize(input) {
385
384
 
386
385
  class WebauthnAuthenticationController extends Controller {
387
386
  static targets = ["response"];
388
- static values = { options: Object };
387
+ static values = {
388
+ options: Object,
389
+ };
389
390
 
390
- authenticate() {
391
- get(this.options).then((response) => {
392
- this.responseTarget.value = JSON.stringify(response);
391
+ async authenticate() {
392
+ const credential = await navigator.credentials.get(this.options);
393
393
 
394
- this.element.requestSubmit();
395
- });
394
+ this.responseTarget.value = JSON.stringify(credential.toJSON());
395
+
396
+ this.element.requestSubmit();
396
397
  }
397
398
 
398
399
  get options() {
399
- return parseRequestOptionsFromJSON(this.optionsValue);
400
+ return {
401
+ publicKey: PublicKeyCredential.parseRequestOptionsFromJSON(
402
+ this.optionsValue.publicKey,
403
+ ),
404
+ };
400
405
  }
401
406
  }
402
407
 
403
408
  class WebauthnRegistrationController extends Controller {
404
409
  static values = {
405
410
  options: Object,
406
- response: String,
411
+ response: Object,
407
412
  };
408
413
  static targets = ["intro", "nickname", "response"];
409
414
 
@@ -413,15 +418,15 @@ class WebauthnRegistrationController extends Controller {
413
418
  e.submitter.formMethod !== "dialog"
414
419
  ) {
415
420
  e.preventDefault();
416
- this.createCredential();
421
+ this.createCredential().then();
417
422
  }
418
423
  }
419
424
 
420
425
  async createCredential() {
421
- const response = await create(this.options);
426
+ const credential = await navigator.credentials.create(this.options);
422
427
 
423
- this.responseValue = JSON.stringify(response);
424
- this.responseTarget.value = JSON.stringify(response);
428
+ this.responseValue = credential.toJSON();
429
+ this.responseTarget.value = JSON.stringify(credential.toJSON());
425
430
  }
426
431
 
427
432
  responseValueChanged(response) {
@@ -431,7 +436,11 @@ class WebauthnRegistrationController extends Controller {
431
436
  }
432
437
 
433
438
  get options() {
434
- return parseCreationOptionsFromJSON(this.optionsValue);
439
+ return {
440
+ publicKey: PublicKeyCredential.parseCreationOptionsFromJSON(
441
+ this.optionsValue.publicKey,
442
+ ),
443
+ };
435
444
  }
436
445
  }
437
446
 
@@ -1,2 +1,2 @@
1
- import"@hotwired/turbo-rails";import e,{initAll as t}from"@katalyst/govuk-formbuilder";import"@rails/actiontext";import"trix";import{Application as i,Controller as r}from"@hotwired/stimulus";import s from"@katalyst/content";import o from"@katalyst/navigation";import n from"@katalyst/tables";import{get as a,parseRequestOptionsFromJSON as l,create as c,parseCreationOptionsFromJSON as u}from"@github/webauthn-json/browser-ponyfill";const h=i.start();h.load(s),h.load(e),h.load(o),h.load(n);const d=[{identifier:"clipboard",controllerConstructor:class extends r{static targets=["source"];static classes=["supported"];connect(){"clipboard"in navigator&&this.element.classList.add(this.supportedClass)}copy(e){e.preventDefault(),navigator.clipboard.writeText(this.sourceTarget.value),this.element.classList.add("copied"),setTimeout(()=>{this.element.classList.remove("copied")},2e3)}}},{identifier:"flash",controllerConstructor:class extends r{close(e){e.target.closest("li").remove(),0===this.element.children.length&&this.element.remove()}}},{identifier:"keyboard",controllerConstructor:class extends r{static values={mapping:String,depth:{type:Number,default:2}};event(e){if(function(e){if(!(e instanceof HTMLElement))return!1;const t=e.nodeName.toLowerCase(),i=(e.getAttribute("type")||"").toLowerCase();return"select"===t||"textarea"===t||"trix-editor"===t||"input"===t&&"submit"!==i&&"reset"!==i&&"checkbox"!==i&&"radio"!==i&&"file"!==i||e.isContentEditable}(e.target)||this.#e(e))return;const t=this.describeEvent(e);this.buffer=[...this.buffer||[],t].slice(0-this.depthValue);const i=this.buffer.reduceRight((e,t)=>"string"==typeof e||void 0===e?e:e[t],this.mappings);if("string"!=typeof i)return;this.buffer=[],e.preventDefault();const r=new CustomEvent(i,{detail:{cause:e},bubbles:!0});e.target.dispatchEvent(r)}describeEvent(e){return[e.ctrlKey&&"C",e.metaKey&&"M",e.altKey&&"A",e.shiftKey&&"S",e.code].filter(e=>e).join("-")}get mappings(){const e=this.mappingValue.replaceAll(/\s+/g," ").split(" ").filter(e=>e.length>0),t={};return e.forEach(e=>this.#t(t,e)),Object.defineProperty(this,"mappings",{value:t,writable:!1}),t}#t(e,t){const[i,r]=t.split("->"),s=i.split("+"),o=s.shift();(e=s.reduceRight((e,t)=>e[t]||={},e))[o]=r}#e(e){switch(e.code){case"ControlLeft":case"ControlRight":case"MetaLeft":case"MetaRight":case"ShiftLeft":case"ShiftRight":case"AltLeft":case"AltRight":return!0;default:return!1}}}},{identifier:"modal",controllerConstructor:class extends r{static targets=["dialog"];connect(){this.element.addEventListener("turbo:submit-end",this.onSubmit)}disconnect(){this.element.removeEventListener("turbo:submit-end",this.onSubmit)}outside(e){"DIALOG"===e.target.tagName&&this.dismiss()}dismiss(){this.dialogTarget&&(this.dialogTarget.open||this.dialogTarget.close(),this.element.removeAttribute("src"),this.dialogTarget.remove())}dialogTargetConnected(e){e.showModal()}onSubmit=e=>{e.detail.success&&"closeDialog"in e.detail.formSubmission?.submitter?.dataset&&(this.dialogTarget.close(),this.element.removeAttribute("src"),this.dialogTarget.remove())}}},{identifier:"navigation",controllerConstructor:class extends r{static targets=["filter"];filter(){const e=this.filterTarget.value;this.clearFilter(e),e.length>0&&this.applyFilter(e)}go(){this.element.querySelector("li:not([hidden]) > a").click()}clear(){0===this.filterTarget.value.length&&this.filterTarget.blur()}applyFilter(e){this.links.filter(t=>!this.prefixSearch(e.toLowerCase(),t.innerText.toLowerCase())).forEach(e=>{e.toggleAttribute("hidden",!0)}),this.menus.filter(e=>!e.matches("li:has(li:not([hidden]) > a)")).forEach(e=>{e.toggleAttribute("hidden",!0)})}clearFilter(e){this.element.querySelectorAll("li").forEach(e=>{e.toggleAttribute("hidden",!1)})}prefixSearch(e,t){const i=t.length,r=e.length;if(r>i)return!1;if(r===i)return e===t;e:for(let s=0,o=0;s<r;s++){const r=e.charCodeAt(s);if(32!==r){for(;o<i;){if(t.charCodeAt(o++)===r)continue e;for(;o<i&&32!==t.charCodeAt(o++););}return!1}for(;o<i&&32!==t.charCodeAt(o++););}return!0}toggle(){this.element.open?this.close():this.open()}open(){this.element.open||this.element.showModal()}close(){this.element.open&&this.element.close()}click(e){e.target===this.element&&this.close()}onMorphAttribute=e=>{if(e.target===this.element&&"open"===e.detail.attributeName)e.preventDefault()};get links(){return Array.from(this.element.querySelectorAll("li:has(> a)"))}get menus(){return Array.from(this.element.querySelectorAll("li:has(> ul)"))}}},{identifier:"navigation-toggle",controllerConstructor:class extends r{trigger(){this.dispatch("toggle",{prefix:"navigation",bubbles:!0})}}},{identifier:"pagy-nav",controllerConstructor:class extends r{connect(){document.addEventListener("shortcut:page-prev",this.prevPage),document.addEventListener("shortcut:page-next",this.nextPage)}disconnect(){document.removeEventListener("shortcut:page-prev",this.prevPage),document.removeEventListener("shortcut:page-next",this.nextPage)}nextPage=()=>{this.element.querySelector("a:last-child").click()};prevPage=()=>{this.element.querySelector("a:first-child").click()}}},{identifier:"sluggable",controllerConstructor:class extends r{static targets=["source","slug"];static values={slug:String};sourceChanged(e){""===this.slugValue&&(this.slugTarget.value=this.sourceTarget.value.toLowerCase().replace(/'/g,"-").replace(/[^-\w\s]/g,"").replace(/[^a-z0-9]+/g,"-").replace(/(^-|-$)/g,""))}slugChanged(e){this.slugValue=this.slugTarget.value}}},{identifier:"webauthn-authentication",controllerConstructor:class extends r{static targets=["response"];static values={options:Object};authenticate(){a(this.options).then(e=>{this.responseTarget.value=JSON.stringify(e),this.element.requestSubmit()})}get options(){return l(this.optionsValue)}}},{identifier:"webauthn-registration",controllerConstructor:class extends r{static values={options:Object,response:String};static targets=["intro","nickname","response"];submit(e){""===this.responseTarget.value&&"dialog"!==e.submitter.formMethod&&(e.preventDefault(),this.createCredential())}async createCredential(){const e=await c(this.options);this.responseValue=JSON.stringify(e),this.responseTarget.value=JSON.stringify(e)}responseValueChanged(e){const t=""!==e;this.introTarget.toggleAttribute("hidden",t),this.nicknameTarget.toggleAttribute("hidden",!t)}get options(){return u(this.optionsValue)}}}];await import("controllers/hw_combobox_controller").then(({default:e})=>{d.push({identifier:"hw-combobox",controllerConstructor:e})}).catch(()=>null),h.load(d);class g extends HTMLElement{constructor(){super(),this.setAttribute("role","toolbar")}}function p(){document.body.classList.toggle("js-enabled",!0),document.body.classList.toggle("govuk-frontend-supported","noModule"in HTMLScriptElement.prototype),t()}customElements.define("koi-toolbar",g),window.addEventListener("turbo:load",p),window.Turbo&&p();
1
+ import"@hotwired/turbo-rails";import e,{initAll as t}from"@katalyst/govuk-formbuilder";import"@rails/actiontext";import"trix";import{Application as i,Controller as r}from"@hotwired/stimulus";import s from"@katalyst/content";import o from"@katalyst/navigation";import n from"@katalyst/tables";const a=i.start();a.load(s),a.load(e),a.load(o),a.load(n);const l=[{identifier:"clipboard",controllerConstructor:class extends r{static targets=["source"];static classes=["supported"];connect(){"clipboard"in navigator&&this.element.classList.add(this.supportedClass)}copy(e){e.preventDefault(),navigator.clipboard.writeText(this.sourceTarget.value),this.element.classList.add("copied"),setTimeout(()=>{this.element.classList.remove("copied")},2e3)}}},{identifier:"flash",controllerConstructor:class extends r{close(e){e.target.closest("li").remove(),0===this.element.children.length&&this.element.remove()}}},{identifier:"keyboard",controllerConstructor:class extends r{static values={mapping:String,depth:{type:Number,default:2}};event(e){if(function(e){if(!(e instanceof HTMLElement))return!1;const t=e.nodeName.toLowerCase(),i=(e.getAttribute("type")||"").toLowerCase();return"select"===t||"textarea"===t||"trix-editor"===t||"input"===t&&"submit"!==i&&"reset"!==i&&"checkbox"!==i&&"radio"!==i&&"file"!==i||e.isContentEditable}(e.target)||this.#e(e))return;const t=this.describeEvent(e);this.buffer=[...this.buffer||[],t].slice(0-this.depthValue);const i=this.buffer.reduceRight((e,t)=>"string"==typeof e||void 0===e?e:e[t],this.mappings);if("string"!=typeof i)return;this.buffer=[],e.preventDefault();const r=new CustomEvent(i,{detail:{cause:e},bubbles:!0});e.target.dispatchEvent(r)}describeEvent(e){return[e.ctrlKey&&"C",e.metaKey&&"M",e.altKey&&"A",e.shiftKey&&"S",e.code].filter(e=>e).join("-")}get mappings(){const e=this.mappingValue.replaceAll(/\s+/g," ").split(" ").filter(e=>e.length>0),t={};return e.forEach(e=>this.#t(t,e)),Object.defineProperty(this,"mappings",{value:t,writable:!1}),t}#t(e,t){const[i,r]=t.split("->"),s=i.split("+"),o=s.shift();(e=s.reduceRight((e,t)=>e[t]||={},e))[o]=r}#e(e){switch(e.code){case"ControlLeft":case"ControlRight":case"MetaLeft":case"MetaRight":case"ShiftLeft":case"ShiftRight":case"AltLeft":case"AltRight":return!0;default:return!1}}}},{identifier:"modal",controllerConstructor:class extends r{static targets=["dialog"];connect(){this.element.addEventListener("turbo:submit-end",this.onSubmit)}disconnect(){this.element.removeEventListener("turbo:submit-end",this.onSubmit)}outside(e){"DIALOG"===e.target.tagName&&this.dismiss()}dismiss(){this.dialogTarget&&(this.dialogTarget.open||this.dialogTarget.close(),this.element.removeAttribute("src"),this.dialogTarget.remove())}dialogTargetConnected(e){e.showModal()}onSubmit=e=>{e.detail.success&&"closeDialog"in e.detail.formSubmission?.submitter?.dataset&&(this.dialogTarget.close(),this.element.removeAttribute("src"),this.dialogTarget.remove())}}},{identifier:"navigation",controllerConstructor:class extends r{static targets=["filter"];filter(){const e=this.filterTarget.value;this.clearFilter(e),e.length>0&&this.applyFilter(e)}go(){this.element.querySelector("li:not([hidden]) > a").click()}clear(){0===this.filterTarget.value.length&&this.filterTarget.blur()}applyFilter(e){this.links.filter(t=>!this.prefixSearch(e.toLowerCase(),t.innerText.toLowerCase())).forEach(e=>{e.toggleAttribute("hidden",!0)}),this.menus.filter(e=>!e.matches("li:has(li:not([hidden]) > a)")).forEach(e=>{e.toggleAttribute("hidden",!0)})}clearFilter(e){this.element.querySelectorAll("li").forEach(e=>{e.toggleAttribute("hidden",!1)})}prefixSearch(e,t){const i=t.length,r=e.length;if(r>i)return!1;if(r===i)return e===t;e:for(let s=0,o=0;s<r;s++){const r=e.charCodeAt(s);if(32!==r){for(;o<i;){if(t.charCodeAt(o++)===r)continue e;for(;o<i&&32!==t.charCodeAt(o++););}return!1}for(;o<i&&32!==t.charCodeAt(o++););}return!0}toggle(){this.element.open?this.close():this.open()}open(){this.element.open||this.element.showModal()}close(){this.element.open&&this.element.close()}click(e){e.target===this.element&&this.close()}onMorphAttribute=e=>{if(e.target===this.element&&"open"===e.detail.attributeName)e.preventDefault()};get links(){return Array.from(this.element.querySelectorAll("li:has(> a)"))}get menus(){return Array.from(this.element.querySelectorAll("li:has(> ul)"))}}},{identifier:"navigation-toggle",controllerConstructor:class extends r{trigger(){this.dispatch("toggle",{prefix:"navigation",bubbles:!0})}}},{identifier:"pagy-nav",controllerConstructor:class extends r{connect(){document.addEventListener("shortcut:page-prev",this.prevPage),document.addEventListener("shortcut:page-next",this.nextPage)}disconnect(){document.removeEventListener("shortcut:page-prev",this.prevPage),document.removeEventListener("shortcut:page-next",this.nextPage)}nextPage=()=>{this.element.querySelector("a:last-child").click()};prevPage=()=>{this.element.querySelector("a:first-child").click()}}},{identifier:"sluggable",controllerConstructor:class extends r{static targets=["source","slug"];static values={slug:String};sourceChanged(e){""===this.slugValue&&(this.slugTarget.value=this.sourceTarget.value.toLowerCase().replace(/'/g,"-").replace(/[^-\w\s]/g,"").replace(/[^a-z0-9]+/g,"-").replace(/(^-|-$)/g,""))}slugChanged(e){this.slugValue=this.slugTarget.value}}},{identifier:"webauthn-authentication",controllerConstructor:class extends r{static targets=["response"];static values={options:Object};async authenticate(){const e=await navigator.credentials.get(this.options);this.responseTarget.value=JSON.stringify(e.toJSON()),this.element.requestSubmit()}get options(){return{publicKey:PublicKeyCredential.parseRequestOptionsFromJSON(this.optionsValue.publicKey)}}}},{identifier:"webauthn-registration",controllerConstructor:class extends r{static values={options:Object,response:Object};static targets=["intro","nickname","response"];submit(e){""===this.responseTarget.value&&"dialog"!==e.submitter.formMethod&&(e.preventDefault(),this.createCredential().then())}async createCredential(){const e=await navigator.credentials.create(this.options);this.responseValue=e.toJSON(),this.responseTarget.value=JSON.stringify(e.toJSON())}responseValueChanged(e){const t=""!==e;this.introTarget.toggleAttribute("hidden",t),this.nicknameTarget.toggleAttribute("hidden",!t)}get options(){return{publicKey:PublicKeyCredential.parseCreationOptionsFromJSON(this.optionsValue.publicKey)}}}}];await import("controllers/hw_combobox_controller").then(({default:e})=>{l.push({identifier:"hw-combobox",controllerConstructor:e})}).catch(()=>null),a.load(l);class c extends HTMLElement{constructor(){super(),this.setAttribute("role","toolbar")}}function u(){document.body.classList.toggle("js-enabled",!0),document.body.classList.toggle("govuk-frontend-supported","noModule"in HTMLScriptElement.prototype),t()}customElements.define("koi-toolbar",c),window.addEventListener("turbo:load",u),window.Turbo&&u();
2
2
  //# sourceMappingURL=koi.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"koi.min.js","sources":["../../../javascript/koi/controllers/application.js","../../../javascript/koi/controllers/index.js","../../../javascript/koi/controllers/clipboard_controller.js","../../../javascript/koi/controllers/flash_controller.js","../../../javascript/koi/controllers/keyboard_controller.js","../../../javascript/koi/controllers/modal_controller.js","../../../javascript/koi/controllers/navigation_controller.js","../../../javascript/koi/controllers/navigation_toggle_controller.js","../../../javascript/koi/controllers/pagy_nav_controller.js","../../../javascript/koi/controllers/sluggable_controller.js","../../../javascript/koi/controllers/webauthn_authentication_controller.js","../../../javascript/koi/controllers/webauthn_registration_controller.js","../../../javascript/koi/elements/toolbar.js","../../../javascript/koi/application.js"],"sourcesContent":["import { Application } from \"@hotwired/stimulus\";\n\nconst application = Application.start();\n\nexport { application };\n","import { application } from \"./application\";\n\nimport content from \"@katalyst/content\";\napplication.load(content);\n\nimport govuk from \"@katalyst/govuk-formbuilder\";\napplication.load(govuk);\n\nimport navigation from \"@katalyst/navigation\";\napplication.load(navigation);\n\nimport tables from \"@katalyst/tables\";\napplication.load(tables);\n\nimport ClipboardController from \"./clipboard_controller\";\nimport FlashController from \"./flash_controller\";\nimport KeyboardController from \"./keyboard_controller\";\nimport ModalController from \"./modal_controller\";\nimport NavigationController from \"./navigation_controller\";\nimport NavigationToggleController from \"./navigation_toggle_controller\";\nimport PagyNavController from \"./pagy_nav_controller\";\nimport SluggableController from \"./sluggable_controller\";\nimport WebauthnAuthenticationController from \"./webauthn_authentication_controller\";\nimport WebauthnRegistrationController from \"./webauthn_registration_controller\";\n\nconst Definitions = [\n {\n identifier: \"clipboard\",\n controllerConstructor: ClipboardController,\n },\n {\n identifier: \"flash\",\n controllerConstructor: FlashController,\n },\n {\n identifier: \"keyboard\",\n controllerConstructor: KeyboardController,\n },\n {\n identifier: \"modal\",\n controllerConstructor: ModalController,\n },\n {\n identifier: \"navigation\",\n controllerConstructor: NavigationController,\n },\n {\n identifier: \"navigation-toggle\",\n controllerConstructor: NavigationToggleController,\n },\n {\n identifier: \"pagy-nav\",\n controllerConstructor: PagyNavController,\n },\n {\n identifier: \"sluggable\",\n controllerConstructor: SluggableController,\n },\n {\n identifier: \"webauthn-authentication\",\n controllerConstructor: WebauthnAuthenticationController,\n },\n {\n identifier: \"webauthn-registration\",\n controllerConstructor: WebauthnRegistrationController,\n },\n];\n\n// dynamically attempt to load hw_combobox_controller, this is an optional dependency\nawait import(\"controllers/hw_combobox_controller\")\n .then(({ default: HwComboboxController }) => {\n Definitions.push({\n identifier: \"hw-combobox\",\n controllerConstructor: HwComboboxController,\n });\n })\n .catch(() => null);\n\napplication.load(Definitions);\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class ClipboardController extends Controller {\n static targets = [\"source\"];\n\n static classes = [\"supported\"];\n\n connect() {\n if (\"clipboard\" in navigator) {\n this.element.classList.add(this.supportedClass);\n }\n }\n\n copy(event) {\n event.preventDefault();\n navigator.clipboard.writeText(this.sourceTarget.value);\n\n this.element.classList.add(\"copied\");\n setTimeout(() => {\n this.element.classList.remove(\"copied\");\n }, 2000);\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class FlashController extends Controller {\n close(e) {\n e.target.closest(\"li\").remove();\n\n // remove the flash container if there are no more flashes\n if (this.element.children.length === 0) {\n this.element.remove();\n }\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nconst DEBUG = false;\n\nexport default class KeyboardController extends Controller {\n static values = {\n mapping: String,\n depth: { type: Number, default: 2 },\n };\n\n event(cause) {\n if (isFormField(cause.target) || this.#ignore(cause)) return;\n\n const key = this.describeEvent(cause);\n\n this.buffer = [...(this.buffer || []), key].slice(0 - this.depthValue);\n\n if (DEBUG) console.debug(\"[keyboard] buffer:\", ...this.buffer);\n\n // test whether the tail of the buffer matches any of the configured chords\n const action = this.buffer.reduceRight((mapping, key) => {\n if (typeof mapping === \"string\" || typeof mapping === \"undefined\") {\n return mapping;\n } else {\n return mapping[key];\n }\n }, this.mappings);\n\n // if we don't have a string we may have a miss or an incomplete chord\n if (typeof action !== \"string\") return;\n\n // clear the buffer and prevent the key from being consumed elsewhere\n this.buffer = [];\n cause.preventDefault();\n\n if (DEBUG) console.debug(\"[keyboard] event: %s\", action);\n\n // fire the configured event\n const event = new CustomEvent(action, {\n detail: { cause: cause },\n bubbles: true,\n });\n cause.target.dispatchEvent(event);\n }\n\n /**\n * @param event KeyboardEvent input event to describe\n * @return String description of keyboard event, e.g. 'C-KeyV' (CTRL+V)\n */\n describeEvent(event) {\n return [\n event.ctrlKey && \"C\",\n event.metaKey && \"M\",\n event.altKey && \"A\",\n event.shiftKey && \"S\",\n event.code,\n ]\n .filter((w) => w)\n .join(\"-\");\n }\n\n /**\n * Build a tree for efficiently looking up key chords, where the last key in the sequence\n * is the first key in tree.\n */\n get mappings() {\n const inputs = this.mappingValue\n .replaceAll(/\\s+/g, \" \")\n .split(\" \")\n .filter((f) => f.length > 0);\n const mappings = {};\n\n inputs.forEach((mapping) => this.#parse(mappings, mapping));\n\n // memoize the result\n Object.defineProperty(this, \"mappings\", {\n value: mappings,\n writable: false,\n });\n\n return mappings;\n }\n\n /**\n * Parse a key chord pattern and an event and store it in the inverted tree lookup structure.\n *\n * @param mappings inverted tree lookup for key chords\n * @param mapping input definition, e.g. \"C-KeyC+C-KeyV->paste\"\n */\n #parse(mappings, mapping) {\n const [pattern, event] = mapping.split(\"->\");\n const keys = pattern.split(\"+\");\n const first = keys.shift();\n\n mappings = keys.reduceRight(\n (mappings, key) => (mappings[key] ||= {}),\n mappings,\n );\n mappings[first] = event;\n }\n\n /**\n * Ignore modifier keys, as they will be captured in normal key presses.\n *\n * @param event KeyboardEvent\n * @returns {boolean} true if key event should be ignored\n */\n #ignore(event) {\n switch (event.code) {\n case \"ControlLeft\":\n case \"ControlRight\":\n case \"MetaLeft\":\n case \"MetaRight\":\n case \"ShiftLeft\":\n case \"ShiftRight\":\n case \"AltLeft\":\n case \"AltRight\":\n return true;\n default:\n return false;\n }\n }\n}\n\n/**\n * Detect input nodes where we should not listen for events.\n *\n * Credit: github.com\n */\nfunction isFormField(element) {\n if (!(element instanceof HTMLElement)) {\n return false;\n }\n\n const name = element.nodeName.toLowerCase();\n const type = (element.getAttribute(\"type\") || \"\").toLowerCase();\n return (\n name === \"select\" ||\n name === \"textarea\" ||\n name === \"trix-editor\" ||\n (name === \"input\" &&\n type !== \"submit\" &&\n type !== \"reset\" &&\n type !== \"checkbox\" &&\n type !== \"radio\" &&\n type !== \"file\") ||\n element.isContentEditable\n );\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class ModalController extends Controller {\n static targets = [\"dialog\"];\n\n connect() {\n this.element.addEventListener(\"turbo:submit-end\", this.onSubmit);\n }\n\n disconnect() {\n this.element.removeEventListener(\"turbo:submit-end\", this.onSubmit);\n }\n\n outside(e) {\n if (e.target.tagName === \"DIALOG\") this.dismiss();\n }\n\n dismiss() {\n if (!this.dialogTarget) return;\n if (!this.dialogTarget.open) this.dialogTarget.close();\n\n this.element.removeAttribute(\"src\");\n this.dialogTarget.remove();\n }\n\n dialogTargetConnected(dialog) {\n dialog.showModal();\n }\n\n onSubmit = (event) => {\n if (\n event.detail.success &&\n \"closeDialog\" in event.detail.formSubmission?.submitter?.dataset\n ) {\n this.dialogTarget.close();\n this.element.removeAttribute(\"src\");\n this.dialogTarget.remove();\n }\n };\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class NavigationController extends Controller {\n static targets = [\"filter\"];\n\n filter() {\n const filter = this.filterTarget.value;\n this.clearFilter(filter);\n\n if (filter.length > 0) {\n this.applyFilter(filter);\n }\n }\n\n go() {\n this.element.querySelector(\"li:not([hidden]) > a\").click();\n }\n\n clear() {\n if (this.filterTarget.value.length === 0) this.filterTarget.blur();\n }\n\n applyFilter(filter) {\n // hide items that don't match the search filter\n this.links\n .filter(\n (li) =>\n !this.prefixSearch(filter.toLowerCase(), li.innerText.toLowerCase()),\n )\n .forEach((li) => {\n li.toggleAttribute(\"hidden\", true);\n });\n\n this.menus\n .filter((li) => !li.matches(\"li:has(li:not([hidden]) > a)\"))\n .forEach((li) => {\n li.toggleAttribute(\"hidden\", true);\n });\n }\n\n clearFilter(filter) {\n this.element.querySelectorAll(\"li\").forEach((li) => {\n li.toggleAttribute(\"hidden\", false);\n });\n }\n\n prefixSearch(needle, haystack) {\n const haystackLength = haystack.length;\n const needleLength = needle.length;\n if (needleLength > haystackLength) {\n return false;\n }\n if (needleLength === haystackLength) {\n return needle === haystack;\n }\n outer: for (let i = 0, j = 0; i < needleLength; i++) {\n const needleChar = needle.charCodeAt(i);\n if (needleChar === 32) {\n // skip ahead to next space in the haystack\n while (j < haystackLength && haystack.charCodeAt(j++) !== 32) {}\n continue;\n }\n while (j < haystackLength) {\n if (haystack.charCodeAt(j++) === needleChar) continue outer;\n // skip ahead to the next space in the haystack\n while (j < haystackLength && haystack.charCodeAt(j++) !== 32) {}\n }\n return false;\n }\n return true;\n }\n\n toggle() {\n this.element.open ? this.close() : this.open();\n }\n\n open() {\n if (!this.element.open) this.element.showModal();\n }\n\n close() {\n if (this.element.open) this.element.close();\n }\n\n click(e) {\n if (e.target === this.element) this.close();\n }\n\n onMorphAttribute = (e) => {\n if (e.target !== this.element) return;\n\n switch (e.detail.attributeName) {\n case \"open\":\n e.preventDefault();\n }\n };\n\n get links() {\n return Array.from(this.element.querySelectorAll(\"li:has(> a)\"));\n }\n\n get menus() {\n return Array.from(this.element.querySelectorAll(\"li:has(> ul)\"));\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class NavigationToggleController extends Controller {\n trigger() {\n this.dispatch(\"toggle\", { prefix: \"navigation\", bubbles: true });\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class PagyNavController extends Controller {\n connect() {\n document.addEventListener(\"shortcut:page-prev\", this.prevPage);\n document.addEventListener(\"shortcut:page-next\", this.nextPage);\n }\n\n disconnect() {\n document.removeEventListener(\"shortcut:page-prev\", this.prevPage);\n document.removeEventListener(\"shortcut:page-next\", this.nextPage);\n }\n\n nextPage = () => {\n this.element.querySelector(\"a:last-child\").click();\n };\n\n prevPage = () => {\n this.element.querySelector(\"a:first-child\").click();\n };\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\n/**\n * Connect an input (e.g. title) to slug.\n */\nexport default class SluggableController extends Controller {\n static targets = [\"source\", \"slug\"];\n static values = {\n slug: String,\n };\n\n sourceChanged(e) {\n if (this.slugValue === \"\") {\n this.slugTarget.value = parameterize(this.sourceTarget.value);\n }\n }\n\n slugChanged(e) {\n this.slugValue = this.slugTarget.value;\n }\n}\n\nfunction parameterize(input) {\n return input\n .toLowerCase()\n .replace(/'/g, \"-\")\n .replace(/[^-\\w\\s]/g, \"\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/(^-|-$)/g, \"\");\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nimport {\n get,\n parseRequestOptionsFromJSON,\n} from \"@github/webauthn-json/browser-ponyfill\";\n\nexport default class WebauthnAuthenticationController extends Controller {\n static targets = [\"response\"];\n static values = { options: Object };\n\n authenticate() {\n get(this.options).then((response) => {\n this.responseTarget.value = JSON.stringify(response);\n\n this.element.requestSubmit();\n });\n }\n\n get options() {\n return parseRequestOptionsFromJSON(this.optionsValue);\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nimport {\n create,\n parseCreationOptionsFromJSON,\n} from \"@github/webauthn-json/browser-ponyfill\";\n\nexport default class WebauthnRegistrationController extends Controller {\n static values = {\n options: Object,\n response: String,\n };\n static targets = [\"intro\", \"nickname\", \"response\"];\n\n submit(e) {\n if (\n this.responseTarget.value === \"\" &&\n e.submitter.formMethod !== \"dialog\"\n ) {\n e.preventDefault();\n this.createCredential();\n }\n }\n\n async createCredential() {\n const response = await create(this.options);\n\n this.responseValue = JSON.stringify(response);\n this.responseTarget.value = JSON.stringify(response);\n }\n\n responseValueChanged(response) {\n const responsePresent = response !== \"\";\n this.introTarget.toggleAttribute(\"hidden\", responsePresent);\n this.nicknameTarget.toggleAttribute(\"hidden\", !responsePresent);\n }\n\n get options() {\n return parseCreationOptionsFromJSON(this.optionsValue);\n }\n}\n","class KoiToolbarElement extends HTMLElement {\n constructor() {\n super();\n\n this.setAttribute(\"role\", \"toolbar\");\n }\n}\n\ncustomElements.define(\"koi-toolbar\", KoiToolbarElement);\n","import \"@hotwired/turbo-rails\";\nimport { initAll } from \"@katalyst/govuk-formbuilder\";\nimport \"@rails/actiontext\";\nimport \"trix\";\n\nimport \"./controllers\";\nimport \"./elements\";\n\n/** Initialize GOVUK */\nfunction initGOVUK() {\n document.body.classList.toggle(\"js-enabled\", true);\n document.body.classList.toggle(\n \"govuk-frontend-supported\",\n \"noModule\" in HTMLScriptElement.prototype,\n );\n initAll();\n}\n\nwindow.addEventListener(\"turbo:load\", initGOVUK);\nif (window.Turbo) initGOVUK();\n"],"names":["application","Application","start","load","content","govuk","navigation","tables","Definitions","identifier","controllerConstructor","Controller","static","connect","navigator","this","element","classList","add","supportedClass","copy","event","preventDefault","clipboard","writeText","sourceTarget","value","setTimeout","remove","close","e","target","closest","children","length","mapping","String","depth","type","Number","default","cause","HTMLElement","name","nodeName","toLowerCase","getAttribute","isContentEditable","isFormField","ignore","key","describeEvent","buffer","slice","depthValue","action","reduceRight","mappings","CustomEvent","detail","bubbles","dispatchEvent","ctrlKey","metaKey","altKey","shiftKey","code","filter","w","join","inputs","mappingValue","replaceAll","split","f","forEach","parse","Object","defineProperty","writable","pattern","keys","first","shift","addEventListener","onSubmit","disconnect","removeEventListener","outside","tagName","dismiss","dialogTarget","open","removeAttribute","dialogTargetConnected","dialog","showModal","success","formSubmission","submitter","dataset","filterTarget","clearFilter","applyFilter","go","querySelector","click","clear","blur","links","li","prefixSearch","innerText","toggleAttribute","menus","matches","querySelectorAll","needle","haystack","haystackLength","needleLength","outer","i","j","needleChar","charCodeAt","toggle","onMorphAttribute","attributeName","Array","from","trigger","dispatch","prefix","document","prevPage","nextPage","slug","sourceChanged","slugValue","slugTarget","replace","slugChanged","options","authenticate","get","then","response","responseTarget","JSON","stringify","requestSubmit","parseRequestOptionsFromJSON","optionsValue","submit","formMethod","createCredential","create","responseValue","responseValueChanged","responsePresent","introTarget","nicknameTarget","parseCreationOptionsFromJSON","import","HwComboboxController","push","catch","KoiToolbarElement","constructor","super","setAttribute","initGOVUK","body","HTMLScriptElement","prototype","initAll","customElements","define","window","Turbo"],"mappings":"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"}
1
+ {"version":3,"file":"koi.min.js","sources":["../../../javascript/koi/controllers/application.js","../../../javascript/koi/controllers/index.js","../../../javascript/koi/controllers/clipboard_controller.js","../../../javascript/koi/controllers/flash_controller.js","../../../javascript/koi/controllers/keyboard_controller.js","../../../javascript/koi/controllers/modal_controller.js","../../../javascript/koi/controllers/navigation_controller.js","../../../javascript/koi/controllers/navigation_toggle_controller.js","../../../javascript/koi/controllers/pagy_nav_controller.js","../../../javascript/koi/controllers/sluggable_controller.js","../../../javascript/koi/controllers/webauthn_authentication_controller.js","../../../javascript/koi/controllers/webauthn_registration_controller.js","../../../javascript/koi/elements/toolbar.js","../../../javascript/koi/application.js"],"sourcesContent":["import { Application } from \"@hotwired/stimulus\";\n\nconst application = Application.start();\n\nexport { application };\n","import { application } from \"./application\";\n\nimport content from \"@katalyst/content\";\napplication.load(content);\n\nimport govuk from \"@katalyst/govuk-formbuilder\";\napplication.load(govuk);\n\nimport navigation from \"@katalyst/navigation\";\napplication.load(navigation);\n\nimport tables from \"@katalyst/tables\";\napplication.load(tables);\n\nimport ClipboardController from \"./clipboard_controller\";\nimport FlashController from \"./flash_controller\";\nimport KeyboardController from \"./keyboard_controller\";\nimport ModalController from \"./modal_controller\";\nimport NavigationController from \"./navigation_controller\";\nimport NavigationToggleController from \"./navigation_toggle_controller\";\nimport PagyNavController from \"./pagy_nav_controller\";\nimport SluggableController from \"./sluggable_controller\";\nimport WebauthnAuthenticationController from \"./webauthn_authentication_controller\";\nimport WebauthnRegistrationController from \"./webauthn_registration_controller\";\n\nconst Definitions = [\n {\n identifier: \"clipboard\",\n controllerConstructor: ClipboardController,\n },\n {\n identifier: \"flash\",\n controllerConstructor: FlashController,\n },\n {\n identifier: \"keyboard\",\n controllerConstructor: KeyboardController,\n },\n {\n identifier: \"modal\",\n controllerConstructor: ModalController,\n },\n {\n identifier: \"navigation\",\n controllerConstructor: NavigationController,\n },\n {\n identifier: \"navigation-toggle\",\n controllerConstructor: NavigationToggleController,\n },\n {\n identifier: \"pagy-nav\",\n controllerConstructor: PagyNavController,\n },\n {\n identifier: \"sluggable\",\n controllerConstructor: SluggableController,\n },\n {\n identifier: \"webauthn-authentication\",\n controllerConstructor: WebauthnAuthenticationController,\n },\n {\n identifier: \"webauthn-registration\",\n controllerConstructor: WebauthnRegistrationController,\n },\n];\n\n// dynamically attempt to load hw_combobox_controller, this is an optional dependency\nawait import(\"controllers/hw_combobox_controller\")\n .then(({ default: HwComboboxController }) => {\n Definitions.push({\n identifier: \"hw-combobox\",\n controllerConstructor: HwComboboxController,\n });\n })\n .catch(() => null);\n\napplication.load(Definitions);\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class ClipboardController extends Controller {\n static targets = [\"source\"];\n\n static classes = [\"supported\"];\n\n connect() {\n if (\"clipboard\" in navigator) {\n this.element.classList.add(this.supportedClass);\n }\n }\n\n copy(event) {\n event.preventDefault();\n navigator.clipboard.writeText(this.sourceTarget.value);\n\n this.element.classList.add(\"copied\");\n setTimeout(() => {\n this.element.classList.remove(\"copied\");\n }, 2000);\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class FlashController extends Controller {\n close(e) {\n e.target.closest(\"li\").remove();\n\n // remove the flash container if there are no more flashes\n if (this.element.children.length === 0) {\n this.element.remove();\n }\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nconst DEBUG = false;\n\nexport default class KeyboardController extends Controller {\n static values = {\n mapping: String,\n depth: { type: Number, default: 2 },\n };\n\n event(cause) {\n if (isFormField(cause.target) || this.#ignore(cause)) return;\n\n const key = this.describeEvent(cause);\n\n this.buffer = [...(this.buffer || []), key].slice(0 - this.depthValue);\n\n if (DEBUG) console.debug(\"[keyboard] buffer:\", ...this.buffer);\n\n // test whether the tail of the buffer matches any of the configured chords\n const action = this.buffer.reduceRight((mapping, key) => {\n if (typeof mapping === \"string\" || typeof mapping === \"undefined\") {\n return mapping;\n } else {\n return mapping[key];\n }\n }, this.mappings);\n\n // if we don't have a string we may have a miss or an incomplete chord\n if (typeof action !== \"string\") return;\n\n // clear the buffer and prevent the key from being consumed elsewhere\n this.buffer = [];\n cause.preventDefault();\n\n if (DEBUG) console.debug(\"[keyboard] event: %s\", action);\n\n // fire the configured event\n const event = new CustomEvent(action, {\n detail: { cause: cause },\n bubbles: true,\n });\n cause.target.dispatchEvent(event);\n }\n\n /**\n * @param event KeyboardEvent input event to describe\n * @return String description of keyboard event, e.g. 'C-KeyV' (CTRL+V)\n */\n describeEvent(event) {\n return [\n event.ctrlKey && \"C\",\n event.metaKey && \"M\",\n event.altKey && \"A\",\n event.shiftKey && \"S\",\n event.code,\n ]\n .filter((w) => w)\n .join(\"-\");\n }\n\n /**\n * Build a tree for efficiently looking up key chords, where the last key in the sequence\n * is the first key in tree.\n */\n get mappings() {\n const inputs = this.mappingValue\n .replaceAll(/\\s+/g, \" \")\n .split(\" \")\n .filter((f) => f.length > 0);\n const mappings = {};\n\n inputs.forEach((mapping) => this.#parse(mappings, mapping));\n\n // memoize the result\n Object.defineProperty(this, \"mappings\", {\n value: mappings,\n writable: false,\n });\n\n return mappings;\n }\n\n /**\n * Parse a key chord pattern and an event and store it in the inverted tree lookup structure.\n *\n * @param mappings inverted tree lookup for key chords\n * @param mapping input definition, e.g. \"C-KeyC+C-KeyV->paste\"\n */\n #parse(mappings, mapping) {\n const [pattern, event] = mapping.split(\"->\");\n const keys = pattern.split(\"+\");\n const first = keys.shift();\n\n mappings = keys.reduceRight(\n (mappings, key) => (mappings[key] ||= {}),\n mappings,\n );\n mappings[first] = event;\n }\n\n /**\n * Ignore modifier keys, as they will be captured in normal key presses.\n *\n * @param event KeyboardEvent\n * @returns {boolean} true if key event should be ignored\n */\n #ignore(event) {\n switch (event.code) {\n case \"ControlLeft\":\n case \"ControlRight\":\n case \"MetaLeft\":\n case \"MetaRight\":\n case \"ShiftLeft\":\n case \"ShiftRight\":\n case \"AltLeft\":\n case \"AltRight\":\n return true;\n default:\n return false;\n }\n }\n}\n\n/**\n * Detect input nodes where we should not listen for events.\n *\n * Credit: github.com\n */\nfunction isFormField(element) {\n if (!(element instanceof HTMLElement)) {\n return false;\n }\n\n const name = element.nodeName.toLowerCase();\n const type = (element.getAttribute(\"type\") || \"\").toLowerCase();\n return (\n name === \"select\" ||\n name === \"textarea\" ||\n name === \"trix-editor\" ||\n (name === \"input\" &&\n type !== \"submit\" &&\n type !== \"reset\" &&\n type !== \"checkbox\" &&\n type !== \"radio\" &&\n type !== \"file\") ||\n element.isContentEditable\n );\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class ModalController extends Controller {\n static targets = [\"dialog\"];\n\n connect() {\n this.element.addEventListener(\"turbo:submit-end\", this.onSubmit);\n }\n\n disconnect() {\n this.element.removeEventListener(\"turbo:submit-end\", this.onSubmit);\n }\n\n outside(e) {\n if (e.target.tagName === \"DIALOG\") this.dismiss();\n }\n\n dismiss() {\n if (!this.dialogTarget) return;\n if (!this.dialogTarget.open) this.dialogTarget.close();\n\n this.element.removeAttribute(\"src\");\n this.dialogTarget.remove();\n }\n\n dialogTargetConnected(dialog) {\n dialog.showModal();\n }\n\n onSubmit = (event) => {\n if (\n event.detail.success &&\n \"closeDialog\" in event.detail.formSubmission?.submitter?.dataset\n ) {\n this.dialogTarget.close();\n this.element.removeAttribute(\"src\");\n this.dialogTarget.remove();\n }\n };\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class NavigationController extends Controller {\n static targets = [\"filter\"];\n\n filter() {\n const filter = this.filterTarget.value;\n this.clearFilter(filter);\n\n if (filter.length > 0) {\n this.applyFilter(filter);\n }\n }\n\n go() {\n this.element.querySelector(\"li:not([hidden]) > a\").click();\n }\n\n clear() {\n if (this.filterTarget.value.length === 0) this.filterTarget.blur();\n }\n\n applyFilter(filter) {\n // hide items that don't match the search filter\n this.links\n .filter(\n (li) =>\n !this.prefixSearch(filter.toLowerCase(), li.innerText.toLowerCase()),\n )\n .forEach((li) => {\n li.toggleAttribute(\"hidden\", true);\n });\n\n this.menus\n .filter((li) => !li.matches(\"li:has(li:not([hidden]) > a)\"))\n .forEach((li) => {\n li.toggleAttribute(\"hidden\", true);\n });\n }\n\n clearFilter(filter) {\n this.element.querySelectorAll(\"li\").forEach((li) => {\n li.toggleAttribute(\"hidden\", false);\n });\n }\n\n prefixSearch(needle, haystack) {\n const haystackLength = haystack.length;\n const needleLength = needle.length;\n if (needleLength > haystackLength) {\n return false;\n }\n if (needleLength === haystackLength) {\n return needle === haystack;\n }\n outer: for (let i = 0, j = 0; i < needleLength; i++) {\n const needleChar = needle.charCodeAt(i);\n if (needleChar === 32) {\n // skip ahead to next space in the haystack\n while (j < haystackLength && haystack.charCodeAt(j++) !== 32) {}\n continue;\n }\n while (j < haystackLength) {\n if (haystack.charCodeAt(j++) === needleChar) continue outer;\n // skip ahead to the next space in the haystack\n while (j < haystackLength && haystack.charCodeAt(j++) !== 32) {}\n }\n return false;\n }\n return true;\n }\n\n toggle() {\n this.element.open ? this.close() : this.open();\n }\n\n open() {\n if (!this.element.open) this.element.showModal();\n }\n\n close() {\n if (this.element.open) this.element.close();\n }\n\n click(e) {\n if (e.target === this.element) this.close();\n }\n\n onMorphAttribute = (e) => {\n if (e.target !== this.element) return;\n\n switch (e.detail.attributeName) {\n case \"open\":\n e.preventDefault();\n }\n };\n\n get links() {\n return Array.from(this.element.querySelectorAll(\"li:has(> a)\"));\n }\n\n get menus() {\n return Array.from(this.element.querySelectorAll(\"li:has(> ul)\"));\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class NavigationToggleController extends Controller {\n trigger() {\n this.dispatch(\"toggle\", { prefix: \"navigation\", bubbles: true });\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class PagyNavController extends Controller {\n connect() {\n document.addEventListener(\"shortcut:page-prev\", this.prevPage);\n document.addEventListener(\"shortcut:page-next\", this.nextPage);\n }\n\n disconnect() {\n document.removeEventListener(\"shortcut:page-prev\", this.prevPage);\n document.removeEventListener(\"shortcut:page-next\", this.nextPage);\n }\n\n nextPage = () => {\n this.element.querySelector(\"a:last-child\").click();\n };\n\n prevPage = () => {\n this.element.querySelector(\"a:first-child\").click();\n };\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\n/**\n * Connect an input (e.g. title) to slug.\n */\nexport default class SluggableController extends Controller {\n static targets = [\"source\", \"slug\"];\n static values = {\n slug: String,\n };\n\n sourceChanged(e) {\n if (this.slugValue === \"\") {\n this.slugTarget.value = parameterize(this.sourceTarget.value);\n }\n }\n\n slugChanged(e) {\n this.slugValue = this.slugTarget.value;\n }\n}\n\nfunction parameterize(input) {\n return input\n .toLowerCase()\n .replace(/'/g, \"-\")\n .replace(/[^-\\w\\s]/g, \"\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/(^-|-$)/g, \"\");\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class WebauthnAuthenticationController extends Controller {\n static targets = [\"response\"];\n static values = {\n options: Object,\n };\n\n async authenticate() {\n const credential = await navigator.credentials.get(this.options);\n\n this.responseTarget.value = JSON.stringify(credential.toJSON());\n\n this.element.requestSubmit();\n }\n\n get options() {\n return {\n publicKey: PublicKeyCredential.parseRequestOptionsFromJSON(\n this.optionsValue.publicKey,\n ),\n };\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class WebauthnRegistrationController extends Controller {\n static values = {\n options: Object,\n response: Object,\n };\n static targets = [\"intro\", \"nickname\", \"response\"];\n\n submit(e) {\n if (\n this.responseTarget.value === \"\" &&\n e.submitter.formMethod !== \"dialog\"\n ) {\n e.preventDefault();\n this.createCredential().then();\n }\n }\n\n async createCredential() {\n const credential = await navigator.credentials.create(this.options);\n\n this.responseValue = credential.toJSON();\n this.responseTarget.value = JSON.stringify(credential.toJSON());\n }\n\n responseValueChanged(response) {\n const responsePresent = response !== \"\";\n this.introTarget.toggleAttribute(\"hidden\", responsePresent);\n this.nicknameTarget.toggleAttribute(\"hidden\", !responsePresent);\n }\n\n get options() {\n return {\n publicKey: PublicKeyCredential.parseCreationOptionsFromJSON(\n this.optionsValue.publicKey,\n ),\n };\n }\n}\n","class KoiToolbarElement extends HTMLElement {\n constructor() {\n super();\n\n this.setAttribute(\"role\", \"toolbar\");\n }\n}\n\ncustomElements.define(\"koi-toolbar\", KoiToolbarElement);\n","import \"@hotwired/turbo-rails\";\nimport { initAll } from \"@katalyst/govuk-formbuilder\";\nimport \"@rails/actiontext\";\nimport \"trix\";\n\nimport \"./controllers\";\nimport \"./elements\";\n\n/** Initialize GOVUK */\nfunction initGOVUK() {\n document.body.classList.toggle(\"js-enabled\", true);\n document.body.classList.toggle(\n \"govuk-frontend-supported\",\n \"noModule\" in HTMLScriptElement.prototype,\n );\n initAll();\n}\n\nwindow.addEventListener(\"turbo:load\", initGOVUK);\nif (window.Turbo) initGOVUK();\n"],"names":["application","Application","start","load","content","govuk","navigation","tables","Definitions","identifier","controllerConstructor","Controller","static","connect","navigator","this","element","classList","add","supportedClass","copy","event","preventDefault","clipboard","writeText","sourceTarget","value","setTimeout","remove","close","e","target","closest","children","length","mapping","String","depth","type","Number","default","cause","HTMLElement","name","nodeName","toLowerCase","getAttribute","isContentEditable","isFormField","ignore","key","describeEvent","buffer","slice","depthValue","action","reduceRight","mappings","CustomEvent","detail","bubbles","dispatchEvent","ctrlKey","metaKey","altKey","shiftKey","code","filter","w","join","inputs","mappingValue","replaceAll","split","f","forEach","parse","Object","defineProperty","writable","pattern","keys","first","shift","addEventListener","onSubmit","disconnect","removeEventListener","outside","tagName","dismiss","dialogTarget","open","removeAttribute","dialogTargetConnected","dialog","showModal","success","formSubmission","submitter","dataset","filterTarget","clearFilter","applyFilter","go","querySelector","click","clear","blur","links","li","prefixSearch","innerText","toggleAttribute","menus","matches","querySelectorAll","needle","haystack","haystackLength","needleLength","outer","i","j","needleChar","charCodeAt","toggle","onMorphAttribute","attributeName","Array","from","trigger","dispatch","prefix","document","prevPage","nextPage","slug","sourceChanged","slugValue","slugTarget","replace","slugChanged","options","authenticate","credential","credentials","get","responseTarget","JSON","stringify","toJSON","requestSubmit","publicKey","PublicKeyCredential","parseRequestOptionsFromJSON","optionsValue","response","submit","formMethod","createCredential","then","create","responseValue","responseValueChanged","responsePresent","introTarget","nicknameTarget","parseCreationOptionsFromJSON","import","HwComboboxController","push","catch","KoiToolbarElement","constructor","super","setAttribute","initGOVUK","body","HTMLScriptElement","prototype","initAll","customElements","define","window","Turbo"],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ Copyright 2006 The Inconsolata Project Authors (https://github.com/cyrealtype/Inconsolata)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,92 @@
1
+ Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+ -----------------------------------------------------------
8
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
9
+ -----------------------------------------------------------
10
+
11
+ PREAMBLE
12
+ The goals of the Open Font License (OFL) are to stimulate worldwide
13
+ development of collaborative font projects, to support the font creation
14
+ efforts of academic and linguistic communities, and to provide a free and
15
+ open framework in which fonts may be shared and improved in partnership
16
+ with others.
17
+
18
+ The OFL allows the licensed fonts to be used, studied, modified and
19
+ redistributed freely as long as they are not sold by themselves. The
20
+ fonts, including any derivative works, can be bundled, embedded,
21
+ redistributed and/or sold with any software provided that any reserved
22
+ names are not used by derivative works. The fonts and derivatives,
23
+ however, cannot be released under any other type of license. The
24
+ requirement for fonts to remain under this license does not apply
25
+ to any document created using the fonts or their derivatives.
26
+
27
+ DEFINITIONS
28
+ "Font Software" refers to the set of files released by the Copyright
29
+ Holder(s) under this license and clearly marked as such. This may
30
+ include source files, build scripts and documentation.
31
+
32
+ "Reserved Font Name" refers to any names specified as such after the
33
+ copyright statement(s).
34
+
35
+ "Original Version" refers to the collection of Font Software components as
36
+ distributed by the Copyright Holder(s).
37
+
38
+ "Modified Version" refers to any derivative made by adding to, deleting,
39
+ or substituting -- in part or in whole -- any of the components of the
40
+ Original Version, by changing formats or by porting the Font Software to a
41
+ new environment.
42
+
43
+ "Author" refers to any designer, engineer, programmer, technical
44
+ writer or other person who contributed to the Font Software.
45
+
46
+ PERMISSION AND CONDITIONS
47
+ Permission is hereby granted, free of charge, to any person obtaining
48
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
49
+ redistribute, and sell modified and unmodified copies of the Font
50
+ Software, subject to the following conditions:
51
+
52
+ 1) Neither the Font Software nor any of its individual components,
53
+ in Original or Modified Versions, may be sold by itself.
54
+
55
+ 2) Original or Modified Versions of the Font Software may be bundled,
56
+ redistributed and/or sold with any software, provided that each copy
57
+ contains the above copyright notice and this license. These can be
58
+ included either as stand-alone text files, human-readable headers or
59
+ in the appropriate machine-readable metadata fields within text or
60
+ binary files as long as those fields can be easily viewed by the user.
61
+
62
+ 3) No Modified Version of the Font Software may use the Reserved Font
63
+ Name(s) unless explicit written permission is granted by the corresponding
64
+ Copyright Holder. This restriction only applies to the primary font name as
65
+ presented to the users.
66
+
67
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
68
+ Software shall not be used to promote, endorse or advertise any
69
+ Modified Version, except to acknowledge the contribution(s) of the
70
+ Copyright Holder(s) and the Author(s) or with their explicit written
71
+ permission.
72
+
73
+ 5) The Font Software, modified or unmodified, in part or in whole,
74
+ must be distributed entirely under this license, and must not be
75
+ distributed under any other license. The requirement for fonts to
76
+ remain under this license does not apply to any document created
77
+ using the Font Software.
78
+
79
+ TERMINATION
80
+ This license becomes null and void if any of the above conditions are
81
+ not met.
82
+
83
+ DISCLAIMER
84
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
85
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
86
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
87
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
88
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
89
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
90
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
91
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
92
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -1,8 +1,5 @@
1
- @import url("https://rsms.me/inter/inter.css");
2
- @import url("https://fonts.googleapis.com/css2?family=Inconsolata:wght@200..900&display=swap");
3
-
4
1
  :root {
5
- --font-base: "Inter", sans-serif;
2
+ --font-base: "InterVariable", sans-serif;
6
3
  --font-mono: "Inconsolata", monospace;
7
4
  --leading-micro: 0.85;
8
5
  --leading-flat: 1;
@@ -15,8 +12,89 @@
15
12
  --font-black: 900;
16
13
  }
17
14
 
18
- @supports (font-variation-settings: normal) {
19
- :root {
20
- --font-base: "Inter var", sans-serif;
15
+ /**
16
+ * Inter
17
+ * Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
18
+ * SIL Open Font License 1.1 https://raw.githubusercontent.com/rsms/inter/refs/heads/master/LICENSE.txt
19
+ */
20
+
21
+ @font-face {
22
+ font-family: InterVariable;
23
+ font-style: normal;
24
+ font-weight: 100 900;
25
+ font-display: optional;
26
+ src: url("../inter-variable-v4-1.woff2") format("woff2");
27
+ }
28
+
29
+ @font-face {
30
+ font-family: InterVariable;
31
+ font-style: italic;
32
+ font-weight: 100 900;
33
+ font-display: optional;
34
+ src: url("../inter-variable-v4-1-italic.woff2") format("woff2");
35
+ }
36
+
37
+ /* From https://github.com/rsms/inter */
38
+ @font-feature-values InterVariable {
39
+ @character-variant {
40
+ cv01: 1;
41
+ cv02: 2;
42
+ cv03: 3;
43
+ cv04: 4;
44
+ cv05: 5;
45
+ cv06: 6;
46
+ cv07: 7;
47
+ cv08: 8;
48
+ cv09: 9;
49
+ cv10: 10;
50
+ cv11: 11;
51
+ cv12: 12;
52
+ cv13: 13;
53
+ alt-1: 1; /* Alternate one */
54
+ alt-3: 9; /* Flat-top three */
55
+ open-4: 2; /* Open four */
56
+ open-6: 3; /* Open six */
57
+ open-9: 4; /* Open nine */
58
+ lc-l-with-tail: 5; /* Lower-case L with tail */
59
+ simplified-u: 6; /* Simplified u */
60
+ alt-double-s: 7; /* Alternate German double s */
61
+ uc-i-with-serif: 8; /* Upper-case i with serif */
62
+ uc-g-with-spur: 10; /* Capital G with spur */
63
+ single-story-a: 11; /* Single-story a */
64
+ compact-lc-f: 12; /* Compact f */
65
+ compact-lc-t: 13; /* Compact t */
21
66
  }
67
+
68
+ @styleset {
69
+ ss01: 1;
70
+ ss02: 2;
71
+ ss03: 3;
72
+ ss04: 4;
73
+ ss05: 5;
74
+ ss06: 6;
75
+ ss07: 7;
76
+ ss08: 8;
77
+ open-digits: 1; /* Open digits */
78
+ disambiguation: 2; /* Disambiguation (with zero) */
79
+ disambiguation-except-zero: 4; /* Disambiguation (no zero) */
80
+ round-quotes-and-commas: 3; /* Round quotes &amp; commas */
81
+ square-punctuation: 7; /* Square punctuation */
82
+ square-quotes: 8; /* Square quotes */
83
+ circled-characters: 5; /* Circled characters */
84
+ squared-characters: 6; /* Squared characters */
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Inconsolata
90
+ * Copyright 2006 The Inconsolata Project Authors (https://github.com/cyrealtype/Inconsolata)
91
+ * SIL Open Font License 1.1 https://raw.githubusercontent.com/googlefonts/Inconsolata/refs/heads/main/OFL.txt
92
+ */
93
+
94
+ @font-face {
95
+ font-display: optional;
96
+ font-family: "Inconsolata";
97
+ font-style: normal;
98
+ font-weight: 400;
99
+ src: url("../inconsolata-v37-latin-regular.woff2") format("woff2");
22
100
  }
@@ -94,8 +94,8 @@ module Admin
94
94
  attribute :email, :string
95
95
  attribute :last_sign_in_at, :date
96
96
  attribute :sign_in_count, :integer
97
+ attribute :password_login, :enum, scope: :has_password_login, multiple: false
97
98
  attribute :passkey, :boolean, scope: :has_passkey
98
- attribute :mfa, :boolean, scope: :has_otp
99
99
  end
100
100
  end
101
101
  end
@@ -97,8 +97,11 @@ module Admin
97
97
  def authenticate_local_admin
98
98
  return if admin_signed_in? || !Rails.env.development?
99
99
 
100
- session[:admin_user_id] =
101
- Admin::User.where(email: %W[#{ENV.fetch('USER', nil)}@katalyst.com.au admin@katalyst.com.au]).first&.id
100
+ @current_admin_user = Admin::User.find_by(email: "#{ENV.fetch('USER', nil)}@katalyst.com.au")
101
+
102
+ return unless admin_signed_in?
103
+
104
+ session[:admin_user_id] = current_admin_user.id
102
105
 
103
106
  flash.delete(:redirect) if (redirect = flash[:redirect])
104
107
 
@@ -8,15 +8,24 @@ module Koi
8
8
  included do
9
9
  helper_method :admin_signed_in?
10
10
  helper_method :current_admin_user
11
+
12
+ # @deprecated use current admin user instead
11
13
  helper_method :current_admin
12
14
  end
13
15
 
14
16
  def admin_signed_in?
15
17
  current_admin_user.present?
18
+ rescue ActiveRecord::RecordNotFound
19
+ false
16
20
  end
17
21
 
18
22
  def current_admin_user
19
- @current_admin_user ||= Admin::User.find(session[:admin_user_id]) if session[:admin_user_id].present?
23
+ return @current_admin_user if instance_variable_defined?(:@current_admin_user)
24
+ return @current_admin_user = nil unless session.has_key?(:admin_user_id)
25
+
26
+ @current_admin_user = Admin::User.find(session[:admin_user_id])
27
+ ensure
28
+ session.delete(:admin_user_id) unless @current_admin_user
20
29
  end
21
30
 
22
31
  # @deprecated Use current_admin_user instead
@@ -41,7 +41,7 @@ module Koi
41
41
  )
42
42
 
43
43
  stored_credential.admin
44
- rescue ActiveRecord::RecordNotFound
44
+ rescue ActiveRecord::RecordNotFound, WebAuthn::VerificationError
45
45
  false
46
46
  end
47
47
  end
@@ -1,21 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class WellKnownsController < ApplicationController
4
- before_action :set_well_known
5
-
6
- attr_reader :well_known
7
-
3
+ class WellKnownsController < ActionController::API
8
4
  def show
9
- if well_known.present?
10
- render renderable: @well_known
5
+ well_known = WellKnown.find_by(name: params[:name])
6
+
7
+ case well_known&.format
8
+ when :text
9
+ render plain: well_known.content
10
+ when :json
11
+ render json: well_known.content
11
12
  else
12
13
  head :not_found
13
14
  end
14
15
  end
15
-
16
- private
17
-
18
- def set_well_known
19
- @well_known = WellKnown.find_by(name: params[:name])
20
- end
21
16
  end
@@ -1,23 +1,24 @@
1
1
  import { Controller } from "@hotwired/stimulus";
2
2
 
3
- import {
4
- get,
5
- parseRequestOptionsFromJSON,
6
- } from "@github/webauthn-json/browser-ponyfill";
7
-
8
3
  export default class WebauthnAuthenticationController extends Controller {
9
4
  static targets = ["response"];
10
- static values = { options: Object };
5
+ static values = {
6
+ options: Object,
7
+ };
8
+
9
+ async authenticate() {
10
+ const credential = await navigator.credentials.get(this.options);
11
11
 
12
- authenticate() {
13
- get(this.options).then((response) => {
14
- this.responseTarget.value = JSON.stringify(response);
12
+ this.responseTarget.value = JSON.stringify(credential.toJSON());
15
13
 
16
- this.element.requestSubmit();
17
- });
14
+ this.element.requestSubmit();
18
15
  }
19
16
 
20
17
  get options() {
21
- return parseRequestOptionsFromJSON(this.optionsValue);
18
+ return {
19
+ publicKey: PublicKeyCredential.parseRequestOptionsFromJSON(
20
+ this.optionsValue.publicKey,
21
+ ),
22
+ };
22
23
  }
23
24
  }
@@ -1,14 +1,9 @@
1
1
  import { Controller } from "@hotwired/stimulus";
2
2
 
3
- import {
4
- create,
5
- parseCreationOptionsFromJSON,
6
- } from "@github/webauthn-json/browser-ponyfill";
7
-
8
3
  export default class WebauthnRegistrationController extends Controller {
9
4
  static values = {
10
5
  options: Object,
11
- response: String,
6
+ response: Object,
12
7
  };
13
8
  static targets = ["intro", "nickname", "response"];
14
9
 
@@ -18,15 +13,15 @@ export default class WebauthnRegistrationController extends Controller {
18
13
  e.submitter.formMethod !== "dialog"
19
14
  ) {
20
15
  e.preventDefault();
21
- this.createCredential();
16
+ this.createCredential().then();
22
17
  }
23
18
  }
24
19
 
25
20
  async createCredential() {
26
- const response = await create(this.options);
21
+ const credential = await navigator.credentials.create(this.options);
27
22
 
28
- this.responseValue = JSON.stringify(response);
29
- this.responseTarget.value = JSON.stringify(response);
23
+ this.responseValue = credential.toJSON();
24
+ this.responseTarget.value = JSON.stringify(credential.toJSON());
30
25
  }
31
26
 
32
27
  responseValueChanged(response) {
@@ -36,6 +31,10 @@ export default class WebauthnRegistrationController extends Controller {
36
31
  }
37
32
 
38
33
  get options() {
39
- return parseCreationOptionsFromJSON(this.optionsValue);
34
+ return {
35
+ publicKey: PublicKeyCredential.parseCreationOptionsFromJSON(
36
+ this.optionsValue.publicKey,
37
+ ),
38
+ };
40
39
  }
41
40
  }
@@ -18,6 +18,9 @@ module Admin
18
18
 
19
19
  has_many :credentials, inverse_of: :admin, class_name: "Admin::Credential", dependent: :destroy
20
20
 
21
+ attribute :password_login, :string
22
+ enum :password_login, { none: "none", password_only: "password_only", mfa: "mfa" }, prefix: true
23
+
21
24
  validates :name, :email, presence: true
22
25
  validates :email, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
23
26
 
@@ -33,6 +36,17 @@ module Admin
33
36
  end
34
37
  end
35
38
 
39
+ scope :has_password_login, ->(type) do
40
+ case type&.to_sym
41
+ when :password_only
42
+ where.not(password_digest: "").where(otp_secret: nil)
43
+ when :mfa
44
+ where.not(password_digest: "").where.not(otp_secret: nil)
45
+ else
46
+ where(password_digest: "")
47
+ end
48
+ end
49
+
36
50
  scope :has_otp, ->(otp) do
37
51
  if otp
38
52
  where.not(otp_secret: nil)
@@ -54,6 +68,16 @@ module Admin
54
68
  end
55
69
  alias passkey passkey?
56
70
 
71
+ def password_login
72
+ if password_digest.blank?
73
+ :none
74
+ elsif otp_secret.nil?
75
+ :password_only
76
+ else
77
+ :mfa
78
+ end
79
+ end
80
+
57
81
  def to_s
58
82
  name
59
83
  end
@@ -15,10 +15,6 @@ class WellKnown < ApplicationRecord
15
15
  where(arel_table[:name].matches("%#{query}%"))
16
16
  end
17
17
 
18
- def render_in(view_context)
19
- view_context.render(plain: content)
20
- end
21
-
22
18
  def format
23
19
  content_type.to_sym
24
20
  end
@@ -19,12 +19,10 @@
19
19
  <% row.select %>
20
20
  <% row.link :name, url: :admin_admin_user_path %>
21
21
  <% row.text :email %>
22
+ <% row.enum :password_login, label: "Password" %>
22
23
  <% row.boolean :credentials, label: "Passkey" do |cell| %>
23
24
  <%= cell.value.any? ? "Yes" : "No" %>
24
25
  <% end %>
25
- <% row.boolean :otp, label: "MFA" do |cell| %>
26
- <%= cell.value.present? ? "Yes" : "No" %>
27
- <% end %>
28
26
  <% row.date :last_sign_in_at, label: "Last active" %>
29
27
  <% end %>
30
28
 
@@ -17,14 +17,19 @@
17
17
  <meta name="turbo-refresh-scroll" content="preserve">
18
18
  <%= yield :head %>
19
19
 
20
+ <!-- fonts -->
21
+ <%= preload_link_tag("koi/inter-variable-v4-1.woff2") %>
22
+ <%= preload_link_tag("koi/inter-variable-v4-1-italic.woff2") %>
23
+ <%= preload_link_tag("koi/inconsolata-v37-latin-regular.woff2") %>
24
+
20
25
  <!-- icons -->
21
- <link rel="icon" sizes="any" type="image/svg+xml" href="<%= path_to_image("koi/logo.svg") %>">
26
+ <%= favicon_link_tag("koi/logo.svg") %>
22
27
 
23
28
  <!-- styles -->
24
- <%= stylesheet_link_tag Koi.config.admin_stylesheet, "data-turbo-track": "reload" %>
29
+ <%= stylesheet_link_tag(Koi.config.admin_stylesheet, "data-turbo-track": "reload") %>
25
30
 
26
31
  <!-- scripts -->
27
- <%= javascript_importmap_tags Koi.config.admin_javascript_entry_point %>
32
+ <%= javascript_importmap_tags(Koi.config.admin_javascript_entry_point) %>
28
33
  </head>
29
34
  <body data-controller="keyboard"
30
35
  data-action="keyup->keyboard#event"
@@ -5,8 +5,8 @@
5
5
  <%= csp_meta_tag %>
6
6
 
7
7
  <%# include all turbo-track elements so turbo knows it can cache frame visits %>
8
- <%= stylesheet_link_tag Koi.config.admin_stylesheet, "data-turbo-track": "reload" %>
9
- <%= javascript_importmap_tags "koi/admin" %>
8
+ <%= stylesheet_link_tag(Koi.config.admin_stylesheet, "data-turbo-track": "reload") %>
9
+ <%= javascript_importmap_tags("koi/admin") %>
10
10
  <%= yield :head %>
11
11
  </head>
12
12
  <body>
@@ -17,15 +17,20 @@
17
17
  <meta name="turbo-refresh-scroll" content="preserve">
18
18
  <%= yield :head %>
19
19
 
20
+ <!-- fonts -->
21
+ <%= preload_link_tag("koi/inter-variable-v4-1.woff2") %>
22
+ <%= preload_link_tag("koi/inter-variable-v4-1-italic.woff2") %>
23
+ <%= preload_link_tag("koi/inconsolata-v37-latin-regular.woff2") %>
24
+
20
25
  <!-- icons -->
21
- <link rel="icon" sizes="any" type="image/svg+xml" href="<%= path_to_image("koi/logo.svg") %>">
26
+ <%= favicon_link_tag("koi/logo.svg") %>
22
27
 
23
28
  <!-- styles -->
24
- <%= stylesheet_link_tag path_to_stylesheet("koi/login.css"), "data-turbo-track": "reload" %>
25
- <%= stylesheet_link_tag Koi.config.admin_stylesheet, "data-turbo-track": "reload" %>
29
+ <%= stylesheet_link_tag(path_to_stylesheet("koi/login.css"), "data-turbo-track": "reload") %>
30
+ <%= stylesheet_link_tag(Koi.config.admin_stylesheet, "data-turbo-track": "reload") %>
26
31
 
27
32
  <!-- scripts -->
28
- <%= javascript_importmap_tags Koi.config.admin_javascript_entry_point %>
33
+ <%= javascript_importmap_tags(Koi.config.admin_javascript_entry_point) %>
29
34
  </head>
30
35
  <body>
31
36
  <main class="cover" data-centered>
data/config/importmap.rb CHANGED
@@ -3,7 +3,6 @@
3
3
  pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
4
4
  pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
5
5
  pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
6
- pin "@github/webauthn-json/browser-ponyfill", to: "https://ga.jspm.io/npm:@github/webauthn-json@2.1.1/dist/esm/webauthn-json.browser-ponyfill.js"
7
6
  pin "@katalyst/koi", to: "katalyst/koi.min.js"
8
7
  pin "@rails/actiontext", to: "actiontext.js", preload: true
9
8
  pin "trix"
data/db/seeds.rb CHANGED
@@ -3,7 +3,6 @@
3
3
  # Create a default admin user (only in development)
4
4
  if Rails.env.development? && !ENV["CI"]
5
5
  Admin::User.create_with(
6
- name: `id -F`.strip,
7
- password: "password",
6
+ name: `id -F`.strip,
8
7
  ).find_or_create_by(email: "#{ENV.fetch('USER', nil)}@katalyst.com.au")
9
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: katalyst-koi
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.0
4
+ version: 5.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Katalyst Interactive
@@ -232,6 +232,11 @@ files:
232
232
  - app/assets/builds/katalyst/koi.min.js
233
233
  - app/assets/builds/katalyst/koi.min.js.map
234
234
  - app/assets/config/koi.js
235
+ - app/assets/fonts/koi/inconsolata-license.txt
236
+ - app/assets/fonts/koi/inconsolata-v37-latin-regular.woff2
237
+ - app/assets/fonts/koi/inter-license.txt
238
+ - app/assets/fonts/koi/inter-variable-v4-1-italic.woff2
239
+ - app/assets/fonts/koi/inter-variable-v4-1.woff2
235
240
  - app/assets/images/koi/application/chevron-right.svg
236
241
  - app/assets/images/koi/application/glyphicons-halflings-white.png
237
242
  - app/assets/images/koi/application/glyphicons-halflings.png
@@ -504,7 +509,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
504
509
  - !ruby/object:Gem::Version
505
510
  version: '0'
506
511
  requirements: []
507
- rubygems_version: 3.6.9
512
+ rubygems_version: 4.0.3
508
513
  specification_version: 4
509
514
  summary: Koi CMS admin framework
510
515
  test_files: []