1/** 2 * CitrusOS themed prompt / confirm / alert dialogs (replaces window.prompt & confirm on shell). 3 */ 4(function (global) { 5 'use strict'; 6 7 var activeOverlay = null; 8 var zBase = 125000; 9 var zCounter = 0; 10 11 function escapeHtml(str) { 12 return String(str == null ? '' : str) 13 .replace(/&/g, '&') 14 .replace(/</g, '<') 15 .replace(/>/g, '>') 16 .replace(/"/g, '"'); 17 } 18 19 function normalizeOpts(opts, type, extraDefault) { 20 if (typeof opts === 'string') { 21 if (type === 'prompt') { 22 return normalizeOpts({ 23 title: opts, 24 label: opts, 25 defaultValue: extraDefault || '' 26 }, type); 27 } 28 return normalizeOpts({ message: opts }, type); 29 } 30 opts = opts || {}; 31 return { 32 title: opts.title || (type === 'alert' ? 'Notice' : type === 'confirm' ? 'Confirm' : 'Enter a value'), 33 message: opts.message || '', 34 label: opts.label || opts.fieldLabel || 'Name', 35 defaultValue: opts.defaultValue != null ? String(opts.defaultValue) : '', 36 confirmLabel: opts.confirmLabel || 'OK', 37 cancelLabel: opts.cancelLabel || 'Cancel', 38 okLabel: opts.okLabel || 'OK', 39 danger: !!opts.danger 40 }; 41 } 42 43 function teardown(overlay) { 44 if (!overlay) return; 45 if (overlay._keydownHandler) { 46 document.removeEventListener('keydown', overlay._keydownHandler); 47 } 48 if (overlay.parentNode) overlay.parentNode.removeChild(overlay); 49 if (activeOverlay === overlay) activeOverlay = null; 50 } 51 52 function openDialog(type, opts, extraDefault) { 53 opts = normalizeOpts(opts, type, extraDefault); 54 55 return new Promise(function (resolve) { 56 if (activeOverlay) teardown(activeOverlay); 57 58 var overlay = document.createElement('div'); 59 overlay.className = 'citrus-shell-dialog'; 60 overlay.setAttribute('role', 'dialog'); 61 overlay.setAttribute('aria-modal', 'true'); 62 overlay.setAttribute('aria-label', opts.title); 63 overlay.style.zIndex = String(zBase + (++zCounter)); 64 65 var confirmClass = 'citrus-shell-dialog__btn citrus-shell-dialog__btn--primary'; 66 if (opts.danger) confirmClass += ' citrus-shell-dialog__btn--danger'; 67 68 var html = '<div class="citrus-shell-dialog__panel">'; 69 html += '<h2 class="citrus-shell-dialog__title">' + escapeHtml(opts.title) + '</h2>'; 70 if (opts.message) { 71 html += '<p class="citrus-shell-dialog__text">' + escapeHtml(opts.message) + '</p>'; 72 } 73 if (type === 'prompt') { 74 html += '<label class="citrus-shell-dialog__field">'; 75 html += '<span class="citrus-shell-dialog__field-label">' + escapeHtml(opts.label) + '</span>'; 76 html += '<input type="text" class="citrus-shell-dialog__input" data-shell-dialog-input autocomplete="off" spellcheck="false">'; 77 html += '</label>'; 78 } 79 html += '<div class="citrus-shell-dialog__actions">'; 80 if (type !== 'alert') { 81 html += '<button type="button" class="citrus-shell-dialog__btn" data-shell-dialog-cancel>' + 82 escapeHtml(opts.cancelLabel) + '</button>'; 83 } 84 html += '<button type="button" class="' + confirmClass + '" data-shell-dialog-confirm>' + 85 escapeHtml(type === 'alert' ? opts.okLabel : opts.confirmLabel) + '</button>'; 86 html += '</div></div>'; 87 88 overlay.innerHTML = html; 89 document.body.appendChild(overlay); 90 activeOverlay = overlay; 91 92 var input = overlay.querySelector('[data-shell-dialog-input]'); 93 var confirmBtn = overlay.querySelector('[data-shell-dialog-confirm]'); 94 var cancelBtn = overlay.querySelector('[data-shell-dialog-cancel]'); 95 96 if (input) { 97 input.value = opts.defaultValue; 98 } 99 100 function finish(value) { 101 teardown(overlay); 102 resolve(value); 103 } 104 105 function onConfirm() { 106 if (type === 'prompt') finish(input ? input.value : ''); 107 else finish(true); 108 } 109 110 function onCancel() { 111 if (type === 'alert') finish(true); 112 else if (type === 'prompt') finish(null); 113 else finish(false); 114 } 115
116 overlay._keydownHandler = function (ev) { 117 if (ev.key === 'Escape') { 118 ev.preventDefault(); 119 onCancel(); 120 } else if (ev.key === 'Enter') { 121 if (type === 'prompt' && document.activeElement === input) { 122 ev.preventDefault(); 123 onConfirm(); 124 } else if (type === 'confirm') { 125 ev.preventDefault(); 126 onConfirm(); 127 } else if (type === 'alert') { 128 ev.preventDefault(); 129 onConfirm(); 130 } 131 } 132 }; 133 document.addEventListener('keydown', overlay._keydownHandler); 134 135 confirmBtn.addEventListener('click', onConfirm); 136 if (cancelBtn) cancelBtn.addEventListener('click', onCancel); 137 138 overlay.addEventListener('click', function (ev) { 139 if (ev.target === overlay && type !== 'alert') onCancel(); 140 }); 141 142 global.setTimeout(function () { 143 if (input) { 144 input.focus(); 145 input.select(); 146 } else if (confirmBtn) { 147 confirmBtn.focus(); 148 } 149 }, 0); 150 }); 151 } 152 153 global.CitrusShellDialogs = { 154 prompt: function (opts, defaultValue) { 155 return openDialog('prompt', opts, defaultValue); 156 }, 157 confirm: function (opts) { 158 return openDialog('confirm', opts); 159 }, 160 alert: function (opts) { 161 return openDialog('alert', opts); 162 } 163 }; 164})(typeof window !== 'undefined' ? window : this);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.