katalyst-koi 5.3.1 → 5.4.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 +4 -4
- data/app/assets/builds/katalyst/koi.esm.js +9 -19
- data/app/assets/builds/katalyst/koi.js +9 -19
- data/app/assets/builds/katalyst/koi.min.js +1 -1
- data/app/assets/builds/katalyst/koi.min.js.map +1 -1
- data/app/assets/stylesheets/koi/blocks/index.css +1 -0
- data/app/assets/stylesheets/koi/{login.css → blocks/roadblock.css} +8 -5
- data/app/controllers/admin/admin_users_controller.rb +2 -2
- data/app/controllers/admin/credentials_controller.rb +32 -51
- data/app/controllers/admin/otps_controller.rb +5 -17
- data/app/controllers/admin/profiles_controller.rb +31 -0
- data/app/controllers/admin/sessions_controller.rb +9 -3
- data/app/controllers/admin/tokens_controller.rb +6 -2
- data/app/controllers/concerns/koi/controller/has_webauthn.rb +53 -9
- data/app/javascript/koi/controllers/webauthn_authentication_controller.js +1 -1
- data/app/javascript/koi/controllers/webauthn_registration_controller.js +8 -18
- data/app/models/admin/credential.rb +4 -0
- data/app/views/admin/admin_users/edit.html.erb +3 -1
- data/app/views/admin/admin_users/show.html.erb +3 -0
- data/app/views/admin/credentials/_credentials.html.erb +8 -5
- data/app/views/admin/credentials/new.html.erb +32 -41
- data/app/views/admin/credentials/show.html.erb +19 -0
- data/app/views/admin/otps/_form.html.erb +1 -1
- data/app/views/admin/{admin_users/_form.html+self.erb → profiles/_form.html.erb} +1 -1
- data/app/views/admin/{admin_users/edit.html+self.erb → profiles/edit.html.erb} +0 -1
- data/app/views/admin/{admin_users/show.html+self.erb → profiles/show.html.erb} +15 -10
- data/app/views/admin/sessions/new.html.erb +26 -27
- data/app/views/admin/sessions/otp.html.erb +13 -5
- data/app/views/admin/sessions/password.html.erb +16 -8
- data/app/views/admin/tokens/show.html.erb +12 -8
- data/app/views/layouts/koi/_application_navigation.html.erb +13 -9
- data/app/views/layouts/koi/application.html.erb +19 -10
- data/config/locales/koi.en.yml +0 -1
- data/config/routes.rb +14 -9
- data/lib/generators/koi/helpers/resource_helpers.rb +1 -1
- data/lib/koi/config.rb +2 -1
- data/lib/koi/engine.rb +1 -0
- metadata +21 -9
- data/app/views/admin/credentials/_credentials.html+self.erb +0 -12
- data/app/views/admin/credentials/create.turbo_stream.erb +0 -5
- data/app/views/admin/credentials/destroy.turbo_stream.erb +0 -5
- data/app/views/layouts/koi/login.html.erb +0 -50
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 559fd46fee1b7874a6593314f73ca7c2f205b31650457f35b171ba4165ff3e91
|
|
4
|
+
data.tar.gz: 7a8ad3000aa8d852adf95943574b0a56aa1eafd72446a9f9cd0abe107d968072
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4f039daf2363cf733d2d271f6a544b843d0ab1b1746db24f2be2177065a8bd4d9495bb7570def596ee86b674558f7dd8632d846522fe6a00b678019752858a17
|
|
7
|
+
data.tar.gz: d1c3be39ebf0fdfceb631a9fd86ffbf86e655f18b6b489b92243b261d4b52deb9165a99ea40b1c3ed62bb7c8fae5457fb5033fd2abd42c474320b3da04c2fdce
|
|
@@ -399,46 +399,36 @@ class WebauthnAuthenticationController extends Controller {
|
|
|
399
399
|
get options() {
|
|
400
400
|
return {
|
|
401
401
|
publicKey: PublicKeyCredential.parseRequestOptionsFromJSON(
|
|
402
|
-
this.optionsValue
|
|
402
|
+
this.optionsValue,
|
|
403
403
|
),
|
|
404
404
|
};
|
|
405
405
|
}
|
|
406
406
|
}
|
|
407
407
|
|
|
408
408
|
class WebauthnRegistrationController extends Controller {
|
|
409
|
+
static targets = ["response"];
|
|
409
410
|
static values = {
|
|
410
411
|
options: Object,
|
|
411
|
-
response: Object,
|
|
412
412
|
};
|
|
413
|
-
static targets = ["intro", "nickname", "response"];
|
|
414
413
|
|
|
415
414
|
submit(e) {
|
|
416
|
-
if (
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
) {
|
|
420
|
-
e.
|
|
421
|
-
|
|
422
|
-
}
|
|
415
|
+
if (this.responseTarget.value) return;
|
|
416
|
+
|
|
417
|
+
e.preventDefault();
|
|
418
|
+
this.createCredential().then(() => {
|
|
419
|
+
e.target.submit();
|
|
420
|
+
});
|
|
423
421
|
}
|
|
424
422
|
|
|
425
423
|
async createCredential() {
|
|
426
424
|
const credential = await navigator.credentials.create(this.options);
|
|
427
|
-
|
|
428
|
-
this.responseValue = credential.toJSON();
|
|
429
425
|
this.responseTarget.value = JSON.stringify(credential.toJSON());
|
|
430
426
|
}
|
|
431
427
|
|
|
432
|
-
responseValueChanged(response) {
|
|
433
|
-
const responsePresent = response !== "";
|
|
434
|
-
this.introTarget.toggleAttribute("hidden", responsePresent);
|
|
435
|
-
this.nicknameTarget.toggleAttribute("hidden", !responsePresent);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
428
|
get options() {
|
|
439
429
|
return {
|
|
440
430
|
publicKey: PublicKeyCredential.parseCreationOptionsFromJSON(
|
|
441
|
-
this.optionsValue
|
|
431
|
+
this.optionsValue,
|
|
442
432
|
),
|
|
443
433
|
};
|
|
444
434
|
}
|
|
@@ -399,46 +399,36 @@ class WebauthnAuthenticationController extends Controller {
|
|
|
399
399
|
get options() {
|
|
400
400
|
return {
|
|
401
401
|
publicKey: PublicKeyCredential.parseRequestOptionsFromJSON(
|
|
402
|
-
this.optionsValue
|
|
402
|
+
this.optionsValue,
|
|
403
403
|
),
|
|
404
404
|
};
|
|
405
405
|
}
|
|
406
406
|
}
|
|
407
407
|
|
|
408
408
|
class WebauthnRegistrationController extends Controller {
|
|
409
|
+
static targets = ["response"];
|
|
409
410
|
static values = {
|
|
410
411
|
options: Object,
|
|
411
|
-
response: Object,
|
|
412
412
|
};
|
|
413
|
-
static targets = ["intro", "nickname", "response"];
|
|
414
413
|
|
|
415
414
|
submit(e) {
|
|
416
|
-
if (
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
) {
|
|
420
|
-
e.
|
|
421
|
-
|
|
422
|
-
}
|
|
415
|
+
if (this.responseTarget.value) return;
|
|
416
|
+
|
|
417
|
+
e.preventDefault();
|
|
418
|
+
this.createCredential().then(() => {
|
|
419
|
+
e.target.submit();
|
|
420
|
+
});
|
|
423
421
|
}
|
|
424
422
|
|
|
425
423
|
async createCredential() {
|
|
426
424
|
const credential = await navigator.credentials.create(this.options);
|
|
427
|
-
|
|
428
|
-
this.responseValue = credential.toJSON();
|
|
429
425
|
this.responseTarget.value = JSON.stringify(credential.toJSON());
|
|
430
426
|
}
|
|
431
427
|
|
|
432
|
-
responseValueChanged(response) {
|
|
433
|
-
const responsePresent = response !== "";
|
|
434
|
-
this.introTarget.toggleAttribute("hidden", responsePresent);
|
|
435
|
-
this.nicknameTarget.toggleAttribute("hidden", !responsePresent);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
428
|
get options() {
|
|
439
429
|
return {
|
|
440
430
|
publicKey: PublicKeyCredential.parseCreationOptionsFromJSON(
|
|
441
|
-
this.optionsValue
|
|
431
|
+
this.optionsValue,
|
|
442
432
|
),
|
|
443
433
|
};
|
|
444
434
|
}
|
|
@@ -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";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
|
|
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)}}}},{identifier:"webauthn-registration",controllerConstructor:class extends r{static targets=["response"];static values={options:Object};submit(e){this.responseTarget.value||(e.preventDefault(),this.createCredential().then(()=>{e.target.submit()}))}async createCredential(){const e=await navigator.credentials.create(this.options);this.responseTarget.value=JSON.stringify(e.toJSON())}get options(){return{publicKey:PublicKeyCredential.parseCreationOptionsFromJSON(this.optionsValue)}}}}];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\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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"}
|
|
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,\n ),\n };\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nexport default class WebauthnRegistrationController extends Controller {\n static targets = [\"response\"];\n static values = {\n options: Object,\n };\n\n submit(e) {\n if (this.responseTarget.value) return;\n\n e.preventDefault();\n this.createCredential().then(() => {\n e.target.submit();\n });\n }\n\n async createCredential() {\n const credential = await navigator.credentials.create(this.options);\n this.responseTarget.value = JSON.stringify(credential.toJSON());\n }\n\n get options() {\n return {\n publicKey: PublicKeyCredential.parseCreationOptionsFromJSON(\n this.optionsValue,\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","submit","createCredential","then","create","parseCreationOptionsFromJSON","import","HwComboboxController","push","catch","KoiToolbarElement","constructor","super","setAttribute","initGOVUK","body","HTMLScriptElement","prototype","initAll","customElements","define","window","Turbo"],"mappings":"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"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
.
|
|
1
|
+
.roadblock {
|
|
2
|
+
min-width: min(max(50%, 20rem), 100%);
|
|
3
|
+
max-width: min(max(50%, 20rem), 100%);
|
|
4
|
+
|
|
2
5
|
header {
|
|
3
6
|
display: grid;
|
|
4
|
-
grid-template-areas: "
|
|
7
|
+
grid-template-areas: "icon heading" "icon sub-heading";
|
|
5
8
|
grid-template-columns: auto 1fr;
|
|
6
9
|
grid-template-rows: auto auto;
|
|
7
10
|
column-gap: var(--space-s);
|
|
@@ -9,17 +12,17 @@
|
|
|
9
12
|
.icon {
|
|
10
13
|
--scale: 4;
|
|
11
14
|
--offset: 2px;
|
|
12
|
-
grid-area:
|
|
15
|
+
grid-area: icon;
|
|
13
16
|
align-self: center;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
h1 {
|
|
17
|
-
grid-area:
|
|
20
|
+
grid-area: heading;
|
|
18
21
|
font-weight: var(--font-bold);
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
h2 {
|
|
22
|
-
grid-area:
|
|
25
|
+
grid-area: sub-heading;
|
|
23
26
|
font-weight: var(--font-medium);
|
|
24
27
|
font-size: var(--size-step-1);
|
|
25
28
|
color: var(--color-mid);
|
|
@@ -63,6 +63,8 @@ module Admin
|
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def destroy
|
|
66
|
+
return redirect_back_or_to(admin_admin_users_path, status: :see_other) if admin_user == current_admin
|
|
67
|
+
|
|
66
68
|
if admin_user.archived?
|
|
67
69
|
admin_user.destroy!
|
|
68
70
|
|
|
@@ -78,8 +80,6 @@ module Admin
|
|
|
78
80
|
|
|
79
81
|
def set_admin_user
|
|
80
82
|
@admin_user = Admin::User.with_archived.find(params[:id])
|
|
81
|
-
|
|
82
|
-
request.variant << :self if admin_user == current_admin_user
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
def admin_user_params
|
|
@@ -4,65 +4,43 @@ module Admin
|
|
|
4
4
|
class CredentialsController < ApplicationController
|
|
5
5
|
include Koi::Controller::HasWebauthn
|
|
6
6
|
|
|
7
|
-
before_action :set_admin_user
|
|
7
|
+
before_action :set_admin_user, only: %i[new create]
|
|
8
|
+
before_action :set_credential, only: %i[show update destroy]
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
before_action :check_authorization!
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
unless admin_user.webauthn_id
|
|
13
|
-
admin_user.update!(webauthn_id: WebAuthn.generate_user_id)
|
|
14
|
-
end
|
|
12
|
+
attr_reader :admin_user, :credential
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
name: admin_user.email,
|
|
20
|
-
display_name: admin_user.name,
|
|
21
|
-
},
|
|
22
|
-
exclude: admin_user.credentials.map(&:external_id),
|
|
23
|
-
)
|
|
14
|
+
def show
|
|
15
|
+
render locals: { credential: }
|
|
16
|
+
end
|
|
24
17
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
def new
|
|
19
|
+
render locals: { admin_user: }
|
|
20
|
+
end
|
|
28
21
|
|
|
29
|
-
|
|
22
|
+
def create
|
|
23
|
+
webauthn_register!(credential_params[:response])
|
|
30
24
|
|
|
31
|
-
|
|
25
|
+
if %r{/credentials/new$}.match?(request.referer)
|
|
26
|
+
redirect_to(admin_root_path, status: :see_other)
|
|
27
|
+
else
|
|
28
|
+
redirect_back_or_to(admin_profile_path, status: :see_other)
|
|
29
|
+
end
|
|
32
30
|
end
|
|
33
31
|
|
|
34
|
-
def
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
session.delete(:creation_challenge),
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
credential = admin_user.credentials.find_or_initialize_by(
|
|
43
|
-
external_id: webauthn_credential.id,
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
credential.update!(
|
|
47
|
-
nickname: credential_params[:nickname],
|
|
48
|
-
public_key: webauthn_credential.public_key,
|
|
49
|
-
sign_count: webauthn_credential.sign_count,
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
respond_to do |format|
|
|
53
|
-
format.html { redirect_to admin_admin_user_path(admin_user), status: :see_other }
|
|
54
|
-
format.turbo_stream { render locals: { admin_user: } }
|
|
32
|
+
def update
|
|
33
|
+
if credential.update(credential_params)
|
|
34
|
+
redirect_to(admin_profile_path, status: :see_other)
|
|
35
|
+
else
|
|
36
|
+
render :show, locals: { credential: }, status: :unprocessable_content
|
|
55
37
|
end
|
|
56
38
|
end
|
|
57
39
|
|
|
58
40
|
def destroy
|
|
59
|
-
credential = admin_user.credentials.find(params[:id])
|
|
60
41
|
credential.destroy!
|
|
61
42
|
|
|
62
|
-
|
|
63
|
-
format.html { redirect_to admin_admin_user_path(admin_user), status: :see_other }
|
|
64
|
-
format.turbo_stream { render locals: { admin_user: } }
|
|
65
|
-
end
|
|
43
|
+
redirect_to(admin_profile_path, status: :see_other)
|
|
66
44
|
end
|
|
67
45
|
|
|
68
46
|
private
|
|
@@ -72,13 +50,16 @@ module Admin
|
|
|
72
50
|
end
|
|
73
51
|
|
|
74
52
|
def set_admin_user
|
|
75
|
-
@admin_user =
|
|
53
|
+
@admin_user = current_admin
|
|
54
|
+
end
|
|
76
55
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
56
|
+
def set_credential
|
|
57
|
+
@credential = Credential.find(params[:id])
|
|
58
|
+
@admin_user = @credential.admin
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def check_authorization!
|
|
62
|
+
head(:forbidden) unless admin_user == current_admin
|
|
82
63
|
end
|
|
83
64
|
end
|
|
84
65
|
end
|
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Admin
|
|
4
4
|
class OtpsController < ApplicationController
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
attr_reader :admin_user
|
|
5
|
+
alias_method :admin_user, :current_admin
|
|
8
6
|
|
|
9
7
|
def new
|
|
10
8
|
admin_user.otp_secret = ROTP::Base32.random
|
|
@@ -16,14 +14,14 @@ module Admin
|
|
|
16
14
|
admin_user.otp_secret = otp_params[:otp_secret]
|
|
17
15
|
|
|
18
16
|
if admin_user.otp.verify(otp_params[:token])
|
|
19
|
-
admin_user.save
|
|
17
|
+
admin_user.save!
|
|
20
18
|
|
|
21
|
-
redirect_to
|
|
19
|
+
redirect_to admin_profile_path, status: :see_other
|
|
22
20
|
else
|
|
23
21
|
admin_user.errors.add(:token, :invalid)
|
|
24
22
|
|
|
25
23
|
respond_to do |format|
|
|
26
|
-
format.html { redirect_to
|
|
24
|
+
format.html { redirect_to admin_profile_path, status: :see_other }
|
|
27
25
|
format.turbo_stream { render locals: { admin_user: }, status: :unprocessable_content }
|
|
28
26
|
end
|
|
29
27
|
end
|
|
@@ -32,7 +30,7 @@ module Admin
|
|
|
32
30
|
def destroy
|
|
33
31
|
admin_user.update!(otp_secret: nil)
|
|
34
32
|
|
|
35
|
-
redirect_to
|
|
33
|
+
redirect_to admin_profile_path, status: :see_other
|
|
36
34
|
end
|
|
37
35
|
|
|
38
36
|
private
|
|
@@ -40,15 +38,5 @@ module Admin
|
|
|
40
38
|
def otp_params
|
|
41
39
|
params.expect(admin_user: %i[otp_secret token])
|
|
42
40
|
end
|
|
43
|
-
|
|
44
|
-
def set_admin_user
|
|
45
|
-
@admin_user = Admin::User.find(params[:admin_user_id])
|
|
46
|
-
|
|
47
|
-
if current_admin == admin_user
|
|
48
|
-
request.variant = :self
|
|
49
|
-
else
|
|
50
|
-
head(:forbidden)
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
41
|
end
|
|
54
42
|
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Admin
|
|
4
|
+
class ProfilesController < ApplicationController
|
|
5
|
+
include Koi::Controller::HasWebauthn
|
|
6
|
+
|
|
7
|
+
alias_method :admin_user, :current_admin
|
|
8
|
+
|
|
9
|
+
def show
|
|
10
|
+
render locals: { admin_user: }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def edit
|
|
14
|
+
render :edit, locals: { admin_user: }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def update
|
|
18
|
+
if admin_user.update(profile_params)
|
|
19
|
+
redirect_to admin_profile_path, status: :see_other
|
|
20
|
+
else
|
|
21
|
+
render :edit, locals: { admin_user: }, status: :unprocessable_content
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def profile_params
|
|
28
|
+
params.expect(admin_user: %i[name email password])
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -8,10 +8,16 @@ module Admin
|
|
|
8
8
|
before_action :redirect_authenticated, only: %i[new], if: :admin_signed_in?
|
|
9
9
|
before_action :authenticate_local_admin, only: %i[new], if: -> { Koi.config.authenticate_local_admins }
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
attr_reader :admin_user
|
|
12
12
|
|
|
13
13
|
def new
|
|
14
|
-
|
|
14
|
+
@admin_user = Admin::User.new
|
|
15
|
+
|
|
16
|
+
if (message = flash.alert || flash.notice)
|
|
17
|
+
admin_user.errors.add(:email, message)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
render locals: { admin_user: }
|
|
15
21
|
end
|
|
16
22
|
|
|
17
23
|
def create
|
|
@@ -80,7 +86,7 @@ module Admin
|
|
|
80
86
|
end
|
|
81
87
|
|
|
82
88
|
def create_session_with_webauthn
|
|
83
|
-
if (admin_user = webauthn_authenticate!)
|
|
89
|
+
if (admin_user = webauthn_authenticate!(session_params[:response]))
|
|
84
90
|
admin_sign_in(admin_user)
|
|
85
91
|
else
|
|
86
92
|
admin_user = Admin::User.new
|
|
@@ -10,7 +10,7 @@ module Admin
|
|
|
10
10
|
|
|
11
11
|
def show
|
|
12
12
|
if (@admin_user = Admin::User.find_by_token_for(:password_reset, params[:token]))
|
|
13
|
-
render locals: { admin_user:, token: params[:token] }
|
|
13
|
+
render locals: { admin_user:, token: params[:token] }
|
|
14
14
|
else
|
|
15
15
|
redirect_to(new_admin_session_path, status: :see_other, notice: I18n.t("koi.auth.token_invalid"))
|
|
16
16
|
end
|
|
@@ -26,7 +26,11 @@ module Admin
|
|
|
26
26
|
|
|
27
27
|
session[:admin_user_id] = admin_user.id
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
if admin_user.credentials.any?
|
|
30
|
+
redirect_to(admin_root_path, status: :see_other)
|
|
31
|
+
else
|
|
32
|
+
redirect_to(new_admin_profile_credential_path, status: :see_other)
|
|
33
|
+
end
|
|
30
34
|
else
|
|
31
35
|
redirect_to(new_admin_session_path, status: :see_other, notice: I18n.t("koi.auth.token_invalid"))
|
|
32
36
|
end
|