diff options
Diffstat (limited to 'web_src')
223 files changed, 17379 insertions, 29518 deletions
diff --git a/web_src/css/actions.css b/web_src/css/actions.css index 0ab09f537a..c43ebe21a0 100644 --- a/web_src/css/actions.css +++ b/web_src/css/actions.css @@ -6,14 +6,6 @@ overflow-x: auto; } -.runner-container .runner-ops > a { - margin-left: 0.5em; -} - -.runner-container .runner-ops-delete { - color: var(--color-red-light); -} - .runner-container .runner-new-text { color: var(--color-white); } @@ -67,6 +59,7 @@ .run-list-ref { display: inline-block !important; + max-width: 105px; } @media (max-width: 767.98px) { diff --git a/web_src/css/base.css b/web_src/css/base.css index 04f3678f3a..529ddd5386 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -2,7 +2,10 @@ /* fonts */ --fonts-proportional: -apple-system, "Segoe UI", system-ui, Roboto, "Helvetica Neue", Arial; --fonts-monospace: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace, var(--fonts-emoji); - --fonts-emoji: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla"; + /* GitHub explicitly sets font names like: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla"; + Actually "Twemoji Mozilla" emoji font is widely used by browsers like Firefox, Pale Moon, and it is more likely up-to-dated than the system emoji font. + So not setting emoji font seems to be the best choice, here we just use a non-existing dummy font name and let browsers choose. */ + --fonts-emoji: -emoji-fallback; /* font weights - use between 400 and 600 for general purposes. Avoid 700 as it is perceived too bold */ --font-weight-light: 300; --font-weight-normal: 400; @@ -26,6 +29,16 @@ --checkbox-size: 15px; /* height and width of checkbox and radio inputs */ --page-spacing: 16px; /* space between page elements */ --page-margin-x: 32px; /* minimum space on left and right side of page */ + --page-space-bottom: 64px; /* space between last page element and footer */ + + /* z-index */ + --z-index-modal: 1001; /* modal dialog, hard-coded from Fomantic modal.css */ + --z-index-toast: 1002; /* should be larger than modal */ + + --font-size-label: 12px; /* font size of individual labels */ + + --gap-inline: 0.25rem; /* gap for inline texts and elements, for example: the spaces for sentence with labels, button text, etc */ + --gap-block: 0.25rem; /* gap for element blocks, for example: spaces between buttons, menu image & title, header icon & title etc */ } @media (min-width: 768px) and (max-width: 1200px) { @@ -224,6 +237,7 @@ progress::-moz-progress-bar { } .unselectable, +.btn, .button, .lines-num, .lines-commit, @@ -313,6 +327,16 @@ a.label, background: var(--color-hover); } +.empty-placeholder { + display: flex; + flex-direction: column; + align-items: center; + padding-top: 40px; + padding-bottom: 40px; + text-align: center; + text-wrap: balance; +} + .inline-code-block { padding: 2px 4px; border-radius: .24em; @@ -336,12 +360,18 @@ a.label, border-color: var(--color-secondary); } +.ui.dropdown .menu > .header { + text-transform: none; /* reset fomantic's "uppercase" */ +} + .ui.dropdown .menu > .header:not(.ui) { color: var(--color-text); + font-size: 0.95em; /* reset fomantic's small font-size */ } .ui.dropdown .menu > .item { color: var(--color-text); + line-height: var(--line-height-default); } .ui.dropdown .menu > .item:hover { @@ -400,11 +430,6 @@ a.label, color: var(--color-text-light-2); } -.ui.form textarea:not([rows]) { - height: var(--min-height-textarea); /* override fomantic default 12em */ - min-height: var(--min-height-textarea); /* override fomantic default 8em */ -} - /* styles from removed fomantic transition module */ .hidden.transition { visibility: hidden; @@ -474,7 +499,7 @@ img.ui.avatar, .full.height { flex-grow: 1; - padding-bottom: 80px; + padding-bottom: var(--page-space-bottom); } .status-page-error { @@ -506,97 +531,6 @@ img.ui.avatar, margin-top: calc(var(--page-spacing) - 1rem); } -.ui.form .fields.error .field textarea, -.ui.form .fields.error .field select, -.ui.form .fields.error .field input:not([type]), -.ui.form .fields.error .field input[type="date"], -.ui.form .fields.error .field input[type="datetime-local"], -.ui.form .fields.error .field input[type="email"], -.ui.form .fields.error .field input[type="number"], -.ui.form .fields.error .field input[type="password"], -.ui.form .fields.error .field input[type="search"], -.ui.form .fields.error .field input[type="tel"], -.ui.form .fields.error .field input[type="time"], -.ui.form .fields.error .field input[type="text"], -.ui.form .fields.error .field input[type="file"], -.ui.form .fields.error .field input[type="url"], -.ui.form .fields.error .field .ui.dropdown, -.ui.form .fields.error .field .ui.dropdown .item, -.ui.form .field.error .ui.dropdown, -.ui.form .field.error .ui.dropdown .text, -.ui.form .field.error .ui.dropdown .item, -.ui.form .field.error textarea, -.ui.form .field.error select, -.ui.form .field.error input:not([type]), -.ui.form .field.error input[type="date"], -.ui.form .field.error input[type="datetime-local"], -.ui.form .field.error input[type="email"], -.ui.form .field.error input[type="number"], -.ui.form .field.error input[type="password"], -.ui.form .field.error input[type="search"], -.ui.form .field.error input[type="tel"], -.ui.form .field.error input[type="time"], -.ui.form .field.error input[type="text"], -.ui.form .field.error input[type="file"], -.ui.form .field.error input[type="url"], -.ui.form .field.error select:focus, -.ui.form .field.error input:not([type]):focus, -.ui.form .field.error input[type="date"]:focus, -.ui.form .field.error input[type="datetime-local"]:focus, -.ui.form .field.error input[type="email"]:focus, -.ui.form .field.error input[type="number"]:focus, -.ui.form .field.error input[type="password"]:focus, -.ui.form .field.error input[type="search"]:focus, -.ui.form .field.error input[type="tel"]:focus, -.ui.form .field.error input[type="time"]:focus, -.ui.form .field.error input[type="text"]:focus, -.ui.form .field.error input[type="file"]:focus, -.ui.form .field.error input[type="url"]:focus { - background-color: var(--color-error-bg); - border-color: var(--color-error-border); - color: var(--color-error-text); -} - -.ui.form .fields.error .field .ui.dropdown, -.ui.form .field.error .ui.dropdown, -.ui.form .fields.error .field .ui.dropdown:hover, -.ui.form .field.error .ui.dropdown:hover { - border-color: var(--color-error-border) !important; -} - -.ui.form .fields.error .field .ui.dropdown .menu .item:hover, -.ui.form .field.error .ui.dropdown .menu .item:hover { - background-color: var(--color-error-bg-hover); -} - -.ui.form .fields.error .field .ui.dropdown .menu .active.item, -.ui.form .field.error .ui.dropdown .menu .active.item { - background-color: var(--color-error-bg-active) !important; -} - -.ui.form .fields.error .dropdown .menu, -.ui.form .field.error .dropdown .menu { - border-color: var(--color-error-border) !important; -} - -input:-webkit-autofill, -input:-webkit-autofill:focus, -input:-webkit-autofill:hover, -input:-webkit-autofill:active, -.ui.form .field.field input:-webkit-autofill, -.ui.form .field.field input:-webkit-autofill:focus, -.ui.form .field.field input:-webkit-autofill:hover, -.ui.form .field.field input:-webkit-autofill:active { - -webkit-background-clip: text; - -webkit-text-fill-color: var(--color-text); - box-shadow: 0 0 0 100px var(--color-primary-light-6) inset !important; - border-color: var(--color-primary-light-4) !important; -} - -.ui.form .field.muted { - opacity: var(--opacity-disabled); -} - .text.primary { color: var(--color-primary) !important; } @@ -613,38 +547,18 @@ input:-webkit-autofill:active, color: var(--color-yellow) !important; } -.text.olive { - color: var(--color-olive) !important; -} - .text.green { color: var(--color-green) !important; } -.text.teal { - color: var(--color-teal) !important; -} - .text.blue { color: var(--color-blue) !important; } -.text.violet { - color: var(--color-violet) !important; -} - .text.purple { color: var(--color-purple) !important; } -.text.pink { - color: var(--color-pink) !important; -} - -.text.brown { - color: var(--color-brown) !important; -} - .text.black { color: var(--color-text) !important; } @@ -691,18 +605,6 @@ input:-webkit-autofill:active, box-shadow: 0 6px 18px var(--color-shadow) !important; } -.ui.dropdown .menu > .header { - font-size: 0.8em; -} - -.ui .text.left { - text-align: left !important; -} - -.ui .text.right { - text-align: right !important; -} - .ui .text.truncate { overflow-x: hidden; text-overflow: ellipsis; @@ -726,31 +628,14 @@ input:-webkit-autofill:active, vertical-align: middle; } -.ui .form .autofill-dummy { - position: absolute; - width: 1px; - height: 1px; - overflow: hidden; - z-index: -10000; -} - -.ui .form .sub.field { - margin-left: 25px; -} - .ui .sha.label { font-family: var(--fonts-monospace); font-size: 13px; font-weight: var(--font-weight-normal); - margin: 0 6px; - padding: 5px 10px; + padding: 3px 5px; flex-shrink: 0; } -.ui .sha.label .shortsha { - display: inline-block; /* not sure whether it is still needed */ -} - .ui .button.truncate { display: inline-block; max-width: 100%; @@ -770,46 +655,6 @@ input:-webkit-autofill:active, font-weight: var(--font-weight-normal); } -.ui .background.red { - background-color: var(--color-red) !important; -} - -.ui .background.blue { - background-color: var(--color-blue) !important; -} - -.ui .background.black { - background-color: var(--color-black) !important; -} - -.ui .background.grey { - background-color: var(--color-grey) !important; -} - -.ui .background.light.grey { - background-color: var(--color-grey) !important; -} - -.ui .background.green { - background-color: var(--color-green) !important; -} - -.ui .background.purple { - background-color: var(--color-purple) !important; -} - -.ui .background.yellow { - background-color: var(--color-yellow) !important; -} - -.ui .background.orange { - background-color: var(--color-orange) !important; -} - -.ui .background.gold { - background-color: var(--color-gold) !important; -} - .ui .migrate { color: var(--color-text-light-2) !important; } @@ -826,50 +671,11 @@ input:-webkit-autofill:active, border: 1px solid; } -.ui .border.red { - border-color: var(--color-red) !important; -} - -.ui .border.blue { - border-color: var(--color-blue) !important; -} - -.ui .border.black { - border-color: var(--color-black) !important; -} - -.ui .border.grey { - border-color: var(--color-grey) !important; -} - -.ui .border.light.grey { - border-color: var(--color-grey) !important; -} - -.ui .border.green { - border-color: var(--color-green) !important; -} - -.ui .border.purple { - border-color: var(--color-purple) !important; -} - -.ui .border.yellow { - border-color: var(--color-yellow) !important; -} - -.ui .border.orange { - border-color: var(--color-orange) !important; -} - -.ui .border.gold { - border-color: var(--color-gold) !important; -} - -.ui.floating.dropdown .overflow.menu .scrolling.menu.items { +.ui.dropdown .menu.context-user-switch .scrolling.menu { border-radius: 0 !important; box-shadow: none !important; border-bottom: 1px solid var(--color-secondary); + max-width: 80vw; } .user-menu > .item { @@ -933,7 +739,8 @@ strong.attention-caution, svg.attention-caution { color: var(--color-red-dark-1); } -.center:not(.popup) { +/* FIXME: this is a longstanding dirty patch since 2015, it only makes the pages more messy and shouldn't be used */ +.center { text-align: center; } @@ -964,6 +771,14 @@ overflow-menu .overflow-menu-button { padding: 0; } +/* match the styles of ".ui.secondary.pointing.menu .active.item" */ +overflow-menu.ui.secondary.pointing.menu .overflow-menu-button.active { + padding: 2px 0 0; + border-bottom: 2px solid currentcolor; + background-color: transparent; + font-weight: var(--font-weight-medium); +} + overflow-menu .overflow-menu-button:hover { color: var(--color-text-dark); } @@ -989,22 +804,6 @@ overflow-menu .ui.label { margin-top: 3em !important; } -/* multiple radio or checkboxes as inline element */ -.inline-grouped-list { - display: inline-block; - vertical-align: top; -} - -.inline-grouped-list > .ui { - display: block; - margin-top: 5px; - margin-bottom: 10px; -} - -.inline-grouped-list > .ui:first-child { - margin-top: 1px; -} - .lines-blame-btn { padding: 0 0 0 5px; display: flex; @@ -1035,10 +834,6 @@ overflow-menu .ui.label { display: block; } -.code-view .lines-num span::after { - cursor: pointer; -} - .lines-type-marker { vertical-align: top; white-space: nowrap; @@ -1075,43 +870,16 @@ overflow-menu .ui.label { .lines-escape { width: 0; white-space: nowrap; + padding: 0; } .lines-code { padding-left: 5px; } -.file-view tr.active { - color: inherit !important; - background: inherit !important; -} - -.file-view tr.active .lines-num, -.file-view tr.active .lines-code { - background: var(--color-highlight-bg) !important; -} - -.file-view tr.active:last-of-type .lines-code { - border-bottom-right-radius: var(--border-radius); -} - -.file-view tr.active .lines-num { - position: relative; -} - -.file-view tr.active .lines-num::before { - content: ""; - position: absolute; - left: 0; - width: 2px; - height: 100%; - background: var(--color-highlight-fg); -} - .code-inner { font: 12px var(--fonts-monospace); white-space: pre-wrap; - word-break: break-all; overflow-wrap: anywhere; line-height: inherit; /* needed for inline code preview in markup */ } @@ -1159,12 +927,12 @@ overflow-menu .ui.label { margin-right: 4px; } -.top-line-blame { +tr.top-line-blame { border-top: 1px solid var(--color-secondary); } -.code-view tr.top-line-blame:first-of-type { - border-top: none; +tr.top-line-blame:first-of-type { + border-top: none; /* merge code lines belonging to the same commit into one block */ } .lines-code .bottom-line, @@ -1172,15 +940,6 @@ overflow-menu .ui.label { border-bottom: 1px solid var(--color-secondary); } -.code-view { - background: var(--color-code-bg); - border-radius: var(--border-radius); -} - -.code-view table { - width: 100%; -} - .migrate .svg.gitea-git { color: var(--color-git); } @@ -1224,14 +983,7 @@ table th[data-sortt-desc] .svg { box-shadow: 0 0 0 1px var(--color-secondary) inset; } -.emoji { - font-size: 1.25em; - line-height: var(--line-height-default); - font-style: normal !important; - font-weight: var(--font-weight-normal) !important; - vertical-align: -0.075em; -} - +/* for "image" emojis like ":git:" ":gitea:" and ":github:" (see CUSTOM_EMOJIS config option) */ .emoji img { border-width: 0 !important; margin: 0 !important; @@ -1270,26 +1022,13 @@ table th[data-sortt-desc] .svg { text-align: left; } -.truncated-item-container { - display: flex !important; - align-items: center; -} - -.ellipsis-button { - padding: 0 5px 8px !important; - display: inline-block !important; - font-weight: var(--font-weight-semibold) !important; - line-height: 6px !important; - vertical-align: middle !important; -} - -.truncated-item-name { - line-height: 2; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - margin-top: -0.5em; - margin-bottom: -0.5em; +.ui.button.ellipsis-button { + padding: 0 5px 8px; + display: inline-block; + font-weight: var(--font-weight-semibold); + line-height: 8px; + vertical-align: middle; + min-height: 0; } .precolors { @@ -1359,7 +1098,7 @@ table th[data-sortt-desc] .svg { .flex-text-inline { display: inline-flex; align-items: center; - gap: .25rem; + gap: var(--gap-inline); vertical-align: middle; min-width: 0; /* make ellipsis work */ } @@ -1382,20 +1121,27 @@ table th[data-sortt-desc] .svg { } .ui.list.flex-items-block > .item, +.ui.form .field > label.flex-text-block, /* override fomantic "block" style */ .flex-items-block > .item, .flex-text-block { display: flex; align-items: center; - gap: .5rem; + gap: var(--gap-block); min-width: 0; } +.ui.dropdown > .ui.button, +.flex-text-block > .ui.button, +.flex-text-inline > .ui.button { + margin: 0; /* fomantic buttons have default margin, when we use them in a flex container with gap, we do not need these margins */ +} + /* to override Fomantic's default display: block for ".menu .item", and use a slightly larger gap for menu item content the "!important" is necessary to override Fomantic UI menu item styles, meanwhile we should keep the "hidden" items still hidden */ .ui.dropdown .menu.flex-items-menu > .item:not(.hidden, .filtered, .tw-hidden) { display: flex !important; align-items: center; - gap: .5rem; + gap: var(--gap-block); min-width: 0; } .ui.dropdown .menu.flex-items-menu > .item img, @@ -1403,15 +1149,33 @@ the "!important" is necessary to override Fomantic UI menu item styles, meanwhil margin: 0; /* use gap, but not margin */ } -.ui.dropdown.ellipsis-items-nowrap > .text { +.ui.dropdown.ellipsis-text-items { + /* reset y padding and use the line-height below instead, to avoid the "overflow: hidden" clips the larger image in the "text" element */ + padding-top: 0; + padding-bottom: 0; +} + +.ui.dropdown.ellipsis-text-items > .text { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + line-height: 2.71; /* matches fomantic dropdown's default min-height */ } -.ellipsis-items-nowrap > .item, -.ui.dropdown.ellipsis-items-nowrap .menu > .item { +.ui.dropdown.ellipsis-text-items .menu > .item { white-space: nowrap !important; overflow: hidden !important; text-overflow: ellipsis !important; } + +.svg.octicon-file-directory-fill, +.svg.octicon-file-directory-open-fill, +.svg.octicon-file-submodule { + color: var(--color-primary); +} + +.svg.octicon-file, +.svg.octicon-file-symlink-file, +.svg.octicon-file-directory-symlink { + color: var(--color-secondary-dark-7); +} diff --git a/web_src/css/editor/combomarkdowneditor.css b/web_src/css/editor/combomarkdowneditor.css index 835286b795..046010c6c8 100644 --- a/web_src/css/editor/combomarkdowneditor.css +++ b/web_src/css/editor/combomarkdowneditor.css @@ -100,67 +100,3 @@ border-bottom: 1px solid var(--color-secondary); padding-bottom: 1rem; } - -text-expander { - display: block; - position: relative; -} - -text-expander .suggestions { - position: absolute; - min-width: 180px; - padding: 0; - margin-top: 24px; - list-style: none; - background: var(--color-box-body); - border-radius: var(--border-radius); - border: 1px solid var(--color-secondary); - box-shadow: 0 .5rem 1rem var(--color-shadow); - z-index: 100; /* needs to be > 20 to be on top of dropzone's .dz-details */ -} - -text-expander .suggestions li { - display: flex; - align-items: center; - cursor: pointer; - padding: 4px 8px; - font-weight: var(--font-weight-medium); -} - -text-expander .suggestions li + li { - border-top: 1px solid var(--color-secondary-alpha-40); -} - -text-expander .suggestions li:first-child { - border-radius: var(--border-radius) var(--border-radius) 0 0; -} - -text-expander .suggestions li:last-child { - border-radius: 0 0 var(--border-radius) var(--border-radius); -} - -text-expander .suggestions li:only-child { - border-radius: var(--border-radius); -} - -text-expander .suggestions li:hover { - background: var(--color-hover); -} - -text-expander .suggestions .fullname { - font-weight: var(--font-weight-normal); - margin-left: 4px; - color: var(--color-text-light-1); -} - -text-expander .suggestions li[aria-selected="true"], -text-expander .suggestions li[aria-selected="true"] span { - background: var(--color-primary); - color: var(--color-primary-contrast); -} - -text-expander .suggestions img { - width: 24px; - height: 24px; - margin-right: 8px; -} diff --git a/web_src/css/editor/fileeditor.css b/web_src/css/editor/fileeditor.css index 444ee8c7e7..698efffc99 100644 --- a/web_src/css/editor/fileeditor.css +++ b/web_src/css/editor/fileeditor.css @@ -74,12 +74,3 @@ padding: 1rem; text-align: center; } - -.edit-diff { - padding: 0 !important; -} - -.edit-diff > div > .ui.table { - border-top: none !important; - border-bottom: none !important; -} diff --git a/web_src/css/explore.css b/web_src/css/explore.css index 5cdee823c0..968a103ce9 100644 --- a/web_src/css/explore.css +++ b/web_src/css/explore.css @@ -1,13 +1,4 @@ -.explore .secondary-nav { - border-width: 1px !important; -} - -.explore .secondary-nav .svg { - width: 16px; - text-align: center; - margin-right: 5px; -} - +/* FIXME: need to refactor the repo branches list page and move these styles to proper place */ .ui.repository.branches .info { font-size: 12px; color: var(--color-text-light); @@ -20,12 +11,3 @@ overflow: hidden; text-overflow: ellipsis; } - -.ui.repository.branches .overflow-visible { - overflow: visible; -} - -/* fix alignment of PR popup in branches table */ -.ui.repository.branches table .ui.popup { - text-align: left; -} diff --git a/web_src/css/features/cropper.css b/web_src/css/features/cropper.css index ed7171e770..f7f8168006 100644 --- a/web_src/css/features/cropper.css +++ b/web_src/css/features/cropper.css @@ -1,6 +1,6 @@ @import "cropperjs/dist/cropper.css"; -.page-content.user.profile .cropper-panel .cropper-wrapper { +.avatar-file-with-cropper + .cropper-panel .cropper-wrapper { max-width: 400px; max-height: 400px; } diff --git a/web_src/css/features/expander.css b/web_src/css/features/expander.css new file mode 100644 index 0000000000..f560b2a9fd --- /dev/null +++ b/web_src/css/features/expander.css @@ -0,0 +1,96 @@ +text-expander .suggestions, +.tribute-container { + position: absolute; + max-height: min(300px, 95vh); + max-width: min(500px, 95vw); + overflow-x: hidden; + overflow-y: auto; + white-space: nowrap; + background: var(--color-menu); + box-shadow: 0 6px 18px var(--color-shadow); + border-radius: var(--border-radius); + border: 1px solid var(--color-secondary); + z-index: 100; /* needs to be > 20 to be on top of dropzone's .dz-details */ +} + +text-expander { + display: block; + position: relative; +} + +text-expander .suggestions { + padding: 0; + margin-top: 24px; + list-style: none; +} + +text-expander .suggestions li, +.tribute-item { + display: flex; + align-items: center; + cursor: pointer; + gap: 6px; + font-weight: var(--font-weight-medium); +} + +text-expander .suggestions li, +.tribute-container li { + padding: 3px 6px; +} + +text-expander .suggestions li + li, +.tribute-container li + li { + border-top: 1px solid var(--color-secondary); +} + +text-expander .suggestions li:first-child { + border-radius: var(--border-radius) var(--border-radius) 0 0; +} + +text-expander .suggestions li:last-child { + border-radius: 0 0 var(--border-radius) var(--border-radius); +} + +text-expander .suggestions li:only-child { + border-radius: var(--border-radius); +} + +text-expander .suggestions .fullname, +.tribute-container li .fullname { + font-weight: var(--font-weight-normal); + color: var(--color-text-light-1); + overflow: hidden; + text-overflow: ellipsis; +} + +text-expander .suggestions li:hover, +text-expander .suggestions li:hover *, +text-expander .suggestions li[aria-selected="true"], +text-expander .suggestions li[aria-selected="true"] *, +.tribute-container li.highlight, +.tribute-container li.highlight * { + background: var(--color-primary); + color: var(--color-primary-contrast); +} + +text-expander .suggestions img, +.tribute-item img { + width: 21px; + height: 21px; + object-fit: contain; + aspect-ratio: 1; +} + +.tribute-container { + display: block; +} + +.tribute-container ul { + margin: 0; + padding: 0; + list-style: none; +} + +.tribute-container li.no-match { + cursor: default; +} diff --git a/web_src/css/features/gitgraph.css b/web_src/css/features/gitgraph.css index f8f7e35cdc..865c82e003 100644 --- a/web_src/css/features/gitgraph.css +++ b/web_src/css/features/gitgraph.css @@ -57,6 +57,12 @@ white-space: nowrap; display: flex; align-items: center; + gap: 0.25em; +} + +#git-graph-container li .ui.label.commit-id-short { + padding-top: 2px; + padding-bottom: 2px; } #git-graph-container li .node-relation { @@ -110,17 +116,7 @@ max-width: 200px; overflow: hidden; text-overflow: ellipsis; -} - -#git-graph-container #rev-list .sha.label { - padding-top: 5px; - padding-bottom: 3px; -} - -#git-graph-container #rev-list .sha.label .ui.detail.icon.button { - padding-top: 3px; - margin-top: -5px; - padding-bottom: 1px; + min-height: 0; } #git-graph-container #graph-raw-list { diff --git a/web_src/css/features/projects.css b/web_src/css/features/projects.css index 8763d3684e..7fd5150970 100644 --- a/web_src/css/features/projects.css +++ b/web_src/css/features/projects.css @@ -1,43 +1,35 @@ -.board { +#project-board { display: flex; + align-items: stretch; flex-direction: row; flex-wrap: nowrap; - overflow-x: auto; - overflow-y: clip; - align-items: stretch; + overflow: auto; margin: 0 0.5em; + min-height: max(calc(100vh - 400px), 300px); + max-height: calc(100vh - 120px); } -.project-toolbar-right .filter.menu { - flex-direction: row; +.project-header { + padding: 0.5em 0; flex-wrap: wrap; } -@media (max-width: 767.98px) { - .project-toolbar-right .dropdown .menu { - left: auto !important; - right: auto !important; - } +.project-header h2 { + white-space: nowrap; + margin: 0; } .project-column { - background-color: var(--color-project-column-bg) !important; - border: 1px solid var(--color-secondary) !important; - border-radius: var(--border-radius); - margin: 0 0.5rem !important; - padding: 0.5rem !important; - width: 320px; - height: initial; - min-height: max(calc(100vh - 400px), 300px); flex: 0 0 auto; - overflow: visible; display: flex; flex-direction: column; - cursor: default; -} - -.project-column .issue-card { - color: var(--color-text); + background-color: var(--color-project-column-bg); + border: 1px solid var(--color-secondary); + border-radius: var(--border-radius); + margin: 0 0.5rem; + padding: 0.5rem; + width: 320px; + overflow: visible; } .project-column-header { @@ -51,16 +43,15 @@ color: inherit; } -.project-column > .cards { +.project-column > .ui.cards { flex: 1; display: flex; - align-content: baseline; - margin: 0 !important; - padding: 0 !important; - flex-wrap: nowrap !important; + flex-wrap: nowrap; flex-direction: column; - overflow-x: clip; + overflow: clip auto; gap: .25rem; + margin: 0; + padding: 0; } .project-column > .divider { @@ -110,3 +101,21 @@ .card-ghost * { opacity: 0; } + +.fullscreen.projects-view { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; +} + +/* Hide project-description in full-screen due to its variable height. No need to show it for better space use. */ +.fullscreen.projects-view .project-description { + display: none; +} + +.fullscreen.projects-view #project-board { + flex: 1; + max-height: unset; + padding-bottom: 0.5em; +} diff --git a/web_src/css/features/tribute.css b/web_src/css/features/tribute.css deleted file mode 100644 index 99a026b9bc..0000000000 --- a/web_src/css/features/tribute.css +++ /dev/null @@ -1,32 +0,0 @@ -@import "tributejs/dist/tribute.css"; - -.tribute-container { - box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.25); - border-radius: var(--border-radius); -} - -.tribute-container ul { - margin-top: 0 !important; - background: var(--color-body) !important; -} - -.tribute-container li { - padding: 3px 0.5rem !important; -} - -.tribute-container li span.fullname { - font-weight: var(--font-weight-normal); - font-size: 0.8rem; -} - -.tribute-container li.highlight, -.tribute-container li:hover { - background: var(--color-primary) !important; - color: var(--color-primary-contrast) !important; -} - -.tribute-item { - display: flex; - align-items: center; - gap: 6px; -} diff --git a/web_src/css/form.css b/web_src/css/form.css index 5dd5e05bec..c51eba1bc9 100644 --- a/web_src/css/form.css +++ b/web_src/css/form.css @@ -1,3 +1,111 @@ +.ui .form .autofill-dummy { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + z-index: -10000; +} + +.ui .form .sub.field { + margin-left: 25px; +} + +.ui.form .fields.error .field textarea, +.ui.form .fields.error .field select, +.ui.form .fields.error .field input:not([type]), +.ui.form .fields.error .field input[type="date"], +.ui.form .fields.error .field input[type="datetime-local"], +.ui.form .fields.error .field input[type="email"], +.ui.form .fields.error .field input[type="number"], +.ui.form .fields.error .field input[type="password"], +.ui.form .fields.error .field input[type="search"], +.ui.form .fields.error .field input[type="tel"], +.ui.form .fields.error .field input[type="time"], +.ui.form .fields.error .field input[type="text"], +.ui.form .fields.error .field input[type="file"], +.ui.form .fields.error .field input[type="url"], +.ui.form .fields.error .field .ui.dropdown, +.ui.form .fields.error .field .ui.dropdown .item, +.ui.form .field.error .ui.dropdown, +.ui.form .field.error .ui.dropdown .text, +.ui.form .field.error .ui.dropdown .item, +.ui.form .field.error textarea, +.ui.form .field.error select, +.ui.form .field.error input:not([type]), +.ui.form .field.error input[type="date"], +.ui.form .field.error input[type="datetime-local"], +.ui.form .field.error input[type="email"], +.ui.form .field.error input[type="number"], +.ui.form .field.error input[type="password"], +.ui.form .field.error input[type="search"], +.ui.form .field.error input[type="tel"], +.ui.form .field.error input[type="time"], +.ui.form .field.error input[type="text"], +.ui.form .field.error input[type="file"], +.ui.form .field.error input[type="url"], +.ui.form .field.error select:focus, +.ui.form .field.error input:not([type]):focus, +.ui.form .field.error input[type="date"]:focus, +.ui.form .field.error input[type="datetime-local"]:focus, +.ui.form .field.error input[type="email"]:focus, +.ui.form .field.error input[type="number"]:focus, +.ui.form .field.error input[type="password"]:focus, +.ui.form .field.error input[type="search"]:focus, +.ui.form .field.error input[type="tel"]:focus, +.ui.form .field.error input[type="time"]:focus, +.ui.form .field.error input[type="text"]:focus, +.ui.form .field.error input[type="file"]:focus, +.ui.form .field.error input[type="url"]:focus { + background-color: var(--color-error-bg); + border-color: var(--color-error-border); + color: var(--color-error-text); +} + +.ui.form .fields.error .field .ui.dropdown, +.ui.form .field.error .ui.dropdown, +.ui.form .fields.error .field .ui.dropdown:hover, +.ui.form .field.error .ui.dropdown:hover { + border-color: var(--color-error-border) !important; +} + +.ui.form .fields.error .field .ui.dropdown .menu .item:hover, +.ui.form .field.error .ui.dropdown .menu .item:hover { + background-color: var(--color-error-bg-hover); +} + +.ui.form .fields.error .field .ui.dropdown .menu .active.item, +.ui.form .field.error .ui.dropdown .menu .active.item { + background-color: var(--color-error-bg-active) !important; +} + +.ui.form .fields.error .dropdown .menu, +.ui.form .field.error .dropdown .menu { + border-color: var(--color-error-border) !important; +} + +input:-webkit-autofill, +input:-webkit-autofill:focus, +input:-webkit-autofill:hover, +input:-webkit-autofill:active, +.ui.form .field.field input:-webkit-autofill, +.ui.form .field.field input:-webkit-autofill:focus, +.ui.form .field.field input:-webkit-autofill:hover, +.ui.form .field.field input:-webkit-autofill:active { + -webkit-background-clip: text; + -webkit-text-fill-color: var(--color-text); + box-shadow: 0 0 0 100px var(--color-primary-light-6) inset !important; + border-color: var(--color-primary-light-4) !important; +} + +.ui.form .field.muted { + opacity: var(--opacity-disabled); +} + +.ui.form textarea:not([rows]) { + height: var(--min-height-textarea); /* override fomantic default 12em */ + min-height: var(--min-height-textarea); /* override fomantic default 8em */ +} + .ui.input textarea, .ui.form textarea, .ui.form input:not([type]), @@ -38,11 +146,6 @@ textarea, color: var(--color-input-text); } -/* fix fomantic small dropdown having inconsistent padding with input */ -.ui.small.selection.dropdown { - padding: .67857143em 1.6em .67857143em 1em; -} - input:hover, textarea:hover, .ui.input input:hover, @@ -109,56 +212,15 @@ textarea:focus, color: var(--color-input-text); } -/* match <select> padding to <input> */ -.ui.form select { - padding: 0.67857143em 1em; +.ui.form .field > .selection.dropdown { + min-width: 14em; /* matches the default min width */ } .form .help { color: var(--color-secondary-dark-5); padding-bottom: 0.6em; display: inline-block; -} - -#create-page-form form { - margin: auto; -} - -#create-page-form form .ui.message { - text-align: center; -} - -@media (min-width: 768px) { - #create-page-form form { - width: 800px !important; - } - #create-page-form form .header { - padding-left: 280px !important; - } - #create-page-form form .inline.field > label { - text-align: right; - width: 250px !important; - word-wrap: break-word; - } - #create-page-form form .help { - margin-left: 265px !important; - } - #create-page-form form .optional .title { - margin-left: 250px !important; - } - #create-page-form form .inline.field > input, - #create-page-form form .inline.field > textarea { - width: 50%; - } -} - -@media (max-width: 767.98px) { - #create-page-form form .optional .title { - margin-left: 15px; - } - #create-page-form form .inline.field > label { - display: block; - } + text-wrap: balance; } .m-captcha-style { @@ -182,12 +244,12 @@ textarea:focus, height: 76px !important; } .m-captcha-style { - width: 50%; + max-width: 450px; } } @media (max-height: 575px) { - #rc-imageselect, + #rc-imageselect, /* google recaptcha */ .g-recaptcha-style, .h-captcha-style { transform: scale(0.77); @@ -195,295 +257,45 @@ textarea:focus, } } -.user.forgot.password form, -.user.reset.password form, -.user.signup form { - margin: auto; - width: 700px !important; -} - -.user.activate form .ui.message, -.user.forgot.password form .ui.message, -.user.reset.password form .ui.message, -.user.link-account form .ui.message, -.user.signin form .ui.message, -.user.signup form .ui.message { - text-align: center; -} - -@media (min-width: 768px) { - .user.activate form, - .user.forgot.password form, - .user.reset.password form, - .user.link-account form, - .user.signin form, - .user.signup form { - width: 800px !important; - } - .user.activate form .header, - .user.forgot.password form .header, - .user.reset.password form .header, - .user.link-account form .header, - .user.signin form .header, - .user.signup form .header { - padding-left: 280px !important; - } - .user.activate form .inline.field > label { - text-align: right; - width: 250px !important; - word-wrap: break-word; - } - .user.activate form .help, - .user.forgot.password form .help, - .user.reset.password form .help, - .user.link-account form .help, - .user.signin form .help, - .user.signup form .help { - margin-left: 265px !important; - } - .user.activate form .optional .title, - .user.forgot.password form .optional .title, - .user.reset.password form .optional .title, - .user.link-account form .optional .title, - .user.signin form .optional .title, - .user.signup form .optional .title { - margin-left: 250px !important; - } -} - -@media (max-width: 767.98px) { - .user.activate form .optional .title, - .user.forgot.password form .optional .title, - .user.reset.password form .optional .title, - .user.link-account form .optional .title, - .user.signin form .optional .title, - .user.signup form .optional .title { - margin-left: 15px; - } - .user.activate form .inline.field > label, - .user.forgot.password form .inline.field > label, - .user.reset.password form .inline.field > label, - .user.link-account form .inline.field > label, - .user.signin form .inline.field > label, - .user.signup form .inline.field > label { - display: block; - } -} - -.user.activate form .header, -.user.forgot.password form .header, -.user.reset.password form .header, -.user.link-account form .header, -.user.signin form .header, -.user.signup form .header { - padding-left: 0 !important; - text-align: center; -} - -.user.activate form .inline.field > label, -.user.forgot.password form .inline.field > label, -.user.reset.password form .inline.field > label, -.user.link-account form .inline.field > label, -.user.signin form .inline.field > label, -.user.signup form .inline.field > label { - width: 200px; -} - -@media (max-width: 767.98px) { - .user.activate form .inline.field > label, - .user.forgot.password form .inline.field > label, - .user.reset.password form .inline.field > label, - .user.link-account form .inline.field > label, - .user.signin form .inline.field > label, - .user.signup form .inline.field > label { - width: 100% !important; - } -} - -.user.activate form input[type="number"], -.user.forgot.password form input[type="number"], -.user.reset.password form input[type="number"], -.user.link-account form input[type="number"], -.user.signin form input[type="number"], -.user.signup form input[type="number"] { - -moz-appearance: textfield; -} - -.user.activate form input::-webkit-outer-spin-button, -.user.forgot.password form input::-webkit-outer-spin-button, -.user.reset.password form input::-webkit-outer-spin-button, -.user.link-account form input::-webkit-outer-spin-button, -.user.signin form input::-webkit-outer-spin-button, -.user.signup form input::-webkit-outer-spin-button, -.user.activate form input::-webkit-inner-spin-button, -.user.forgot.password form input::-webkit-inner-spin-button, -.user.reset.password form input::-webkit-inner-spin-button, -.user.link-account form input::-webkit-inner-spin-button, -.user.signin form input::-webkit-inner-spin-button, -.user.signup form input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} - -.repository.new.repo form, -.repository.new.migrate form, -.repository.new.fork form { - margin: auto; -} - -.repository.new.repo form .ui.message, -.repository.new.migrate form .ui.message, -.repository.new.fork form .ui.message { - text-align: center; -} - -@media (min-width: 768px) { - .repository.new.repo form, - .repository.new.migrate form, - .repository.new.fork form { - width: 800px !important; - } - .repository.new.repo form .header, - .repository.new.migrate form .header, - .repository.new.fork form .header { - padding-left: 280px !important; - } - .repository.new.repo form .inline.field > label, - .repository.new.migrate form .inline.field > label, - .repository.new.fork form .inline.field > label { - text-align: right; - width: 250px !important; - word-wrap: break-word; - } - .repository.new.repo form .help, - .repository.new.migrate form .help, - .repository.new.fork form .help { - margin-left: 265px !important; - } - .repository.new.repo form .optional .title, - .repository.new.migrate form .optional .title, - .repository.new.fork form .optional .title { - margin-left: 250px !important; - } - .repository.new.repo form .inline.field > input, - .repository.new.migrate form .inline.field > input, - .repository.new.fork form .inline.field > input, - .repository.new.repo form .inline.field > textarea, - .repository.new.migrate form .inline.field > textarea, - .repository.new.fork form .inline.field > textarea { - width: 50%; - } +.ui.form.left-right-form .inline.field > label { + text-align: right; + width: 250px; + margin-right: 10px; } -@media (max-width: 767.98px) { - .repository.new.repo form .optional .title, - .repository.new.migrate form .optional .title, - .repository.new.fork form .optional .title { - margin-left: 15px; - } - .repository.new.repo form .inline.field > label, - .repository.new.migrate form .inline.field > label, - .repository.new.fork form .inline.field > label { - display: block; - } +.ui.form.left-right-form .inline.field > .help { + display: block; + margin-left: calc(250px + 15px); } -.repository.new.repo form .dropdown .text, -.repository.new.migrate form .dropdown .text, -.repository.new.fork form .dropdown .text { - margin-right: 0 !important; +.ui.form.left-right-form .inline.field input:not([type="checkbox"], [type="radio"]), +.ui.form.left-right-form .inline.field .ui.dropdown, +.ui.form.left-right-form .inline.field textarea { + width: 50%; } -.repository.new.repo form .header, -.repository.new.migrate form .header, -.repository.new.fork form .header { - padding-left: 0 !important; - text-align: center; +.ui.form.left-right-form .inline.field .ui.dropdown input.search { + width: 100%; } -.repository.new.repo form .selection.dropdown, -.repository.new.migrate form .selection.dropdown, -.repository.new.fork form .selection.dropdown, -.repository.new.fork form .field a { - vertical-align: middle; - width: 50% !important; +.ui.form.left-right-form .inline.field .inline-right { + display: inline-flex; + flex-direction: column; + gap: 0.5em; } @media (max-width: 767.98px) { - .repository.new.repo form label, - .repository.new.migrate form label, - .repository.new.fork form label, - .repository.new.repo form .inline.field > input, - .repository.new.migrate form .inline.field > input, - .repository.new.fork form .inline.field > input, - .repository.new.fork form .field a, - .repository.new.repo form .selection.dropdown, - .repository.new.migrate form .selection.dropdown, - .repository.new.fork form .selection.dropdown { - width: 100% !important; - } - .repository.new.repo form .field button, - .repository.new.migrate form .field button, - .repository.new.fork form .field button, - .repository.new.repo form .field a, - .repository.new.migrate form .field a { - margin-bottom: 1em; + .ui.form.left-right-form .inline.field > label { width: 100%; + margin: 0; + text-align: left; } -} - -@media (min-width: 768px) { - .repository.new.repo .ui.form #auto-init { - margin-left: 265px !important; - } -} - -.repository.new.repo .ui.form .selection.dropdown:not(.owner) { - width: 50% !important; -} - -@media (max-width: 767.98px) { - .repository.new.repo .ui.form .selection.dropdown:not(.owner) { - width: 100% !important; + .ui.form.left-right-form .inline.field > .help { + margin: 0; } -} - -/* form fields with additional content besides their label, used on login form - * use like <div class="field"><label/><a/><input/></div> */ -.form-field-content-aside-label { - display: grid; - grid-template-columns: 1fr 1fr; -} -.form-field-content-aside-label > *:nth-child(2) { - text-align: right; -} -.form-field-content-aside-label input { - grid-column: span 2; -} - -.ui.form .field > .selection.dropdown { - min-width: 14em; /* matches the default min width */ -} - -.new.webhook form .help { - margin-left: 25px; -} - -.new.webhook .events.fields .column { - padding-left: 40px; -} - -.githook textarea { - font-family: var(--fonts-monospace); -} - -@media (max-width: 767.98px) { - .new.org .ui.form .field button, - .new.org .ui.form .field a { - margin-bottom: 1em; + .ui.form.left-right-form .inline.field input:not([type="checkbox"], [type="radio"]), + .ui.form.left-right-form .inline.field .ui.dropdown, + .ui.form.left-right-form .inline.field textarea { width: 100%; } - .new.org .ui.form .field input { - width: 100% !important; - } } diff --git a/web_src/css/home.css b/web_src/css/home.css index 77d2ecf92b..195d1f5d96 100644 --- a/web_src/css/home.css +++ b/web_src/css/home.css @@ -21,7 +21,7 @@ } .home .hero .svg { - color: var(--color-green); + color: var(--color-logo); height: 40px; width: 50px; vertical-align: bottom; @@ -40,7 +40,7 @@ } .home a { - color: var(--color-green); + color: var(--color-logo); } .page-footer { diff --git a/web_src/css/index.css b/web_src/css/index.css index 02513aebc1..291cd04b2b 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -18,8 +18,8 @@ @import "./modules/checkbox.css"; @import "./modules/dimmer.css"; @import "./modules/modal.css"; +@import "./modules/tab.css"; -@import "./modules/select.css"; @import "./modules/tippy.css"; @import "./modules/breadcrumb.css"; @import "./modules/comment.css"; @@ -39,7 +39,7 @@ @import "./features/imagediff.css"; @import "./features/codeeditor.css"; @import "./features/projects.css"; -@import "./features/tribute.css"; +@import "./features/expander.css"; @import "./features/cropper.css"; @import "./features/console.css"; @@ -62,7 +62,7 @@ @import "./repo/issue-label.css"; @import "./repo/issue-list.css"; @import "./repo/list-header.css"; -@import "./repo/linebutton.css"; +@import "./repo/file-view.css"; @import "./repo/wiki.css"; @import "./repo/header.css"; @import "./repo/home.css"; @@ -70,6 +70,7 @@ @import "./repo/reactions.css"; @import "./repo/clone.css"; @import "./repo/commit-sign.css"; +@import "./repo/packages.css"; @import "./editor/fileeditor.css"; @import "./editor/combomarkdowneditor.css"; @@ -82,5 +83,6 @@ @import "./review.css"; @import "./actions.css"; -@tailwind utilities; @import "./helpers.css"; + +@tailwind utilities; diff --git a/web_src/css/markup/codecopy.css b/web_src/css/markup/codecopy.css index e3017ae962..5a7b9955e7 100644 --- a/web_src/css/markup/codecopy.css +++ b/web_src/css/markup/codecopy.css @@ -1,8 +1,3 @@ -.markup .code-block, -.markup .mermaid-block { - position: relative; -} - .markup .code-copy { position: absolute; top: 8px; @@ -28,8 +23,8 @@ background: var(--color-secondary-dark-1) !important; } -.markup .code-block:hover .code-copy, -.markup .mermaid-block:hover .code-copy { +.markup .code-block-container:hover .code-copy, +.markup .code-block:hover .code-copy { visibility: visible; animation: fadein 0.2s both; } diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css index d2dcf2ec6e..c6a89edf25 100644 --- a/web_src/css/markup/content.css +++ b/web_src/css/markup/content.css @@ -5,10 +5,6 @@ overflow-wrap: break-word; } -.conversation-holder .markup { - overflow-wrap: anywhere; /* prevent overflow in code comments. TODO: properly restrict .conversation-holder width and remove this */ -} - .markup > *:first-child { margin-top: 0 !important; } @@ -138,6 +134,13 @@ margin-bottom: 16px; } +/* override p:last-child from base.css. +Fomantic assumes that <p>/<hX> elements only have margins between elements, but not for the first's top or last's bottom. +In markup content, we always use bottom margin for all elements */ +.markup p:last-child { + margin-bottom: 16px; +} + .markup hr { height: 4px; padding: 0; @@ -285,7 +288,6 @@ .markup table { display: block; width: 100%; - width: max-content; max-width: 100%; overflow: auto; } @@ -314,10 +316,18 @@ box-sizing: initial; } +.file-view.markup { + padding: 1em 2em; +} + +.file-view.markup:has(.file-not-rendered-prompt) { + padding: 0; /* let the file-not-rendered-prompt layout itself */ +} + /* this background ensures images can break <hr>. We can only do this on cases where the background is known and not transparent. */ -.markup.file-view img, -.markup.file-view video, +.file-view.markup img, +.file-view.markup video, .comment-body .markup img, /* regular comment */ .comment-body .markup video, .comment-content .markup img, /* code comment */ @@ -337,11 +347,6 @@ padding-right: 28px; } -.markup .emoji { - max-width: none; - vertical-align: text-top; -} - .markup span.frame { display: block; overflow: hidden; @@ -453,14 +458,25 @@ } .markup pre > code { - padding: 0; - margin: 0; font-size: 100%; +} + +.markup .code-block, +.markup .code-block-container { + position: relative; +} + +.markup .code-block-container.code-overflow-wrap pre > code { white-space: pre-wrap; - word-break: break-all; - overflow-wrap: break-word; - background: transparent; - border: 0; +} + +.markup .code-block-container.code-overflow-scroll pre { + overflow-x: auto; +} + +.markup .code-block-container.code-overflow-scroll pre > code { + white-space: pre; + overflow-wrap: normal; } .markup .highlight { @@ -481,16 +497,11 @@ word-break: normal; } -.markup pre { - word-wrap: normal; -} - .markup pre code, .markup pre tt { display: inline; padding: 0; line-height: inherit; - word-wrap: normal; background-color: transparent; border: 0; } @@ -521,21 +532,19 @@ padding-left: 2em; } -.file-revisions-btn { - display: block; - float: left; - margin-bottom: 2px !important; - padding: 11px !important; - margin-right: 10px !important; +.markup details.frontmatter-content summary { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + margin-bottom: 0.25em; } -.file-revisions-btn i { - -webkit-touch-callout: none; - -webkit-user-select: none; - user-select: none; +.markup details.frontmatter-content svg { + vertical-align: middle; + margin: 0 0.25em; } -.markup-render { +.markup-content-iframe { display: block; border: none; width: 100%; diff --git a/web_src/css/modules/animations.css b/web_src/css/modules/animations.css index 481e997d4f..deaaf83680 100644 --- a/web_src/css/modules/animations.css +++ b/web_src/css/modules/animations.css @@ -52,8 +52,7 @@ form.single-button-form.is-loading .button { } .markup pre.is-loading, -.editor-loading.is-loading, -.pdf-content.is-loading { +.editor-loading.is-loading { height: var(--height-loading); } @@ -116,3 +115,15 @@ code.language-math.is-loading::after { animation-duration: 100ms; animation-timing-function: ease-in-out; } + +/* FIXME: `octicon-sync` is counterclockwise, so this animation is also counterclockwise, it looks somewhat strange. +Ideally in the future we should use a better image for clockwise animation. */ +.circular-spin { + animation: circular-spin-keyframes 1s linear infinite; +} + +@keyframes circular-spin-keyframes { + 100% { + transform: rotate(-360deg); + } +} diff --git a/web_src/css/modules/breadcrumb.css b/web_src/css/modules/breadcrumb.css index ca488c2150..77e31ef627 100644 --- a/web_src/css/modules/breadcrumb.css +++ b/web_src/css/modules/breadcrumb.css @@ -1,14 +1,10 @@ .breadcrumb { display: flex; - flex-wrap: wrap; align-items: center; gap: 3px; + overflow-wrap: anywhere; } .breadcrumb .breadcrumb-divider { color: var(--color-text-light-2); } - -.breadcrumb > * { - display: inline; -} diff --git a/web_src/css/modules/button.css b/web_src/css/modules/button.css index c4addd05f0..b105bb5de2 100644 --- a/web_src/css/modules/button.css +++ b/web_src/css/modules/button.css @@ -1,20 +1,15 @@ -/* based on Fomantic UI checkbox module, with just the parts extracted that we use. If you find any - unused rules here after refactoring, please remove them. */ - .ui.button { cursor: pointer; - display: inline-block; - min-height: 1em; + display: inline-flex; outline: none; - vertical-align: baseline; font-family: var(--fonts-regular); margin: 0 0.25em 0 0; - padding: 0.78571429em 1.5em; font-weight: var(--font-weight-normal); + font-size: 1rem; text-align: center; text-decoration: none; line-height: 1; - border-radius: 0.28571429rem; + border-radius: var(--border-radius); user-select: none; -webkit-tap-highlight-color: transparent; justify-content: center; @@ -58,12 +53,13 @@ pointer-events: none !important; } +/* there is no "ui labeled icon button" support" because it is not used */ .ui.labeled.button:not(.icon) { - display: inline-flex; flex-direction: row; background: none; - padding: 0 !important; + padding: 0; border: none; + min-height: unset; } .ui.labeled.button > .button { margin: 0; @@ -102,47 +98,60 @@ margin: 0 -0.21428571em 0 0.42857143em; } +/* reference sizes (not exactly at the moment): normal: padding-x=21, height=38 ; compact: padding-x=15, height=32 */ +.ui.button { /* stylelint-disable-line no-duplicate-selectors */ + min-height: 38px; + padding: 0.57em /* around 8px */ 1.43em /* around 20px */; +} .ui.compact.buttons .button, .ui.compact.button { - padding: 0.58928571em 1.125em; + padding: 0.42em /* around 8px */ 1.07em /* around 15px */; + min-height: 32px; } .ui.compact.icon.buttons .button, .ui.compact.icon.button { - padding: 0.58928571em; -} -.ui.compact.labeled.icon.button { - padding: 0.58928571em 3.69642857em; -} -.ui.compact.labeled.icon.button > .icon { - padding: 0.58928571em 0; + padding: 0.57em /* around 8px */; } -.ui.buttons .button, -.ui.button { - font-size: 1rem; -} +/* reference size: mini: padding-x=16, height=30 ; compact: padding-x=12, height=26 */ .ui.mini.buttons .dropdown, .ui.mini.buttons .dropdown .menu > .item, .ui.mini.buttons .button, .ui.ui.ui.ui.mini.button { - font-size: 0.78571429rem; + font-size: 11px; + min-height: 30px; +} +.ui.ui.ui.ui.mini.button.compact { + min-height: 26px; } + +/* reference size: tiny: padding-x=18, height=32 ; compact: padding-x=13, height=28 */ .ui.tiny.buttons .dropdown, .ui.tiny.buttons .dropdown .menu > .item, .ui.tiny.buttons .button, .ui.ui.ui.ui.tiny.button { - font-size: 0.85714286rem; + font-size: 12px; + min-height: 32px; +} +.ui.ui.ui.ui.tiny.button.compact { + min-height: 28px; } + +/* reference size: small: padding-x=19, height=34 ; compact: padding-x=14, height=30 */ .ui.small.buttons .dropdown, .ui.small.buttons .dropdown .menu > .item, .ui.small.buttons .button, .ui.ui.ui.ui.small.button { - font-size: 0.92857143rem; + font-size: 13px; + min-height: 34px; +} +.ui.ui.ui.ui.small.button.compact { + min-height: 30px; } .ui.icon.buttons .button, .ui.icon.button:not(.compact) { - padding: 0.78571429em; + padding: 0.57em; } .ui.icon.buttons .button > .icon, .ui.icon.button > .icon { @@ -152,12 +161,12 @@ .ui.basic.buttons .button, .ui.basic.button { - border-radius: 0.28571429rem; + border-radius: var(--border-radius); background: none; } .ui.basic.buttons { border: 1px solid var(--color-secondary); - border-radius: 0.28571429rem; + border-radius: var(--border-radius); } .ui.basic.buttons .button { border-radius: 0; @@ -188,29 +197,6 @@ background: var(--color-active); } -.ui.labeled.icon.button { - position: relative; - padding-left: 4.07142857em !important; - padding-right: 1.5em !important; -} - -.ui.labeled.icon.button > .icon { - position: absolute; - top: 0; - left: 0; - height: 100%; - line-height: 1; - border-radius: 0; - border-top-left-radius: inherit; - border-bottom-left-radius: inherit; - text-align: center; - animation: none; - padding: 0.78571429em 0; - margin: 0; - width: 2.57142857em; - background: var(--color-hover); -} - .ui.button.toggle.active { background-color: var(--color-green); color: var(--color-white); @@ -366,6 +352,14 @@ a.btn:hover { color: inherit; } +.btn.tiny { + font-size: 12px; +} + +.btn.small { + font-size: 13px; +} + /* By default, Fomantic UI doesn't support "bordered" buttons group, but Gitea would like to use it. And the default buttons always have borders now (not the same as Fomantic UI's default buttons, see above). It needs some tricks to tweak the left/right borders with active state */ @@ -379,12 +373,12 @@ It needs some tricks to tweak the left/right borders with active state */ .ui.buttons .button:first-child { border-left: none; margin-left: 0; - border-top-left-radius: 0.28571429rem; - border-bottom-left-radius: 0.28571429rem; + border-top-left-radius: var(--border-radius); + border-bottom-left-radius: var(--border-radius); } .ui.buttons .button:last-child { - border-top-right-radius: 0.28571429rem; - border-bottom-right-radius: 0.28571429rem; + border-top-right-radius: var(--border-radius); + border-bottom-right-radius: var(--border-radius); } .ui.buttons .button:hover { @@ -414,10 +408,3 @@ It needs some tricks to tweak the left/right borders with active state */ .ui.buttons .button.active + .button { border-left: none; } - -/* apply the vertical padding of .compact to non-compact buttons when they contain a svg as they - would otherwise appear too large. Seen on "RSS Feed" button on repo releases tab. */ -.ui.small.button:not(.compact):has(.svg) { - padding-top: 0.58928571em; - padding-bottom: 0.58928571em; -} diff --git a/web_src/css/modules/checkbox.css b/web_src/css/modules/checkbox.css index 0a3a71acaa..f7e61ba360 100644 --- a/web_src/css/modules/checkbox.css +++ b/web_src/css/modules/checkbox.css @@ -119,3 +119,13 @@ input[type="radio"] { .ui.toggle.checkbox input:focus:checked ~ label::before { background: var(--color-primary) !important; } + +label.gt-checkbox { + display: inline-flex; + align-items: center; + gap: 0.25em; +} + +.ui.form .field > label.gt-checkbox { + display: flex; +} diff --git a/web_src/css/modules/container.css b/web_src/css/modules/container.css index 4a442c35b1..236cb986fd 100644 --- a/web_src/css/modules/container.css +++ b/web_src/css/modules/container.css @@ -11,7 +11,6 @@ .ui.fluid.container { width: 100%; } - -.ui[class*="center aligned"].container { - text-align: center; +.ui.container.medium-width { + width: 800px; } diff --git a/web_src/css/modules/dimmer.css b/web_src/css/modules/dimmer.css index 8924821370..7d1ca6171a 100644 --- a/web_src/css/modules/dimmer.css +++ b/web_src/css/modules/dimmer.css @@ -20,7 +20,7 @@ opacity: 1; } -.ui.dimmer > * { +.ui.dimmer > .ui.modal { position: static; margin-top: auto !important; margin-bottom: auto !important; diff --git a/web_src/css/modules/grid.css b/web_src/css/modules/grid.css index a2c558047d..b4f4e16105 100644 --- a/web_src/css/modules/grid.css +++ b/web_src/css/modules/grid.css @@ -393,58 +393,6 @@ margin-right: 2.5rem; } -.ui[class*="middle aligned"].grid > .column:not(.row), -.ui[class*="middle aligned"].grid > .row > .column, -.ui.grid > [class*="middle aligned"].row > .column, -.ui.grid > [class*="middle aligned"].column:not(.row), -.ui.grid > .row > [class*="middle aligned"].column { - flex-direction: column; - vertical-align: middle; - align-self: center !important; -} - -.ui[class*="left aligned"].grid > .column, -.ui[class*="left aligned"].grid > .row > .column, -.ui.grid > [class*="left aligned"].row > .column, -.ui.grid > [class*="left aligned"].column.column, -.ui.grid > .row > [class*="left aligned"].column.column { - text-align: left; - align-self: inherit; -} - -.ui[class*="center aligned"].grid > .column, -.ui[class*="center aligned"].grid > .row > .column, -.ui.grid > [class*="center aligned"].row > .column, -.ui.grid > [class*="center aligned"].column.column, -.ui.grid > .row > [class*="center aligned"].column.column { - text-align: center; - align-self: inherit; -} -.ui[class*="center aligned"].grid { - justify-content: center; -} - -.ui[class*="right aligned"].grid > .column, -.ui[class*="right aligned"].grid > .row > .column, -.ui.grid > [class*="right aligned"].row > .column, -.ui.grid > [class*="right aligned"].column.column, -.ui.grid > .row > [class*="right aligned"].column.column { - text-align: right; - align-self: inherit; -} - -.ui[class*="equal width"].grid > .column:not(.row), -.ui[class*="equal width"].grid > .row > .column, -.ui.grid > [class*="equal width"].row > .column { - display: inline-block; - flex-grow: 1; -} -.ui[class*="equal width"].grid > .wide.column, -.ui[class*="equal width"].grid > .row > .wide.column, -.ui.grid > [class*="equal width"].row > .wide.column { - flex-grow: 0; -} - @media only screen and (max-width: 767.98px) { .ui[class*="mobile reversed"].grid, .ui[class*="mobile reversed"].grid > .row, diff --git a/web_src/css/modules/label.css b/web_src/css/modules/label.css index 1e42668aa1..bb6f1b512f 100644 --- a/web_src/css/modules/label.css +++ b/web_src/css/modules/label.css @@ -4,25 +4,19 @@ .ui.label { display: inline-flex; align-items: center; - gap: .25rem; + gap: var(--gap-inline); min-width: 0; - vertical-align: middle; - line-height: 1; + max-width: 100%; background: var(--color-label-bg); color: var(--color-label-text); - padding: 0.3em 0.5em; - font-size: 0.85714286rem; + padding: 2px 6px; + font-size: var(--font-size-label); font-weight: var(--font-weight-medium); border: 0 solid transparent; - border-radius: 0.28571429rem; + border-radius: var(--border-radius); white-space: nowrap; -} - -.ui.label:first-child { - margin-left: 0; -} -.ui.label:last-child { - margin-right: 0; + overflow: hidden; + text-overflow: ellipsis; } a.ui.label { @@ -292,3 +286,58 @@ a.ui.ui.ui.basic.grey.label:hover { .ui.large.label { font-size: 1rem; } + +/* To let labels break up and wrap across multiple lines (issue title, comment event), use "display: contents here" to apply parent layout. +If the labels-list itself needs some layouts, use extra classes or "tw" helpers. */ +.labels-list { + display: contents; + font-size: var(--font-size-label); /* it must match the label font size, otherwise the height mismatches */ +} + +.labels-list a { + max-width: 100%; /* for ellipsis */ +} + +.labels-list .ui.label { + min-height: 20px; + padding-top: 0; + padding-bottom: 0; +} + +.with-labels-list-inline .labels-list .ui.label + .ui.label { + margin-left: var(--gap-inline); +} + +.with-labels-list-inline .labels-list .ui.label { + line-height: var(--line-height-default); +} + +/* Scoped labels with different colors on left and right */ +.ui.label.scope-parent { + background: none !important; + padding: 0 !important; + gap: 0 !important; +} + +.ui.label.scope-parent > .ui.label { + margin: 0 !important; /* scoped label's margin is handled by the parent */ +} + +.ui.label.scope-left { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} + +.ui.label.scope-middle { + border-radius: 0; +} + +.ui.label.scope-right { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} + +.ui.label.archived-label { + filter: grayscale(0.5); + opacity: 0.5; +} diff --git a/web_src/css/modules/list.css b/web_src/css/modules/list.css index 73760390de..46422cb97d 100644 --- a/web_src/css/modules/list.css +++ b/web_src/css/modules/list.css @@ -5,7 +5,6 @@ list-style-type: none; margin: 1em 0; padding: 0; - font-size: 1em; } .ui.list:first-child { diff --git a/web_src/css/modules/menu.css b/web_src/css/modules/menu.css index a5efd23053..5072dcbd0e 100644 --- a/web_src/css/modules/menu.css +++ b/web_src/css/modules/menu.css @@ -1,5 +1,6 @@ .ui.menu { display: flex; + flex-shrink: 0; margin: 1rem 0; font-family: var(--fonts-regular); font-weight: var(--font-weight-normal); @@ -643,6 +644,7 @@ display: inline-flex; margin: 0; vertical-align: middle; + flex-shrink: 0; } .ui.compact.vertical.menu { display: inline-block; diff --git a/web_src/css/modules/modal.css b/web_src/css/modules/modal.css index 427d2529c8..fd6dacc30c 100644 --- a/web_src/css/modules/modal.css +++ b/web_src/css/modules/modal.css @@ -67,6 +67,7 @@ These inconsistent layouts should be refactored to simple ones. border-radius: 0 0 var(--border-radius) var(--border-radius); } +.ui.modal .content > form > .actions, .ui.modal .content > .actions { padding-top: 1em; /* if the "actions" is in the "content", some paddings are already added by the "content" */ text-align: right; diff --git a/web_src/css/modules/navbar.css b/web_src/css/modules/navbar.css index b5bc95b058..ab431e3675 100644 --- a/web_src/css/modules/navbar.css +++ b/web_src/css/modules/navbar.css @@ -48,7 +48,8 @@ align-items: stretch; } /* hide all items */ - #navbar .item { + #navbar .navbar-left > .item, + #navbar .navbar-right > .item { display: none; } #navbar #navbar-logo { @@ -103,11 +104,11 @@ #navbar .ui.dropdown .navbar-profile-admin { display: block; position: absolute; - font-size: 10px; + font-size: 9px; font-weight: var(--font-weight-bold); color: var(--color-nav-bg); background: var(--color-primary); - padding: 2px 4px; + padding: 2px 3px; border-radius: 10px; top: -1px; left: 18px; @@ -128,8 +129,8 @@ background: var(--color-primary); border: 2px solid var(--color-nav-bg); position: absolute; - left: 6px; - top: -9px; + left: calc(100% - 9px); + bottom: calc(100% - 9px); min-width: 17px; height: 17px; border-radius: 11px; /* (height + 2 * borderThickness) / 2 */ diff --git a/web_src/css/modules/segment.css b/web_src/css/modules/segment.css index 0f555cea93..adb514be59 100644 --- a/web_src/css/modules/segment.css +++ b/web_src/css/modules/segment.css @@ -123,13 +123,6 @@ clear: both; } -.ui[class*="left aligned"].segment { - text-align: left; -} -.ui[class*="center aligned"].segment { - text-align: center; -} - .ui.secondary.segment { background: var(--color-secondary-bg); color: var(--color-text-light); diff --git a/web_src/css/modules/select.css b/web_src/css/modules/select.css deleted file mode 100644 index 1d7d749d4a..0000000000 --- a/web_src/css/modules/select.css +++ /dev/null @@ -1,25 +0,0 @@ -.gitea-select { - position: relative; -} - -.gitea-select select { - appearance: none; /* hide default triangle */ -} - -/* ::before and ::after pseudo elements don't work on select elements, - so we need to put it on the parent. */ -.gitea-select::after { - position: absolute; - top: 12px; - right: 8px; - pointer-events: none; - content: ""; - width: 14px; - height: 14px; - mask-size: cover; - -webkit-mask-size: cover; - mask-image: var(--octicon-chevron-right); - -webkit-mask-image: var(--octicon-chevron-right); - transform: rotate(90deg); /* point the chevron down */ - background: currentcolor; -} diff --git a/web_src/css/modules/svg.css b/web_src/css/modules/svg.css index b3060bddd6..738ec22cd3 100644 --- a/web_src/css/modules/svg.css +++ b/web_src/css/modules/svg.css @@ -4,6 +4,10 @@ fill: currentcolor; } +.svg.git-entry-icon { + fill: transparent; /* some material icons have dark background fill, so need to reset */ +} + .middle .svg { vertical-align: middle; } diff --git a/web_src/css/modules/tab.css b/web_src/css/modules/tab.css new file mode 100644 index 0000000000..63c83179b2 --- /dev/null +++ b/web_src/css/modules/tab.css @@ -0,0 +1,7 @@ +.ui.tab { + display: none; +} + +.ui.tab.active { + display: block; +} diff --git a/web_src/css/modules/table.css b/web_src/css/modules/table.css index 4fb9d4214e..eabca31a17 100644 --- a/web_src/css/modules/table.css +++ b/web_src/css/modules/table.css @@ -152,31 +152,6 @@ } } -.ui.table[class*="left aligned"], -.ui.table [class*="left aligned"] { - text-align: left; -} - -.ui.table[class*="center aligned"], -.ui.table [class*="center aligned"] { - text-align: center; -} - -.ui.table[class*="right aligned"], -.ui.table [class*="right aligned"] { - text-align: right; -} - -.ui.table[class*="top aligned"], -.ui.table [class*="top aligned"] { - vertical-align: top; -} - -.ui.table[class*="middle aligned"], -.ui.table [class*="middle aligned"] { - vertical-align: middle; -} - .ui.table th.collapsing, .ui.table td.collapsing { width: 1px; diff --git a/web_src/css/modules/tippy.css b/web_src/css/modules/tippy.css index 55b9751cc6..3c0d63f2fb 100644 --- a/web_src/css/modules/tippy.css +++ b/web_src/css/modules/tippy.css @@ -28,6 +28,10 @@ z-index: 1; } +.tippy-box[data-theme="default"] { + box-shadow: 0 6px 18px var(--color-shadow); +} + /* bare theme, no styling at all, except box-shadow */ .tippy-box[data-theme="bare"] { border: none; @@ -88,6 +92,10 @@ } .tippy-box[data-theme="menu"] .item:focus { + background: var(--color-hover); +} + +.tippy-box[data-theme="menu"] .item.active { background: var(--color-active); } diff --git a/web_src/css/modules/toast.css b/web_src/css/modules/toast.css index 1145f3b1b5..330d3b176e 100644 --- a/web_src/css/modules/toast.css +++ b/web_src/css/modules/toast.css @@ -3,7 +3,7 @@ position: fixed; opacity: 0; transition: all .2s ease; - z-index: 500; + z-index: var(--z-index-toast); border-radius: var(--border-radius); box-shadow: 0 8px 24px var(--color-shadow); display: flex; diff --git a/web_src/css/org.css b/web_src/css/org.css index 1082625041..48b41de297 100644 --- a/web_src/css/org.css +++ b/web_src/css/org.css @@ -1,94 +1,7 @@ -#create-page-form form { - margin: auto; -} - -#create-page-form form .ui.message { - text-align: center; -} - -@media (min-width: 768px) { - #create-page-form form { - width: 800px !important; - } - #create-page-form form .header { - padding-left: 280px !important; - } - #create-page-form form .inline.field > label { - text-align: right; - width: 250px !important; - word-wrap: break-word; - } - #create-page-form form .help { - margin-left: 265px !important; - } - #create-page-form form .optional .title { - margin-left: 250px !important; - } - #create-page-form form .inline.field > input, - #create-page-form form .inline.field > textarea { - width: 50%; - } -} - -@media (max-width: 767.98px) { - #create-page-form form .optional .title { - margin-left: 15px; - } - #create-page-form form .inline.field > label { - display: block; - } -} - .organization .head .ui.header .ui.right { margin-top: 5px; } -.organization.new.org form { - margin: auto; -} - -.organization.new.org form .ui.message { - text-align: center; -} - -@media (min-width: 768px) { - .organization.new.org form { - width: 800px !important; - } - .organization.new.org form .header { - padding-left: 280px !important; - } - .organization.new.org form .inline.field > label { - text-align: right; - width: 250px !important; - word-wrap: break-word; - } - .organization.new.org form .help { - margin-left: 265px !important; - } - .organization.new.org form .optional .title { - margin-left: 250px !important; - } - .organization.new.org form .inline.field > input, - .organization.new.org form .inline.field > textarea { - width: 50%; - } -} - -@media (max-width: 767.98px) { - .organization.new.org form .optional .title { - margin-left: 15px; - } - .organization.new.org form .inline.field > label { - display: block; - } -} - -.organization.new.org form .header { - padding-left: 0 !important; - text-align: center; -} - .page-content.organization .org-avatar { margin-right: 15px; } diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 6fdc9ec2a8..a72709c382 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -50,23 +50,44 @@ width: 300px; } -.issue-sidebar-combo .ui.dropdown .item:not(.checked) .item-check-mark { - visibility: hidden; +.issue-content-right .ui.dropdown.full-width { + width: 100%; } -.issue-content-right .dropdown > .menu { +.issue-content-right .ui.dropdown.full-width > .fixed-text { + display: flex; + flex-grow: 1; + justify-content: space-between; +} + +.issue-content-right .ui.dropdown > .menu { max-width: 270px; min-width: 0; max-height: 500px; overflow-x: auto; } -.issue-content-right .dropdown > .menu .item-secondary-info small { +.issue-content-right .ui.dropdown > .menu .item-secondary-info small { display: block; text-overflow: ellipsis; overflow: hidden; } +.issue-content-right .ui.list { + margin: 0.5em 0; + max-width: 100%; +} + +.issue-sidebar-combo > .ui.dropdown .item:not(.checked) .item-check-mark { + visibility: hidden; +} + +.issue-content-right .ui.list.labels-list { + display: flex; + gap: var(--gap-inline); + flex-wrap: wrap; +} + @media (max-width: 767.98px) { .issue-content-left, .issue-content-right { @@ -120,20 +141,13 @@ td .commit-summary { align-items: center; overflow: hidden; text-overflow: ellipsis; + gap: 0.25em; } @media (max-width: 767.98px) { - .latest-commit .sha { + .latest-commit .commit-id-short { display: none; } - .latest-commit .commit-summary { - margin-left: 8px; - } -} - -.repo-path { - display: flex; - overflow-wrap: anywhere; } .repository.file.list .non-diff-file-content .header .icon { @@ -169,42 +183,6 @@ td .commit-summary { cursor: default; } -.view-raw { - display: flex; - justify-content: center; - align-items: center; -} - -.view-raw > * { - max-width: 100%; -} - -.view-raw audio, -.view-raw video, -.view-raw img { - margin: 1rem 0; - border-radius: 0; - object-fit: contain; -} - -.view-raw img[src$=".svg" i] { - max-height: 600px !important; - max-width: 600px !important; -} - -.pdf-content { - width: 100%; - height: 600px; - border: none !important; - display: flex; - align-items: center; - justify-content: center; -} - -.pdf-content .pdf-fallback-button { - margin: 50px auto; -} - .repository.file.list .non-diff-file-content .plain-text { padding: 1em 2em; } @@ -227,10 +205,6 @@ td .commit-summary { padding: 0 !important; } -.non-diff-file-content .pdfobject { - border-radius: 0 0 var(--border-radius) var(--border-radius); -} - .repo-editor-header { width: 100%; } @@ -478,14 +452,6 @@ td .commit-summary { margin-right: 5px; } -.repository.view.issue .merge.box .branch-update.grid .row { - padding-bottom: 1rem; -} - -.repository.view.issue .merge.box .branch-update.grid .row .icon { - margin-top: 1.1rem; -} - .repository.view.issue .comment-list:not(.prevent-before-timeline)::before { display: block; content: ""; @@ -523,7 +489,7 @@ td .commit-summary { .repository.view.issue .comment-list .timeline-item, .repository.view.issue .comment-list .timeline-item-group { - padding: 16px 0; + padding: 8px 0; } .repository.view.issue .comment-list .timeline-item-group .timeline-item { @@ -577,6 +543,11 @@ td .commit-summary { justify-content: center; } +.repository.view.issue .comment-list .timeline-item.commits-list .badge { + margin-right: 0; + height: 28px; +} + .repository.view.issue .comment-list .timeline-item .badge .svg { width: 22px; height: 22px; @@ -601,19 +572,6 @@ td .commit-summary { padding-top: 0; } -.repository.view.issue .comment-list .timeline-item.commits-list .ui.avatar { - margin-right: 0.25em; -} - -.singular-commit { - display: flex; - align-items: center; -} - -.singular-commit .badge { - height: 30px !important; -} - .repository.view.issue .comment-list .timeline-item.event > .commit-status-link { float: right; margin-right: 8px; @@ -795,7 +753,7 @@ td .commit-summary { box-shadow: none; } -.repository.view.issue .ui.depending .item.is-closed .title { +.repository.view.issue .ui.depending .item.is-closed .issue-dependency-title { text-decoration: line-through; } @@ -836,10 +794,6 @@ td .commit-summary { height: 10px; } -.repository.compare.pull .show-form-container { - text-align: left; -} - .repository .choose.branch { display: flex; align-items: center; @@ -877,11 +831,6 @@ td .commit-summary { margin-top: -8px; } -.repository.compare.pull .pullrequest-form { - margin-top: 16px; - margin-bottom: 16px; -} - .repository.compare.pull .markup { font-size: 14px; } @@ -936,14 +885,6 @@ td .commit-summary { width: 200px; } -.repository #commits-table thead .shatd { - text-align: center; -} - -.repository #commits-table td.sha .sha.label { - margin: 0; -} - .repository #commits-table.ui.basic.striped.table tbody tr:nth-child(2n) { background-color: var(--color-light) !important; } @@ -1113,10 +1054,6 @@ td .commit-summary { height: 30px; } -.repository .diff-box .resolved-placeholder .button { - padding: 8px 12px; -} - .repository .diff-file-box .header { background-color: var(--color-box-header); } @@ -1256,33 +1193,6 @@ td .commit-summary { font-weight: var(--font-weight-normal); } -.empty-placeholder { - display: flex; - flex-direction: column; - align-items: center; - padding-top: 40px; - padding-bottom: 40px; -} - -.repository.packages .file-size { - white-space: nowrap; -} - -.file-view.markup { - padding: 1em 2em; -} - -.file-view.markup:has(.file-not-rendered-prompt) { - padding: 0; /* let the file-not-rendered-prompt layout itself */ -} - -.file-not-rendered-prompt { - padding: 1rem; - text-align: center; - font-size: 1rem !important; /* use consistent styles for various containers (code, markup, etc) */ - line-height: var(--line-height-default) !important; /* same as above */ -} - .repository .activity-header { display: flex; justify-content: space-between; @@ -1440,12 +1350,6 @@ td .commit-summary { padding-top: 15px; } -.commit-header-row { - min-height: 50px !important; - padding-top: 0 !important; - padding-bottom: 0 !important; -} - .commit-header-buttons { display: flex; gap: 4px; @@ -1514,16 +1418,12 @@ td .commit-summary { } .comment-header { - border: none !important; background: var(--color-box-header); - border-bottom: 1px solid var(--color-secondary) !important; - font-weight: var(--font-weight-normal) !important; - padding: 0.5rem 1rem; - margin: 0 !important; + border-bottom: 1px solid var(--color-secondary); + padding: 0 1rem; position: relative; color: var(--color-text); min-height: 41px; - background-color: var(--color-box-header); display: flex; justify-content: space-between; align-items: center; @@ -1615,43 +1515,6 @@ td .commit-summary { border-bottom-right-radius: 4px; } -.labels-list { - display: inline-flex; - flex-wrap: wrap; - gap: 2.5px; - align-items: center; -} - -.labels-list .label { - padding: 0 6px; - min-height: 20px; - line-height: 1.3; /* there is a `font-size: 1.25em` for inside emoji, so here the line-height needs to be larger slightly */ -} - -/* Scoped labels with different colors on left and right */ -.ui.label.scope-parent { - background: none !important; - padding: 0 !important; - gap: 0 !important; -} - -.archived-label { - filter: grayscale(0.5); - opacity: 0.5; -} - -.ui.label.scope-left { - border-bottom-right-radius: 0; - border-top-right-radius: 0; - margin-right: 0; -} - -.ui.label.scope-right { - border-bottom-left-radius: 0; - border-top-left-radius: 0; - margin-left: 0; -} - .repo-button-row { margin: 8px 0; display: flex; @@ -1665,21 +1528,17 @@ td .commit-summary { display: flex; align-items: center; gap: 0.5rem; + flex-wrap: wrap; } .repo-button-row-left { - flex: 1; + flex-grow: 1; } -.repo-button-row .button { - padding: 6px 10px !important; - height: 30px; +.repo-button-row .ui.button { flex-shrink: 0; margin: 0; -} - -.repo-button-row .button.dropdown:not(.icon) { - padding-right: 22px !important; /* normal buttons have !important paddings, so we need to override it for dropdown (Add File) icons */ + min-height: 30px; } tbody.commit-list { @@ -1701,6 +1560,10 @@ tbody.commit-list { white-space: nowrap; } +.latest-commit .message-wrapper { + max-width: calc(100% - 2.5rem); +} + /* in the commit list, messages can wrap so we can use inline */ .commit-list .message-wrapper { display: inline; @@ -1762,8 +1625,7 @@ tbody.commit-list { line-height: 18px; margin: 1em; white-space: pre-wrap; - word-break: break-all; - overflow-wrap: break-word; + overflow-wrap: anywhere; } .content-history-detail-dialog .header .avatar { @@ -1821,12 +1683,12 @@ tbody.commit-list { .resolved-placeholder { display: flex; align-items: center; - font-size: 14px !important; - padding: 8px !important; - font-weight: var(--font-weight-normal) !important; - border: 1px solid var(--color-secondary) !important; - border-radius: var(--border-radius) !important; - margin: 4px !important; + justify-content: space-between; + margin: 4px; + padding: 8px; + border: 1px solid var(--color-secondary); + border-radius: var(--border-radius); + background: var(--color-box-header); } .resolved-placeholder + .comment-code-cloud { @@ -1900,6 +1762,7 @@ tbody.commit-list { border-radius: 0; display: flex; flex-direction: column; + gap: 0.5em; } /* fomantic's last-child selector does not work with hidden last child */ @@ -2089,10 +1952,6 @@ tbody.commit-list { box-shadow: 0 0.5rem 1rem var(--color-shadow) !important; } -.migrate-entry .description { - text-wrap: balance; -} - .commits-table .commits-table-right form { display: flex; align-items: center; @@ -2128,18 +1987,6 @@ tbody.commit-list { .repository.view.issue .comment-list .timeline .comment-header-right .role-label { display: none; } - .commit-header-row .ui.horizontal.list { - width: 100%; - overflow-x: auto; - margin-top: 2px; - } - .commit-header-row .ui.horizontal.list .item { - align-items: center; - display: flex; - } - .commit-header-row .author { - padding: 3px 0; - } .commit-header h3 { flex-basis: auto !important; margin-bottom: 0.5rem !important; @@ -2266,10 +2113,11 @@ tbody.commit-list { max-width: min(400px, 90vw); } -.branch-selector-dropdown .branch-dropdown-button { +.branch-selector-dropdown .ui.button.branch-dropdown-button { margin: 0; max-width: 340px; line-height: var(--line-height-default); + padding: 0 0.5em 0 0.75em; } /* FIXME: These media selectors are not ideal (just keep them from old code). diff --git a/web_src/css/repo/clone.css b/web_src/css/repo/clone.css index 3f6a1323fe..53eb8b7b87 100644 --- a/web_src/css/repo/clone.css +++ b/web_src/css/repo/clone.css @@ -1,14 +1,16 @@ /* only used by "repo/empty.tmpl" */ .clone-buttons-combo { display: flex; - align-items: center; + align-items: stretch; flex: 1; } -.clone-buttons-combo input { - border-left: none !important; - border-radius: 0 !important; - height: 30px; +.clone-buttons-combo > .ui.button:not(:last-child) { + border-right: none; +} + +.ui.action.input.clone-buttons-combo input { + border-radius: 0; /* override fomantic border-radius for ".ui.input > input" */ } /* used by the clone-panel popup */ @@ -20,10 +22,12 @@ .clone-panel-tab .item { padding: 5px 10px; background: none; + color: var(--color-text-light-2); } .clone-panel-tab .item.active { - border-bottom: 3px solid var(--color-secondary); + color: var(--color-text-dark); + border-bottom: 3px solid currentcolor; } .clone-panel-tab + .divider { diff --git a/web_src/css/repo/commit-sign.css b/web_src/css/repo/commit-sign.css index e757030419..56eee62ffc 100644 --- a/web_src/css/repo/commit-sign.css +++ b/web_src/css/repo/commit-sign.css @@ -1,272 +1,61 @@ - -.repository .ui.attached.isSigned.isWarning { - border-left: 1px solid var(--color-error-border); - border-right: 1px solid var(--color-error-border); -} - -.repository .ui.attached.isSigned.isWarning.top, -.repository .ui.attached.isSigned.isWarning.message { - border-top: 1px solid var(--color-error-border); -} - -.repository .ui.attached.isSigned.isWarning.message { - box-shadow: none; - background-color: var(--color-error-bg); - color: var(--color-error-text); -} - -.repository .ui.attached.isSigned.isWarning.message .ui.text { - color: var(--color-error-text); -} - -.repository .ui.attached.isSigned.isWarning:last-child, -.repository .ui.attached.isSigned.isWarning.bottom { - border-bottom: 1px solid var(--color-error-border); -} - -.repository .ui.attached.isSigned.isVerified { - border-left: 1px solid var(--color-success-border); - border-right: 1px solid var(--color-success-border); -} - -.repository .ui.attached.isSigned.isVerified.top, -.repository .ui.attached.isSigned.isVerified.message { - border-top: 1px solid var(--color-success-border); -} - -.repository .ui.attached.isSigned.isVerified.message { - box-shadow: none; - background-color: var(--color-success-bg); - color: var(--color-success-text); -} - -.repository .ui.attached.isSigned.isVerified.message .pull-right { - color: var(--color-text); -} - -.repository .ui.attached.isSigned.isVerified.message .ui.text { - color: var(--color-success-text); -} - -.repository .ui.attached.isSigned.isVerified:last-child, -.repository .ui.attached.isSigned.isVerified.bottom { - border-bottom: 1px solid var(--color-success-border); -} - -.repository .ui.attached.isSigned.isVerifiedUntrusted, -.repository .ui.attached.isSigned.isVerifiedUnmatched { - border-left: 1px solid var(--color-warning-border); - border-right: 1px solid var(--color-warning-border); -} - -.repository .ui.attached.isSigned.isVerifiedUntrusted.top, -.repository .ui.attached.isSigned.isVerifiedUnmatched.top, -.repository .ui.attached.isSigned.isVerifiedUntrusted.message, -.repository .ui.attached.isSigned.isVerifiedUnmatched.message { - border-top: 1px solid var(--color-warning-border); -} - -.repository .ui.attached.isSigned.isVerifiedUntrusted.message, -.repository .ui.attached.isSigned.isVerifiedUnmatched.message { - box-shadow: none; - background-color: var(--color-warning-bg); - color: var(--color-warning-text); -} - -.repository .ui.attached.isSigned.isVerifiedUntrusted.message .ui.text, -.repository .ui.attached.isSigned.isVerifiedUnmatched.message .ui.text { - color: var(--color-warning-text); -} - -.repository .ui.attached.isSigned.isVerifiedUntrusted:last-child, -.repository .ui.attached.isSigned.isVerifiedUnmatched:last-child, -.repository .ui.attached.isSigned.isVerifiedUntrusted.bottom, -.repository .ui.attached.isSigned.isVerifiedUnmatched.bottom { - border-bottom: 1px solid var(--color-warning-border); -} - -.repository #commits-table td.sha .sha.label, -.repository #repo-files-table .sha.label, -.repository #repo-file-commit-box .sha.label, -.repository #rev-list .sha.label, -.repository .timeline-item.commits-list .singular-commit .sha.label { +.ui.label.commit-id-short, +.ui.label.commit-sign-badge { border: 1px solid var(--color-light-border); + font-size: 13px; + font-weight: var(--font-weight-normal); + padding: 3px 5px; + flex-shrink: 0; } -.repository #commits-table td.sha .sha.label .detail.icon, -.repository #repo-files-table .sha.label .detail.icon, -.repository #repo-file-commit-box .sha.label .detail.icon, -.repository #rev-list .sha.label .detail.icon, -.repository .timeline-item.commits-list .singular-commit .sha.label .detail.icon { - background: var(--color-light); - margin: -6px -10px -4px 0; - padding: 5px 4px 5px 6px; - border-left: 1px solid var(--color-light-border); - border-top: 0; - border-right: 0; - border-bottom: 0; - border-top-left-radius: 0; - border-bottom-left-radius: 0; +.ui.label.commit-id-short { + font-family: var(--fonts-monospace); + height: 24px; } -.repository #commits-table td.sha .sha.label .detail.icon .svg, -.repository #repo-files-table .sha.label .detail.icon .svg, -.repository #repo-file-commit-box .sha.label .detail.icon .svg, -.repository #rev-list .sha.label .detail.icon .svg, -.repository .timeline-item.commits-list .singular-commit .sha.label .detail.icon .svg { - margin: 0 0.25em 0 0; -} - -.repository #commits-table td.sha .sha.label .detail.icon > div, -.repository #repo-files-table .sha.label .detail.icon > div, -.repository #repo-file-commit-box .sha.label .detail.icon > div, -.repository #rev-list .sha.label .detail.icon > div, -.repository .timeline-item.commits-list .singular-commit .sha.label .detail.icon > div { - display: flex; - align-items: center; -} - -.repository #commits-table td.sha .sha.label.isSigned.isWarning, -.repository #repo-files-table .sha.label.isSigned.isWarning, -.repository #repo-file-commit-box .sha.label.isSigned.isWarning, -.repository #rev-list .sha.label.isSigned.isWarning, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isWarning { - border: 1px solid var(--color-red-badge); - background: var(--color-red-badge-bg); -} - -.repository #commits-table td.sha .sha.label.isSigned.isWarning .detail.icon, -.repository #repo-files-table .sha.label.isSigned.isWarning .detail.icon, -.repository #repo-file-commit-box .sha.label.isSigned.isWarning .detail.icon, -.repository #rev-list .sha.label.isSigned.isWarning .detail.icon, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isWarning .detail.icon { - border-left: 1px solid var(--color-red-badge); - color: var(--color-red-badge); -} - -.repository #commits-table td.sha .sha.label.isSigned.isWarning:hover, -.repository #repo-files-table .sha.label.isSigned.isWarning:hover, -.repository #repo-file-commit-box .sha.label.isSigned.isWarning:hover, -.repository #rev-list .sha.label.isSigned.isWarning:hover, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isWarning:hover { - background: var(--color-red-badge-hover-bg) !important; +.ui.label.commit-id-short > .commit-sign-badge { + margin: 0; + padding: 0; + border: 0 !important; + border-radius: 0; + background: transparent !important; } -.repository #commits-table td.sha .sha.label.isSigned.isVerified, -.repository #repo-files-table .sha.label.isSigned.isVerified, -.repository #repo-file-commit-box .sha.label.isSigned.isVerified, -.repository #rev-list .sha.label.isSigned.isVerified, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerified { - border: 1px solid var(--color-green-badge); - background: var(--color-green-badge-bg); +.ui.label.commit-id-short > .commit-sign-badge:hover { + background: transparent !important; } -.repository #commits-table td.sha .sha.label.isSigned.isVerified .detail.icon, -.repository #repo-files-table .sha.label.isSigned.isVerified .detail.icon, -.repository #repo-file-commit-box .sha.label.isSigned.isVerified .detail.icon, -.repository #rev-list .sha.label.isSigned.isVerified .detail.icon, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerified .detail.icon { - border-left: 1px solid var(--color-green-badge); - color: var(--color-green-badge); +.commit-is-signed.sign-trusted { + border: 1px solid var(--color-green-badge) !important; + background: var(--color-green-badge-bg) !important; } -.repository #commits-table td.sha .sha.label.isSigned.isVerified:hover, -.repository #repo-files-table .sha.label.isSigned.isVerified:hover, -.repository #repo-file-commit-box .sha.label.isSigned.isVerified:hover, -.repository #rev-list .sha.label.isSigned.isVerified:hover, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerified:hover { +.commit-is-signed.sign-trusted:hover { background: var(--color-green-badge-hover-bg) !important; } -.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUntrusted, -.repository #repo-files-table .sha.label.isSigned.isVerifiedUntrusted, -.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUntrusted, -.repository #rev-list .sha.label.isSigned.isVerifiedUntrusted, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUntrusted { - border: 1px solid var(--color-yellow-badge); - background: var(--color-yellow-badge-bg); -} - -.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUntrusted .detail.icon, -.repository #repo-files-table .sha.label.isSigned.isVerifiedUntrusted .detail.icon, -.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUntrusted .detail.icon, -.repository #rev-list .sha.label.isSigned.isVerifiedUntrusted .detail.icon, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUntrusted .detail.icon { - border-left: 1px solid var(--color-yellow-badge); - color: var(--color-yellow-badge); +.commit-is-signed.sign-untrusted { + border: 1px solid var(--color-yellow-badge) !important; + background: var(--color-yellow-badge-bg) !important; } -.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUntrusted:hover, -.repository #repo-files-table .sha.label.isSigned.isVerifiedUntrusted:hover, -.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUntrusted:hover, -.repository #rev-list .sha.label.isSigned.isVerifiedUntrusted:hover, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUntrusted:hover { +.commit-is-signed.sign-untrusted:hover { background: var(--color-yellow-badge-hover-bg) !important; } -.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUnmatched, -.repository #repo-files-table .sha.label.isSigned.isVerifiedUnmatched, -.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUnmatched, -.repository #rev-list .sha.label.isSigned.isVerifiedUnmatched, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUnmatched { - border: 1px solid var(--color-orange-badge); - background: var(--color-orange-badge-bg); -} - -.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUnmatched .detail.icon, -.repository #repo-files-table .sha.label.isSigned.isVerifiedUnmatched .detail.icon, -.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUnmatched .detail.icon, -.repository #rev-list .sha.label.isSigned.isVerifiedUnmatched .detail.icon, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUnmatched .detail.icon { - border-left: 1px solid var(--color-orange-badge); - color: var(--color-orange-badge); +.commit-is-signed.sign-unmatched { + border: 1px solid var(--color-orange-badge) !important; + background: var(--color-orange-badge-bg) !important; } -.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUnmatched:hover, -.repository #repo-files-table .sha.label.isSigned.isVerifiedUnmatched:hover, -.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUnmatched:hover, -.repository #rev-list .sha.label.isSigned.isVerifiedUnmatched:hover, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUnmatched:hover { +.commit-is-signed.sign-unmatched:hover { background: var(--color-orange-badge-hover-bg) !important; } -.singular-commit .shabox .sha.label { - margin: 0; - border: 1px solid var(--color-light-border); +.commit-is-signed.sign-warning { + border: 1px solid var(--color-red-badge) !important; + background: var(--color-red-badge-bg) !important; } -.singular-commit .shabox .sha.label.isSigned.isWarning { - border: 1px solid var(--color-red-badge); - background: var(--color-red-badge-bg); -} - -.singular-commit .shabox .sha.label.isSigned.isWarning:hover { +.commit-is-signed.sign-warning:hover { background: var(--color-red-badge-hover-bg) !important; } - -.singular-commit .shabox .sha.label.isSigned.isVerified { - border: 1px solid var(--color-green-badge); - background: var(--color-green-badge-bg); -} - -.singular-commit .shabox .sha.label.isSigned.isVerified:hover { - background: var(--color-green-badge-hover-bg) !important; -} - -.singular-commit .shabox .sha.label.isSigned.isVerifiedUntrusted { - border: 1px solid var(--color-yellow-badge); - background: var(--color-yellow-badge-bg); -} - -.singular-commit .shabox .sha.label.isSigned.isVerifiedUntrusted:hover { - background: var(--color-yellow-badge-hover-bg) !important; -} - -.singular-commit .shabox .sha.label.isSigned.isVerifiedUnmatched { - border: 1px solid var(--color-orange-badge); - background: var(--color-orange-badge-bg); -} - -.singular-commit .shabox .sha.label.isSigned.isVerifiedUnmatched:hover { - background: var(--color-orange-badge-hover-bg) !important; -} diff --git a/web_src/css/repo/file-view.css b/web_src/css/repo/file-view.css new file mode 100644 index 0000000000..907f136afe --- /dev/null +++ b/web_src/css/repo/file-view.css @@ -0,0 +1,92 @@ +.file-view tr.active .lines-num, +.file-view tr.active .lines-escape, +.file-view tr.active .lines-code { + background: var(--color-highlight-bg); +} + +/* set correct border radius on the last active lines, to avoid border overflow */ +.file-view tr.active:last-of-type .lines-code { + border-bottom-right-radius: var(--border-radius); +} + +.file-view tr.active .lines-num { + position: relative; +} + +/* add a darker "handler" at the beginning of the active line */ +.file-view tr.active .lines-num::before { + content: ""; + position: absolute; + left: 0; + width: 2px; + height: 100%; + background: var(--color-highlight-fg); +} + +.file-view .file-not-rendered-prompt { + padding: 1rem; + text-align: center; + font-size: 1rem !important; /* use consistent styles for various containers (code, markup, etc) */ + line-height: var(--line-height-default) !important; /* same as above */ +} + +/* ".code-view" is always used with ".file-view", to show the code of a file */ +.file-view.code-view { + background: var(--color-code-bg); + border-radius: var(--border-radius); +} + +.file-view.code-view table { + width: 100%; +} + +.file-view.code-view .lines-num span::after { + cursor: pointer; +} + +.file-view.code-view .lines-num:hover { + color: var(--color-text-dark); +} + +.file-view.code-view .ui.button.code-line-button { + border: 1px solid var(--color-secondary); + padding: 1px 4px; + margin: 0; + min-height: 0; + position: absolute; + left: 6px; +} + +.file-view.code-view .ui.button.code-line-button:hover { + background: var(--color-secondary); +} + +.view-raw { + display: flex; + justify-content: center; +} + +.view-raw > * { + max-width: 100%; +} + +.view-raw audio, +.view-raw video, +.view-raw img { + margin: 1rem; + border-radius: 0; + object-fit: contain; +} + +.view-raw img[src$=".svg" i] { + max-height: 600px !important; + max-width: 600px !important; +} + +.file-view-render-container { + width: 100%; +} + +.file-view-render-container :last-child { + border-radius: 0 0 var(--border-radius) var(--border-radius); /* to match the "ui segment" bottom radius */ +} diff --git a/web_src/css/repo/header.css b/web_src/css/repo/header.css index b70691435f..910648ea32 100644 --- a/web_src/css/repo/header.css +++ b/web_src/css/repo/header.css @@ -27,47 +27,3 @@ .repo-header .flex-item-trailing { flex-wrap: nowrap; } - -.repo-buttons { - align-items: center; - display: flex; - flex-flow: row wrap; - word-break: keep-all; - gap: 0.25em; -} - -.repo-buttons button[disabled] ~ .label { - opacity: var(--opacity-disabled); - color: var(--color-text-dark); - background: var(--color-light-mimic-enabled) !important; -} - -.repo-buttons button[disabled] ~ .label:hover { - color: var(--color-primary-dark-1); -} - -.repo-buttons .ui.labeled.button.disabled { - pointer-events: inherit !important; -} - -.repo-buttons .ui.labeled.button.disabled > .label { - color: var(--color-text-dark); - background: var(--color-light-mimic-enabled) !important; -} - -.repo-buttons .ui.labeled.button.disabled > .label:hover { - color: var(--color-primary-dark-1); -} - -.repo-buttons .ui.labeled.button.disabled > .button { - pointer-events: none !important; -} - -@media (max-width: 767.98px) { - .repo-buttons .ui.button, - .repo-buttons .ui.label { - padding-left: 8px; - padding-right: 8px; - margin: 0; - } -} diff --git a/web_src/css/repo/home-file-list.css b/web_src/css/repo/home-file-list.css index 19ba1f2bcb..f2ab052a54 100644 --- a/web_src/css/repo/home-file-list.css +++ b/web_src/css/repo/home-file-list.css @@ -14,21 +14,6 @@ } } -#repo-files-table .repo-file-cell.name .svg { - margin-right: 2px; -} - -#repo-files-table .svg.octicon-file-directory-fill, -#repo-files-table .svg.octicon-file-submodule { - color: var(--color-primary); -} - -#repo-files-table .svg.octicon-file, -#repo-files-table .svg.octicon-file-symlink-file, -#repo-files-table .svg.octicon-file-directory-symlink { - color: var(--color-secondary-dark-7); -} - #repo-files-table .repo-file-item { display: contents; } @@ -65,15 +50,30 @@ } #repo-files-table .repo-file-last-commit { + min-width: 0; /* otherwise the flex axis is not limited and the text might overflow in Pale Moon */ background: var(--color-box-header); } #repo-files-table .repo-file-cell.name { + display: flex; + align-items: center; + gap: 0.5em; + overflow: hidden; +} + +#repo-files-table .repo-file-cell.name > a, +#repo-files-table .repo-file-cell.name > span { + flex-shrink: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +#repo-files-table .repo-file-cell.name .entry-name { + flex-shrink: 1; + min-width: 3em; +} + @media (max-width: 767.98px) { #repo-files-table .repo-file-cell.name { max-width: 35vw; diff --git a/web_src/css/repo/home.css b/web_src/css/repo/home.css index 65005e2263..ee371f1b1c 100644 --- a/web_src/css/repo/home.css +++ b/web_src/css/repo/home.css @@ -1,7 +1,8 @@ .repo-grid-filelist-sidebar { display: grid; - grid-template-columns: auto 300px; + grid-template-columns: auto 280px; grid-template-rows: auto auto 1fr; + gap: var(--page-spacing); } .repo-home-filelist { @@ -13,13 +14,11 @@ .repo-home-sidebar-top { grid-column: 2; grid-row: 1; - padding-left: 1em; } .repo-home-sidebar-bottom { grid-column: 2; grid-row: 2; - padding-left: 1em; } .repo-home-sidebar-bottom .flex-list > :first-child { @@ -50,6 +49,27 @@ } } +.repo-view-container { + display: flex; + gap: var(--page-spacing); +} + +.repo-view-container .repo-view-file-tree-container { + flex: 0 0 15%; + min-width: 0; + max-height: 100vh; + position: sticky; + top: 0; + bottom: 0; + height: 100%; + overflow-y: hidden; +} + +.repo-view-content { + flex: 1; + min-width: 0; +} + .language-stats { display: flex; gap: 2px; diff --git a/web_src/css/repo/issue-card.css b/web_src/css/repo/issue-card.css index fb832bd05a..327919b1fe 100644 --- a/web_src/css/repo/issue-card.css +++ b/web_src/css/repo/issue-card.css @@ -7,6 +7,7 @@ padding: 8px 10px; border: 1px solid var(--color-secondary); background: var(--color-card); + color: var(--color-text); /* it can't inherit from parent because the card already has its own background */ } .issue-card-icon, @@ -28,13 +29,16 @@ display: flex; width: 100%; justify-content: space-between; - gap: 0.25em; + gap: 1em; } -.issue-card-assignees { +.issue-card-bottom-part { display: flex; + flex: 1; align-items: center; gap: 0.25em; - justify-content: end; flex-wrap: wrap; + overflow: hidden; + max-width: fit-content; + max-height: fit-content; } diff --git a/web_src/css/repo/issue-label.css b/web_src/css/repo/issue-label.css index 0a25d31da9..f75c73b50f 100644 --- a/web_src/css/repo/issue-label.css +++ b/web_src/css/repo/issue-label.css @@ -4,41 +4,46 @@ margin: 0; } -.issue-label-list .item { +.issue-label-list > .item { border-bottom: 1px solid var(--color-secondary); display: flex; padding: 1em 0; margin: 0; } -.issue-label-list .item:first-child { +.issue-label-list > .item:first-child { padding-top: 0; } -.issue-label-list .item:last-child { +.issue-label-list > .item:last-child { border-bottom: none; padding-bottom: 0; } -.issue-label-list .item .label-title { +.issue-label-list > .item .label-title { width: 33%; + padding-right: 1em; } -.issue-label-list .item .label-issues { +.issue-label-list > .item .label-issues { width: 33%; + padding-right: 1em; } -.issue-label-list .item .label-operation { +.issue-label-list > .item .label-operation { width: 33%; + display: flex; + flex-wrap: wrap; + gap: 0.5em; + justify-content: end; + align-items: center; } -.issue-label-list .item a { +.issue-label-list > .item .label-operation a { font-size: 12px; - padding-right: 10px; - color: var(--color-text-light); } -.issue-label-list .item.org-label { +.issue-label-list > .item.org-label { opacity: 0.7; } diff --git a/web_src/css/repo/linebutton.css b/web_src/css/repo/linebutton.css deleted file mode 100644 index e99d0399d1..0000000000 --- a/web_src/css/repo/linebutton.css +++ /dev/null @@ -1,18 +0,0 @@ -.code-view .lines-num:hover { - color: var(--color-text-dark) !important; -} - -.code-line-button { - border: 1px solid var(--color-secondary); - border-radius: var(--border-radius); - padding: 1px 4px !important; - position: absolute; - font-family: var(--fonts-regular); - left: 0; - transform: translateX(calc(-50% + 6px)); - cursor: pointer; -} - -.code-line-button:hover { - background: var(--color-secondary) !important; -} diff --git a/web_src/css/repo/list-header.css b/web_src/css/repo/list-header.css index e666e046d3..9d0b13933a 100644 --- a/web_src/css/repo/list-header.css +++ b/web_src/css/repo/list-header.css @@ -1,6 +1,6 @@ .list-header { display: flex; - align-items: center; + align-items: stretch; flex-wrap: wrap; gap: .5rem; } @@ -8,9 +8,8 @@ .list-header-search { display: flex; flex: 1; - align-items: center; + align-items: stretch; flex-wrap: wrap; - justify-content: center; min-width: 200px; /* to enable flexbox wrapping on mobile */ } diff --git a/web_src/css/repo/packages.css b/web_src/css/repo/packages.css new file mode 100644 index 0000000000..75675f5243 --- /dev/null +++ b/web_src/css/repo/packages.css @@ -0,0 +1,25 @@ +.packages-content { + display: flex; + align-items: flex-start; + gap: 16px; +} + +.packages-content-left { + margin: 0 !important; + width: calc(100% - 250px - 16px); +} + +.packages-content-right { + margin: 0 !important; + width: 250px; +} + +@media (max-width: 767.98px) { + .packages-content { + flex-direction: column; + } + .packages-content-left, + .packages-content-right { + width: 100%; + } +} diff --git a/web_src/css/repo/release-tag.css b/web_src/css/repo/release-tag.css index 32027dd886..4b42c992ef 100644 --- a/web_src/css/repo/release-tag.css +++ b/web_src/css/repo/release-tag.css @@ -31,6 +31,7 @@ #release-list .release-entry .detail { flex: 1; margin: 0; + min-width: 0; } @media (max-width: 767.98px) { @@ -45,7 +46,7 @@ display: flex; align-items: center; } - #release-list .js-branch-tag-selector { + #release-list .release-branch-tag-selector { margin-left: auto; } #release-list .branch-selector-dropdown .menu { /* open menu to left */ @@ -58,17 +59,24 @@ margin-bottom: 2px; /* the legacy trick to align the avatar vertically, no better solution at the moment */ } -#release-list .release-entry .detail .download .list { - padding-left: 0; +#release-list .release-entry .attachment-list { border: 1px solid var(--color-secondary); border-radius: var(--border-radius); } -#release-list .release-entry .detail .download .list li { +#release-list .release-entry .attachment-list > .item { display: flex; - justify-content: space-between; padding: 8px; - border-bottom: 1px solid var(--color-secondary); + flex-wrap: wrap; +} + +#release-list .release-entry .attachment-list > .item a { + min-width: 300px; +} + +#release-list .release-entry .attachment-list .attachment-right-info { + flex-shrink: 0; + min-width: 300px; } #release-list .release-entry .detail .download[open] summary { @@ -76,7 +84,6 @@ } #release-list .download-icon { - margin-right: .25rem; color: var(--color-text-light-1); } @@ -84,10 +91,6 @@ border-bottom: none; } -#tags-table .tag-list-row { - padding: 8px 12px; -} - #tags-table .tag-list-row-title { font-size: 18px; font-weight: var(--font-weight-normal); diff --git a/web_src/css/repo/wiki.css b/web_src/css/repo/wiki.css index ca59dadb9c..144cb1206c 100644 --- a/web_src/css/repo/wiki.css +++ b/web_src/css/repo/wiki.css @@ -39,10 +39,6 @@ min-width: 150px; } -.repository.wiki .wiki-content-sidebar .ui.message.unicode-escape-prompt p { - display: none; -} - .repository.wiki .wiki-content-footer { margin-top: 1em; } diff --git a/web_src/css/review.css b/web_src/css/review.css index 036ad017f8..23383c051c 100644 --- a/web_src/css/review.css +++ b/web_src/css/review.css @@ -1,15 +1,8 @@ -.show-outdated, -.hide-outdated { - -webkit-touch-callout: none; - -webkit-user-select: none; - user-select: none; - margin-right: 0 !important; -} - .ui.button.add-code-comment { padding: 2px; position: absolute; margin-left: -22px; + min-height: 0; z-index: 5; opacity: 0; transition: transform 0.1s ease-in-out; @@ -58,11 +51,6 @@ margin-bottom: 0.5em; } -.show-outdated:hover, -.hide-outdated:hover { - text-decoration: underline; -} - .comment-code-cloud { padding: 0.5rem 1rem !important; position: relative; diff --git a/web_src/css/shared/flex-list.css b/web_src/css/shared/flex-list.css index 0f54779252..24abe8fd9d 100644 --- a/web_src/css/shared/flex-list.css +++ b/web_src/css/shared/flex-list.css @@ -33,14 +33,6 @@ color: var(--color-primary) !important; } -.flex-item .flex-item-icon { - align-self: baseline; /* mainly used by the issue list, to align the leading icon with the title */ -} - -.flex-item .flex-item-icon + .flex-item-main { - align-self: baseline; -} - .flex-item .flex-item-trailing { display: flex; gap: 0.5rem; @@ -54,7 +46,9 @@ display: inline-flex; flex-wrap: wrap; align-items: center; - gap: .25rem; + /* labels are under effect of this gap here because they are display:contents. Ideally we should make wrapping + of labels work without display: contents and set this to a static value again. */ + gap: var(--gap-inline); max-width: 100%; color: var(--color-text); font-size: 16px; diff --git a/web_src/css/shared/milestone.css b/web_src/css/shared/milestone.css index 91e6b5e387..47e822f8d3 100644 --- a/web_src/css/shared/milestone.css +++ b/web_src/css/shared/milestone.css @@ -12,7 +12,7 @@ border-top: 1px solid var(--color-secondary); } -.milestone-card .content { +.milestone-card .render-content { padding-top: 10px; } diff --git a/web_src/css/themes/theme-gitea-auto-protanopia-deuteranopia.css b/web_src/css/themes/theme-gitea-auto-protanopia-deuteranopia.css index bcbf67d13d..418d7daeab 100644 --- a/web_src/css/themes/theme-gitea-auto-protanopia-deuteranopia.css +++ b/web_src/css/themes/theme-gitea-auto-protanopia-deuteranopia.css @@ -1,2 +1,6 @@ @import "./theme-gitea-light-protanopia-deuteranopia.css" (prefers-color-scheme: light); @import "./theme-gitea-dark-protanopia-deuteranopia.css" (prefers-color-scheme: dark); + +gitea-theme-meta-info { + --theme-display-name: "Auto (Red/Green Colorblind-friendly)"; +} diff --git a/web_src/css/themes/theme-gitea-auto.css b/web_src/css/themes/theme-gitea-auto.css index 509889e802..cca49be99e 100644 --- a/web_src/css/themes/theme-gitea-auto.css +++ b/web_src/css/themes/theme-gitea-auto.css @@ -1,2 +1,6 @@ @import "./theme-gitea-light.css" (prefers-color-scheme: light); @import "./theme-gitea-dark.css" (prefers-color-scheme: dark); + +gitea-theme-meta-info { + --theme-display-name: "Auto"; +} diff --git a/web_src/css/themes/theme-gitea-dark-protanopia-deuteranopia.css b/web_src/css/themes/theme-gitea-dark-protanopia-deuteranopia.css index c1a6edaf35..928cb8ba19 100644 --- a/web_src/css/themes/theme-gitea-dark-protanopia-deuteranopia.css +++ b/web_src/css/themes/theme-gitea-dark-protanopia-deuteranopia.css @@ -1,5 +1,9 @@ @import "./theme-gitea-dark.css"; +gitea-theme-meta-info { + --theme-display-name: "Dark (Red/Green Colorblind-friendly)"; +} + /* red/green colorblind-friendly colors */ /* from GitHub: --diffBlob-addition-*, --diffBlob-deletion-*, etc */ :root { diff --git a/web_src/css/themes/theme-gitea-dark.css b/web_src/css/themes/theme-gitea-dark.css index 9bc7747697..48fbd14dfb 100644 --- a/web_src/css/themes/theme-gitea-dark.css +++ b/web_src/css/themes/theme-gitea-dark.css @@ -1,6 +1,10 @@ @import "../chroma/dark.css"; @import "../codemirror/dark.css"; +gitea-theme-meta-info { + --theme-display-name: "Dark"; +} + :root { --is-dark-theme: true; --color-primary: #4183c4; @@ -181,6 +185,7 @@ --color-orange-badge-bg: #f2711c1a; --color-orange-badge-hover-bg: #f2711c4d; --color-git: #f05133; + --color-logo: #609926; /* target-based colors */ --color-body: #1b1f23; --color-box-header: #1a1d1f; diff --git a/web_src/css/themes/theme-gitea-light-protanopia-deuteranopia.css b/web_src/css/themes/theme-gitea-light-protanopia-deuteranopia.css index f42fa1db2c..32d920582c 100644 --- a/web_src/css/themes/theme-gitea-light-protanopia-deuteranopia.css +++ b/web_src/css/themes/theme-gitea-light-protanopia-deuteranopia.css @@ -1,5 +1,9 @@ @import "./theme-gitea-light.css"; +gitea-theme-meta-info { + --theme-display-name: "Light (Red/Green Colorblind-friendly)"; +} + /* red/green colorblind-friendly colors */ /* from GitHub: --diffBlob-addition-*, --diffBlob-deletion-*, etc */ :root { diff --git a/web_src/css/themes/theme-gitea-light.css b/web_src/css/themes/theme-gitea-light.css index d7f9debf90..eaff717417 100644 --- a/web_src/css/themes/theme-gitea-light.css +++ b/web_src/css/themes/theme-gitea-light.css @@ -1,6 +1,10 @@ @import "../chroma/light.css"; @import "../codemirror/light.css"; +gitea-theme-meta-info { + --theme-display-name: "Light"; +} + :root { --is-dark-theme: false; --color-primary: #4183c4; @@ -181,6 +185,7 @@ --color-orange-badge-bg: #f2711c1a; --color-orange-badge-hover-bg: #f2711c4d; --color-git: #f05133; + --color-logo: #609926; /* target-based colors */ --color-body: #ffffff; --color-box-header: #f1f3f5; diff --git a/web_src/fomantic/.npmrc b/web_src/fomantic/.npmrc deleted file mode 100644 index fbacc988dc..0000000000 --- a/web_src/fomantic/.npmrc +++ /dev/null @@ -1,7 +0,0 @@ -audit=false -fund=false -update-notifier=false -package-lock=true -save-exact=true -lockfile-version=3 -optional=false diff --git a/web_src/fomantic/build/components/api.js b/web_src/fomantic/build/components/api.js new file mode 100644 index 0000000000..a104cfbc1b --- /dev/null +++ b/web_src/fomantic/build/components/api.js @@ -0,0 +1,1169 @@ +/*! + * # Fomantic-UI - API + * http://github.com/fomantic/Fomantic-UI/ + * + * + * Released under the MIT license + * http://opensource.org/licenses/MIT + * + */ + +;(function ($, window, document, undefined) { + +'use strict'; + +$.isWindow = $.isWindow || function(obj) { + return obj != null && obj === obj.window; +}; + + window = (typeof window != 'undefined' && window.Math == Math) + ? window + : (typeof self != 'undefined' && self.Math == Math) + ? self + : Function('return this')() +; + +$.api = $.fn.api = function(parameters) { + + var + // use window context if none specified + $allModules = $.isFunction(this) + ? $(window) + : $(this), + moduleSelector = $allModules.selector || '', + time = new Date().getTime(), + performance = [], + + query = arguments[0], + methodInvoked = (typeof query == 'string'), + queryArguments = [].slice.call(arguments, 1), + + returnedValue + ; + + $allModules + .each(function() { + var + settings = ( $.isPlainObject(parameters) ) + ? $.extend(true, {}, $.fn.api.settings, parameters) + : $.extend({}, $.fn.api.settings), + + // internal aliases + namespace = settings.namespace, + metadata = settings.metadata, + selector = settings.selector, + error = settings.error, + className = settings.className, + + // define namespaces for modules + eventNamespace = '.' + namespace, + moduleNamespace = 'module-' + namespace, + + // element that creates request + $module = $(this), + $form = $module.closest(selector.form), + + // context used for state + $context = (settings.stateContext) + ? $(settings.stateContext) + : $module, + + // request details + ajaxSettings, + requestSettings, + url, + data, + requestStartTime, + + // standard module + element = this, + context = $context[0], + instance = $module.data(moduleNamespace), + module + ; + + module = { + + initialize: function() { + if(!methodInvoked) { + module.bind.events(); + } + module.instantiate(); + }, + + instantiate: function() { + module.verbose('Storing instance of module', module); + instance = module; + $module + .data(moduleNamespace, instance) + ; + }, + + destroy: function() { + module.verbose('Destroying previous module for', element); + $module + .removeData(moduleNamespace) + .off(eventNamespace) + ; + }, + + bind: { + events: function() { + var + triggerEvent = module.get.event() + ; + if( triggerEvent ) { + module.verbose('Attaching API events to element', triggerEvent); + $module + .on(triggerEvent + eventNamespace, module.event.trigger) + ; + } + else if(settings.on == 'now') { + module.debug('Querying API endpoint immediately'); + module.query(); + } + } + }, + + decode: { + json: function(response) { + if(response !== undefined && typeof response == 'string') { + try { + response = JSON.parse(response); + } + catch(e) { + // isnt json string + } + } + return response; + } + }, + + read: { + cachedResponse: function(url) { + var + response + ; + if(window.Storage === undefined) { + module.error(error.noStorage); + return; + } + response = sessionStorage.getItem(url); + module.debug('Using cached response', url, response); + response = module.decode.json(response); + return response; + } + }, + write: { + cachedResponse: function(url, response) { + if(response && response === '') { + module.debug('Response empty, not caching', response); + return; + } + if(window.Storage === undefined) { + module.error(error.noStorage); + return; + } + if( $.isPlainObject(response) ) { + response = JSON.stringify(response); + } + sessionStorage.setItem(url, response); + module.verbose('Storing cached response for url', url, response); + } + }, + + query: function() { + + if(module.is.disabled()) { + module.debug('Element is disabled API request aborted'); + return; + } + + if(module.is.loading()) { + if(settings.interruptRequests) { + module.debug('Interrupting previous request'); + module.abort(); + } + else { + module.debug('Cancelling request, previous request is still pending'); + return; + } + } + + // pass element metadata to url (value, text) + if(settings.defaultData) { + $.extend(true, settings.urlData, module.get.defaultData()); + } + + // Add form content + if(settings.serializeForm) { + settings.data = module.add.formData(settings.data); + } + + // call beforesend and get any settings changes + requestSettings = module.get.settings(); + + // check if before send cancelled request + if(requestSettings === false) { + module.cancelled = true; + module.error(error.beforeSend); + return; + } + else { + module.cancelled = false; + } + + // get url + url = module.get.templatedURL(); + + if(!url && !module.is.mocked()) { + module.error(error.missingURL); + return; + } + + // replace variables + url = module.add.urlData( url ); + // missing url parameters + if( !url && !module.is.mocked()) { + return; + } + + requestSettings.url = settings.base + url; + + // look for jQuery ajax parameters in settings + ajaxSettings = $.extend(true, {}, settings, { + type : settings.method || settings.type, + data : data, + url : settings.base + url, + beforeSend : settings.beforeXHR, + success : function() {}, + failure : function() {}, + complete : function() {} + }); + + module.debug('Querying URL', ajaxSettings.url); + module.verbose('Using AJAX settings', ajaxSettings); + if(settings.cache === 'local' && module.read.cachedResponse(url)) { + module.debug('Response returned from local cache'); + module.request = module.create.request(); + module.request.resolveWith(context, [ module.read.cachedResponse(url) ]); + return; + } + + if( !settings.throttle ) { + module.debug('Sending request', data, ajaxSettings.method); + module.send.request(); + } + else { + if(!settings.throttleFirstRequest && !module.timer) { + module.debug('Sending request', data, ajaxSettings.method); + module.send.request(); + module.timer = setTimeout(function(){}, settings.throttle); + } + else { + module.debug('Throttling request', settings.throttle); + clearTimeout(module.timer); + module.timer = setTimeout(function() { + if(module.timer) { + delete module.timer; + } + module.debug('Sending throttled request', data, ajaxSettings.method); + module.send.request(); + }, settings.throttle); + } + } + + }, + + should: { + removeError: function() { + return ( settings.hideError === true || (settings.hideError === 'auto' && !module.is.form()) ); + } + }, + + is: { + disabled: function() { + return ($module.filter(selector.disabled).length > 0); + }, + expectingJSON: function() { + return settings.dataType === 'json' || settings.dataType === 'jsonp'; + }, + form: function() { + return $module.is('form') || $context.is('form'); + }, + mocked: function() { + return (settings.mockResponse || settings.mockResponseAsync || settings.response || settings.responseAsync); + }, + input: function() { + return $module.is('input'); + }, + loading: function() { + return (module.request) + ? (module.request.state() == 'pending') + : false + ; + }, + abortedRequest: function(xhr) { + if(xhr && xhr.readyState !== undefined && xhr.readyState === 0) { + module.verbose('XHR request determined to be aborted'); + return true; + } + else { + module.verbose('XHR request was not aborted'); + return false; + } + }, + validResponse: function(response) { + if( (!module.is.expectingJSON()) || !$.isFunction(settings.successTest) ) { + module.verbose('Response is not JSON, skipping validation', settings.successTest, response); + return true; + } + module.debug('Checking JSON returned success', settings.successTest, response); + if( settings.successTest(response) ) { + module.debug('Response passed success test', response); + return true; + } + else { + module.debug('Response failed success test', response); + return false; + } + } + }, + + was: { + cancelled: function() { + return (module.cancelled || false); + }, + succesful: function() { + module.verbose('This behavior will be deleted due to typo. Use "was successful" instead.'); + return module.was.successful(); + }, + successful: function() { + return (module.request && module.request.state() == 'resolved'); + }, + failure: function() { + return (module.request && module.request.state() == 'rejected'); + }, + complete: function() { + return (module.request && (module.request.state() == 'resolved' || module.request.state() == 'rejected') ); + } + }, + + add: { + urlData: function(url, urlData) { + var + requiredVariables, + optionalVariables + ; + if(url) { + requiredVariables = url.match(settings.regExp.required); + optionalVariables = url.match(settings.regExp.optional); + urlData = urlData || settings.urlData; + if(requiredVariables) { + module.debug('Looking for required URL variables', requiredVariables); + $.each(requiredVariables, function(index, templatedString) { + var + // allow legacy {$var} style + variable = (templatedString.indexOf('$') !== -1) + ? templatedString.substr(2, templatedString.length - 3) + : templatedString.substr(1, templatedString.length - 2), + value = ($.isPlainObject(urlData) && urlData[variable] !== undefined) + ? urlData[variable] + : ($module.data(variable) !== undefined) + ? $module.data(variable) + : ($context.data(variable) !== undefined) + ? $context.data(variable) + : urlData[variable] + ; + // remove value + if(value === undefined) { + module.error(error.requiredParameter, variable, url); + url = false; + return false; + } + else { + module.verbose('Found required variable', variable, value); + value = (settings.encodeParameters) + ? module.get.urlEncodedValue(value) + : value + ; + url = url.replace(templatedString, value); + } + }); + } + if(optionalVariables) { + module.debug('Looking for optional URL variables', requiredVariables); + $.each(optionalVariables, function(index, templatedString) { + var + // allow legacy {/$var} style + variable = (templatedString.indexOf('$') !== -1) + ? templatedString.substr(3, templatedString.length - 4) + : templatedString.substr(2, templatedString.length - 3), + value = ($.isPlainObject(urlData) && urlData[variable] !== undefined) + ? urlData[variable] + : ($module.data(variable) !== undefined) + ? $module.data(variable) + : ($context.data(variable) !== undefined) + ? $context.data(variable) + : urlData[variable] + ; + // optional replacement + if(value !== undefined) { + module.verbose('Optional variable Found', variable, value); + url = url.replace(templatedString, value); + } + else { + module.verbose('Optional variable not found', variable); + // remove preceding slash if set + if(url.indexOf('/' + templatedString) !== -1) { + url = url.replace('/' + templatedString, ''); + } + else { + url = url.replace(templatedString, ''); + } + } + }); + } + } + return url; + }, + formData: function(data) { + var + canSerialize = ($.fn.serializeObject !== undefined), + formData = (canSerialize) + ? $form.serializeObject() + : $form.serialize(), + hasOtherData + ; + data = data || settings.data; + hasOtherData = $.isPlainObject(data); + + if(hasOtherData) { + if(canSerialize) { + module.debug('Extending existing data with form data', data, formData); + data = $.extend(true, {}, data, formData); + } + else { + module.error(error.missingSerialize); + module.debug('Cant extend data. Replacing data with form data', data, formData); + data = formData; + } + } + else { + module.debug('Adding form data', formData); + data = formData; + } + return data; + } + }, + + send: { + request: function() { + module.set.loading(); + module.request = module.create.request(); + if( module.is.mocked() ) { + module.mockedXHR = module.create.mockedXHR(); + } + else { + module.xhr = module.create.xhr(); + } + settings.onRequest.call(context, module.request, module.xhr); + } + }, + + event: { + trigger: function(event) { + module.query(); + if(event.type == 'submit' || event.type == 'click') { + event.preventDefault(); + } + }, + xhr: { + always: function() { + // nothing special + }, + done: function(response, textStatus, xhr) { + var + context = this, + elapsedTime = (new Date().getTime() - requestStartTime), + timeLeft = (settings.loadingDuration - elapsedTime), + translatedResponse = ( $.isFunction(settings.onResponse) ) + ? module.is.expectingJSON() && !settings.rawResponse + ? settings.onResponse.call(context, $.extend(true, {}, response)) + : settings.onResponse.call(context, response) + : false + ; + timeLeft = (timeLeft > 0) + ? timeLeft + : 0 + ; + if(translatedResponse) { + module.debug('Modified API response in onResponse callback', settings.onResponse, translatedResponse, response); + response = translatedResponse; + } + if(timeLeft > 0) { + module.debug('Response completed early delaying state change by', timeLeft); + } + setTimeout(function() { + if( module.is.validResponse(response) ) { + module.request.resolveWith(context, [response, xhr]); + } + else { + module.request.rejectWith(context, [xhr, 'invalid']); + } + }, timeLeft); + }, + fail: function(xhr, status, httpMessage) { + var + context = this, + elapsedTime = (new Date().getTime() - requestStartTime), + timeLeft = (settings.loadingDuration - elapsedTime) + ; + timeLeft = (timeLeft > 0) + ? timeLeft + : 0 + ; + if(timeLeft > 0) { + module.debug('Response completed early delaying state change by', timeLeft); + } + setTimeout(function() { + if( module.is.abortedRequest(xhr) ) { + module.request.rejectWith(context, [xhr, 'aborted', httpMessage]); + } + else { + module.request.rejectWith(context, [xhr, 'error', status, httpMessage]); + } + }, timeLeft); + } + }, + request: { + done: function(response, xhr) { + module.debug('Successful API Response', response); + if(settings.cache === 'local' && url) { + module.write.cachedResponse(url, response); + module.debug('Saving server response locally', module.cache); + } + settings.onSuccess.call(context, response, $module, xhr); + }, + complete: function(firstParameter, secondParameter) { + var + xhr, + response + ; + // have to guess callback parameters based on request success + if( module.was.successful() ) { + response = firstParameter; + xhr = secondParameter; + } + else { + xhr = firstParameter; + response = module.get.responseFromXHR(xhr); + } + module.remove.loading(); + settings.onComplete.call(context, response, $module, xhr); + }, + fail: function(xhr, status, httpMessage) { + var + // pull response from xhr if available + response = module.get.responseFromXHR(xhr), + errorMessage = module.get.errorFromRequest(response, status, httpMessage) + ; + if(status == 'aborted') { + module.debug('XHR Aborted (Most likely caused by page navigation or CORS Policy)', status, httpMessage); + settings.onAbort.call(context, status, $module, xhr); + return true; + } + else if(status == 'invalid') { + module.debug('JSON did not pass success test. A server-side error has most likely occurred', response); + } + else if(status == 'error') { + if(xhr !== undefined) { + module.debug('XHR produced a server error', status, httpMessage); + // make sure we have an error to display to console + if( (xhr.status < 200 || xhr.status >= 300) && httpMessage !== undefined && httpMessage !== '') { + module.error(error.statusMessage + httpMessage, ajaxSettings.url); + } + settings.onError.call(context, errorMessage, $module, xhr); + } + } + + if(settings.errorDuration && status !== 'aborted') { + module.debug('Adding error state'); + module.set.error(); + if( module.should.removeError() ) { + setTimeout(module.remove.error, settings.errorDuration); + } + } + module.debug('API Request failed', errorMessage, xhr); + settings.onFailure.call(context, response, $module, xhr); + } + } + }, + + create: { + + request: function() { + // api request promise + return $.Deferred() + .always(module.event.request.complete) + .done(module.event.request.done) + .fail(module.event.request.fail) + ; + }, + + mockedXHR: function () { + var + // xhr does not simulate these properties of xhr but must return them + textStatus = false, + status = false, + httpMessage = false, + responder = settings.mockResponse || settings.response, + asyncResponder = settings.mockResponseAsync || settings.responseAsync, + asyncCallback, + response, + mockedXHR + ; + + mockedXHR = $.Deferred() + .always(module.event.xhr.complete) + .done(module.event.xhr.done) + .fail(module.event.xhr.fail) + ; + + if(responder) { + if( $.isFunction(responder) ) { + module.debug('Using specified synchronous callback', responder); + response = responder.call(context, requestSettings); + } + else { + module.debug('Using settings specified response', responder); + response = responder; + } + // simulating response + mockedXHR.resolveWith(context, [ response, textStatus, { responseText: response }]); + } + else if( $.isFunction(asyncResponder) ) { + asyncCallback = function(response) { + module.debug('Async callback returned response', response); + + if(response) { + mockedXHR.resolveWith(context, [ response, textStatus, { responseText: response }]); + } + else { + mockedXHR.rejectWith(context, [{ responseText: response }, status, httpMessage]); + } + }; + module.debug('Using specified async response callback', asyncResponder); + asyncResponder.call(context, requestSettings, asyncCallback); + } + return mockedXHR; + }, + + xhr: function() { + var + xhr + ; + // ajax request promise + xhr = $.ajax(ajaxSettings) + .always(module.event.xhr.always) + .done(module.event.xhr.done) + .fail(module.event.xhr.fail) + ; + module.verbose('Created server request', xhr, ajaxSettings); + return xhr; + } + }, + + set: { + error: function() { + module.verbose('Adding error state to element', $context); + $context.addClass(className.error); + }, + loading: function() { + module.verbose('Adding loading state to element', $context); + $context.addClass(className.loading); + requestStartTime = new Date().getTime(); + } + }, + + remove: { + error: function() { + module.verbose('Removing error state from element', $context); + $context.removeClass(className.error); + }, + loading: function() { + module.verbose('Removing loading state from element', $context); + $context.removeClass(className.loading); + } + }, + + get: { + responseFromXHR: function(xhr) { + return $.isPlainObject(xhr) + ? (module.is.expectingJSON()) + ? module.decode.json(xhr.responseText) + : xhr.responseText + : false + ; + }, + errorFromRequest: function(response, status, httpMessage) { + return ($.isPlainObject(response) && response.error !== undefined) + ? response.error // use json error message + : (settings.error[status] !== undefined) // use server error message + ? settings.error[status] + : httpMessage + ; + }, + request: function() { + return module.request || false; + }, + xhr: function() { + return module.xhr || false; + }, + settings: function() { + var + runSettings + ; + runSettings = settings.beforeSend.call($module, settings); + if(runSettings) { + if(runSettings.success !== undefined) { + module.debug('Legacy success callback detected', runSettings); + module.error(error.legacyParameters, runSettings.success); + runSettings.onSuccess = runSettings.success; + } + if(runSettings.failure !== undefined) { + module.debug('Legacy failure callback detected', runSettings); + module.error(error.legacyParameters, runSettings.failure); + runSettings.onFailure = runSettings.failure; + } + if(runSettings.complete !== undefined) { + module.debug('Legacy complete callback detected', runSettings); + module.error(error.legacyParameters, runSettings.complete); + runSettings.onComplete = runSettings.complete; + } + } + if(runSettings === undefined) { + module.error(error.noReturnedValue); + } + if(runSettings === false) { + return runSettings; + } + return (runSettings !== undefined) + ? $.extend(true, {}, runSettings) + : $.extend(true, {}, settings) + ; + }, + urlEncodedValue: function(value) { + // GITEA-PATCH: always encode the value. + // Old code does "decodeURIComponent" first to guess whether the value is encoded, it is not right. + return window.encodeURIComponent(value); + }, + defaultData: function() { + var + data = {} + ; + if( !$.isWindow(element) ) { + if( module.is.input() ) { + data.value = $module.val(); + } + else if( module.is.form() ) { + + } + else { + data.text = $module.text(); + } + } + return data; + }, + event: function() { + if( $.isWindow(element) || settings.on == 'now' ) { + module.debug('API called without element, no events attached'); + return false; + } + else if(settings.on == 'auto') { + if( $module.is('input') ) { + return (element.oninput !== undefined) + ? 'input' + : (element.onpropertychange !== undefined) + ? 'propertychange' + : 'keyup' + ; + } + else if( $module.is('form') ) { + return 'submit'; + } + else { + return 'click'; + } + } + else { + return settings.on; + } + }, + templatedURL: function(action) { + action = action || $module.data(metadata.action) || settings.action || false; + url = $module.data(metadata.url) || settings.url || false; + if(url) { + module.debug('Using specified url', url); + return url; + } + if(action) { + module.debug('Looking up url for action', action, settings.api); + if(settings.api[action] === undefined && !module.is.mocked()) { + module.error(error.missingAction, settings.action, settings.api); + return; + } + url = settings.api[action]; + } + else if( module.is.form() ) { + url = $module.attr('action') || $context.attr('action') || false; + module.debug('No url or action specified, defaulting to form action', url); + } + return url; + } + }, + + abort: function() { + var + xhr = module.get.xhr() + ; + if( xhr && xhr.state() !== 'resolved') { + module.debug('Cancelling API request'); + xhr.abort(); + } + }, + + // reset state + reset: function() { + module.remove.error(); + module.remove.loading(); + }, + + setting: function(name, value) { + module.debug('Changing setting', name, value); + if( $.isPlainObject(name) ) { + $.extend(true, settings, name); + } + else if(value !== undefined) { + if($.isPlainObject(settings[name])) { + $.extend(true, settings[name], value); + } + else { + settings[name] = value; + } + } + else { + return settings[name]; + } + }, + internal: function(name, value) { + if( $.isPlainObject(name) ) { + $.extend(true, module, name); + } + else if(value !== undefined) { + module[name] = value; + } + else { + return module[name]; + } + }, + debug: function() { + if(!settings.silent && settings.debug) { + if(settings.performance) { + module.performance.log(arguments); + } + else { + module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); + module.debug.apply(console, arguments); + } + } + }, + verbose: function() { + if(!settings.silent && settings.verbose && settings.debug) { + if(settings.performance) { + module.performance.log(arguments); + } + else { + module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); + module.verbose.apply(console, arguments); + } + } + }, + error: function() { + if(!settings.silent) { + module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); + module.error.apply(console, arguments); + } + }, + performance: { + log: function(message) { + var + currentTime, + executionTime, + previousTime + ; + if(settings.performance) { + currentTime = new Date().getTime(); + previousTime = time || currentTime; + executionTime = currentTime - previousTime; + time = currentTime; + performance.push({ + 'Name' : message[0], + 'Arguments' : [].slice.call(message, 1) || '', + //'Element' : element, + 'Execution Time' : executionTime + }); + } + clearTimeout(module.performance.timer); + module.performance.timer = setTimeout(module.performance.display, 500); + }, + display: function() { + var + title = settings.name + ':', + totalTime = 0 + ; + time = false; + clearTimeout(module.performance.timer); + $.each(performance, function(index, data) { + totalTime += data['Execution Time']; + }); + title += ' ' + totalTime + 'ms'; + if(moduleSelector) { + title += ' \'' + moduleSelector + '\''; + } + if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { + console.groupCollapsed(title); + if(console.table) { + console.table(performance); + } + else { + $.each(performance, function(index, data) { + console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); + }); + } + console.groupEnd(); + } + performance = []; + } + }, + invoke: function(query, passedArguments, context) { + var + object = instance, + maxDepth, + found, + response + ; + passedArguments = passedArguments || queryArguments; + context = element || context; + if(typeof query == 'string' && object !== undefined) { + query = query.split(/[\. ]/); + maxDepth = query.length - 1; + $.each(query, function(depth, value) { + var camelCaseValue = (depth != maxDepth) + ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) + : query + ; + if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { + object = object[camelCaseValue]; + } + else if( object[camelCaseValue] !== undefined ) { + found = object[camelCaseValue]; + return false; + } + else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { + object = object[value]; + } + else if( object[value] !== undefined ) { + found = object[value]; + return false; + } + else { + module.error(error.method, query); + return false; + } + }); + } + if ( $.isFunction( found ) ) { + response = found.apply(context, passedArguments); + } + else if(found !== undefined) { + response = found; + } + if(Array.isArray(returnedValue)) { + returnedValue.push(response); + } + else if(returnedValue !== undefined) { + returnedValue = [returnedValue, response]; + } + else if(response !== undefined) { + returnedValue = response; + } + return found; + } + }; + + if(methodInvoked) { + if(instance === undefined) { + module.initialize(); + } + module.invoke(query); + } + else { + if(instance !== undefined) { + instance.invoke('destroy'); + } + module.initialize(); + } + }) + ; + + return (returnedValue !== undefined) + ? returnedValue + : this + ; +}; + +$.api.settings = { + + name : 'API', + namespace : 'api', + + debug : false, + verbose : false, + performance : true, + + // object containing all templates endpoints + api : {}, + + // whether to cache responses + cache : true, + + // whether new requests should abort previous requests + interruptRequests : true, + + // event binding + on : 'auto', + + // context for applying state classes + stateContext : false, + + // duration for loading state + loadingDuration : 0, + + // whether to hide errors after a period of time + hideError : 'auto', + + // duration for error state + errorDuration : 2000, + + // whether parameters should be encoded with encodeURIComponent + encodeParameters : true, + + // API action to use + action : false, + + // templated URL to use + url : false, + + // base URL to apply to all endpoints + base : '', + + // data that will + urlData : {}, + + // whether to add default data to url data + defaultData : true, + + // whether to serialize closest form + serializeForm : false, + + // how long to wait before request should occur + throttle : 0, + + // whether to throttle first request or only repeated + throttleFirstRequest : true, + + // standard ajax settings + method : 'get', + data : {}, + dataType : 'json', + + // mock response + mockResponse : false, + mockResponseAsync : false, + + // aliases for mock + response : false, + responseAsync : false, + +// whether onResponse should work with response value without force converting into an object + rawResponse : false, + + // callbacks before request + beforeSend : function(settings) { return settings; }, + beforeXHR : function(xhr) {}, + onRequest : function(promise, xhr) {}, + + // after request + onResponse : false, // function(response) { }, + + // response was successful, if JSON passed validation + onSuccess : function(response, $module) {}, + + // request finished without aborting + onComplete : function(response, $module) {}, + + // failed JSON success test + onFailure : function(response, $module) {}, + + // server error + onError : function(errorMessage, $module) {}, + + // request aborted + onAbort : function(errorMessage, $module) {}, + + successTest : false, + + // errors + error : { + beforeSend : 'The before send function has aborted the request', + error : 'There was an error with your request', + exitConditions : 'API Request Aborted. Exit conditions met', + JSONParse : 'JSON could not be parsed during error handling', + legacyParameters : 'You are using legacy API success callback names', + method : 'The method you called is not defined', + missingAction : 'API action used but no url was defined', + missingSerialize : 'jquery-serialize-object is required to add form data to an existing data object', + missingURL : 'No URL specified for api event', + noReturnedValue : 'The beforeSend callback must return a settings object, beforeSend ignored.', + noStorage : 'Caching responses locally requires session storage', + parseError : 'There was an error parsing your request', + requiredParameter : 'Missing a required URL parameter: ', + statusMessage : 'Server gave an error: ', + timeout : 'Your request timed out' + }, + + regExp : { + required : /\{\$*[A-z0-9]+\}/g, + optional : /\{\/\$*[A-z0-9]+\}/g, + }, + + className: { + loading : 'loading', + error : 'error' + }, + + selector: { + disabled : '.disabled', + form : 'form' + }, + + metadata: { + action : 'action', + url : 'url' + } +}; + + + +})( jQuery, window, document ); diff --git a/web_src/fomantic/build/components/dropdown.css b/web_src/fomantic/build/components/dropdown.css new file mode 100644 index 0000000000..b7b35a2f05 --- /dev/null +++ b/web_src/fomantic/build/components/dropdown.css @@ -0,0 +1,1755 @@ +/*! + * # Fomantic-UI - Dropdown + * http://github.com/fomantic/Fomantic-UI/ + * + * + * Released under the MIT license + * http://opensource.org/licenses/MIT + * + */ + + +/******************************* + Dropdown +*******************************/ + +.ui.dropdown { + cursor: pointer; + position: relative; + display: inline-block; + outline: none; + text-align: left; + transition: box-shadow 0.1s ease, width 0.1s ease; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + + +/******************************* + Content +*******************************/ + + +/*-------------- + Menu +---------------*/ + +.ui.dropdown .menu { + cursor: auto; + position: absolute; + display: none; + outline: none; + top: 100%; + min-width: -moz-max-content; + min-width: max-content; + margin: 0; + padding: 0 0; + background: #FFFFFF; + font-size: 1em; + text-shadow: none; + text-align: left; + box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15); + border: 1px solid rgba(34, 36, 38, 0.15); + border-radius: 0.28571429rem; + transition: opacity 0.1s ease; + z-index: 11; + will-change: transform, opacity; +} +.ui.dropdown .menu > * { + white-space: nowrap; +} + +/*-------------- + Hidden Input +---------------*/ + +.ui.dropdown > input:not(.search):first-child, +.ui.dropdown > select { + display: none !important; +} + +/*-------------- + Dropdown Icon +---------------*/ + +.ui.dropdown:not(.labeled) > .dropdown.icon { + position: relative; + width: auto; + font-size: 0.85714286em; + margin: 0 0 0 1em; +} +.ui.dropdown .menu > .item .dropdown.icon { + width: auto; + float: right; + margin: 0em 0 0 1em; +} +.ui.dropdown .menu > .item .dropdown.icon + .text { + margin-right: 1em; +} + +/*-------------- + Text +---------------*/ + +.ui.dropdown > .text { + display: inline-block; + transition: none; +} + +/*-------------- + Menu Item +---------------*/ + +.ui.dropdown .menu > .item { + position: relative; + cursor: pointer; + display: block; + border: none; + height: auto; + min-height: 2.57142857rem; + text-align: left; + border-top: none; + line-height: 1em; + font-size: 1rem; + color: rgba(0, 0, 0, 0.87); + padding: 0.78571429rem 1.14285714rem !important; + text-transform: none; + font-weight: normal; + box-shadow: none; + -webkit-touch-callout: none; +} +.ui.dropdown .menu > .item:first-child { + border-top-width: 0; +} +.ui.dropdown .menu > .item.vertical { + display: flex; + flex-direction: column-reverse; +} + +/*-------------- + Floated Content +---------------*/ + +.ui.dropdown > .text > [class*="right floated"], +.ui.dropdown .menu .item > [class*="right floated"] { + float: right !important; + margin-right: 0 !important; + margin-left: 1em !important; +} +.ui.dropdown > .text > [class*="left floated"], +.ui.dropdown .menu .item > [class*="left floated"] { + float: left !important; + margin-left: 0 !important; + margin-right: 1em !important; +} +.ui.dropdown .menu .item > i.icon.floated, +.ui.dropdown .menu .item > .flag.floated, +.ui.dropdown .menu .item > .image.floated, +.ui.dropdown .menu .item > img.floated { + margin-top: 0em; +} + +/*-------------- + Menu Divider +---------------*/ + +.ui.dropdown .menu > .header { + margin: 1rem 0 0.75rem; + padding: 0 1.14285714rem; + font-weight: 500; + text-transform: uppercase; +} +.ui.dropdown .menu > .header:not(.ui) { + color: rgba(0, 0, 0, 0.85); + font-size: 0.78571429em; +} +.ui.dropdown .menu > .divider { + border-top: 1px solid rgba(34, 36, 38, 0.1); + height: 0; + margin: 0.5em 0; +} +.ui.dropdown .menu > .horizontal.divider { + border-top: none; +} +.ui.dropdown.dropdown .menu > .input { + width: auto; + display: flex; + margin: 1.14285714rem 0.78571429rem; + min-width: 10rem; +} +.ui.dropdown .menu > .header + .input { + margin-top: 0; +} +.ui.dropdown .menu > .input:not(.transparent) input { + padding: 0.5em 1em; +} +.ui.dropdown .menu > .input:not(.transparent) .button, +.ui.dropdown .menu > .input:not(.transparent) i.icon, +.ui.dropdown .menu > .input:not(.transparent) .label { + padding-top: 0.5em; + padding-bottom: 0.5em; +} + +/*----------------- + Item Description +-------------------*/ + +.ui.dropdown > .text > .description, +.ui.dropdown .menu > .item > .description { + float: right; + margin: 0 0 0 1em; + color: rgba(0, 0, 0, 0.4); +} +.ui.dropdown .menu > .item.vertical > .description { + margin: 0; +} + +/*----------------- + Item Text +-------------------*/ + +.ui.dropdown .menu > .item.vertical > .text { + margin-bottom: 0.25em; +} + +/*----------------- + Message +-------------------*/ + +.ui.dropdown .menu > .message { + padding: 0.78571429rem 1.14285714rem; + font-weight: normal; +} +.ui.dropdown .menu > .message:not(.ui) { + color: rgba(0, 0, 0, 0.4); +} + +/*-------------- + Sub Menu +---------------*/ + +.ui.dropdown .menu .menu { + top: 0; + left: 100%; + right: auto; + margin: 0 -0.5em !important; + border-radius: 0.28571429rem !important; + z-index: 21 !important; +} + +/* Hide Arrow */ +.ui.dropdown .menu .menu:after { + display: none; +} + +/*-------------- + Sub Elements +---------------*/ + + +/* Icons / Flags / Labels / Image */ +.ui.dropdown > .text > i.icon, +.ui.dropdown > .text > .label, +.ui.dropdown > .text > .flag, +.ui.dropdown > .text > img, +.ui.dropdown > .text > .image { + margin-top: 0em; +} +.ui.dropdown .menu > .item > i.icon, +.ui.dropdown .menu > .item > .label, +.ui.dropdown .menu > .item > .flag, +.ui.dropdown .menu > .item > .image, +.ui.dropdown .menu > .item > img { + margin-top: 0em; +} +.ui.dropdown > .text > i.icon, +.ui.dropdown > .text > .label, +.ui.dropdown > .text > .flag, +.ui.dropdown > .text > img, +.ui.dropdown > .text > .image, +.ui.dropdown .menu > .item > i.icon, +.ui.dropdown .menu > .item > .label, +.ui.dropdown .menu > .item > .flag, +.ui.dropdown .menu > .item > .image, +.ui.dropdown .menu > .item > img { + margin-left: 0; + float: none; + margin-right: 0.78571429rem; +} + +/*-------------- + Image +---------------*/ + +.ui.dropdown > .text > img, +.ui.dropdown > .text > .image:not(.icon), +.ui.dropdown .menu > .item > .image:not(.icon), +.ui.dropdown .menu > .item > img { + display: inline-block; + vertical-align: top; + width: auto; + margin-top: -0.5em; + margin-bottom: -0.5em; + max-height: 2em; +} + + +/******************************* + Coupling +*******************************/ + + +/*-------------- + Menu +---------------*/ + + +/* Remove Menu Item Divider */ +.ui.dropdown .ui.menu > .item:before, +.ui.menu .ui.dropdown .menu > .item:before { + display: none; +} + +/* Prevent Menu Item Border */ +.ui.menu .ui.dropdown .menu .active.item { + border-left: none; +} + +/* Automatically float dropdown menu right on last menu item */ +.ui.menu .right.menu .dropdown:last-child > .menu:not(.left), +.ui.menu .right.dropdown.item > .menu:not(.left), +.ui.buttons > .ui.dropdown:last-child > .menu:not(.left) { + left: auto; + right: 0; +} + +/*-------------- + Label + ---------------*/ + + +/* Dropdown Menu */ +.ui.label.dropdown .menu { + min-width: 100%; +} + +/*-------------- + Button + ---------------*/ + + +/* No Margin On Icon Button */ +.ui.dropdown.icon.button > .dropdown.icon { + margin: 0; +} +.ui.button.dropdown .menu { + min-width: 100%; +} + + +/******************************* + Types +*******************************/ + +select.ui.dropdown { + height: 38px; + padding: 0.5em; + border: 1px solid rgba(34, 36, 38, 0.15); + visibility: visible; +} + +/*-------------- + Selection + ---------------*/ + + +/* Displays like a select box */ +.ui.selection.dropdown { + cursor: pointer; + word-wrap: break-word; + line-height: 1em; + white-space: normal; + outline: 0; + transform: rotateZ(0deg); + min-width: 14em; + min-height: 2.71428571em; + background: #FFFFFF; + display: inline-block; + padding: 0.78571429em 3.2em 0.78571429em 1em; + color: rgba(0, 0, 0, 0.87); + box-shadow: none; + border: 1px solid rgba(34, 36, 38, 0.15); + border-radius: 0.28571429rem; + transition: box-shadow 0.1s ease, width 0.1s ease; +} +.ui.selection.dropdown.visible, +.ui.selection.dropdown.active { + z-index: 10; +} +.ui.selection.dropdown > .search.icon, +.ui.selection.dropdown > .delete.icon, +.ui.selection.dropdown > .dropdown.icon { + cursor: pointer; + position: absolute; + width: auto; + height: auto; + line-height: 1.21428571em; + top: 0.78571429em; + right: 1em; + z-index: 3; + margin: -0.78571429em; + padding: 0.91666667em; + opacity: 0.8; + transition: opacity 0.1s ease; +} + +/* Compact */ +.ui.compact.selection.dropdown { + min-width: 0; +} + +/* Selection Menu */ +.ui.selection.dropdown .menu { + overflow-x: hidden; + overflow-y: auto; + backface-visibility: hidden; + -webkit-overflow-scrolling: touch; + border-top-width: 0 !important; + width: auto; + outline: none; + margin: 0 -1px; + min-width: calc(100% + 2px); + width: calc(100% + 2px); + border-radius: 0 0 0.28571429rem 0.28571429rem; + box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15); + transition: opacity 0.1s ease; +} +.ui.selection.dropdown .menu:after, +.ui.selection.dropdown .menu:before { + display: none; +} + +/*-------------- + Message + ---------------*/ + +.ui.selection.dropdown .menu > .message { + padding: 0.78571429rem 1.14285714rem; +} +@media only screen and (max-width: 767.98px) { + .ui.selection.dropdown.short .menu { + max-height: 6.01071429rem; + } + .ui.selection.dropdown[class*="very short"] .menu { + max-height: 4.00714286rem; + } + .ui.selection.dropdown .menu { + max-height: 8.01428571rem; + } + .ui.selection.dropdown.long .menu { + max-height: 16.02857143rem; + } + .ui.selection.dropdown[class*="very long"] .menu { + max-height: 24.04285714rem; + } +} +@media only screen and (min-width: 768px) { + .ui.selection.dropdown.short .menu { + max-height: 8.01428571rem; + } + .ui.selection.dropdown[class*="very short"] .menu { + max-height: 5.34285714rem; + } + .ui.selection.dropdown .menu { + max-height: 10.68571429rem; + } + .ui.selection.dropdown.long .menu { + max-height: 21.37142857rem; + } + .ui.selection.dropdown[class*="very long"] .menu { + max-height: 32.05714286rem; + } +} +@media only screen and (min-width: 992px) { + .ui.selection.dropdown.short .menu { + max-height: 12.02142857rem; + } + .ui.selection.dropdown[class*="very short"] .menu { + max-height: 8.01428571rem; + } + .ui.selection.dropdown .menu { + max-height: 16.02857143rem; + } + .ui.selection.dropdown.long .menu { + max-height: 32.05714286rem; + } + .ui.selection.dropdown[class*="very long"] .menu { + max-height: 48.08571429rem; + } +} +@media only screen and (min-width: 1920px) { + .ui.selection.dropdown.short .menu { + max-height: 16.02857143rem; + } + .ui.selection.dropdown[class*="very short"] .menu { + max-height: 10.68571429rem; + } + .ui.selection.dropdown .menu { + max-height: 21.37142857rem; + } + .ui.selection.dropdown.long .menu { + max-height: 42.74285714rem; + } + .ui.selection.dropdown[class*="very long"] .menu { + max-height: 64.11428571rem; + } +} + +/* Menu Item */ +.ui.selection.dropdown .menu > .item { + border-top: 1px solid #FAFAFA; + padding: 0.78571429rem 1.14285714rem !important; + white-space: normal; + word-wrap: normal; +} + +/* User Item */ +.ui.selection.dropdown .menu > .hidden.addition.item { + display: none; +} + +/* Hover */ +.ui.selection.dropdown:hover { + border-color: rgba(34, 36, 38, 0.35); + box-shadow: none; +} + +/* Active */ +.ui.selection.active.dropdown { + border-color: #96C8DA; + box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15); +} +.ui.selection.active.dropdown .menu { + border-color: #96C8DA; + box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15); +} + +/* Focus */ +.ui.selection.dropdown:focus { + border-color: #96C8DA; + box-shadow: none; +} +.ui.selection.dropdown:focus .menu { + border-color: #96C8DA; + box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15); +} + +/* Visible */ +.ui.selection.visible.dropdown > .text:not(.default) { + font-weight: normal; + color: rgba(0, 0, 0, 0.8); +} + +/* Visible Hover */ +.ui.selection.active.dropdown:hover { + border-color: #96C8DA; + box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15); +} +.ui.selection.active.dropdown:hover .menu { + border-color: #96C8DA; + box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15); +} + +/* Dropdown Icon */ +.ui.active.selection.dropdown > .dropdown.icon, +.ui.visible.selection.dropdown > .dropdown.icon { + opacity: ''; + z-index: 3; +} + +/* Connecting Border */ +.ui.active.selection.dropdown { + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} + +/* Empty Connecting Border */ +.ui.active.empty.selection.dropdown { + border-radius: 0.28571429rem !important; + box-shadow: none !important; +} +.ui.active.empty.selection.dropdown .menu { + border: none !important; + box-shadow: none !important; +} + +/* CSS specific to iOS devices or firefox mobile only */ +@supports (-webkit-touch-callout: none) or (-webkit-overflow-scrolling: touch) or (-moz-appearance:none) { + @media (-moz-touch-enabled), (pointer: coarse) { + .ui.dropdown .scrollhint.menu:not(.hidden):before { + animation: scrollhint 2s ease 2; + content: ''; + z-index: 15; + display: block; + position: absolute; + opacity: 0; + right: 0.25em; + top: 0; + height: 100%; + border-right: 0.25em solid; + border-left: 0; + -o-border-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0)) 1 100%; + border-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0)) 1 100%; + } + .ui.inverted.dropdown .scrollhint.menu:not(.hidden):before { + -o-border-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.75), rgba(255, 255, 255, 0)) 1 100%; + border-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.75), rgba(255, 255, 255, 0)) 1 100%; + } + @keyframes scrollhint { + 0% { + opacity: 1; + top: 100%; + } + 100% { + opacity: 0; + top: 0; + } + } + } +} + +/*-------------- + Searchable + ---------------*/ + + +/* Search Selection */ +.ui.search.dropdown { + min-width: ''; +} + +/* Search Dropdown */ +.ui.search.dropdown > input.search { + background: none transparent !important; + border: none !important; + box-shadow: none !important; + cursor: text; + top: 0; + left: 1px; + width: 100%; + outline: none; + -webkit-tap-highlight-color: rgba(255, 255, 255, 0); + padding: inherit; +} + +/* Text Layering */ +.ui.search.dropdown > input.search { + position: absolute; + z-index: 2; +} +.ui.search.dropdown > .text { + cursor: text; + position: relative; + left: 1px; + z-index: auto; +} + +/* Search Selection */ +.ui.search.selection.dropdown > input.search { + line-height: 1.21428571em; + padding: 0.67857143em 3.2em 0.67857143em 1em; +} + +/* Used to size multi select input to character width */ +.ui.search.selection.dropdown > span.sizer { + line-height: 1.21428571em; + padding: 0.67857143em 3.2em 0.67857143em 1em; + display: none; + white-space: pre; +} + +/* Active/Visible Search */ +.ui.search.dropdown.active > input.search, +.ui.search.dropdown.visible > input.search { + cursor: auto; +} +.ui.search.dropdown.active > .text, +.ui.search.dropdown.visible > .text { + pointer-events: none; +} + +/* Filtered Text */ +.ui.active.search.dropdown input.search:focus + .text i.icon, +.ui.active.search.dropdown input.search:focus + .text .flag { + opacity: var(--opacity-disabled); +} +.ui.active.search.dropdown input.search:focus + .text { + color: rgba(115, 115, 115, 0.87) !important; +} +.ui.search.dropdown.button > span.sizer { + display: none; +} + +/* Search Menu */ +.ui.search.dropdown .menu { + overflow-x: hidden; + overflow-y: auto; + backface-visibility: hidden; + -webkit-overflow-scrolling: touch; +} +@media only screen and (max-width: 767.98px) { + .ui.search.dropdown .menu { + max-height: 8.01428571rem; + } +} +@media only screen and (min-width: 768px) { + .ui.search.dropdown .menu { + max-height: 10.68571429rem; + } +} +@media only screen and (min-width: 992px) { + .ui.search.dropdown .menu { + max-height: 16.02857143rem; + } +} +@media only screen and (min-width: 1920px) { + .ui.search.dropdown .menu { + max-height: 21.37142857rem; + } +} + +/* Clearable Selection */ +.ui.dropdown > .remove.icon { + cursor: pointer; + font-size: 0.85714286em; + margin: -0.78571429em; + padding: 0.91666667em; + right: 3em; + top: 0.78571429em; + position: absolute; + opacity: 0.6; + z-index: 3; +} +.ui.clearable.dropdown .text, +.ui.clearable.dropdown a:last-of-type { + margin-right: 1.5em; +} +.ui.dropdown select.noselection ~ .remove.icon, +.ui.dropdown input[value=''] ~ .remove.icon, +.ui.dropdown input:not([value]) ~ .remove.icon, +.ui.dropdown.loading > .remove.icon { + display: none; +} + +/*-------------- + Multiple + ---------------*/ + + +/* Multiple Selection */ +.ui.ui.multiple.dropdown { + padding: 0.22619048em 3.2em 0.22619048em 0.35714286em; +} +.ui.multiple.dropdown .menu { + cursor: auto; +} + +/* Selection Label */ +.ui.multiple.dropdown > .label { + display: inline-block; + white-space: normal; + font-size: 1em; + padding: 0.35714286em 0.78571429em; + margin: 0.14285714rem 0.28571429rem 0.14285714rem 0; + box-shadow: 0 0 0 1px rgba(34, 36, 38, 0.15) inset; +} + +/* Dropdown Icon */ +.ui.multiple.dropdown .dropdown.icon { + margin: ''; + padding: ''; +} + +/* Text */ +.ui.multiple.dropdown > .text { + position: static; + padding: 0; + max-width: 100%; + margin: 0.45238095em 0 0.45238095em 0.64285714em; + line-height: 1.21428571em; +} +.ui.multiple.dropdown > .text.default { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.ui.multiple.dropdown > .label ~ input.search { + margin-left: 0.14285714em !important; +} +.ui.multiple.dropdown > .label ~ .text { + display: none; +} +.ui.multiple.dropdown > .label:not(.image) > img:not(.centered) { + margin-right: 0.78571429rem; +} +.ui.multiple.dropdown > .label:not(.image) > img.ui:not(.avatar) { + margin-bottom: 0.39285714rem; +} +.ui.multiple.dropdown > .image.label img { + margin: -0.35714286em 0.78571429em -0.35714286em -0.78571429em; + height: 1.71428571em; +} + +/*----------------- + Multiple Search + -----------------*/ + + +/* Multiple Search Selection */ +.ui.multiple.search.dropdown, +.ui.multiple.search.dropdown > input.search { + cursor: text; +} + +/* Prompt Text */ +.ui.multiple.search.dropdown > .text { + display: inline-block; + position: absolute; + top: 0; + left: 0; + padding: inherit; + margin: 0.45238095em 0 0.45238095em 0.64285714em; + line-height: 1.21428571em; +} +.ui.multiple.search.dropdown > .label ~ .text { + display: none; +} + +/* Search */ +.ui.multiple.search.dropdown > input.search { + position: static; + padding: 0; + max-width: 100%; + margin: 0.45238095em 0 0.45238095em 0.64285714em; + width: 2.2em; + line-height: 1.21428571em; +} +.ui.multiple.search.dropdown.button { + min-width: 14em; +} + +/*-------------- + Inline + ---------------*/ + +.ui.inline.dropdown { + cursor: pointer; + display: inline-block; + color: inherit; +} +.ui.inline.dropdown .dropdown.icon { + margin: 0 0.21428571em 0 0.21428571em; + vertical-align: baseline; +} +.ui.inline.dropdown > .text { + font-weight: 500; +} +.ui.inline.dropdown .menu { + cursor: auto; + margin-top: 0.21428571em; + border-radius: 0.28571429rem; +} + + +/******************************* + States +*******************************/ + + +/*-------------------- + Active +----------------------*/ + + +/* Menu Item Active */ +.ui.dropdown .menu .active.item { + background: transparent; + font-weight: 500; + color: rgba(0, 0, 0, 0.95); + box-shadow: none; + z-index: 12; +} + +/*-------------------- + Hover +----------------------*/ + + +/* Menu Item Hover */ +.ui.dropdown .menu > .item:hover { + background: rgba(0, 0, 0, 0.05); + color: rgba(0, 0, 0, 0.95); + z-index: 13; +} + +/*-------------------- + Default Text +----------------------*/ + +.ui.dropdown:not(.button) > .default.text, +.ui.default.dropdown:not(.button) > .text { + color: rgba(191, 191, 191, 0.87); +} +.ui.dropdown:not(.button) > input:focus ~ .default.text, +.ui.default.dropdown:not(.button) > input:focus ~ .text { + color: rgba(115, 115, 115, 0.87); +} + +/*-------------------- + Loading + ---------------------*/ + +.ui.loading.dropdown > i.icon { + height: 1em !important; +} +.ui.loading.selection.dropdown > i.icon { + padding: 1.5em 1.28571429em !important; +} +.ui.loading.dropdown > i.icon:before { + position: absolute; + content: ''; + top: 50%; + left: 50%; + margin: -0.64285714em 0 0 -0.64285714em; + width: 1.28571429em; + height: 1.28571429em; + border-radius: 500rem; + border: 0.2em solid rgba(0, 0, 0, 0.1); +} +.ui.loading.dropdown > i.icon:after { + position: absolute; + content: ''; + top: 50%; + left: 50%; + box-shadow: 0 0 0 1px transparent; + margin: -0.64285714em 0 0 -0.64285714em; + width: 1.28571429em; + height: 1.28571429em; + animation: loader 0.6s infinite linear; + border: 0.2em solid #767676; + border-radius: 500rem; +} + +/* Coupling */ +.ui.loading.dropdown.button > i.icon:before, +.ui.loading.dropdown.button > i.icon:after { + display: none; +} +.ui.loading.dropdown > .text { + transition: none; +} + +/* Used To Check Position */ +.ui.dropdown .loading.menu { + display: block; + visibility: hidden; + z-index: -1; +} +.ui.dropdown > .loading.menu { + left: 0 !important; + right: auto !important; +} +.ui.dropdown > .menu .loading.menu { + left: 100% !important; + right: auto !important; +} + +/*-------------------- + Keyboard Select +----------------------*/ + + +/* Selected Item */ +.ui.dropdown.selected, +.ui.dropdown .menu .selected.item { + background: rgba(0, 0, 0, 0.03); + color: rgba(0, 0, 0, 0.95); +} + +/*-------------------- + Search Filtered +----------------------*/ + + +/* Filtered Item */ +.ui.dropdown > .filtered.text { + visibility: hidden; +} +.ui.dropdown .filtered.item { + display: none !important; +} + +/*-------------------- + States + ----------------------*/ + +.ui.dropdown.error, +.ui.dropdown.error > .text, +.ui.dropdown.error > .default.text { + color: #9F3A38; +} +.ui.selection.dropdown.error { + background: #FFF6F6; + border-color: #E0B4B4; +} +.ui.selection.dropdown.error:hover { + border-color: #E0B4B4; +} +.ui.multiple.selection.error.dropdown > .label { + border-color: #E0B4B4; +} +.ui.dropdown.error > .menu, +.ui.dropdown.error > .menu .menu { + border-color: #E0B4B4; +} +.ui.dropdown.error > .menu > .item { + color: #9F3A38; +} + +/* Item Hover */ +.ui.dropdown.error > .menu > .item:hover { + background-color: #FBE7E7; +} + +/* Item Active */ +.ui.dropdown.error > .menu .active.item { + background-color: #FDCFCF; +} +.ui.dropdown.info, +.ui.dropdown.info > .text, +.ui.dropdown.info > .default.text { + color: #276F86; +} +.ui.selection.dropdown.info { + background: #F8FFFF; + border-color: #A9D5DE; +} +.ui.selection.dropdown.info:hover { + border-color: #A9D5DE; +} +.ui.multiple.selection.info.dropdown > .label { + border-color: #A9D5DE; +} +.ui.dropdown.info > .menu, +.ui.dropdown.info > .menu .menu { + border-color: #A9D5DE; +} +.ui.dropdown.info > .menu > .item { + color: #276F86; +} + +/* Item Hover */ +.ui.dropdown.info > .menu > .item:hover { + background-color: #e9f2fb; +} + +/* Item Active */ +.ui.dropdown.info > .menu .active.item { + background-color: #cef1fd; +} +.ui.dropdown.success, +.ui.dropdown.success > .text, +.ui.dropdown.success > .default.text { + color: #2C662D; +} +.ui.selection.dropdown.success { + background: #FCFFF5; + border-color: #A3C293; +} +.ui.selection.dropdown.success:hover { + border-color: #A3C293; +} +.ui.multiple.selection.success.dropdown > .label { + border-color: #A3C293; +} +.ui.dropdown.success > .menu, +.ui.dropdown.success > .menu .menu { + border-color: #A3C293; +} +.ui.dropdown.success > .menu > .item { + color: #2C662D; +} + +/* Item Hover */ +.ui.dropdown.success > .menu > .item:hover { + background-color: #e9fbe9; +} + +/* Item Active */ +.ui.dropdown.success > .menu .active.item { + background-color: #dafdce; +} +.ui.dropdown.warning, +.ui.dropdown.warning > .text, +.ui.dropdown.warning > .default.text { + color: #573A08; +} +.ui.selection.dropdown.warning { + background: #FFFAF3; + border-color: #C9BA9B; +} +.ui.selection.dropdown.warning:hover { + border-color: #C9BA9B; +} +.ui.multiple.selection.warning.dropdown > .label { + border-color: #C9BA9B; +} +.ui.dropdown.warning > .menu, +.ui.dropdown.warning > .menu .menu { + border-color: #C9BA9B; +} +.ui.dropdown.warning > .menu > .item { + color: #573A08; +} + +/* Item Hover */ +.ui.dropdown.warning > .menu > .item:hover { + background-color: #fbfbe9; +} + +/* Item Active */ +.ui.dropdown.warning > .menu .active.item { + background-color: #fdfdce; +} + +/*-------------------- + Clear +----------------------*/ + +.ui.dropdown > .clear.dropdown.icon { + opacity: 0.8; + transition: opacity 0.1s ease; +} +.ui.dropdown > .clear.dropdown.icon:hover { + opacity: 1; +} + +/*-------------------- + Disabled + ----------------------*/ + + +/* Disabled */ +.ui.disabled.dropdown, +.ui.dropdown .menu > .disabled.item { + cursor: default; + pointer-events: none; + opacity: var(--opacity-disabled); +} + + +/******************************* + Variations +*******************************/ + + +/*-------------- + Direction +---------------*/ + + +/* Flyout Direction */ +.ui.dropdown .menu { + left: 0; +} + +/* Default Side (Right) */ +.ui.dropdown .right.menu > .menu, +.ui.dropdown .menu .right.menu { + left: 100% !important; + right: auto !important; + border-radius: 0.28571429rem !important; +} + +/* Leftward Opening Menu */ +.ui.dropdown > .left.menu { + left: auto !important; + right: 0 !important; +} +.ui.dropdown > .left.menu .menu, +.ui.dropdown .menu .left.menu { + left: auto; + right: 100%; + margin: 0 -0.5em 0 0 !important; + border-radius: 0.28571429rem !important; +} +.ui.dropdown .item .left.dropdown.icon, +.ui.dropdown .left.menu .item .dropdown.icon { + width: auto; + float: left; + margin: 0em 0 0 0; +} +.ui.dropdown .item .left.dropdown.icon, +.ui.dropdown .left.menu .item .dropdown.icon { + width: auto; + float: left; + margin: 0em 0 0 0; +} +.ui.dropdown .item .left.dropdown.icon + .text, +.ui.dropdown .left.menu .item .dropdown.icon + .text { + margin-left: 1em; + margin-right: 0; +} + +/*-------------- + Upward + ---------------*/ + + +/* Upward Main Menu */ +.ui.upward.dropdown > .menu { + top: auto; + bottom: 100%; + box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.08); + border-radius: 0.28571429rem 0.28571429rem 0 0; +} + +/* Upward Sub Menu */ +.ui.dropdown .upward.menu { + top: auto !important; + bottom: 0 !important; +} + +/* Active Upward */ +.ui.simple.upward.active.dropdown, +.ui.simple.upward.dropdown:hover { + border-radius: 0.28571429rem 0.28571429rem 0 0 !important; +} +.ui.upward.dropdown.button:not(.pointing):not(.floating).active { + border-radius: 0.28571429rem 0.28571429rem 0 0; +} + +/* Selection */ +.ui.upward.selection.dropdown .menu { + border-top-width: 1px !important; + border-bottom-width: 0 !important; + box-shadow: 0 -2px 3px 0 rgba(0, 0, 0, 0.08); +} +.ui.upward.selection.dropdown:hover { + box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.05); +} + +/* Active Upward */ +.ui.active.upward.selection.dropdown { + border-radius: 0 0 0.28571429rem 0.28571429rem !important; +} + +/* Visible Upward */ +.ui.upward.selection.dropdown.visible { + box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.08); + border-radius: 0 0 0.28571429rem 0.28571429rem !important; +} + +/* Visible Hover Upward */ +.ui.upward.active.selection.dropdown:hover { + box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.05); +} +.ui.upward.active.selection.dropdown:hover .menu { + box-shadow: 0 -2px 3px 0 rgba(0, 0, 0, 0.08); +} + +/*-------------- + Scrolling + ---------------*/ + + +/* Selection Menu */ +.ui.scrolling.dropdown .menu, +.ui.dropdown .scrolling.menu { + overflow-x: hidden; + overflow-y: auto; +} +.ui.scrolling.dropdown .menu { + overflow-x: hidden; + overflow-y: auto; + backface-visibility: hidden; + -webkit-overflow-scrolling: touch; + min-width: 100% !important; + width: auto !important; +} +.ui.dropdown .scrolling.menu { + position: static; + overflow-y: auto; + border: none; + box-shadow: none !important; + border-radius: 0 !important; + margin: 0 !important; + min-width: 100% !important; + width: auto !important; + border-top: 1px solid rgba(34, 36, 38, 0.15); +} +.ui.scrolling.dropdown .menu .item.item.item, +.ui.dropdown .scrolling.menu > .item.item.item { + border-top: none; +} +.ui.scrolling.dropdown .menu .item:first-child, +.ui.dropdown .scrolling.menu .item:first-child { + border-top: none; +} +.ui.dropdown > .animating.menu .scrolling.menu, +.ui.dropdown > .visible.menu .scrolling.menu { + display: block; +} + +/* Scrollbar in IE */ +@media all and (-ms-high-contrast: none) { + .ui.scrolling.dropdown .menu, + .ui.dropdown .scrolling.menu { + min-width: calc(100% - 17px); + } +} +@media only screen and (max-width: 767.98px) { + .ui.scrolling.dropdown .menu, + .ui.dropdown .scrolling.menu { + max-height: 10.28571429rem; + } +} +@media only screen and (min-width: 768px) { + .ui.scrolling.dropdown .menu, + .ui.dropdown .scrolling.menu { + max-height: 15.42857143rem; + } +} +@media only screen and (min-width: 992px) { + .ui.scrolling.dropdown .menu, + .ui.dropdown .scrolling.menu { + max-height: 20.57142857rem; + } +} +@media only screen and (min-width: 1920px) { + .ui.scrolling.dropdown .menu, + .ui.dropdown .scrolling.menu { + max-height: 20.57142857rem; + } +} + +/*-------------- + Columnar +---------------*/ + +.ui.column.dropdown > .menu { + flex-wrap: wrap; +} +.ui.dropdown[class*="two column"] > .menu > .item { + width: 50%; +} +.ui.dropdown[class*="three column"] > .menu > .item { + width: 33%; +} +.ui.dropdown[class*="four column"] > .menu > .item { + width: 25%; +} +.ui.dropdown[class*="five column"] > .menu > .item { + width: 20%; +} + +/*-------------- + Simple + ---------------*/ + + +/* Displays without javascript */ +.ui.simple.dropdown .menu:before, +.ui.simple.dropdown .menu:after { + display: none; +} +.ui.simple.dropdown .menu { + position: absolute; + +/* IE hack to make dropdown icons appear inline */ + display: -ms-inline-flexbox !important; + display: block; + overflow: hidden; + top: -9999px; + opacity: 0; + width: 0; + height: 0; + transition: opacity 0.1s ease; + margin-top: 0 !important; +} +.ui.simple.active.dropdown, +.ui.simple.dropdown:hover { + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} +.ui.simple.active.dropdown > .menu, +.ui.simple.dropdown:hover > .menu { + overflow: visible; + width: auto; + height: auto; + top: 100%; + opacity: 1; +} +.ui.simple.dropdown > .menu > .item:active > .menu, +.ui.simple.dropdown .menu .item:hover > .menu { + overflow: visible; + width: auto; + height: auto; + top: 0 !important; + left: 100%; + opacity: 1; +} +.ui.simple.dropdown > .menu > .item:active > .left.menu, +.ui.simple.dropdown .menu .item:hover > .left.menu, +.right.menu .ui.simple.dropdown > .menu > .item:active > .menu:not(.right), +.right.menu .ui.simple.dropdown > .menu .item:hover > .menu:not(.right) { + left: auto; + right: 100%; +} +.ui.simple.disabled.dropdown:hover .menu { + display: none; + height: 0; + width: 0; + overflow: hidden; +} + +/* Visible */ +.ui.simple.visible.dropdown > .menu { + display: block; +} + +/* Scrolling */ +.ui.simple.scrolling.active.dropdown > .menu, +.ui.simple.scrolling.dropdown:hover > .menu { + overflow-x: hidden; + overflow-y: auto; +} + +/*-------------- + Fluid + ---------------*/ + +.ui.fluid.dropdown { + display: block; + width: 100% !important; + min-width: 0; +} +.ui.fluid.dropdown > .dropdown.icon { + float: right; +} + +/*-------------- + Floating + ---------------*/ + +.ui.floating.dropdown .menu { + left: 0; + right: auto; + box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15) !important; + border-radius: 0.28571429rem !important; +} +.ui.floating.dropdown > .menu { + border-radius: 0.28571429rem !important; +} +.ui:not(.upward).floating.dropdown > .menu { + margin-top: 0.5em; +} +.ui.upward.floating.dropdown > .menu { + margin-bottom: 0.5em; +} + +/*-------------- + Pointing + ---------------*/ + +.ui.pointing.dropdown > .menu { + top: 100%; + margin-top: 0.78571429rem; + border-radius: 0.28571429rem; +} +.ui.pointing.dropdown > .menu:not(.hidden):after { + display: block; + position: absolute; + pointer-events: none; + content: ''; + visibility: visible; + transform: rotate(45deg); + width: 0.5em; + height: 0.5em; + box-shadow: -1px -1px 0 0 rgba(34, 36, 38, 0.15); + background: #FFFFFF; + z-index: 2; +} +.ui.pointing.dropdown > .menu:not(.hidden):after { + top: -0.25em; + left: 50%; + margin: 0 0 0 -0.25em; +} + +/* Top Left Pointing */ +.ui.top.left.pointing.dropdown > .menu { + top: 100%; + bottom: auto; + left: 0; + right: auto; + margin: 1em 0 0; +} +.ui.top.left.pointing.dropdown > .menu { + top: 100%; + bottom: auto; + left: 0; + right: auto; + margin: 1em 0 0; +} +.ui.top.left.pointing.dropdown > .menu:after { + top: -0.25em; + left: 1em; + right: auto; + margin: 0; + transform: rotate(45deg); +} + +/* Top Right Pointing */ +.ui.top.right.pointing.dropdown > .menu { + top: 100%; + bottom: auto; + right: 0; + left: auto; + margin: 1em 0 0; +} +.ui.top.pointing.dropdown > .left.menu:after, +.ui.top.right.pointing.dropdown > .menu:after { + top: -0.25em; + left: auto !important; + right: 1em !important; + margin: 0; + transform: rotate(45deg); +} + +/* Left Pointing */ +.ui.left.pointing.dropdown > .menu { + top: 0; + left: 100%; + right: auto; + margin: 0 0 0 1em; +} +.ui.left.pointing.dropdown > .menu:after { + top: 1em; + left: -0.25em; + margin: 0 0 0 0; + transform: rotate(-45deg); +} +.ui.left:not(.top):not(.bottom).pointing.dropdown > .left.menu { + left: auto !important; + right: 100% !important; + margin: 0 1em 0 0; +} +.ui.left:not(.top):not(.bottom).pointing.dropdown > .left.menu:after { + top: 1em; + left: auto; + right: -0.25em; + margin: 0 0 0 0; + transform: rotate(135deg); +} + +/* Right Pointing */ +.ui.right.pointing.dropdown > .menu { + top: 0; + left: auto; + right: 100%; + margin: 0 1em 0 0; +} +.ui.right.pointing.dropdown > .menu:after { + top: 1em; + left: auto; + right: -0.25em; + margin: 0 0 0 0; + transform: rotate(135deg); +} + +/* Bottom Pointing */ +.ui.bottom.pointing.dropdown > .menu { + top: auto; + bottom: 100%; + left: 0; + right: auto; + margin: 0 0 1em; +} +.ui.bottom.pointing.dropdown > .menu:after { + top: auto; + bottom: -0.25em; + right: auto; + margin: 0; + transform: rotate(-135deg); +} + +/* Reverse Sub-Menu Direction */ +.ui.bottom.pointing.dropdown > .menu .menu { + top: auto !important; + bottom: 0 !important; +} + +/* Bottom Left */ +.ui.bottom.left.pointing.dropdown > .menu { + left: 0; + right: auto; +} +.ui.bottom.left.pointing.dropdown > .menu:after { + left: 1em; + right: auto; +} + +/* Bottom Right */ +.ui.bottom.right.pointing.dropdown > .menu { + right: 0; + left: auto; +} +.ui.bottom.right.pointing.dropdown > .menu:after { + left: auto; + right: 1em; +} + +/* Upward pointing */ +.ui.pointing.upward.dropdown .menu, +.ui.top.pointing.upward.dropdown .menu { + top: auto !important; + bottom: 100% !important; + margin: 0 0 0.78571429rem; + border-radius: 0.28571429rem; +} +.ui.pointing.upward.dropdown .menu:after, +.ui.top.pointing.upward.dropdown .menu:after { + top: 100% !important; + bottom: auto !important; + box-shadow: 1px 1px 0 0 rgba(34, 36, 38, 0.15); + margin: -0.25em 0 0; +} + +/* Right Pointing Upward */ +.ui.right.pointing.upward.dropdown:not(.top):not(.bottom) .menu { + top: auto !important; + bottom: 0 !important; + margin: 0 1em 0 0; +} +.ui.right.pointing.upward.dropdown:not(.top):not(.bottom) .menu:after { + top: auto !important; + bottom: 0 !important; + margin: 0 0 1em 0; + box-shadow: -1px -1px 0 0 rgba(34, 36, 38, 0.15); +} + +/* Left Pointing Upward */ +.ui.left.pointing.upward.dropdown:not(.top):not(.bottom) .menu { + top: auto !important; + bottom: 0 !important; + margin: 0 0 0 1em; +} +.ui.left.pointing.upward.dropdown:not(.top):not(.bottom) .menu:after { + top: auto !important; + bottom: 0 !important; + margin: 0 0 1em 0; + box-shadow: -1px -1px 0 0 rgba(34, 36, 38, 0.15); +} + +/*-------------------- + Sizes +---------------------*/ + +.ui.dropdown, +.ui.dropdown .menu > .item { + font-size: 1rem; +} +.ui.mini.dropdown, +.ui.mini.dropdown .menu > .item { + font-size: 0.78571429rem; +} +.ui.tiny.dropdown, +.ui.tiny.dropdown .menu > .item { + font-size: 0.85714286rem; +} +.ui.small.dropdown, +.ui.small.dropdown .menu > .item { + font-size: 0.92857143rem; +} +.ui.large.dropdown, +.ui.large.dropdown .menu > .item { + font-size: 1.14285714rem; +} +.ui.big.dropdown, +.ui.big.dropdown .menu > .item { + font-size: 1.28571429rem; +} +.ui.huge.dropdown, +.ui.huge.dropdown .menu > .item { + font-size: 1.42857143rem; +} +.ui.massive.dropdown, +.ui.massive.dropdown .menu > .item { + font-size: 1.71428571rem; +} + + +/******************************* + Theme Overrides +*******************************/ + + +/* Dropdown Carets */ +@font-face { + font-family: 'Dropdown'; + src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMggjB5AAAAC8AAAAYGNtYXAPfuIIAAABHAAAAExnYXNwAAAAEAAAAWgAAAAIZ2x5Zjo82LgAAAFwAAABVGhlYWQAQ88bAAACxAAAADZoaGVhAwcB6QAAAvwAAAAkaG10eAS4ABIAAAMgAAAAIGxvY2EBNgDeAAADQAAAABJtYXhwAAoAFgAAA1QAAAAgbmFtZVcZpu4AAAN0AAABRXBvc3QAAwAAAAAEvAAAACAAAwIAAZAABQAAAUwBZgAAAEcBTAFmAAAA9QAZAIQAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADw2gHg/+D/4AHgACAAAAABAAAAAAAAAAAAAAAgAAAAAAACAAAAAwAAABQAAwABAAAAFAAEADgAAAAKAAgAAgACAAEAIPDa//3//wAAAAAAIPDX//3//wAB/+MPLQADAAEAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAIABJQElABMAABM0NzY3BTYXFhUUDwEGJwYvASY1AAUGBwEACAUGBoAFCAcGgAUBEgcGBQEBAQcECQYHfwYBAQZ/BwYAAQAAAG4BJQESABMAADc0PwE2MzIfARYVFAcGIyEiJyY1AAWABgcIBYAGBgUI/wAHBgWABwaABQWABgcHBgUFBgcAAAABABIASQC3AW4AEwAANzQ/ATYXNhcWHQEUBwYnBi8BJjUSBoAFCAcFBgYFBwgFgAbbBwZ/BwEBBwQJ/wgEBwEBB38GBgAAAAABAAAASQClAW4AEwAANxE0NzYzMh8BFhUUDwEGIyInJjUABQYHCAWABgaABQgHBgVbAQAIBQYGgAUIBwWABgYFBwAAAAEAAAABAADZuaKOXw889QALAgAAAAAA0ABHWAAAAADQAEdYAAAAAAElAW4AAAAIAAIAAAAAAAAAAQAAAeD/4AAAAgAAAAAAASUAAQAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAABAAAAASUAAAElAAAAtwASALcAAAAAAAAACgAUAB4AQgBkAIgAqgAAAAEAAAAIABQAAQAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAOAK4AAQAAAAAAAQAOAAAAAQAAAAAAAgAOAEcAAQAAAAAAAwAOACQAAQAAAAAABAAOAFUAAQAAAAAABQAWAA4AAQAAAAAABgAHADIAAQAAAAAACgA0AGMAAwABBAkAAQAOAAAAAwABBAkAAgAOAEcAAwABBAkAAwAOACQAAwABBAkABAAOAFUAAwABBAkABQAWAA4AAwABBAkABgAOADkAAwABBAkACgA0AGMAaQBjAG8AbQBvAG8AbgBWAGUAcgBzAGkAbwBuACAAMQAuADAAaQBjAG8AbQBvAG8Abmljb21vb24AaQBjAG8AbQBvAG8AbgBSAGUAZwB1AGwAYQByAGkAYwBvAG0AbwBvAG4ARgBvAG4AdAAgAGcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAASQBjAG8ATQBvAG8AbgAuAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=) format('truetype'), url(data:application/font-woff;charset=utf-8;base64,d09GRk9UVE8AAAVwAAoAAAAABSgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABDRkYgAAAA9AAAAdkAAAHZLDXE/09TLzIAAALQAAAAYAAAAGAIIweQY21hcAAAAzAAAABMAAAATA9+4ghnYXNwAAADfAAAAAgAAAAIAAAAEGhlYWQAAAOEAAAANgAAADYAQ88baGhlYQAAA7wAAAAkAAAAJAMHAelobXR4AAAD4AAAACAAAAAgBLgAEm1heHAAAAQAAAAABgAAAAYACFAAbmFtZQAABAgAAAFFAAABRVcZpu5wb3N0AAAFUAAAACAAAAAgAAMAAAEABAQAAQEBCGljb21vb24AAQIAAQA6+BwC+BsD+BgEHgoAGVP/i4seCgAZU/+LiwwHi2v4lPh0BR0AAACIDx0AAACNER0AAAAJHQAAAdASAAkBAQgPERMWGyAlKmljb21vb25pY29tb29udTB1MXUyMHVGMEQ3dUYwRDh1RjBEOXVGMERBAAACAYkABgAIAgABAAQABwAKAA0AVgCfAOgBL/yUDvyUDvyUDvuUDvtvi/emFYuQjZCOjo+Pj42Qiwj3lIsFkIuQiY6Hj4iNhouGi4aJh4eHCPsU+xQFiIiGiYaLhouHjYeOCPsU9xQFiI+Jj4uQCA77b4v3FBWLkI2Pjo8I9xT3FAWPjo+NkIuQi5CJjogI9xT7FAWPh42Hi4aLhomHh4eIiIaJhosI+5SLBYaLh42HjoiPiY+LkAgO+92d928Vi5CNkI+OCPcU9xQFjo+QjZCLkIuPiY6Hj4iNhouGCIv7lAWLhomHh4iIh4eJhouGi4aNiI8I+xT3FAWHjomPi5AIDvvdi+YVi/eUBYuQjZCOjo+Pj42Qi5CLkImOhwj3FPsUBY+IjYaLhouGiYeHiAj7FPsUBYiHhomGi4aLh42Hj4iOiY+LkAgO+JQU+JQViwwKAAAAAAMCAAGQAAUAAAFMAWYAAABHAUwBZgAAAPUAGQCEAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAA8NoB4P/g/+AB4AAgAAAAAQAAAAAAAAAAAAAAIAAAAAAAAgAAAAMAAAAUAAMAAQAAABQABAA4AAAACgAIAAIAAgABACDw2v/9//8AAAAAACDw1//9//8AAf/jDy0AAwABAAAAAAAAAAAAAAABAAH//wAPAAEAAAABAAA5emozXw889QALAgAAAAAA0ABHWAAAAADQAEdYAAAAAAElAW4AAAAIAAIAAAAAAAAAAQAAAeD/4AAAAgAAAAAAASUAAQAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAABAAAAASUAAAElAAAAtwASALcAAAAAUAAACAAAAAAADgCuAAEAAAAAAAEADgAAAAEAAAAAAAIADgBHAAEAAAAAAAMADgAkAAEAAAAAAAQADgBVAAEAAAAAAAUAFgAOAAEAAAAAAAYABwAyAAEAAAAAAAoANABjAAMAAQQJAAEADgAAAAMAAQQJAAIADgBHAAMAAQQJAAMADgAkAAMAAQQJAAQADgBVAAMAAQQJAAUAFgAOAAMAAQQJAAYADgA5AAMAAQQJAAoANABjAGkAYwBvAG0AbwBvAG4AVgBlAHIAcwBpAG8AbgAgADEALgAwAGkAYwBvAG0AbwBvAG5pY29tb29uAGkAYwBvAG0AbwBvAG4AUgBlAGcAdQBsAGEAcgBpAGMAbwBtAG8AbwBuAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format('woff'); + font-weight: normal; + font-style: normal; +} +.ui.dropdown > .dropdown.icon { + font-family: 'Dropdown'; + line-height: 1; + height: 1em; + width: 1.23em; + backface-visibility: hidden; + font-weight: normal; + font-style: normal; + text-align: center; +} +.ui.dropdown > .dropdown.icon { + width: auto; +} +.ui.dropdown > .dropdown.icon:before { + content: '\f0d7'; +} + +/* Sub Menu */ +.ui.dropdown .menu .item .dropdown.icon:before { + content: '\f0da' /*rtl:'\f0d9'*/; +} +.ui.dropdown .item .left.dropdown.icon:before, +.ui.dropdown .left.menu .item .dropdown.icon:before { + content: "\f0d9" /*rtl:"\f0da"*/; +} + +/* Vertical Menu Dropdown */ +.ui.vertical.menu .dropdown.item > .dropdown.icon:before { + content: "\f0da" /*rtl:"\f0d9"*/; +} +/* Icons for Reference +.dropdown.down.icon { + content: "\f0d7"; +} +.dropdown.up.icon { + content: "\f0d8"; +} +.dropdown.left.icon { + content: "\f0d9"; +} +.dropdown.icon.icon { + content: "\f0da"; +} +*/ + + +/******************************* + User Overrides +*******************************/ + diff --git a/web_src/fomantic/build/components/dropdown.js b/web_src/fomantic/build/components/dropdown.js new file mode 100644 index 0000000000..3ad0984865 --- /dev/null +++ b/web_src/fomantic/build/components/dropdown.js @@ -0,0 +1,4245 @@ +/*! + * # Fomantic-UI - Dropdown + * http://github.com/fomantic/Fomantic-UI/ + * + * + * Released under the MIT license + * http://opensource.org/licenses/MIT + * + */ + +;(function ($, window, document, undefined) { + +'use strict'; + +$.isFunction = $.isFunction || function(obj) { + return typeof obj === "function" && typeof obj.nodeType !== "number"; +}; + +window = (typeof window != 'undefined' && window.Math == Math) + ? window + : (typeof self != 'undefined' && self.Math == Math) + ? self + : Function('return this')() +; + +$.fn.dropdown = function(parameters) { + var + $allModules = $(this), + $document = $(document), + + moduleSelector = $allModules.selector || '', + + hasTouch = ('ontouchstart' in document.documentElement), + // GITEA-PATCH: always "click" as clickEvent, old code used "touchstart" as clickEvent, it is wrong, + // because "touchstart" caused problems when users try to scroll and the touch point is in the dropdown. + clickEvent = 'click', + + time = new Date().getTime(), + performance = [], + + query = arguments[0], + methodInvoked = (typeof query == 'string'), + queryArguments = [].slice.call(arguments, 1), + returnedValue + ; + + $allModules + .each(function(elementIndex) { + var + settings = ( $.isPlainObject(parameters) ) + ? $.extend(true, {}, $.fn.dropdown.settings, parameters) + : $.extend({}, $.fn.dropdown.settings), + + className = settings.className, + message = settings.message, + fields = settings.fields, + keys = settings.keys, + metadata = settings.metadata, + namespace = settings.namespace, + regExp = settings.regExp, + selector = settings.selector, + error = settings.error, + templates = settings.templates, + + eventNamespace = '.' + namespace, + moduleNamespace = 'module-' + namespace, + + $module = $(this), + $context = $(settings.context), + $text = $module.find(selector.text), + $search = $module.find(selector.search), + $sizer = $module.find(selector.sizer), + $input = $module.find(selector.input), + $icon = $module.find(selector.icon), + $clear = $module.find(selector.clearIcon), + + $combo = ($module.prev().find(selector.text).length > 0) + ? $module.prev().find(selector.text) + : $module.prev(), + + $menu = $module.children(selector.menu), + $item = $menu.find(selector.item), + $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(), + + activated = false, + itemActivated = false, + internalChange = false, + iconClicked = false, + element = this, + instance = $module.data(moduleNamespace), + + selectActionActive, + initialLoad, + pageLostFocus, + willRefocus, + elementNamespace, + id, + selectObserver, + menuObserver, + classObserver, + module + ; + + module = { + + initialize: function() { + module.debug('Initializing dropdown', settings); + + if( module.is.alreadySetup() ) { + module.setup.reference(); + } + else { + if (settings.ignoreDiacritics && !String.prototype.normalize) { + settings.ignoreDiacritics = false; + module.error(error.noNormalize, element); + } + + module.setup.layout(); + + if(settings.values) { + module.set.initialLoad(); + module.change.values(settings.values); + module.remove.initialLoad(); + } + + module.refreshData(); + + module.save.defaults(); + module.restore.selected(); + + module.create.id(); + module.bind.events(); + + module.observeChanges(); + module.instantiate(); + } + + }, + + instantiate: function() { + module.verbose('Storing instance of dropdown', module); + instance = module; + $module + .data(moduleNamespace, module) + ; + }, + + destroy: function() { + module.verbose('Destroying previous dropdown', $module); + module.remove.tabbable(); + module.remove.active(); + $menu.transition('stop all'); + $menu.removeClass(className.visible).addClass(className.hidden); + $module + .off(eventNamespace) + .removeData(moduleNamespace) + ; + $menu + .off(eventNamespace) + ; + $document + .off(elementNamespace) + ; + module.disconnect.menuObserver(); + module.disconnect.selectObserver(); + module.disconnect.classObserver(); + }, + + observeChanges: function() { + if('MutationObserver' in window) { + selectObserver = new MutationObserver(module.event.select.mutation); + menuObserver = new MutationObserver(module.event.menu.mutation); + classObserver = new MutationObserver(module.event.class.mutation); + module.debug('Setting up mutation observer', selectObserver, menuObserver, classObserver); + module.observe.select(); + module.observe.menu(); + module.observe.class(); + } + }, + + disconnect: { + menuObserver: function() { + if(menuObserver) { + menuObserver.disconnect(); + } + }, + selectObserver: function() { + if(selectObserver) { + selectObserver.disconnect(); + } + }, + classObserver: function() { + if(classObserver) { + classObserver.disconnect(); + } + } + }, + observe: { + select: function() { + if(module.has.input() && selectObserver) { + selectObserver.observe($module[0], { + childList : true, + subtree : true + }); + } + }, + menu: function() { + if(module.has.menu() && menuObserver) { + menuObserver.observe($menu[0], { + childList : true, + subtree : true + }); + } + }, + class: function() { + if(module.has.search() && classObserver) { + classObserver.observe($module[0], { + attributes : true + }); + } + } + }, + + create: { + id: function() { + id = (Math.random().toString(16) + '000000000').substr(2, 8); + elementNamespace = '.' + id; + module.verbose('Creating unique id for element', id); + }, + userChoice: function(values) { + var + $userChoices, + $userChoice, + isUserValue, + html + ; + values = values || module.get.userValues(); + if(!values) { + return false; + } + values = Array.isArray(values) + ? values + : [values] + ; + $.each(values, function(index, value) { + if(module.get.item(value) === false) { + html = settings.templates.addition( module.add.variables(message.addResult, value) ); + $userChoice = $('<div />') + .html(html) + .attr('data-' + metadata.value, value) + .attr('data-' + metadata.text, value) + .addClass(className.addition) + .addClass(className.item) + ; + if(settings.hideAdditions) { + $userChoice.addClass(className.hidden); + } + $userChoices = ($userChoices === undefined) + ? $userChoice + : $userChoices.add($userChoice) + ; + module.verbose('Creating user choices for value', value, $userChoice); + } + }); + return $userChoices; + }, + userLabels: function(value) { + var + userValues = module.get.userValues() + ; + if(userValues) { + module.debug('Adding user labels', userValues); + $.each(userValues, function(index, value) { + module.verbose('Adding custom user value'); + module.add.label(value, value); + }); + } + }, + menu: function() { + $menu = $('<div />') + .addClass(className.menu) + .appendTo($module) + ; + }, + sizer: function() { + $sizer = $('<span />') + .addClass(className.sizer) + .insertAfter($search) + ; + } + }, + + search: function(query) { + query = (query !== undefined) + ? query + : module.get.query() + ; + module.verbose('Searching for query', query); + if(module.has.minCharacters(query)) { + module.filter(query); + } + else { + module.hide(null,true); + } + }, + + select: { + firstUnfiltered: function() { + module.verbose('Selecting first non-filtered element'); + module.remove.selectedItem(); + $item + .not(selector.unselectable) + .not(selector.addition + selector.hidden) + .eq(0) + .addClass(className.selected) + ; + }, + nextAvailable: function($selected) { + $selected = $selected.eq(0); + var + $nextAvailable = $selected.nextAll(selector.item).not(selector.unselectable).eq(0), + $prevAvailable = $selected.prevAll(selector.item).not(selector.unselectable).eq(0), + hasNext = ($nextAvailable.length > 0) + ; + if(hasNext) { + module.verbose('Moving selection to', $nextAvailable); + $nextAvailable.addClass(className.selected); + } + else { + module.verbose('Moving selection to', $prevAvailable); + $prevAvailable.addClass(className.selected); + } + } + }, + + setup: { + api: function() { + var + apiSettings = { + debug : settings.debug, + urlData : { + value : module.get.value(), + query : module.get.query() + }, + on : false + } + ; + module.verbose('First request, initializing API'); + $module + .api(apiSettings) + ; + }, + layout: function() { + if( $module.is('select') ) { + module.setup.select(); + module.setup.returnedObject(); + } + if( !module.has.menu() ) { + module.create.menu(); + } + if ( module.is.selection() && module.is.clearable() && !module.has.clearItem() ) { + module.verbose('Adding clear icon'); + $clear = $('<i />') + .addClass('remove icon') + .insertBefore($text) + ; + } + if( module.is.search() && !module.has.search() ) { + module.verbose('Adding search input'); + $search = $('<input />') + .addClass(className.search) + .prop('autocomplete', 'off') + .insertBefore($text) + ; + } + if( module.is.multiple() && module.is.searchSelection() && !module.has.sizer()) { + module.create.sizer(); + } + if(settings.allowTab) { + module.set.tabbable(); + } + }, + select: function() { + var + selectValues = module.get.selectValues() + ; + module.debug('Dropdown initialized on a select', selectValues); + if( $module.is('select') ) { + $input = $module; + } + // see if select is placed correctly already + if($input.parent(selector.dropdown).length > 0) { + module.debug('UI dropdown already exists. Creating dropdown menu only'); + $module = $input.closest(selector.dropdown); + if( !module.has.menu() ) { + module.create.menu(); + } + $menu = $module.children(selector.menu); + module.setup.menu(selectValues); + } + else { + module.debug('Creating entire dropdown from select'); + $module = $('<div />') + .attr('class', $input.attr('class') ) + .addClass(className.selection) + .addClass(className.dropdown) + .html( templates.dropdown(selectValues, fields, settings.preserveHTML, settings.className) ) + .insertBefore($input) + ; + if($input.hasClass(className.multiple) && $input.prop('multiple') === false) { + module.error(error.missingMultiple); + $input.prop('multiple', true); + } + if($input.is('[multiple]')) { + module.set.multiple(); + } + if ($input.prop('disabled')) { + module.debug('Disabling dropdown'); + $module.addClass(className.disabled); + } + $input + .removeAttr('required') + .removeAttr('class') + .detach() + .prependTo($module) + ; + } + module.refresh(); + }, + menu: function(values) { + $menu.html( templates.menu(values, fields,settings.preserveHTML,settings.className)); + $item = $menu.find(selector.item); + $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); + }, + reference: function() { + module.debug('Dropdown behavior was called on select, replacing with closest dropdown'); + // replace module reference + $module = $module.parent(selector.dropdown); + instance = $module.data(moduleNamespace); + element = $module.get(0); + module.refresh(); + module.setup.returnedObject(); + }, + returnedObject: function() { + var + $firstModules = $allModules.slice(0, elementIndex), + $lastModules = $allModules.slice(elementIndex + 1) + ; + // adjust all modules to use correct reference + $allModules = $firstModules.add($module).add($lastModules); + } + }, + + refresh: function() { + module.refreshSelectors(); + module.refreshData(); + }, + + refreshItems: function() { + $item = $menu.find(selector.item); + $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); + }, + + refreshSelectors: function() { + module.verbose('Refreshing selector cache'); + $text = $module.find(selector.text); + $search = $module.find(selector.search); + $input = $module.find(selector.input); + $icon = $module.find(selector.icon); + $combo = ($module.prev().find(selector.text).length > 0) + ? $module.prev().find(selector.text) + : $module.prev() + ; + $menu = $module.children(selector.menu); + $item = $menu.find(selector.item); + $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); + }, + + refreshData: function() { + module.verbose('Refreshing cached metadata'); + $item + .removeData(metadata.text) + .removeData(metadata.value) + ; + }, + + clearData: function() { + module.verbose('Clearing metadata'); + $item + .removeData(metadata.text) + .removeData(metadata.value) + ; + $module + .removeData(metadata.defaultText) + .removeData(metadata.defaultValue) + .removeData(metadata.placeholderText) + ; + }, + + toggle: function() { + module.verbose('Toggling menu visibility'); + if( !module.is.active() ) { + module.show(); + } + else { + module.hide(); + } + }, + + show: function(callback, preventFocus) { + callback = $.isFunction(callback) + ? callback + : function(){} + ; + if(!module.can.show() && module.is.remote()) { + module.debug('No API results retrieved, searching before show'); + module.queryRemote(module.get.query(), module.show); + } + if( module.can.show() && !module.is.active() ) { + module.debug('Showing dropdown'); + if(module.has.message() && !(module.has.maxSelections() || module.has.allResultsFiltered()) ) { + module.remove.message(); + } + if(module.is.allFiltered()) { + return true; + } + if(settings.onShow.call(element) !== false) { + $module.fomanticExt.onDropdownAfterFiltered.call(element); // GITEA-PATCH: callback to correctly handle the filtered items + module.animate.show(function() { + if( module.can.click() ) { + module.bind.intent(); + } + if(module.has.search() && !preventFocus) { + module.focusSearch(); + } + module.set.visible(); + callback.call(element); + }); + } + } + }, + + hide: function(callback, preventBlur) { + callback = $.isFunction(callback) + ? callback + : function(){} + ; + if( module.is.active() && !module.is.animatingOutward() ) { + module.debug('Hiding dropdown'); + if(settings.onHide.call(element) !== false) { + module.animate.hide(function() { + module.remove.visible(); + // hidding search focus + if ( module.is.focusedOnSearch() && preventBlur !== true ) { + $search.blur(); + } + callback.call(element); + }); + } + } else if( module.can.click() ) { + module.unbind.intent(); + } + iconClicked = false; + }, + + hideOthers: function() { + module.verbose('Finding other dropdowns to hide'); + $allModules + .not($module) + .has(selector.menu + '.' + className.visible) + .dropdown('hide') + ; + }, + + hideMenu: function() { + module.verbose('Hiding menu instantaneously'); + module.remove.active(); + module.remove.visible(); + $menu.transition('hide'); + }, + + hideSubMenus: function() { + var + $subMenus = $menu.children(selector.item).find(selector.menu) + ; + module.verbose('Hiding sub menus', $subMenus); + $subMenus.transition('hide'); + }, + + bind: { + events: function() { + module.bind.keyboardEvents(); + module.bind.inputEvents(); + module.bind.mouseEvents(); + }, + keyboardEvents: function() { + module.verbose('Binding keyboard events'); + $module + .on('keydown' + eventNamespace, module.event.keydown) + ; + if( module.has.search() ) { + $module + .on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input) + ; + } + if( module.is.multiple() ) { + $document + .on('keydown' + elementNamespace, module.event.document.keydown) + ; + } + }, + inputEvents: function() { + module.verbose('Binding input change events'); + $module + .on('change' + eventNamespace, selector.input, module.event.change) + ; + }, + mouseEvents: function() { + module.verbose('Binding mouse events'); + if(module.is.multiple()) { + $module + .on(clickEvent + eventNamespace, selector.label, module.event.label.click) + .on(clickEvent + eventNamespace, selector.remove, module.event.remove.click) + ; + } + if( module.is.searchSelection() ) { + $module + .on('mousedown' + eventNamespace, module.event.mousedown) + .on('mouseup' + eventNamespace, module.event.mouseup) + .on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown) + .on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup) + .on(clickEvent + eventNamespace, selector.icon, module.event.icon.click) + .on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click) + .on('focus' + eventNamespace, selector.search, module.event.search.focus) + .on(clickEvent + eventNamespace, selector.search, module.event.search.focus) + .on('blur' + eventNamespace, selector.search, module.event.search.blur) + .on(clickEvent + eventNamespace, selector.text, module.event.text.focus) + ; + if(module.is.multiple()) { + $module + .on(clickEvent + eventNamespace, module.event.click) + ; + } + } + else { + if(settings.on == 'click') { + $module + .on(clickEvent + eventNamespace, selector.icon, module.event.icon.click) + .on(clickEvent + eventNamespace, module.event.test.toggle) + ; + } + else if(settings.on == 'hover') { + $module + .on('mouseenter' + eventNamespace, module.delay.show) + .on('mouseleave' + eventNamespace, module.delay.hide) + ; + } + else { + $module + .on(settings.on + eventNamespace, module.toggle) + ; + } + $module + .on('mousedown' + eventNamespace, module.event.mousedown) + .on('mouseup' + eventNamespace, module.event.mouseup) + .on('focus' + eventNamespace, module.event.focus) + .on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click) + ; + if(module.has.menuSearch() ) { + $module + .on('blur' + eventNamespace, selector.search, module.event.search.blur) + ; + } + else { + $module + .on('blur' + eventNamespace, module.event.blur) + ; + } + } + $menu + .on((hasTouch ? 'touchstart' : 'mouseenter') + eventNamespace, selector.item, module.event.item.mouseenter) + .on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave) + .on('click' + eventNamespace, selector.item, module.event.item.click) + ; + }, + intent: function() { + module.verbose('Binding hide intent event to document'); + if(hasTouch) { + $document + .on('touchstart' + elementNamespace, module.event.test.touch) + .on('touchmove' + elementNamespace, module.event.test.touch) + ; + } + $document + .on(clickEvent + elementNamespace, module.event.test.hide) + ; + } + }, + + unbind: { + intent: function() { + module.verbose('Removing hide intent event from document'); + if(hasTouch) { + $document + .off('touchstart' + elementNamespace) + .off('touchmove' + elementNamespace) + ; + } + $document + .off(clickEvent + elementNamespace) + ; + } + }, + + filter: function(query) { + var + searchTerm = (query !== undefined) + ? query + : module.get.query(), + afterFiltered = function() { + if(module.is.multiple()) { + module.filterActive(); + } + if(query || (!query && module.get.activeItem().length == 0)) { + module.select.firstUnfiltered(); + } + if( module.has.allResultsFiltered() ) { + if( settings.onNoResults.call(element, searchTerm) ) { + if(settings.allowAdditions) { + if(settings.hideAdditions) { + module.verbose('User addition with no menu, setting empty style'); + module.set.empty(); + module.hideMenu(); + } + } + else { + module.verbose('All items filtered, showing message', searchTerm); + module.add.message(message.noResults); + } + } + else { + module.verbose('All items filtered, hiding dropdown', searchTerm); + module.hideMenu(); + } + } + else { + module.remove.empty(); + module.remove.message(); + } + if(settings.allowAdditions) { + module.add.userSuggestion(module.escape.htmlEntities(query)); + } + if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) { + module.show(); + } + $module.fomanticExt.onDropdownAfterFiltered.call(element); // GITEA-PATCH: callback to correctly handle the filtered items + } + ; + if(settings.useLabels && module.has.maxSelections()) { + return; + } + if(settings.apiSettings) { + if( module.can.useAPI() ) { + module.queryRemote(searchTerm, function() { + if(settings.filterRemoteData) { + module.filterItems(searchTerm); + } + var preSelected = $input.val(); + if(!Array.isArray(preSelected)) { + preSelected = preSelected && preSelected!=="" ? preSelected.split(settings.delimiter) : []; + } + $.each(preSelected,function(index,value){ + $item.filter('[data-value="'+CSS.escape(value)+'"]') // GITEA-PATCH: use "CSS.escape" for query selector + .addClass(className.filtered) + ; + }); + afterFiltered(); + }); + } + else { + module.error(error.noAPI); + } + } + else { + module.filterItems(searchTerm); + afterFiltered(); + } + }, + + queryRemote: function(query, callback) { + var + apiSettings = { + errorDuration : false, + cache : 'local', + throttle : settings.throttle, + urlData : { + query: query + }, + onError: function() { + module.add.message(message.serverError); + callback(); + }, + onFailure: function() { + module.add.message(message.serverError); + callback(); + }, + onSuccess : function(response) { + var + values = response[fields.remoteValues] + ; + if (!Array.isArray(values)){ + values = []; + } + module.remove.message(); + var menuConfig = {}; + menuConfig[fields.values] = values; + module.setup.menu(menuConfig); + + if(values.length===0 && !settings.allowAdditions) { + module.add.message(message.noResults); + } + callback(); + } + } + ; + if( !$module.api('get request') ) { + module.setup.api(); + } + apiSettings = $.extend(true, {}, apiSettings, settings.apiSettings); + $module + .api('setting', apiSettings) + .api('query') + ; + }, + + filterItems: function(query) { + var + searchTerm = module.remove.diacritics(query !== undefined + ? query + : module.get.query() + ), + results = null, + escapedTerm = module.escape.string(searchTerm), + regExpFlags = (settings.ignoreSearchCase ? 'i' : '') + 'gm', + beginsWithRegExp = new RegExp('^' + escapedTerm, regExpFlags) + ; + // avoid loop if we're matching nothing + if( module.has.query() ) { + results = []; + + module.verbose('Searching for matching values', searchTerm); + $item + .each(function(){ + var + $choice = $(this), + text, + value + ; + if($choice.hasClass(className.unfilterable)) { + results.push(this); + return true; + } + if(settings.match === 'both' || settings.match === 'text') { + text = module.remove.diacritics(String(module.get.choiceText($choice, false))); + if(text.search(beginsWithRegExp) !== -1) { + results.push(this); + return true; + } + else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text)) { + results.push(this); + return true; + } + else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, text)) { + results.push(this); + return true; + } + } + if(settings.match === 'both' || settings.match === 'value') { + value = module.remove.diacritics(String(module.get.choiceValue($choice, text))); + if(value.search(beginsWithRegExp) !== -1) { + results.push(this); + return true; + } + else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, value)) { + results.push(this); + return true; + } + else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, value)) { + results.push(this); + return true; + } + } + }) + ; + } + module.debug('Showing only matched items', searchTerm); + module.remove.filteredItem(); + if(results) { + $item + .not(results) + .addClass(className.filtered) + ; + } + + if(!module.has.query()) { + $divider + .removeClass(className.hidden); + } else if(settings.hideDividers === true) { + $divider + .addClass(className.hidden); + } else if(settings.hideDividers === 'empty') { + $divider + .removeClass(className.hidden) + .filter(function() { + // First find the last divider in this divider group + // Dividers which are direct siblings are considered a group + var lastDivider = $(this).nextUntil(selector.item); + + return (lastDivider.length ? lastDivider : $(this)) + // Count all non-filtered items until the next divider (or end of the dropdown) + .nextUntil(selector.divider) + .filter(selector.item + ":not(." + className.filtered + ")") + // Hide divider if no items are found + .length === 0; + }) + .addClass(className.hidden); + } + }, + + fuzzySearch: function(query, term) { + var + termLength = term.length, + queryLength = query.length + ; + query = (settings.ignoreSearchCase ? query.toLowerCase() : query); + term = (settings.ignoreSearchCase ? term.toLowerCase() : term); + if(queryLength > termLength) { + return false; + } + if(queryLength === termLength) { + return (query === term); + } + search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) { + var + queryCharacter = query.charCodeAt(characterIndex) + ; + while(nextCharacterIndex < termLength) { + if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) { + continue search; + } + } + return false; + } + return true; + }, + exactSearch: function (query, term) { + query = (settings.ignoreSearchCase ? query.toLowerCase() : query); + term = (settings.ignoreSearchCase ? term.toLowerCase() : term); + return term.indexOf(query) > -1; + + }, + filterActive: function() { + if(settings.useLabels) { + $item.filter('.' + className.active) + .addClass(className.filtered) + ; + } + }, + + focusSearch: function(skipHandler) { + if( module.has.search() && !module.is.focusedOnSearch() ) { + if(skipHandler) { + $module.off('focus' + eventNamespace, selector.search); + $search.focus(); + $module.on('focus' + eventNamespace, selector.search, module.event.search.focus); + } + else { + $search.focus(); + } + } + }, + + blurSearch: function() { + if( module.has.search() ) { + $search.blur(); + } + }, + + forceSelection: function() { + var + $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0), + $activeItem = $item.not(className.filtered).filter('.' + className.active).eq(0), + $selectedItem = ($currentlySelected.length > 0) + ? $currentlySelected + : $activeItem, + hasSelected = ($selectedItem.length > 0) + ; + if(settings.allowAdditions || (hasSelected && !module.is.multiple())) { + module.debug('Forcing partial selection to selected item', $selectedItem); + module.event.item.click.call($selectedItem, {}, true); + } + else { + module.remove.searchTerm(); + } + }, + + change: { + values: function(values) { + if(!settings.allowAdditions) { + module.clear(); + } + module.debug('Creating dropdown with specified values', values); + var menuConfig = {}; + menuConfig[fields.values] = values; + module.setup.menu(menuConfig); + $.each(values, function(index, item) { + if(item.selected == true) { + module.debug('Setting initial selection to', item[fields.value]); + module.set.selected(item[fields.value]); + if(!module.is.multiple()) { + return false; + } + } + }); + + if(module.has.selectInput()) { + module.disconnect.selectObserver(); + $input.html(''); + $input.append('<option disabled selected value></option>'); + $.each(values, function(index, item) { + var + value = settings.templates.escape(item[fields.value]), // GITEA-PATCH: use "escape" for attribute value + name = settings.templates.escape( + item[fields.name] || '', + settings.preserveHTML + ) + ; + $input.append('<option value="' + value + '">' + name + '</option>'); + }); + module.observe.select(); + } + } + }, + + event: { + change: function() { + if(!internalChange) { + module.debug('Input changed, updating selection'); + module.set.selected(); + } + }, + focus: function() { + if(settings.showOnFocus && !activated && module.is.hidden() && !pageLostFocus) { + module.show(); + } + }, + blur: function(event) { + pageLostFocus = (document.activeElement === this); + if(!activated && !pageLostFocus) { + module.remove.activeLabel(); + module.hide(); + } + }, + mousedown: function() { + if(module.is.searchSelection()) { + // prevent menu hiding on immediate re-focus + willRefocus = true; + } + else { + // prevents focus callback from occurring on mousedown + activated = true; + } + }, + mouseup: function() { + if(module.is.searchSelection()) { + // prevent menu hiding on immediate re-focus + willRefocus = false; + } + else { + activated = false; + } + }, + click: function(event) { + var + $target = $(event.target) + ; + // focus search + if($target.is($module)) { + if(!module.is.focusedOnSearch()) { + module.focusSearch(); + } + else { + module.show(); + } + } + }, + search: { + focus: function(event) { + activated = true; + if(module.is.multiple()) { + module.remove.activeLabel(); + } + if(settings.showOnFocus || (event.type !== 'focus' && event.type !== 'focusin')) { + module.search(); + } + }, + blur: function(event) { + pageLostFocus = (document.activeElement === this); + if(module.is.searchSelection() && !willRefocus) { + if(!itemActivated && !pageLostFocus) { + if(settings.forceSelection) { + module.forceSelection(); + } else if(!settings.allowAdditions){ + module.remove.searchTerm(); + } + module.hide(); + } + } + willRefocus = false; + } + }, + clearIcon: { + click: function(event) { + module.clear(); + if(module.is.searchSelection()) { + module.remove.searchTerm(); + } + module.hide(); + event.stopPropagation(); + } + }, + icon: { + click: function(event) { + iconClicked=true; + // GITEA-PATCH: official dropdown doesn't support the search input in menu + // so we need to make the menu could be shown when the search input is in menu and user clicks the icon + const searchInputInMenu = Boolean($menu.find('.search > input').length); + if(module.has.search() && !searchInputInMenu) { + // the search input is in the dropdown element (but not in the popup menu), try to focus it + if(!module.is.active()) { + if(settings.showOnFocus){ + module.focusSearch(); + } else { + module.toggle(); + } + } else { + module.blurSearch(); + } + } else { + module.toggle(); + } + } + }, + text: { + focus: function(event) { + activated = true; + module.focusSearch(); + } + }, + input: function(event) { + if(module.is.multiple() || module.is.searchSelection()) { + module.set.filtered(); + } + clearTimeout(module.timer); + module.timer = setTimeout(module.search, settings.delay.search); + }, + label: { + click: function(event) { + var + $label = $(this), + $labels = $module.find(selector.label), + $activeLabels = $labels.filter('.' + className.active), + $nextActive = $label.nextAll('.' + className.active), + $prevActive = $label.prevAll('.' + className.active), + $range = ($nextActive.length > 0) + ? $label.nextUntil($nextActive).add($activeLabels).add($label) + : $label.prevUntil($prevActive).add($activeLabels).add($label) + ; + if(event.shiftKey) { + $activeLabels.removeClass(className.active); + $range.addClass(className.active); + } + else if(event.ctrlKey) { + $label.toggleClass(className.active); + } + else { + $activeLabels.removeClass(className.active); + $label.addClass(className.active); + } + settings.onLabelSelect.apply(this, $labels.filter('.' + className.active)); + } + }, + remove: { + click: function() { + var + $label = $(this).parent() + ; + if( $label.hasClass(className.active) ) { + // remove all selected labels + module.remove.activeLabels(); + } + else { + // remove this label only + module.remove.activeLabels( $label ); + } + } + }, + test: { + toggle: function(event) { + var + toggleBehavior = (module.is.multiple()) + ? module.show + : module.toggle + ; + if(module.is.bubbledLabelClick(event) || module.is.bubbledIconClick(event)) { + return; + } + if( module.determine.eventOnElement(event, toggleBehavior) ) { + event.preventDefault(); + } + }, + touch: function(event) { + module.determine.eventOnElement(event, function() { + if(event.type == 'touchstart') { + module.timer = setTimeout(function() { + module.hide(); + }, settings.delay.touch); + } + else if(event.type == 'touchmove') { + clearTimeout(module.timer); + } + }); + event.stopPropagation(); + }, + hide: function(event) { + if(module.determine.eventInModule(event, module.hide)){ + if(element.id && $(event.target).attr('for') === element.id){ + event.preventDefault(); + } + } + } + }, + class: { + mutation: function(mutations) { + mutations.forEach(function(mutation) { + if(mutation.attributeName === "class") { + module.check.disabled(); + } + }); + } + }, + select: { + mutation: function(mutations) { + module.debug('<select> modified, recreating menu'); + if(module.is.selectMutation(mutations)) { + module.disconnect.selectObserver(); + module.refresh(); + module.setup.select(); + module.set.selected(); + module.observe.select(); + } + } + }, + menu: { + mutation: function(mutations) { + var + mutation = mutations[0], + $addedNode = mutation.addedNodes + ? $(mutation.addedNodes[0]) + : $(false), + $removedNode = mutation.removedNodes + ? $(mutation.removedNodes[0]) + : $(false), + $changedNodes = $addedNode.add($removedNode), + isUserAddition = $changedNodes.is(selector.addition) || $changedNodes.closest(selector.addition).length > 0, + isMessage = $changedNodes.is(selector.message) || $changedNodes.closest(selector.message).length > 0 + ; + if(isUserAddition || isMessage) { + module.debug('Updating item selector cache'); + module.refreshItems(); + } + else { + module.debug('Menu modified, updating selector cache'); + module.refresh(); + } + }, + mousedown: function() { + itemActivated = true; + }, + mouseup: function() { + itemActivated = false; + } + }, + item: { + mouseenter: function(event) { + var + $target = $(event.target), + $item = $(this), + $subMenu = $item.children(selector.menu), + $otherMenus = $item.siblings(selector.item).children(selector.menu), + hasSubMenu = ($subMenu.length > 0), + isBubbledEvent = ($subMenu.find($target).length > 0) + ; + if( !isBubbledEvent && hasSubMenu ) { + clearTimeout(module.itemTimer); + module.itemTimer = setTimeout(function() { + module.verbose('Showing sub-menu', $subMenu); + $.each($otherMenus, function() { + module.animate.hide(false, $(this)); + }); + module.animate.show(false, $subMenu); + }, settings.delay.show); + event.preventDefault(); + } + }, + mouseleave: function(event) { + var + $subMenu = $(this).children(selector.menu) + ; + if($subMenu.length > 0) { + clearTimeout(module.itemTimer); + module.itemTimer = setTimeout(function() { + module.verbose('Hiding sub-menu', $subMenu); + module.animate.hide(false, $subMenu); + }, settings.delay.hide); + } + }, + click: function (event, skipRefocus) { + var + $choice = $(this), + $target = (event) + ? $(event.target) + : $(''), + $subMenu = $choice.find(selector.menu), + text = module.get.choiceText($choice), + value = module.get.choiceValue($choice, text), + hasSubMenu = ($subMenu.length > 0), + isBubbledEvent = ($subMenu.find($target).length > 0) + ; + // prevents IE11 bug where menu receives focus even though `tabindex=-1` + if (document.activeElement.tagName.toLowerCase() !== 'input') { + $(document.activeElement).blur(); + } + if(!isBubbledEvent && (!hasSubMenu || settings.allowCategorySelection)) { + if(module.is.searchSelection()) { + if(settings.allowAdditions) { + module.remove.userAddition(); + } + module.remove.searchTerm(); + if(!module.is.focusedOnSearch() && !(skipRefocus == true)) { + module.focusSearch(true); + } + } + if(!settings.useLabels) { + module.remove.filteredItem(); + module.set.scrollPosition($choice); + } + module.determine.selectAction.call(this, text, value); + } + } + }, + + document: { + // label selection should occur even when element has no focus + keydown: function(event) { + var + pressedKey = event.which, + isShortcutKey = module.is.inObject(pressedKey, keys) + ; + if(isShortcutKey) { + var + $label = $module.find(selector.label), + $activeLabel = $label.filter('.' + className.active), + activeValue = $activeLabel.data(metadata.value), + labelIndex = $label.index($activeLabel), + labelCount = $label.length, + hasActiveLabel = ($activeLabel.length > 0), + hasMultipleActive = ($activeLabel.length > 1), + isFirstLabel = (labelIndex === 0), + isLastLabel = (labelIndex + 1 == labelCount), + isSearch = module.is.searchSelection(), + isFocusedOnSearch = module.is.focusedOnSearch(), + isFocused = module.is.focused(), + caretAtStart = (isFocusedOnSearch && module.get.caretPosition(false) === 0), + isSelectedSearch = (caretAtStart && module.get.caretPosition(true) !== 0), + $nextLabel + ; + if(isSearch && !hasActiveLabel && !isFocusedOnSearch) { + return; + } + + if(pressedKey == keys.leftArrow) { + // activate previous label + if((isFocused || caretAtStart) && !hasActiveLabel) { + module.verbose('Selecting previous label'); + $label.last().addClass(className.active); + } + else if(hasActiveLabel) { + if(!event.shiftKey) { + module.verbose('Selecting previous label'); + $label.removeClass(className.active); + } + else { + module.verbose('Adding previous label to selection'); + } + if(isFirstLabel && !hasMultipleActive) { + $activeLabel.addClass(className.active); + } + else { + $activeLabel.prev(selector.siblingLabel) + .addClass(className.active) + .end() + ; + } + event.preventDefault(); + } + } + else if(pressedKey == keys.rightArrow) { + // activate first label + if(isFocused && !hasActiveLabel) { + $label.first().addClass(className.active); + } + // activate next label + if(hasActiveLabel) { + if(!event.shiftKey) { + module.verbose('Selecting next label'); + $label.removeClass(className.active); + } + else { + module.verbose('Adding next label to selection'); + } + if(isLastLabel) { + if(isSearch) { + if(!isFocusedOnSearch) { + module.focusSearch(); + } + else { + $label.removeClass(className.active); + } + } + else if(hasMultipleActive) { + $activeLabel.next(selector.siblingLabel).addClass(className.active); + } + else { + $activeLabel.addClass(className.active); + } + } + else { + $activeLabel.next(selector.siblingLabel).addClass(className.active); + } + event.preventDefault(); + } + } + else if(pressedKey == keys.deleteKey || pressedKey == keys.backspace) { + if(hasActiveLabel) { + module.verbose('Removing active labels'); + if(isLastLabel) { + if(isSearch && !isFocusedOnSearch) { + module.focusSearch(); + } + } + $activeLabel.last().next(selector.siblingLabel).addClass(className.active); + module.remove.activeLabels($activeLabel); + event.preventDefault(); + } + else if(caretAtStart && !isSelectedSearch && !hasActiveLabel && pressedKey == keys.backspace) { + module.verbose('Removing last label on input backspace'); + $activeLabel = $label.last().addClass(className.active); + module.remove.activeLabels($activeLabel); + } + } + else { + $activeLabel.removeClass(className.active); + } + } + } + }, + + keydown: function(event) { + var + pressedKey = event.which, + isShortcutKey = module.is.inObject(pressedKey, keys) + ; + if(isShortcutKey) { + var + $currentlySelected = $item.not(selector.unselectable).filter('.' + className.selected).eq(0), + $activeItem = $menu.children('.' + className.active).eq(0), + $selectedItem = ($currentlySelected.length > 0) + ? $currentlySelected + : $activeItem, + $visibleItems = ($selectedItem.length > 0) + ? $selectedItem.siblings(':not(.' + className.filtered +')').addBack() + : $menu.children(':not(.' + className.filtered +')'), + $subMenu = $selectedItem.children(selector.menu), + $parentMenu = $selectedItem.closest(selector.menu), + inVisibleMenu = ($parentMenu.hasClass(className.visible) || $parentMenu.hasClass(className.animating) || $parentMenu.parent(selector.menu).length > 0), + hasSubMenu = ($subMenu.length> 0), + hasSelectedItem = ($selectedItem.length > 0), + selectedIsSelectable = ($selectedItem.not(selector.unselectable).length > 0), + delimiterPressed = (pressedKey == keys.delimiter && settings.allowAdditions && module.is.multiple()), + isAdditionWithoutMenu = (settings.allowAdditions && settings.hideAdditions && (pressedKey == keys.enter || delimiterPressed) && selectedIsSelectable), + $nextItem, + isSubMenuItem, + newIndex + ; + // allow selection with menu closed + if(isAdditionWithoutMenu) { + module.verbose('Selecting item from keyboard shortcut', $selectedItem); + module.event.item.click.call($selectedItem, event); + if(module.is.searchSelection()) { + module.remove.searchTerm(); + } + if(module.is.multiple()){ + event.preventDefault(); + } + } + + // visible menu keyboard shortcuts + if( module.is.visible() ) { + + // enter (select or open sub-menu) + if(pressedKey == keys.enter || delimiterPressed) { + if(pressedKey == keys.enter && hasSelectedItem && hasSubMenu && !settings.allowCategorySelection) { + module.verbose('Pressed enter on unselectable category, opening sub menu'); + pressedKey = keys.rightArrow; + } + else if(selectedIsSelectable) { + module.verbose('Selecting item from keyboard shortcut', $selectedItem); + module.event.item.click.call($selectedItem, event); + if(module.is.searchSelection()) { + module.remove.searchTerm(); + if(module.is.multiple()) { + $search.focus(); + } + } + } + event.preventDefault(); + } + + // sub-menu actions + if(hasSelectedItem) { + + if(pressedKey == keys.leftArrow) { + + isSubMenuItem = ($parentMenu[0] !== $menu[0]); + + if(isSubMenuItem) { + module.verbose('Left key pressed, closing sub-menu'); + module.animate.hide(false, $parentMenu); + $selectedItem + .removeClass(className.selected) + ; + $parentMenu + .closest(selector.item) + .addClass(className.selected) + ; + event.preventDefault(); + } + } + + // right arrow (show sub-menu) + if(pressedKey == keys.rightArrow) { + if(hasSubMenu) { + module.verbose('Right key pressed, opening sub-menu'); + module.animate.show(false, $subMenu); + $selectedItem + .removeClass(className.selected) + ; + $subMenu + .find(selector.item).eq(0) + .addClass(className.selected) + ; + event.preventDefault(); + } + } + } + + // up arrow (traverse menu up) + if(pressedKey == keys.upArrow) { + $nextItem = (hasSelectedItem && inVisibleMenu) + ? $selectedItem.prevAll(selector.item + ':not(' + selector.unselectable + ')').eq(0) + : $item.eq(0) + ; + if($visibleItems.index( $nextItem ) < 0) { + module.verbose('Up key pressed but reached top of current menu'); + event.preventDefault(); + return; + } + else { + module.verbose('Up key pressed, changing active item'); + $selectedItem + .removeClass(className.selected) + ; + $nextItem + .addClass(className.selected) + ; + module.set.scrollPosition($nextItem); + if(settings.selectOnKeydown && module.is.single()) { + module.set.selectedItem($nextItem); + } + } + event.preventDefault(); + } + + // down arrow (traverse menu down) + if(pressedKey == keys.downArrow) { + $nextItem = (hasSelectedItem && inVisibleMenu) + ? $nextItem = $selectedItem.nextAll(selector.item + ':not(' + selector.unselectable + ')').eq(0) + : $item.eq(0) + ; + if($nextItem.length === 0) { + module.verbose('Down key pressed but reached bottom of current menu'); + event.preventDefault(); + return; + } + else { + module.verbose('Down key pressed, changing active item'); + $item + .removeClass(className.selected) + ; + $nextItem + .addClass(className.selected) + ; + module.set.scrollPosition($nextItem); + if(settings.selectOnKeydown && module.is.single()) { + module.set.selectedItem($nextItem); + } + } + event.preventDefault(); + } + + // page down (show next page) + if(pressedKey == keys.pageUp) { + module.scrollPage('up'); + event.preventDefault(); + } + if(pressedKey == keys.pageDown) { + module.scrollPage('down'); + event.preventDefault(); + } + + // escape (close menu) + if(pressedKey == keys.escape) { + module.verbose('Escape key pressed, closing dropdown'); + module.hide(); + } + + } + else { + // delimiter key + if(delimiterPressed) { + event.preventDefault(); + } + // down arrow (open menu) + if(pressedKey == keys.downArrow && !module.is.visible()) { + module.verbose('Down key pressed, showing dropdown'); + module.show(); + event.preventDefault(); + } + } + } + else { + if( !module.has.search() ) { + module.set.selectedLetter( String.fromCharCode(pressedKey) ); + } + } + } + }, + + trigger: { + change: function() { + var + inputElement = $input[0] + ; + if(inputElement) { + var events = document.createEvent('HTMLEvents'); + module.verbose('Triggering native change event'); + events.initEvent('change', true, false); + inputElement.dispatchEvent(events); + } + } + }, + + determine: { + selectAction: function(text, value) { + selectActionActive = true; + module.verbose('Determining action', settings.action); + if( $.isFunction( module.action[settings.action] ) ) { + module.verbose('Triggering preset action', settings.action, text, value); + module.action[ settings.action ].call(element, text, value, this); + } + else if( $.isFunction(settings.action) ) { + module.verbose('Triggering user action', settings.action, text, value); + settings.action.call(element, text, value, this); + } + else { + module.error(error.action, settings.action); + } + selectActionActive = false; + }, + eventInModule: function(event, callback) { + var + $target = $(event.target), + inDocument = ($target.closest(document.documentElement).length > 0), + inModule = ($target.closest($module).length > 0) + ; + callback = $.isFunction(callback) + ? callback + : function(){} + ; + if(inDocument && !inModule) { + module.verbose('Triggering event', callback); + callback(); + return true; + } + else { + module.verbose('Event occurred in dropdown, canceling callback'); + return false; + } + }, + eventOnElement: function(event, callback) { + var + $target = $(event.target), + $label = $target.closest(selector.siblingLabel), + inVisibleDOM = document.body.contains(event.target), + notOnLabel = ($module.find($label).length === 0 || !(module.is.multiple() && settings.useLabels)), + notInMenu = ($target.closest($menu).length === 0) + ; + callback = $.isFunction(callback) + ? callback + : function(){} + ; + if(inVisibleDOM && notOnLabel && notInMenu) { + module.verbose('Triggering event', callback); + callback(); + return true; + } + else { + module.verbose('Event occurred in dropdown menu, canceling callback'); + return false; + } + } + }, + + action: { + + nothing: function() {}, + + activate: function(text, value, element) { + value = (value !== undefined) + ? value + : text + ; + if( module.can.activate( $(element) ) ) { + module.set.selected(value, $(element)); + if(!module.is.multiple()) { + module.hideAndClear(); + } + } + }, + + select: function(text, value, element) { + value = (value !== undefined) + ? value + : text + ; + if( module.can.activate( $(element) ) ) { + module.set.value(value, text, $(element)); + if(!module.is.multiple()) { + module.hideAndClear(); + } + } + }, + + combo: function(text, value, element) { + value = (value !== undefined) + ? value + : text + ; + module.set.selected(value, $(element)); + module.hideAndClear(); + }, + + hide: function(text, value, element) { + module.set.value(value, text, $(element)); + module.hideAndClear(); + } + + }, + + get: { + id: function() { + return id; + }, + defaultText: function() { + return $module.data(metadata.defaultText); + }, + defaultValue: function() { + return $module.data(metadata.defaultValue); + }, + placeholderText: function() { + if(settings.placeholder != 'auto' && typeof settings.placeholder == 'string') { + return settings.placeholder; + } + return $module.data(metadata.placeholderText) || ''; + }, + text: function() { + return settings.preserveHTML ? $text.html() : $text.text(); + }, + query: function() { + return String($search.val()).trim(); + }, + searchWidth: function(value) { + value = (value !== undefined) + ? value + : $search.val() + ; + $sizer.text(value); + // prevent rounding issues + return Math.ceil( $sizer.width() + 1); + }, + selectionCount: function() { + var + values = module.get.values(), + count + ; + count = ( module.is.multiple() ) + ? Array.isArray(values) + ? values.length + : 0 + : (module.get.value() !== '') + ? 1 + : 0 + ; + return count; + }, + transition: function($subMenu) { + return (settings.transition == 'auto') + ? module.is.upward($subMenu) + ? 'slide up' + : 'slide down' + : settings.transition + ; + }, + userValues: function() { + var + values = module.get.values() + ; + if(!values) { + return false; + } + values = Array.isArray(values) + ? values + : [values] + ; + return $.grep(values, function(value) { + return (module.get.item(value) === false); + }); + }, + uniqueArray: function(array) { + return $.grep(array, function (value, index) { + return $.inArray(value, array) === index; + }); + }, + caretPosition: function(returnEndPos) { + var + input = $search.get(0), + range, + rangeLength + ; + if(returnEndPos && 'selectionEnd' in input){ + return input.selectionEnd; + } + else if(!returnEndPos && 'selectionStart' in input) { + return input.selectionStart; + } + if (document.selection) { + input.focus(); + range = document.selection.createRange(); + rangeLength = range.text.length; + if(returnEndPos) { + return rangeLength; + } + range.moveStart('character', -input.value.length); + return range.text.length - rangeLength; + } + }, + value: function() { + var + value = ($input.length > 0) + ? $input.val() + : $module.data(metadata.value), + isEmptyMultiselect = (Array.isArray(value) && value.length === 1 && value[0] === '') + ; + // prevents placeholder element from being selected when multiple + return (value === undefined || isEmptyMultiselect) + ? '' + : value + ; + }, + values: function() { + var + value = module.get.value() + ; + if(value === '') { + return ''; + } + return ( !module.has.selectInput() && module.is.multiple() ) + ? (typeof value == 'string') // delimited string + ? module.escape.htmlEntities(value).split(settings.delimiter) + : '' + : value + ; + }, + remoteValues: function() { + var + values = module.get.values(), + remoteValues = false + ; + if(values) { + if(typeof values == 'string') { + values = [values]; + } + $.each(values, function(index, value) { + var + name = module.read.remoteData(value) + ; + module.verbose('Restoring value from session data', name, value); + if(name) { + if(!remoteValues) { + remoteValues = {}; + } + remoteValues[value] = name; + } + }); + } + return remoteValues; + }, + choiceText: function($choice, preserveHTML) { + preserveHTML = (preserveHTML !== undefined) + ? preserveHTML + : settings.preserveHTML + ; + if($choice) { + if($choice.find(selector.menu).length > 0) { + module.verbose('Retrieving text of element with sub-menu'); + $choice = $choice.clone(); + $choice.find(selector.menu).remove(); + $choice.find(selector.menuIcon).remove(); + } + return ($choice.data(metadata.text) !== undefined) + ? $choice.data(metadata.text) + : (preserveHTML) + ? $choice.html().trim() + : $choice.text().trim() + ; + } + }, + choiceValue: function($choice, choiceText) { + choiceText = choiceText || module.get.choiceText($choice); + if(!$choice) { + return false; + } + return ($choice.data(metadata.value) !== undefined) + ? String( $choice.data(metadata.value) ) + : (typeof choiceText === 'string') + ? String( + settings.ignoreSearchCase + ? choiceText.toLowerCase() + : choiceText + ).trim() + : String(choiceText) + ; + }, + inputEvent: function() { + var + input = $search[0] + ; + if(input) { + return (input.oninput !== undefined) + ? 'input' + : (input.onpropertychange !== undefined) + ? 'propertychange' + : 'keyup' + ; + } + return false; + }, + selectValues: function() { + var + select = {}, + oldGroup = [], + values = [] + ; + $module + .find('option') + .each(function() { + var + $option = $(this), + name = $option.html(), + disabled = $option.attr('disabled'), + value = ( $option.attr('value') !== undefined ) + ? $option.attr('value') + : name, + text = ( $option.data(metadata.text) !== undefined ) + ? $option.data(metadata.text) + : name, + group = $option.parent('optgroup') + ; + if(settings.placeholder === 'auto' && value === '') { + select.placeholder = name; + } + else { + if(group.length !== oldGroup.length || group[0] !== oldGroup[0]) { + values.push({ + type: 'header', + divider: settings.headerDivider, + name: group.attr('label') || '' + }); + oldGroup = group; + } + values.push({ + name : name, + value : value, + text : text, + disabled : disabled + }); + } + }) + ; + if(settings.placeholder && settings.placeholder !== 'auto') { + module.debug('Setting placeholder value to', settings.placeholder); + select.placeholder = settings.placeholder; + } + if(settings.sortSelect) { + if(settings.sortSelect === true) { + values.sort(function(a, b) { + return a.name.localeCompare(b.name); + }); + } else if(settings.sortSelect === 'natural') { + values.sort(function(a, b) { + return (a.name.toLowerCase().localeCompare(b.name.toLowerCase())); + }); + } else if($.isFunction(settings.sortSelect)) { + values.sort(settings.sortSelect); + } + select[fields.values] = values; + module.debug('Retrieved and sorted values from select', select); + } + else { + select[fields.values] = values; + module.debug('Retrieved values from select', select); + } + return select; + }, + activeItem: function() { + return $item.filter('.' + className.active); + }, + selectedItem: function() { + var + $selectedItem = $item.not(selector.unselectable).filter('.' + className.selected) + ; + return ($selectedItem.length > 0) + ? $selectedItem + : $item.eq(0) + ; + }, + itemWithAdditions: function(value) { + var + $items = module.get.item(value), + $userItems = module.create.userChoice(value), + hasUserItems = ($userItems && $userItems.length > 0) + ; + if(hasUserItems) { + $items = ($items.length > 0) + ? $items.add($userItems) + : $userItems + ; + } + return $items; + }, + item: function(value, strict) { + var + $selectedItem = false, + shouldSearch, + isMultiple + ; + value = (value !== undefined) + ? value + : ( module.get.values() !== undefined) + ? module.get.values() + : module.get.text() + ; + isMultiple = (module.is.multiple() && Array.isArray(value)); + shouldSearch = (isMultiple) + ? (value.length > 0) + : (value !== undefined && value !== null) + ; + strict = (value === '' || value === false || value === true) + ? true + : strict || false + ; + if(shouldSearch) { + $item + .each(function() { + var + $choice = $(this), + optionText = module.get.choiceText($choice), + optionValue = module.get.choiceValue($choice, optionText) + ; + // safe early exit + if(optionValue === null || optionValue === undefined) { + return; + } + if(isMultiple) { + if($.inArray(module.escape.htmlEntities(String(optionValue)), value.map(function(v){return String(v);})) !== -1) { + $selectedItem = ($selectedItem) + ? $selectedItem.add($choice) + : $choice + ; + } + } + else if(strict) { + module.verbose('Ambiguous dropdown value using strict type check', $choice, value); + if( optionValue === value) { + $selectedItem = $choice; + return true; + } + } + else { + if(settings.ignoreCase) { + optionValue = optionValue.toLowerCase(); + value = value.toLowerCase(); + } + if(module.escape.htmlEntities(String(optionValue)) === module.escape.htmlEntities(String(value))) { + module.verbose('Found select item by value', optionValue, value); + $selectedItem = $choice; + return true; + } + } + }) + ; + } + return $selectedItem; + } + }, + + check: { + maxSelections: function(selectionCount) { + if(settings.maxSelections) { + selectionCount = (selectionCount !== undefined) + ? selectionCount + : module.get.selectionCount() + ; + if(selectionCount >= settings.maxSelections) { + module.debug('Maximum selection count reached'); + if(settings.useLabels) { + $item.addClass(className.filtered); + module.add.message(message.maxSelections); + } + return true; + } + else { + module.verbose('No longer at maximum selection count'); + module.remove.message(); + module.remove.filteredItem(); + if(module.is.searchSelection()) { + module.filterItems(); + } + return false; + } + } + return true; + }, + disabled: function(){ + $search.attr('tabindex',module.is.disabled() ? -1 : 0); + } + }, + + restore: { + defaults: function(preventChangeTrigger) { + module.clear(preventChangeTrigger); + module.restore.defaultText(); + module.restore.defaultValue(); + }, + defaultText: function() { + var + defaultText = module.get.defaultText(), + placeholderText = module.get.placeholderText + ; + if(defaultText === placeholderText) { + module.debug('Restoring default placeholder text', defaultText); + module.set.placeholderText(defaultText); + } + else { + module.debug('Restoring default text', defaultText); + module.set.text(defaultText); + } + }, + placeholderText: function() { + module.set.placeholderText(); + }, + defaultValue: function() { + var + defaultValue = module.get.defaultValue() + ; + if(defaultValue !== undefined) { + module.debug('Restoring default value', defaultValue); + if(defaultValue !== '') { + module.set.value(defaultValue); + module.set.selected(); + } + else { + module.remove.activeItem(); + module.remove.selectedItem(); + } + } + }, + labels: function() { + if(settings.allowAdditions) { + if(!settings.useLabels) { + module.error(error.labels); + settings.useLabels = true; + } + module.debug('Restoring selected values'); + module.create.userLabels(); + } + module.check.maxSelections(); + }, + selected: function() { + module.restore.values(); + if(module.is.multiple()) { + module.debug('Restoring previously selected values and labels'); + module.restore.labels(); + } + else { + module.debug('Restoring previously selected values'); + } + }, + values: function() { + // prevents callbacks from occurring on initial load + module.set.initialLoad(); + if(settings.apiSettings && settings.saveRemoteData && module.get.remoteValues()) { + module.restore.remoteValues(); + } + else { + module.set.selected(); + } + var value = module.get.value(); + if(value && value !== '' && !(Array.isArray(value) && value.length === 0)) { + $input.removeClass(className.noselection); + } else { + $input.addClass(className.noselection); + } + module.remove.initialLoad(); + }, + remoteValues: function() { + var + values = module.get.remoteValues() + ; + module.debug('Recreating selected from session data', values); + if(values) { + if( module.is.single() ) { + $.each(values, function(value, name) { + module.set.text(name); + }); + } + else { + $.each(values, function(value, name) { + module.add.label(value, name); + }); + } + } + } + }, + + read: { + remoteData: function(value) { + var + name + ; + if(window.Storage === undefined) { + module.error(error.noStorage); + return; + } + name = sessionStorage.getItem(value); + return (name !== undefined) + ? name + : false + ; + } + }, + + save: { + defaults: function() { + module.save.defaultText(); + module.save.placeholderText(); + module.save.defaultValue(); + }, + defaultValue: function() { + var + value = module.get.value() + ; + module.verbose('Saving default value as', value); + $module.data(metadata.defaultValue, value); + }, + defaultText: function() { + var + text = module.get.text() + ; + module.verbose('Saving default text as', text); + $module.data(metadata.defaultText, text); + }, + placeholderText: function() { + var + text + ; + if(settings.placeholder !== false && $text.hasClass(className.placeholder)) { + text = module.get.text(); + module.verbose('Saving placeholder text as', text); + $module.data(metadata.placeholderText, text); + } + }, + remoteData: function(name, value) { + if(window.Storage === undefined) { + module.error(error.noStorage); + return; + } + module.verbose('Saving remote data to session storage', value, name); + sessionStorage.setItem(value, name); + } + }, + + clear: function(preventChangeTrigger) { + if(module.is.multiple() && settings.useLabels) { + module.remove.labels(); + } + else { + module.remove.activeItem(); + module.remove.selectedItem(); + module.remove.filteredItem(); + } + module.set.placeholderText(); + module.clearValue(preventChangeTrigger); + }, + + clearValue: function(preventChangeTrigger) { + module.set.value('', null, null, preventChangeTrigger); + }, + + scrollPage: function(direction, $selectedItem) { + var + $currentItem = $selectedItem || module.get.selectedItem(), + $menu = $currentItem.closest(selector.menu), + menuHeight = $menu.outerHeight(), + currentScroll = $menu.scrollTop(), + itemHeight = $item.eq(0).outerHeight(), + itemsPerPage = Math.floor(menuHeight / itemHeight), + maxScroll = $menu.prop('scrollHeight'), + newScroll = (direction == 'up') + ? currentScroll - (itemHeight * itemsPerPage) + : currentScroll + (itemHeight * itemsPerPage), + $selectableItem = $item.not(selector.unselectable), + isWithinRange, + $nextSelectedItem, + elementIndex + ; + elementIndex = (direction == 'up') + ? $selectableItem.index($currentItem) - itemsPerPage + : $selectableItem.index($currentItem) + itemsPerPage + ; + isWithinRange = (direction == 'up') + ? (elementIndex >= 0) + : (elementIndex < $selectableItem.length) + ; + $nextSelectedItem = (isWithinRange) + ? $selectableItem.eq(elementIndex) + : (direction == 'up') + ? $selectableItem.first() + : $selectableItem.last() + ; + if($nextSelectedItem.length > 0) { + module.debug('Scrolling page', direction, $nextSelectedItem); + $currentItem + .removeClass(className.selected) + ; + $nextSelectedItem + .addClass(className.selected) + ; + if(settings.selectOnKeydown && module.is.single()) { + module.set.selectedItem($nextSelectedItem); + } + $menu + .scrollTop(newScroll) + ; + } + }, + + set: { + filtered: function() { + var + isMultiple = module.is.multiple(), + isSearch = module.is.searchSelection(), + isSearchMultiple = (isMultiple && isSearch), + searchValue = (isSearch) + ? module.get.query() + : '', + hasSearchValue = (typeof searchValue === 'string' && searchValue.length > 0), + searchWidth = module.get.searchWidth(), + valueIsSet = searchValue !== '' + ; + if(isMultiple && hasSearchValue) { + module.verbose('Adjusting input width', searchWidth, settings.glyphWidth); + $search.css('width', searchWidth); + } + if(hasSearchValue || (isSearchMultiple && valueIsSet)) { + module.verbose('Hiding placeholder text'); + $text.addClass(className.filtered); + } + else if(!isMultiple || (isSearchMultiple && !valueIsSet)) { + module.verbose('Showing placeholder text'); + $text.removeClass(className.filtered); + } + }, + empty: function() { + $module.addClass(className.empty); + }, + loading: function() { + $module.addClass(className.loading); + }, + placeholderText: function(text) { + text = text || module.get.placeholderText(); + module.debug('Setting placeholder text', text); + module.set.text(text); + $text.addClass(className.placeholder); + }, + tabbable: function() { + if( module.is.searchSelection() ) { + module.debug('Added tabindex to searchable dropdown'); + $search + .val('') + ; + module.check.disabled(); + $menu + .attr('tabindex', -1) + ; + } + else { + module.debug('Added tabindex to dropdown'); + if( $module.attr('tabindex') === undefined) { + $module + .attr('tabindex', 0) + ; + $menu + .attr('tabindex', -1) + ; + } + } + }, + initialLoad: function() { + module.verbose('Setting initial load'); + initialLoad = true; + }, + activeItem: function($item) { + if( settings.allowAdditions && $item.filter(selector.addition).length > 0 ) { + $item.addClass(className.filtered); + } + else { + $item.addClass(className.active); + } + }, + partialSearch: function(text) { + var + length = module.get.query().length + ; + $search.val( text.substr(0, length)); + }, + scrollPosition: function($item, forceScroll) { + var + edgeTolerance = 5, + $menu, + hasActive, + offset, + itemHeight, + itemOffset, + menuOffset, + menuScroll, + menuHeight, + abovePage, + belowPage + ; + + $item = $item || module.get.selectedItem(); + $menu = $item.closest(selector.menu); + hasActive = ($item && $item.length > 0); + forceScroll = (forceScroll !== undefined) + ? forceScroll + : false + ; + if(module.get.activeItem().length === 0){ + forceScroll = false; + } + if($item && $menu.length > 0 && hasActive) { + itemOffset = $item.position().top; + + $menu.addClass(className.loading); + menuScroll = $menu.scrollTop(); + menuOffset = $menu.offset().top; + itemOffset = $item.offset().top; + offset = menuScroll - menuOffset + itemOffset; + if(!forceScroll) { + menuHeight = $menu.height(); + belowPage = menuScroll + menuHeight < (offset + edgeTolerance); + abovePage = ((offset - edgeTolerance) < menuScroll); + } + module.debug('Scrolling to active item', offset); + if(forceScroll || abovePage || belowPage) { + $menu.scrollTop(offset); + } + $menu.removeClass(className.loading); + } + }, + text: function(text) { + if(settings.action === 'combo') { + module.debug('Changing combo button text', text, $combo); + if(settings.preserveHTML) { + $combo.html(text); + } + else { + $combo.text(text); + } + } + else if(settings.action === 'activate') { + if(text !== module.get.placeholderText()) { + $text.removeClass(className.placeholder); + } + module.debug('Changing text', text, $text); + $text + .removeClass(className.filtered) + ; + if(settings.preserveHTML) { + $text.html(text); + } + else { + $text.text(text); + } + } + }, + selectedItem: function($item) { + var + value = module.get.choiceValue($item), + searchText = module.get.choiceText($item, false), + text = module.get.choiceText($item, true) + ; + module.debug('Setting user selection to item', $item); + module.remove.activeItem(); + module.set.partialSearch(searchText); + module.set.activeItem($item); + module.set.selected(value, $item); + module.set.text(text); + }, + selectedLetter: function(letter) { + var + $selectedItem = $item.filter('.' + className.selected), + alreadySelectedLetter = $selectedItem.length > 0 && module.has.firstLetter($selectedItem, letter), + $nextValue = false, + $nextItem + ; + // check next of same letter + if(alreadySelectedLetter) { + $nextItem = $selectedItem.nextAll($item).eq(0); + if( module.has.firstLetter($nextItem, letter) ) { + $nextValue = $nextItem; + } + } + // check all values + if(!$nextValue) { + $item + .each(function(){ + if(module.has.firstLetter($(this), letter)) { + $nextValue = $(this); + return false; + } + }) + ; + } + // set next value + if($nextValue) { + module.verbose('Scrolling to next value with letter', letter); + module.set.scrollPosition($nextValue); + $selectedItem.removeClass(className.selected); + $nextValue.addClass(className.selected); + if(settings.selectOnKeydown && module.is.single()) { + module.set.selectedItem($nextValue); + } + } + }, + direction: function($menu) { + if(settings.direction == 'auto') { + // reset position, remove upward if it's base menu + if (!$menu) { + module.remove.upward(); + } else if (module.is.upward($menu)) { + //we need make sure when make assertion openDownward for $menu, $menu does not have upward class + module.remove.upward($menu); + } + + if(module.can.openDownward($menu)) { + module.remove.upward($menu); + } + else { + module.set.upward($menu); + } + if(!module.is.leftward($menu) && !module.can.openRightward($menu)) { + module.set.leftward($menu); + } + } + else if(settings.direction == 'upward') { + module.set.upward($menu); + } + }, + upward: function($currentMenu) { + var $element = $currentMenu || $module; + $element.addClass(className.upward); + }, + leftward: function($currentMenu) { + var $element = $currentMenu || $menu; + $element.addClass(className.leftward); + }, + value: function(value, text, $selected, preventChangeTrigger) { + if(value !== undefined && value !== '' && !(Array.isArray(value) && value.length === 0)) { + $input.removeClass(className.noselection); + } else { + $input.addClass(className.noselection); + } + var + escapedValue = module.escape.value(value), + hasInput = ($input.length > 0), + currentValue = module.get.values(), + stringValue = (value !== undefined) + ? String(value) + : value, + newValue + ; + if(hasInput) { + if(!settings.allowReselection && stringValue == currentValue) { + module.verbose('Skipping value update already same value', value, currentValue); + if(!module.is.initialLoad()) { + return; + } + } + + if( module.is.single() && module.has.selectInput() && module.can.extendSelect() ) { + module.debug('Adding user option', value); + module.add.optionValue(value); + } + module.debug('Updating input value', escapedValue, currentValue); + internalChange = true; + $input + .val(escapedValue) + ; + if(settings.fireOnInit === false && module.is.initialLoad()) { + module.debug('Input native change event ignored on initial load'); + } + else if(preventChangeTrigger !== true) { + module.trigger.change(); + } + internalChange = false; + } + else { + module.verbose('Storing value in metadata', escapedValue, $input); + if(escapedValue !== currentValue) { + $module.data(metadata.value, stringValue); + } + } + if(settings.fireOnInit === false && module.is.initialLoad()) { + module.verbose('No callback on initial load', settings.onChange); + } + else if(preventChangeTrigger !== true) { + settings.onChange.call(element, value, text, $selected); + } + }, + active: function() { + $module + .addClass(className.active) + ; + }, + multiple: function() { + $module.addClass(className.multiple); + }, + visible: function() { + $module.addClass(className.visible); + }, + exactly: function(value, $selectedItem) { + module.debug('Setting selected to exact values'); + module.clear(); + module.set.selected(value, $selectedItem); + }, + selected: function(value, $selectedItem) { + var + isMultiple = module.is.multiple() + ; + $selectedItem = (settings.allowAdditions) + ? $selectedItem || module.get.itemWithAdditions(value) + : $selectedItem || module.get.item(value) + ; + if(!$selectedItem) { + return; + } + module.debug('Setting selected menu item to', $selectedItem); + if(module.is.multiple()) { + module.remove.searchWidth(); + } + if(module.is.single()) { + module.remove.activeItem(); + module.remove.selectedItem(); + } + else if(settings.useLabels) { + module.remove.selectedItem(); + } + // select each item + $selectedItem + .each(function() { + var + $selected = $(this), + selectedText = module.get.choiceText($selected), + selectedValue = module.get.choiceValue($selected, selectedText), + + isFiltered = $selected.hasClass(className.filtered), + isActive = $selected.hasClass(className.active), + isUserValue = $selected.hasClass(className.addition), + shouldAnimate = (isMultiple && $selectedItem.length == 1) + ; + if(isMultiple) { + if(!isActive || isUserValue) { + if(settings.apiSettings && settings.saveRemoteData) { + module.save.remoteData(selectedText, selectedValue); + } + if(settings.useLabels) { + module.add.label(selectedValue, selectedText, shouldAnimate); + module.add.value(selectedValue, selectedText, $selected); + module.set.activeItem($selected); + module.filterActive(); + module.select.nextAvailable($selectedItem); + } + else { + module.add.value(selectedValue, selectedText, $selected); + module.set.text(module.add.variables(message.count)); + module.set.activeItem($selected); + } + } + else if(!isFiltered && (settings.useLabels || selectActionActive)) { + module.debug('Selected active value, removing label'); + module.remove.selected(selectedValue); + } + } + else { + if(settings.apiSettings && settings.saveRemoteData) { + module.save.remoteData(selectedText, selectedValue); + } + module.set.text(selectedText); + module.set.value(selectedValue, selectedText, $selected); + $selected + .addClass(className.active) + .addClass(className.selected) + ; + } + }) + ; + module.remove.searchTerm(); + } + }, + + add: { + label: function(value, text, shouldAnimate) { + var + $next = module.is.searchSelection() + ? $search + : $text, + escapedValue = module.escape.value(value), + $label + ; + if(settings.ignoreCase) { + escapedValue = escapedValue.toLowerCase(); + } + $label = $('<a />') + .addClass(className.label) + .attr('data-' + metadata.value, escapedValue) + .html(templates.label(escapedValue, text, settings.preserveHTML, settings.className)) + ; + $label = settings.onLabelCreate.call($label, escapedValue, text); + + if(module.has.label(value)) { + module.debug('User selection already exists, skipping', escapedValue); + return; + } + if(settings.label.variation) { + $label.addClass(settings.label.variation); + } + if(shouldAnimate === true) { + module.debug('Animating in label', $label); + $label + .addClass(className.hidden) + .insertBefore($next) + .transition({ + animation : settings.label.transition, + debug : settings.debug, + verbose : settings.verbose, + duration : settings.label.duration + }) + ; + } + else { + module.debug('Adding selection label', $label); + $label + .insertBefore($next) + ; + } + }, + message: function(message) { + var + $message = $menu.children(selector.message), + html = settings.templates.message(module.add.variables(message)) + ; + if($message.length > 0) { + $message + .html(html) + ; + } + else { + $message = $('<div/>') + .html(html) + .addClass(className.message) + .appendTo($menu) + ; + } + }, + optionValue: function(value) { + var + escapedValue = module.escape.value(value), + $option = $input.find('option[value="' + module.escape.string(escapedValue) + '"]'), + hasOption = ($option.length > 0) + ; + if(hasOption) { + return; + } + // temporarily disconnect observer + module.disconnect.selectObserver(); + if( module.is.single() ) { + module.verbose('Removing previous user addition'); + $input.find('option.' + className.addition).remove(); + } + $('<option/>') + .prop('value', escapedValue) + .addClass(className.addition) + .html(value) + .appendTo($input) + ; + module.verbose('Adding user addition as an <option>', value); + module.observe.select(); + }, + userSuggestion: function(value) { + var + $addition = $menu.children(selector.addition), + $existingItem = module.get.item(value), + alreadyHasValue = $existingItem && $existingItem.not(selector.addition).length, + hasUserSuggestion = $addition.length > 0, + html + ; + if(settings.useLabels && module.has.maxSelections()) { + return; + } + if(value === '' || alreadyHasValue) { + $addition.remove(); + return; + } + if(hasUserSuggestion) { + $addition + .data(metadata.value, value) + .data(metadata.text, value) + .attr('data-' + metadata.value, value) + .attr('data-' + metadata.text, value) + .removeClass(className.filtered) + ; + if(!settings.hideAdditions) { + html = settings.templates.addition( module.add.variables(message.addResult, value) ); + $addition + .html(html) + ; + } + module.verbose('Replacing user suggestion with new value', $addition); + } + else { + $addition = module.create.userChoice(value); + $addition + .prependTo($menu) + ; + module.verbose('Adding item choice to menu corresponding with user choice addition', $addition); + } + if(!settings.hideAdditions || module.is.allFiltered()) { + $addition + .addClass(className.selected) + .siblings() + .removeClass(className.selected) + ; + } + module.refreshItems(); + }, + variables: function(message, term) { + var + hasCount = (message.search('{count}') !== -1), + hasMaxCount = (message.search('{maxCount}') !== -1), + hasTerm = (message.search('{term}') !== -1), + count, + query + ; + module.verbose('Adding templated variables to message', message); + if(hasCount) { + count = module.get.selectionCount(); + message = message.replace('{count}', count); + } + if(hasMaxCount) { + count = module.get.selectionCount(); + message = message.replace('{maxCount}', settings.maxSelections); + } + if(hasTerm) { + query = term || module.get.query(); + message = message.replace('{term}', query); + } + return message; + }, + value: function(addedValue, addedText, $selectedItem) { + var + currentValue = module.get.values(), + newValue + ; + if(module.has.value(addedValue)) { + module.debug('Value already selected'); + return; + } + if(addedValue === '') { + module.debug('Cannot select blank values from multiselect'); + return; + } + // extend current array + if(Array.isArray(currentValue)) { + newValue = currentValue.concat([addedValue]); + newValue = module.get.uniqueArray(newValue); + } + else { + newValue = [addedValue]; + } + // add values + if( module.has.selectInput() ) { + if(module.can.extendSelect()) { + module.debug('Adding value to select', addedValue, newValue, $input); + module.add.optionValue(addedValue); + } + } + else { + newValue = newValue.join(settings.delimiter); + module.debug('Setting hidden input to delimited value', newValue, $input); + } + + if(settings.fireOnInit === false && module.is.initialLoad()) { + module.verbose('Skipping onadd callback on initial load', settings.onAdd); + } + else { + settings.onAdd.call(element, addedValue, addedText, $selectedItem); + } + module.set.value(newValue, addedText, $selectedItem); + module.check.maxSelections(); + }, + }, + + remove: { + active: function() { + $module.removeClass(className.active); + }, + activeLabel: function() { + $module.find(selector.label).removeClass(className.active); + }, + empty: function() { + $module.removeClass(className.empty); + }, + loading: function() { + $module.removeClass(className.loading); + }, + initialLoad: function() { + initialLoad = false; + }, + upward: function($currentMenu) { + var $element = $currentMenu || $module; + $element.removeClass(className.upward); + }, + leftward: function($currentMenu) { + var $element = $currentMenu || $menu; + $element.removeClass(className.leftward); + }, + visible: function() { + $module.removeClass(className.visible); + }, + activeItem: function() { + $item.removeClass(className.active); + }, + filteredItem: function() { + if(settings.useLabels && module.has.maxSelections() ) { + return; + } + if(settings.useLabels && module.is.multiple()) { + $item.not('.' + className.active).removeClass(className.filtered); + } + else { + $item.removeClass(className.filtered); + } + if(settings.hideDividers) { + $divider.removeClass(className.hidden); + } + module.remove.empty(); + }, + optionValue: function(value) { + var + escapedValue = module.escape.value(value), + $option = $input.find('option[value="' + module.escape.string(escapedValue) + '"]'), + hasOption = ($option.length > 0) + ; + if(!hasOption || !$option.hasClass(className.addition)) { + return; + } + // temporarily disconnect observer + if(selectObserver) { + selectObserver.disconnect(); + module.verbose('Temporarily disconnecting mutation observer'); + } + $option.remove(); + module.verbose('Removing user addition as an <option>', escapedValue); + if(selectObserver) { + selectObserver.observe($input[0], { + childList : true, + subtree : true + }); + } + }, + message: function() { + $menu.children(selector.message).remove(); + }, + searchWidth: function() { + $search.css('width', ''); + }, + searchTerm: function() { + module.verbose('Cleared search term'); + $search.val(''); + module.set.filtered(); + }, + userAddition: function() { + $item.filter(selector.addition).remove(); + }, + selected: function(value, $selectedItem) { + $selectedItem = (settings.allowAdditions) + ? $selectedItem || module.get.itemWithAdditions(value) + : $selectedItem || module.get.item(value) + ; + + if(!$selectedItem) { + return false; + } + + $selectedItem + .each(function() { + var + $selected = $(this), + selectedText = module.get.choiceText($selected), + selectedValue = module.get.choiceValue($selected, selectedText) + ; + if(module.is.multiple()) { + if(settings.useLabels) { + module.remove.value(selectedValue, selectedText, $selected); + module.remove.label(selectedValue); + } + else { + module.remove.value(selectedValue, selectedText, $selected); + if(module.get.selectionCount() === 0) { + module.set.placeholderText(); + } + else { + module.set.text(module.add.variables(message.count)); + } + } + } + else { + module.remove.value(selectedValue, selectedText, $selected); + } + $selected + .removeClass(className.filtered) + .removeClass(className.active) + ; + if(settings.useLabels) { + $selected.removeClass(className.selected); + } + }) + ; + }, + selectedItem: function() { + $item.removeClass(className.selected); + }, + value: function(removedValue, removedText, $removedItem) { + var + values = module.get.values(), + newValue + ; + removedValue = module.escape.htmlEntities(removedValue); + if( module.has.selectInput() ) { + module.verbose('Input is <select> removing selected option', removedValue); + newValue = module.remove.arrayValue(removedValue, values); + module.remove.optionValue(removedValue); + } + else { + module.verbose('Removing from delimited values', removedValue); + newValue = module.remove.arrayValue(removedValue, values); + newValue = newValue.join(settings.delimiter); + } + if(settings.fireOnInit === false && module.is.initialLoad()) { + module.verbose('No callback on initial load', settings.onRemove); + } + else { + settings.onRemove.call(element, removedValue, removedText, $removedItem); + } + module.set.value(newValue, removedText, $removedItem); + module.check.maxSelections(); + }, + arrayValue: function(removedValue, values) { + if( !Array.isArray(values) ) { + values = [values]; + } + values = $.grep(values, function(value){ + return (removedValue != value); + }); + module.verbose('Removed value from delimited string', removedValue, values); + return values; + }, + label: function(value, shouldAnimate) { + var + $labels = $module.find(selector.label), + $removedLabel = $labels.filter('[data-' + metadata.value + '="' + module.escape.string(settings.ignoreCase ? value.toLowerCase() : value) +'"]') + ; + module.verbose('Removing label', $removedLabel); + $removedLabel.remove(); + }, + activeLabels: function($activeLabels) { + $activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active); + module.verbose('Removing active label selections', $activeLabels); + module.remove.labels($activeLabels); + }, + labels: function($labels) { + $labels = $labels || $module.find(selector.label); + module.verbose('Removing labels', $labels); + $labels + .each(function(){ + var + $label = $(this), + value = $label.data(metadata.value), + stringValue = (value !== undefined) + ? String(value) + : value, + isUserValue = module.is.userValue(stringValue) + ; + if(settings.onLabelRemove.call($label, value) === false) { + module.debug('Label remove callback cancelled removal'); + return; + } + module.remove.message(); + if(isUserValue) { + module.remove.value(stringValue); + module.remove.label(stringValue); + } + else { + // selected will also remove label + module.remove.selected(stringValue); + } + }) + ; + }, + tabbable: function() { + if( module.is.searchSelection() ) { + module.debug('Searchable dropdown initialized'); + $search + .removeAttr('tabindex') + ; + $menu + .removeAttr('tabindex') + ; + } + else { + module.debug('Simple selection dropdown initialized'); + $module + .removeAttr('tabindex') + ; + $menu + .removeAttr('tabindex') + ; + } + }, + diacritics: function(text) { + return settings.ignoreDiacritics ? text.normalize('NFD').replace(/[\u0300-\u036f]/g, '') : text; + } + }, + + has: { + menuSearch: function() { + return (module.has.search() && $search.closest($menu).length > 0); + }, + clearItem: function() { + return ($clear.length > 0); + }, + search: function() { + return ($search.length > 0); + }, + sizer: function() { + return ($sizer.length > 0); + }, + selectInput: function() { + return ( $input.is('select') ); + }, + minCharacters: function(searchTerm) { + if(settings.minCharacters && !iconClicked) { + searchTerm = (searchTerm !== undefined) + ? String(searchTerm) + : String(module.get.query()) + ; + return (searchTerm.length >= settings.minCharacters); + } + iconClicked=false; + return true; + }, + firstLetter: function($item, letter) { + var + text, + firstLetter + ; + if(!$item || $item.length === 0 || typeof letter !== 'string') { + return false; + } + text = module.get.choiceText($item, false); + letter = letter.toLowerCase(); + firstLetter = String(text).charAt(0).toLowerCase(); + return (letter == firstLetter); + }, + input: function() { + return ($input.length > 0); + }, + items: function() { + return ($item.length > 0); + }, + menu: function() { + return ($menu.length > 0); + }, + message: function() { + return ($menu.children(selector.message).length !== 0); + }, + label: function(value) { + var + escapedValue = module.escape.value(value), + $labels = $module.find(selector.label) + ; + if(settings.ignoreCase) { + escapedValue = escapedValue.toLowerCase(); + } + return ($labels.filter('[data-' + metadata.value + '="' + module.escape.string(escapedValue) +'"]').length > 0); + }, + maxSelections: function() { + return (settings.maxSelections && module.get.selectionCount() >= settings.maxSelections); + }, + allResultsFiltered: function() { + var + $normalResults = $item.not(selector.addition) + ; + return ($normalResults.filter(selector.unselectable).length === $normalResults.length); + }, + userSuggestion: function() { + return ($menu.children(selector.addition).length > 0); + }, + query: function() { + return (module.get.query() !== ''); + }, + value: function(value) { + return (settings.ignoreCase) + ? module.has.valueIgnoringCase(value) + : module.has.valueMatchingCase(value) + ; + }, + valueMatchingCase: function(value) { + var + values = module.get.values(), + hasValue = Array.isArray(values) + ? values && ($.inArray(value, values) !== -1) + : (values == value) + ; + return (hasValue) + ? true + : false + ; + }, + valueIgnoringCase: function(value) { + var + values = module.get.values(), + hasValue = false + ; + if(!Array.isArray(values)) { + values = [values]; + } + $.each(values, function(index, existingValue) { + if(String(value).toLowerCase() == String(existingValue).toLowerCase()) { + hasValue = true; + return false; + } + }); + return hasValue; + } + }, + + is: { + active: function() { + return $module.hasClass(className.active); + }, + animatingInward: function() { + return $menu.transition('is inward'); + }, + animatingOutward: function() { + return $menu.transition('is outward'); + }, + bubbledLabelClick: function(event) { + return $(event.target).is('select, input') && $module.closest('label').length > 0; + }, + bubbledIconClick: function(event) { + return $(event.target).closest($icon).length > 0; + }, + alreadySetup: function() { + return ($module.is('select') && $module.parent(selector.dropdown).data(moduleNamespace) !== undefined && $module.prev().length === 0); + }, + animating: function($subMenu) { + return ($subMenu) + ? $subMenu.transition && $subMenu.transition('is animating') + : $menu.transition && $menu.transition('is animating') + ; + }, + leftward: function($subMenu) { + var $selectedMenu = $subMenu || $menu; + return $selectedMenu.hasClass(className.leftward); + }, + clearable: function() { + return ($module.hasClass(className.clearable) || settings.clearable); + }, + disabled: function() { + return $module.hasClass(className.disabled); + }, + focused: function() { + return (document.activeElement === $module[0]); + }, + focusedOnSearch: function() { + return (document.activeElement === $search[0]); + }, + allFiltered: function() { + return( (module.is.multiple() || module.has.search()) && !(settings.hideAdditions == false && module.has.userSuggestion()) && !module.has.message() && module.has.allResultsFiltered() ); + }, + hidden: function($subMenu) { + return !module.is.visible($subMenu); + }, + initialLoad: function() { + return initialLoad; + }, + inObject: function(needle, object) { + var + found = false + ; + $.each(object, function(index, property) { + if(property == needle) { + found = true; + return true; + } + }); + return found; + }, + multiple: function() { + return $module.hasClass(className.multiple); + }, + remote: function() { + return settings.apiSettings && module.can.useAPI(); + }, + single: function() { + return !module.is.multiple(); + }, + selectMutation: function(mutations) { + var + selectChanged = false + ; + $.each(mutations, function(index, mutation) { + if($(mutation.target).is('select') || $(mutation.addedNodes).is('select')) { + selectChanged = true; + return false; + } + }); + return selectChanged; + }, + search: function() { + return $module.hasClass(className.search); + }, + searchSelection: function() { + return ( module.has.search() && $search.parent(selector.dropdown).length === 1 ); + }, + selection: function() { + return $module.hasClass(className.selection); + }, + userValue: function(value) { + return ($.inArray(value, module.get.userValues()) !== -1); + }, + upward: function($menu) { + var $element = $menu || $module; + return $element.hasClass(className.upward); + }, + visible: function($subMenu) { + return ($subMenu) + ? $subMenu.hasClass(className.visible) + : $menu.hasClass(className.visible) + ; + }, + verticallyScrollableContext: function() { + var + overflowY = ($context.get(0) !== window) + ? $context.css('overflow-y') + : false + ; + return (overflowY == 'auto' || overflowY == 'scroll'); + }, + horizontallyScrollableContext: function() { + var + overflowX = ($context.get(0) !== window) + ? $context.css('overflow-X') + : false + ; + return (overflowX == 'auto' || overflowX == 'scroll'); + } + }, + + can: { + activate: function($item) { + if(settings.useLabels) { + return true; + } + if(!module.has.maxSelections()) { + return true; + } + if(module.has.maxSelections() && $item.hasClass(className.active)) { + return true; + } + return false; + }, + openDownward: function($subMenu) { + var + $currentMenu = $subMenu || $menu, + canOpenDownward = true, + onScreen = {}, + calculations + ; + $currentMenu + .addClass(className.loading) + ; + calculations = { + context: { + offset : ($context.get(0) === window) + ? { top: 0, left: 0} + : $context.offset(), + scrollTop : $context.scrollTop(), + height : $context.outerHeight() + }, + menu : { + offset: $currentMenu.offset(), + height: $currentMenu.outerHeight() + } + }; + if(module.is.verticallyScrollableContext()) { + calculations.menu.offset.top += calculations.context.scrollTop; + } + onScreen = { + above : (calculations.context.scrollTop) <= calculations.menu.offset.top - calculations.context.offset.top - calculations.menu.height, + below : (calculations.context.scrollTop + calculations.context.height) >= calculations.menu.offset.top - calculations.context.offset.top + calculations.menu.height + }; + if(onScreen.below) { + module.verbose('Dropdown can fit in context downward', onScreen); + canOpenDownward = true; + } + else if(!onScreen.below && !onScreen.above) { + module.verbose('Dropdown cannot fit in either direction, favoring downward', onScreen); + canOpenDownward = true; + } + else { + module.verbose('Dropdown cannot fit below, opening upward', onScreen); + canOpenDownward = false; + } + $currentMenu.removeClass(className.loading); + return canOpenDownward; + }, + openRightward: function($subMenu) { + var + $currentMenu = $subMenu || $menu, + canOpenRightward = true, + isOffscreenRight = false, + calculations + ; + $currentMenu + .addClass(className.loading) + ; + calculations = { + context: { + offset : ($context.get(0) === window) + ? { top: 0, left: 0} + : $context.offset(), + scrollLeft : $context.scrollLeft(), + width : $context.outerWidth() + }, + menu: { + offset : $currentMenu.offset(), + width : $currentMenu.outerWidth() + } + }; + if(module.is.horizontallyScrollableContext()) { + calculations.menu.offset.left += calculations.context.scrollLeft; + } + isOffscreenRight = (calculations.menu.offset.left - calculations.context.offset.left + calculations.menu.width >= calculations.context.scrollLeft + calculations.context.width); + if(isOffscreenRight) { + module.verbose('Dropdown cannot fit in context rightward', isOffscreenRight); + canOpenRightward = false; + } + $currentMenu.removeClass(className.loading); + return canOpenRightward; + }, + click: function() { + return (hasTouch || settings.on == 'click'); + }, + extendSelect: function() { + return settings.allowAdditions || settings.apiSettings; + }, + show: function() { + return !module.is.disabled() && (module.has.items() || module.has.message()); + }, + useAPI: function() { + return $.fn.api !== undefined; + } + }, + + animate: { + show: function(callback, $subMenu) { + var + $currentMenu = $subMenu || $menu, + start = ($subMenu) + ? function() {} + : function() { + module.hideSubMenus(); + module.hideOthers(); + module.set.active(); + }, + transition + ; + callback = $.isFunction(callback) + ? callback + : function(){} + ; + module.verbose('Doing menu show animation', $currentMenu); + module.set.direction($subMenu); + transition = module.get.transition($subMenu); + if( module.is.selection() ) { + module.set.scrollPosition(module.get.selectedItem(), true); + } + if( module.is.hidden($currentMenu) || module.is.animating($currentMenu) ) { + var displayType = $module.hasClass('column') ? 'flex' : false; + if(transition == 'none') { + start(); + $currentMenu.transition({ + displayType: displayType + }).transition('show'); + callback.call(element); + } + else if($.fn.transition !== undefined && $module.transition('is supported')) { + $currentMenu + .transition({ + animation : transition + ' in', + debug : settings.debug, + verbose : settings.verbose, + duration : settings.duration, + queue : true, + onStart : start, + displayType: displayType, + onComplete : function() { + callback.call(element); + } + }) + ; + } + else { + module.error(error.noTransition, transition); + } + } + }, + hide: function(callback, $subMenu) { + var + $currentMenu = $subMenu || $menu, + start = ($subMenu) + ? function() {} + : function() { + if( module.can.click() ) { + module.unbind.intent(); + } + module.remove.active(); + }, + transition = module.get.transition($subMenu) + ; + callback = $.isFunction(callback) + ? callback + : function(){} + ; + if( module.is.visible($currentMenu) || module.is.animating($currentMenu) ) { + module.verbose('Doing menu hide animation', $currentMenu); + + if(transition == 'none') { + start(); + $currentMenu.transition('hide'); + callback.call(element); + } + else if($.fn.transition !== undefined && $module.transition('is supported')) { + $currentMenu + .transition({ + animation : transition + ' out', + duration : settings.duration, + debug : settings.debug, + verbose : settings.verbose, + queue : false, + onStart : start, + onComplete : function() { + callback.call(element); + } + }) + ; + } + else { + module.error(error.transition); + } + } + } + }, + + hideAndClear: function() { + module.remove.searchTerm(); + if( module.has.maxSelections() ) { + return; + } + if(module.has.search()) { + module.hide(function() { + module.remove.filteredItem(); + }); + } + else { + module.hide(); + } + }, + + delay: { + show: function() { + module.verbose('Delaying show event to ensure user intent'); + clearTimeout(module.timer); + module.timer = setTimeout(module.show, settings.delay.show); + }, + hide: function() { + module.verbose('Delaying hide event to ensure user intent'); + clearTimeout(module.timer); + module.timer = setTimeout(module.hide, settings.delay.hide); + } + }, + + escape: { + value: function(value) { + var + multipleValues = Array.isArray(value), + stringValue = (typeof value === 'string'), + isUnparsable = (!stringValue && !multipleValues), + hasQuotes = (stringValue && value.search(regExp.quote) !== -1), + values = [] + ; + if(isUnparsable || !hasQuotes) { + return value; + } + module.debug('Encoding quote values for use in select', value); + if(multipleValues) { + $.each(value, function(index, value){ + values.push(value.replace(regExp.quote, '"')); + }); + return values; + } + return value.replace(regExp.quote, '"'); + }, + string: function(text) { + text = String(text); + return text.replace(regExp.escape, '\\$&'); + }, + htmlEntities: function(string) { + var + badChars = /[<>"'`]/g, + shouldEscape = /[&<>"'`]/, + escape = { + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" + }, + escapedChar = function(chr) { + return escape[chr]; + } + ; + if(shouldEscape.test(string)) { + string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); + return string.replace(badChars, escapedChar); + } + return string; + } + }, + + setting: function(name, value) { + module.debug('Changing setting', name, value); + if( $.isPlainObject(name) ) { + $.extend(true, settings, name); + } + else if(value !== undefined) { + if($.isPlainObject(settings[name])) { + $.extend(true, settings[name], value); + } + else { + settings[name] = value; + } + } + else { + return settings[name]; + } + }, + internal: function(name, value) { + if( $.isPlainObject(name) ) { + $.extend(true, module, name); + } + else if(value !== undefined) { + module[name] = value; + } + else { + return module[name]; + } + }, + debug: function() { + if(!settings.silent && settings.debug) { + if(settings.performance) { + module.performance.log(arguments); + } + else { + module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); + module.debug.apply(console, arguments); + } + } + }, + verbose: function() { + if(!settings.silent && settings.verbose && settings.debug) { + if(settings.performance) { + module.performance.log(arguments); + } + else { + module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); + module.verbose.apply(console, arguments); + } + } + }, + error: function() { + if(!settings.silent) { + module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); + module.error.apply(console, arguments); + } + }, + performance: { + log: function(message) { + var + currentTime, + executionTime, + previousTime + ; + if(settings.performance) { + currentTime = new Date().getTime(); + previousTime = time || currentTime; + executionTime = currentTime - previousTime; + time = currentTime; + performance.push({ + 'Name' : message[0], + 'Arguments' : [].slice.call(message, 1) || '', + 'Element' : element, + 'Execution Time' : executionTime + }); + } + clearTimeout(module.performance.timer); + module.performance.timer = setTimeout(module.performance.display, 500); + }, + display: function() { + var + title = settings.name + ':', + totalTime = 0 + ; + time = false; + clearTimeout(module.performance.timer); + $.each(performance, function(index, data) { + totalTime += data['Execution Time']; + }); + title += ' ' + totalTime + 'ms'; + if(moduleSelector) { + title += ' \'' + moduleSelector + '\''; + } + if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { + console.groupCollapsed(title); + if(console.table) { + console.table(performance); + } + else { + $.each(performance, function(index, data) { + console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); + }); + } + console.groupEnd(); + } + performance = []; + } + }, + invoke: function(query, passedArguments, context) { + var + object = instance, + maxDepth, + found, + response + ; + passedArguments = passedArguments || queryArguments; + context = element || context; + if(typeof query == 'string' && object !== undefined) { + query = query.split(/[\. ]/); + maxDepth = query.length - 1; + $.each(query, function(depth, value) { + var camelCaseValue = (depth != maxDepth) + ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) + : query + ; + if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { + object = object[camelCaseValue]; + } + else if( object[camelCaseValue] !== undefined ) { + found = object[camelCaseValue]; + return false; + } + else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { + object = object[value]; + } + else if( object[value] !== undefined ) { + found = object[value]; + return false; + } + else { + module.error(error.method, query); + return false; + } + }); + } + if ( $.isFunction( found ) ) { + response = found.apply(context, passedArguments); + } + else if(found !== undefined) { + response = found; + } + if(Array.isArray(returnedValue)) { + returnedValue.push(response); + } + else if(returnedValue !== undefined) { + returnedValue = [returnedValue, response]; + } + else if(response !== undefined) { + returnedValue = response; + } + return found; + } + }; + + if(methodInvoked) { + if(instance === undefined) { + module.initialize(); + } + module.invoke(query); + } + else { + if(instance !== undefined) { + instance.invoke('destroy'); + } + module.initialize(); + } + }) + ; + return (returnedValue !== undefined) + ? returnedValue + : $allModules + ; +}; + +$.fn.dropdown.settings = { + + silent : false, + debug : false, + verbose : false, + performance : true, + + on : 'click', // what event should show menu action on item selection + action : 'activate', // action on item selection (nothing, activate, select, combo, hide, function(){}) + + values : false, // specify values to use for dropdown + + clearable : false, // whether the value of the dropdown can be cleared + + apiSettings : false, + selectOnKeydown : true, // Whether selection should occur automatically when keyboard shortcuts used + minCharacters : 0, // Minimum characters required to trigger API call + + filterRemoteData : false, // Whether API results should be filtered after being returned for query term + saveRemoteData : true, // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh + + throttle : 200, // How long to wait after last user input to search remotely + + context : window, // Context to use when determining if on screen + direction : 'auto', // Whether dropdown should always open in one direction + keepOnScreen : true, // Whether dropdown should check whether it is on screen before showing + + match : 'both', // what to match against with search selection (both, text, or label) + fullTextSearch : false, // search anywhere in value (set to 'exact' to require exact matches) + ignoreDiacritics : false, // match results also if they contain diacritics of the same base character (for example searching for "a" will also match "á" or "â" or "à", etc...) + hideDividers : false, // Whether to hide any divider elements (specified in selector.divider) that are sibling to any items when searched (set to true will hide all dividers, set to 'empty' will hide them when they are not followed by a visible item) + + placeholder : 'auto', // whether to convert blank <select> values to placeholder text + preserveHTML : true, // preserve html when selecting value + sortSelect : false, // sort selection on init + + forceSelection : true, // force a choice on blur with search selection + + allowAdditions : false, // whether multiple select should allow user added values + ignoreCase : false, // whether to consider case sensitivity when creating labels + ignoreSearchCase : true, // whether to consider case sensitivity when filtering items + hideAdditions : true, // whether or not to hide special message prompting a user they can enter a value + + maxSelections : false, // When set to a number limits the number of selections to this count + useLabels : true, // whether multiple select should filter currently active selections from choices + delimiter : ',', // when multiselect uses normal <input> the values will be delimited with this character + + showOnFocus : true, // show menu on focus + allowReselection : false, // whether current value should trigger callbacks when reselected + allowTab : true, // add tabindex to element + allowCategorySelection : false, // allow elements with sub-menus to be selected + + fireOnInit : false, // Whether callbacks should fire when initializing dropdown values + + transition : 'auto', // auto transition will slide down or up based on direction + duration : 200, // duration of transition + + glyphWidth : 1.037, // widest glyph width in em (W is 1.037 em) used to calculate multiselect input width + + headerDivider : true, // whether option headers should have an additional divider line underneath when converted from <select> <optgroup> + + // label settings on multi-select + label: { + transition : 'scale', + duration : 200, + variation : false + }, + + // delay before event + delay : { + hide : 300, + show : 200, + search : 20, + touch : 50 + }, + + /* Callbacks */ + onChange : function(value, text, $selected){}, + onAdd : function(value, text, $selected){}, + onRemove : function(value, text, $selected){}, + + onLabelSelect : function($selectedLabels){}, + onLabelCreate : function(value, text) { return $(this); }, + onLabelRemove : function(value) { return true; }, + onNoResults : function(searchTerm) { return true; }, + onShow : function(){}, + onHide : function(){}, + + /* Component */ + name : 'Dropdown', + namespace : 'dropdown', + + message: { + addResult : 'Add <b>{term}</b>', + count : '{count} selected', + maxSelections : 'Max {maxCount} selections', + noResults : 'No results found.', + serverError : 'There was an error contacting the server' + }, + + error : { + action : 'You called a dropdown action that was not defined', + alreadySetup : 'Once a select has been initialized behaviors must be called on the created ui dropdown', + labels : 'Allowing user additions currently requires the use of labels.', + missingMultiple : '<select> requires multiple property to be set to correctly preserve multiple values', + method : 'The method you called is not defined.', + noAPI : 'The API module is required to load resources remotely', + noStorage : 'Saving remote data requires session storage', + noTransition : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>', + noNormalize : '"ignoreDiacritics" setting will be ignored. Browser does not support String().normalize(). You may consider including <https://cdn.jsdelivr.net/npm/unorm@1.4.1/lib/unorm.min.js> as a polyfill.' + }, + + regExp : { + escape : /[-[\]{}()*+?.,\\^$|#\s:=@]/g, + quote : /"/g + }, + + metadata : { + defaultText : 'defaultText', + defaultValue : 'defaultValue', + placeholderText : 'placeholder', + text : 'text', + value : 'value' + }, + + // property names for remote query + fields: { + remoteValues : 'results', // grouping for api results + values : 'values', // grouping for all dropdown values + disabled : 'disabled', // whether value should be disabled + name : 'name', // displayed dropdown text + value : 'value', // actual dropdown value + text : 'text', // displayed text when selected + type : 'type', // type of dropdown element + image : 'image', // optional image path + imageClass : 'imageClass', // optional individual class for image + icon : 'icon', // optional icon name + iconClass : 'iconClass', // optional individual class for icon (for example to use flag instead) + class : 'class', // optional individual class for item/header + divider : 'divider' // optional divider append for group headers + }, + + keys : { + backspace : 8, + delimiter : 188, // comma + deleteKey : 46, + enter : 13, + escape : 27, + pageUp : 33, + pageDown : 34, + leftArrow : 37, + upArrow : 38, + rightArrow : 39, + downArrow : 40 + }, + + selector : { + addition : '.addition', + divider : '.divider, .header', + dropdown : '.ui.dropdown', + hidden : '.hidden', + icon : '> .dropdown.icon', + input : '> input[type="hidden"], > select', + item : '.item', + label : '> .label', + remove : '> .label > .delete.icon', + siblingLabel : '.label', + menu : '.menu', + message : '.message', + menuIcon : '.dropdown.icon', + search : 'input.search, .menu > .search > input, .menu input.search', + sizer : '> span.sizer', + text : '> .text:not(.icon)', + unselectable : '.disabled, .filtered, .tw-hidden', // GITEA-PATCH: tw-hidden hides the item so it is also unselectable + clearIcon : '> .remove.icon' + }, + + className : { + active : 'active', + addition : 'addition', + animating : 'animating', + disabled : 'disabled', + empty : 'empty', + dropdown : 'ui dropdown', + filtered : 'filtered', + hidden : 'hidden transition', + icon : 'icon', + image : 'image', + item : 'item', + label : 'ui label', + loading : 'loading', + menu : 'menu', + message : 'message', + multiple : 'multiple', + placeholder : 'default', + sizer : 'sizer', + search : 'search', + selected : 'selected', + selection : 'selection', + upward : 'upward', + leftward : 'left', + visible : 'visible', + clearable : 'clearable', + noselection : 'noselection', + delete : 'delete', + header : 'header', + divider : 'divider', + groupIcon : '', + unfilterable : 'unfilterable' + } + +}; + +/* Templates */ +$.fn.dropdown.settings.templates = { + deQuote: function(string) { + return String(string).replace(/"/g,""); + }, + escape: function(string, preserveHTML) { + if (preserveHTML){ + return string; + } + var + badChars = /[<>"'`]/g, + shouldEscape = /[&<>"'`]/, + escape = { + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" + }, + escapedChar = function(chr) { + return escape[chr]; + } + ; + if(shouldEscape.test(string)) { + string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); + return string.replace(badChars, escapedChar); + } + return string; + }, + // generates dropdown from select values + dropdown: function(select, fields, preserveHTML, className) { + var + placeholder = select.placeholder || false, + html = '', + escape = $.fn.dropdown.settings.templates.escape + ; + html += '<i class="dropdown icon"></i>'; + if(placeholder) { + html += '<div class="default text">' + escape(placeholder,preserveHTML) + '</div>'; + } + else { + html += '<div class="text"></div>'; + } + html += '<div class="'+className.menu+'">'; + html += $.fn.dropdown.settings.templates.menu(select, fields, preserveHTML,className); + html += '</div>'; + return html; + }, + + // generates just menu from select + menu: function(response, fields, preserveHTML, className) { + var + values = response[fields.values] || [], + html = '', + escape = $.fn.dropdown.settings.templates.escape, + deQuote = $.fn.dropdown.settings.templates.deQuote + ; + $.each(values, function(index, option) { + var + itemType = (option[fields.type]) + ? option[fields.type] + : 'item' + ; + + if( itemType === 'item' ) { + var + maybeText = (option[fields.text]) + ? ' data-text="' + escape(option[fields.text]) + '"' // GITEA-PATCH: use "escape" for attribute value + : '', + maybeDisabled = (option[fields.disabled]) + ? className.disabled+' ' + : '' + ; + // GITEA-PATCH: use "escape" for attribute value + html += '<div class="'+ maybeDisabled + (option[fields.class] ? deQuote(option[fields.class]) : className.item)+'" data-value="' + escape(option[fields.value]) + '"' + maybeText + '>'; + if(option[fields.image]) { + html += '<img class="'+(option[fields.imageClass] ? deQuote(option[fields.imageClass]) : className.image)+'" src="' + deQuote(option[fields.image]) + '">'; + } + if(option[fields.icon]) { + html += '<i class="'+deQuote(option[fields.icon])+' '+(option[fields.iconClass] ? deQuote(option[fields.iconClass]) : className.icon)+'"></i>'; + } + html += escape(option[fields.name] || '', preserveHTML); + html += '</div>'; + } else if (itemType === 'header') { + var groupName = escape(option[fields.name] || '', preserveHTML), + groupIcon = option[fields.icon] ? deQuote(option[fields.icon]) : className.groupIcon + ; + if(groupName !== '' || groupIcon !== '') { + html += '<div class="' + (option[fields.class] ? deQuote(option[fields.class]) : className.header) + '">'; + if (groupIcon !== '') { + html += '<i class="' + groupIcon + ' ' + (option[fields.iconClass] ? deQuote(option[fields.iconClass]) : className.icon) + '"></i>'; + } + html += groupName; + html += '</div>'; + } + if(option[fields.divider]){ + html += '<div class="'+className.divider+'"></div>'; + } + } + }); + return html; + }, + + // generates label for multiselect + label: function(value, text, preserveHTML, className) { + var + escape = $.fn.dropdown.settings.templates.escape; + return escape(text,preserveHTML) + '<i class="'+className.delete+' icon"></i>'; + }, + + + // generates messages like "No results" + message: function(message) { + return message; + }, + + // generates user addition to selection menu + addition: function(choice) { + return choice; + } + +}; + +})( jQuery, window, document ); diff --git a/web_src/fomantic/build/components/form.css b/web_src/fomantic/build/components/form.css new file mode 100644 index 0000000000..0124e2d6b5 --- /dev/null +++ b/web_src/fomantic/build/components/form.css @@ -0,0 +1,1633 @@ +/*! + * # Fomantic-UI - Form + * http://github.com/fomantic/Fomantic-UI/ + * + * + * Released under the MIT license + * http://opensource.org/licenses/MIT + * + */ + + +/******************************* + Elements +*******************************/ + + +/*-------------------- + Form +---------------------*/ + +.ui.form { + position: relative; + max-width: 100%; +} + +/*-------------------- + Content +---------------------*/ + +.ui.form > p { + margin: 1em 0; +} + +/*-------------------- + Field +---------------------*/ + +.ui.form .field { + clear: both; + margin: 0 0 1em; +} +.ui.form .fields .fields, +.ui.form .field:last-child, +.ui.form .fields:last-child .field { + margin-bottom: 0; +} +.ui.form .fields .field { + clear: both; + margin: 0; +} + +/*-------------------- + Labels +---------------------*/ + +.ui.form .field > label { + display: block; + margin: 0 0 0.28571429rem 0; + color: rgba(0, 0, 0, 0.87); + font-size: 0.92857143em; + font-weight: 500; + text-transform: none; +} + +/*-------------------- + Standard Inputs +---------------------*/ + +.ui.form textarea, +.ui.form input:not([type]), +.ui.form input[type="date"], +.ui.form input[type="datetime-local"], +.ui.form input[type="email"], +.ui.form input[type="number"], +.ui.form input[type="password"], +.ui.form input[type="search"], +.ui.form input[type="tel"], +.ui.form input[type="time"], +.ui.form input[type="text"], +.ui.form input[type="file"], +.ui.form input[type="url"] { + width: 100%; + vertical-align: top; +} + +/* Set max height on unusual input */ +.ui.form ::-webkit-datetime-edit, +.ui.form ::-webkit-inner-spin-button { + height: 1.21428571em; +} +.ui.form input:not([type]), +.ui.form input[type="date"], +.ui.form input[type="datetime-local"], +.ui.form input[type="email"], +.ui.form input[type="number"], +.ui.form input[type="password"], +.ui.form input[type="search"], +.ui.form input[type="tel"], +.ui.form input[type="time"], +.ui.form input[type="text"], +.ui.form input[type="file"], +.ui.form input[type="url"] { + font-family: var(--fonts-regular); + margin: 0; + outline: none; + -webkit-appearance: none; + -webkit-tap-highlight-color: rgba(255, 255, 255, 0); + line-height: 1.21428571em; + padding: 0.67857143em 1em; + font-size: 1em; + background: #FFFFFF; + border: 1px solid rgba(34, 36, 38, 0.15); + color: rgba(0, 0, 0, 0.87); + border-radius: 0.28571429rem; + box-shadow: 0 0 0 0 transparent inset; + transition: color 0.1s ease, border-color 0.1s ease; +} + +/* Text Area */ +.ui.input textarea, +.ui.form textarea { + margin: 0; + -webkit-appearance: none; + -webkit-tap-highlight-color: rgba(255, 255, 255, 0); + padding: 0.78571429em 1em; + background: #FFFFFF; + border: 1px solid rgba(34, 36, 38, 0.15); + outline: none; + color: rgba(0, 0, 0, 0.87); + border-radius: 0.28571429rem; + box-shadow: 0 0 0 0 transparent inset; + transition: color 0.1s ease, border-color 0.1s ease; + font-size: 1em; + font-family: var(--fonts-regular); + line-height: 1.2857; + resize: vertical; +} +.ui.form textarea:not([rows]) { + height: 12em; + min-height: 8em; + max-height: 24em; +} +.ui.form textarea, +.ui.form input[type="checkbox"] { + vertical-align: top; +} + +/*-------------------- + Checkbox margin +---------------------*/ + +.ui.form .fields:not(.grouped):not(.inline) .field:not(:only-child) label + .ui.ui.checkbox { + margin-top: 0.7em; +} +.ui.form .fields:not(.grouped):not(.inline) .field:not(:only-child) .ui.checkbox { + margin-top: 2.41428571em; +} +.ui.form .fields:not(.grouped):not(.inline) .field:not(:only-child) .ui.toggle.checkbox { + margin-top: 2.21428571em; +} +.ui.form .fields:not(.grouped):not(.inline) .field:not(:only-child) .ui.slider.checkbox { + margin-top: 2.61428571em; +} +.ui.ui.form .field .fields .field:not(:only-child) .ui.checkbox { + margin-top: 0.6em; +} +.ui.ui.form .field .fields .field:not(:only-child) .ui.toggle.checkbox { + margin-top: 0.5em; +} +.ui.ui.form .field .fields .field:not(:only-child) .ui.slider.checkbox { + margin-top: 0.7em; +} + +/*-------------------------- + Input w/ attached Button +---------------------------*/ + +.ui.form input.attached { + width: auto; +} + +/*-------------------- + Basic Select +---------------------*/ + +.ui.form select { + display: block; + height: auto; + width: 100%; + background: #FFFFFF; + border: 1px solid rgba(34, 36, 38, 0.15); + border-radius: 0.28571429rem; + box-shadow: 0 0 0 0 transparent inset; + padding: 0.62em 1em; + color: rgba(0, 0, 0, 0.87); + transition: color 0.1s ease, border-color 0.1s ease; +} + +/*-------------------- + Dropdown +---------------------*/ + + +/* Block */ +.ui.form .field > .selection.dropdown { + min-width: auto; + width: 100%; +} +.ui.form .field > .selection.dropdown > .dropdown.icon { + float: right; +} + +/* Inline */ +.ui.form .inline.fields .field > .selection.dropdown, +.ui.form .inline.field > .selection.dropdown { + width: auto; +} +.ui.form .inline.fields .field > .selection.dropdown > .dropdown.icon, +.ui.form .inline.field > .selection.dropdown > .dropdown.icon { + float: none; +} + +/*-------------------- + UI Input +---------------------*/ + + +/* Block */ +.ui.form .field .ui.input, +.ui.form .fields .field .ui.input, +.ui.form .wide.field .ui.input { + width: 100%; +} + +/* Inline */ +.ui.form .inline.fields .field:not(.wide) .ui.input, +.ui.form .inline.field:not(.wide) .ui.input { + width: auto; + vertical-align: middle; +} + +/* Auto Input */ +.ui.form .fields .field .ui.input input, +.ui.form .field .ui.input input { + width: auto; +} + +/* Full Width Input */ +.ui.form .ten.fields .ui.input input, +.ui.form .nine.fields .ui.input input, +.ui.form .eight.fields .ui.input input, +.ui.form .seven.fields .ui.input input, +.ui.form .six.fields .ui.input input, +.ui.form .five.fields .ui.input input, +.ui.form .four.fields .ui.input input, +.ui.form .three.fields .ui.input input, +.ui.form .two.fields .ui.input input, +.ui.form .wide.field .ui.input input { + flex: 1 0 auto; + width: 0; +} + +/*-------------------- + Types of Messages +---------------------*/ + +.ui.form .error.message, +.ui.form .error.message:empty { + display: none; +} +.ui.form .info.message, +.ui.form .info.message:empty { + display: none; +} +.ui.form .success.message, +.ui.form .success.message:empty { + display: none; +} +.ui.form .warning.message, +.ui.form .warning.message:empty { + display: none; +} + +/* Assumptions */ +.ui.form .message:first-child { + margin-top: 0; +} + +/*-------------------- + Validation Prompt +---------------------*/ + +.ui.form .field .prompt.label { + white-space: normal; + background: #FFFFFF !important; + border: 1px solid #E0B4B4 !important; + color: #9F3A38 !important; +} +.ui.form .inline.fields .field .prompt, +.ui.form .inline.field .prompt { + vertical-align: top; + margin: -0.25em 0 -0.5em 0.5em; +} +.ui.form .inline.fields .field .prompt:before, +.ui.form .inline.field .prompt:before { + border-width: 0 0 1px 1px; + bottom: auto; + right: auto; + top: 50%; + left: 0; +} + + +/******************************* + States +*******************************/ + + +/*-------------------- + Autofilled +---------------------*/ + +.ui.form .field.field input:-webkit-autofill { + box-shadow: 0 0 0 100px #FFFFF0 inset !important; + border-color: #E5DFA1 !important; +} + +/* Focus */ +.ui.form .field.field input:-webkit-autofill:focus { + box-shadow: 0 0 0 100px #FFFFF0 inset !important; + border-color: #D5C315 !important; +} + +/*-------------------- + Placeholder +---------------------*/ + + +/* browsers require these rules separate */ +.ui.form ::-webkit-input-placeholder { + color: rgba(191, 191, 191, 0.87); +} +.ui.form :-ms-input-placeholder { + color: rgba(191, 191, 191, 0.87) !important; +} +.ui.form ::-moz-placeholder { + color: rgba(191, 191, 191, 0.87); +} +.ui.form :focus::-webkit-input-placeholder { + color: rgba(115, 115, 115, 0.87); +} +.ui.form :focus:-ms-input-placeholder { + color: rgba(115, 115, 115, 0.87) !important; +} +.ui.form :focus::-moz-placeholder { + color: rgba(115, 115, 115, 0.87); +} + +/*-------------------- + Focus +---------------------*/ + +.ui.form input:not([type]):focus, +.ui.form input[type="date"]:focus, +.ui.form input[type="datetime-local"]:focus, +.ui.form input[type="email"]:focus, +.ui.form input[type="number"]:focus, +.ui.form input[type="password"]:focus, +.ui.form input[type="search"]:focus, +.ui.form input[type="tel"]:focus, +.ui.form input[type="time"]:focus, +.ui.form input[type="text"]:focus, +.ui.form input[type="file"]:focus, +.ui.form input[type="url"]:focus { + color: rgba(0, 0, 0, 0.95); + border-color: #85B7D9; + border-radius: 0.28571429rem; + background: #FFFFFF; + box-shadow: 0 0 0 0 rgba(34, 36, 38, 0.35) inset; +} +.ui.form .ui.action.input:not([class*="left action"]) input:not([type]):focus, +.ui.form .ui.action.input:not([class*="left action"]) input[type="date"]:focus, +.ui.form .ui.action.input:not([class*="left action"]) input[type="datetime-local"]:focus, +.ui.form .ui.action.input:not([class*="left action"]) input[type="email"]:focus, +.ui.form .ui.action.input:not([class*="left action"]) input[type="number"]:focus, +.ui.form .ui.action.input:not([class*="left action"]) input[type="password"]:focus, +.ui.form .ui.action.input:not([class*="left action"]) input[type="search"]:focus, +.ui.form .ui.action.input:not([class*="left action"]) input[type="tel"]:focus, +.ui.form .ui.action.input:not([class*="left action"]) input[type="time"]:focus, +.ui.form .ui.action.input:not([class*="left action"]) input[type="text"]:focus, +.ui.form .ui.action.input:not([class*="left action"]) input[type="file"]:focus, +.ui.form .ui.action.input:not([class*="left action"]) input[type="url"]:focus { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.ui.form .ui[class*="left action"].input input:not([type]), +.ui.form .ui[class*="left action"].input input[type="date"], +.ui.form .ui[class*="left action"].input input[type="datetime-local"], +.ui.form .ui[class*="left action"].input input[type="email"], +.ui.form .ui[class*="left action"].input input[type="number"], +.ui.form .ui[class*="left action"].input input[type="password"], +.ui.form .ui[class*="left action"].input input[type="search"], +.ui.form .ui[class*="left action"].input input[type="tel"], +.ui.form .ui[class*="left action"].input input[type="time"], +.ui.form .ui[class*="left action"].input input[type="text"], +.ui.form .ui[class*="left action"].input input[type="file"], +.ui.form .ui[class*="left action"].input input[type="url"] { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.ui.form textarea:focus { + color: rgba(0, 0, 0, 0.95); + border-color: #85B7D9; + border-radius: 0.28571429rem; + background: #FFFFFF; + box-shadow: 0 0 0 0 rgba(34, 36, 38, 0.35) inset; + -webkit-appearance: none; +} + +/*-------------------- + States + ---------------------*/ + + +/* On Form */ +.ui.form.error .error.message:not(:empty) { + display: block; +} +.ui.form.error .compact.error.message:not(:empty) { + display: inline-block; +} +.ui.form.error .icon.error.message:not(:empty) { + display: flex; +} + +/* On Field(s) */ +.ui.form .fields.error .error.message:not(:empty), +.ui.form .field.error .error.message:not(:empty) { + display: block; +} +.ui.form .fields.error .compact.error.message:not(:empty), +.ui.form .field.error .compact.error.message:not(:empty) { + display: inline-block; +} +.ui.form .fields.error .icon.error.message:not(:empty), +.ui.form .field.error .icon.error.message:not(:empty) { + display: flex; +} +.ui.ui.form .fields.error .field label, +.ui.ui.form .field.error label, +.ui.ui.form .fields.error .field .input, +.ui.ui.form .field.error .input { + color: #9F3A38; +} +.ui.form .fields.error .field .corner.label, +.ui.form .field.error .corner.label { + border-color: #9F3A38; + color: #FFFFFF; +} +.ui.form .fields.error .field textarea, +.ui.form .fields.error .field select, +.ui.form .fields.error .field input:not([type]), +.ui.form .fields.error .field input[type="date"], +.ui.form .fields.error .field input[type="datetime-local"], +.ui.form .fields.error .field input[type="email"], +.ui.form .fields.error .field input[type="number"], +.ui.form .fields.error .field input[type="password"], +.ui.form .fields.error .field input[type="search"], +.ui.form .fields.error .field input[type="tel"], +.ui.form .fields.error .field input[type="time"], +.ui.form .fields.error .field input[type="text"], +.ui.form .fields.error .field input[type="file"], +.ui.form .fields.error .field input[type="url"], +.ui.form .field.error textarea, +.ui.form .field.error select, +.ui.form .field.error input:not([type]), +.ui.form .field.error input[type="date"], +.ui.form .field.error input[type="datetime-local"], +.ui.form .field.error input[type="email"], +.ui.form .field.error input[type="number"], +.ui.form .field.error input[type="password"], +.ui.form .field.error input[type="search"], +.ui.form .field.error input[type="tel"], +.ui.form .field.error input[type="time"], +.ui.form .field.error input[type="text"], +.ui.form .field.error input[type="file"], +.ui.form .field.error input[type="url"] { + color: #9F3A38; + background: #FFF6F6; + border-color: #E0B4B4; + border-radius: ''; + box-shadow: none; +} +.ui.form .field.error textarea:focus, +.ui.form .field.error select:focus, +.ui.form .field.error input:not([type]):focus, +.ui.form .field.error input[type="date"]:focus, +.ui.form .field.error input[type="datetime-local"]:focus, +.ui.form .field.error input[type="email"]:focus, +.ui.form .field.error input[type="number"]:focus, +.ui.form .field.error input[type="password"]:focus, +.ui.form .field.error input[type="search"]:focus, +.ui.form .field.error input[type="tel"]:focus, +.ui.form .field.error input[type="time"]:focus, +.ui.form .field.error input[type="text"]:focus, +.ui.form .field.error input[type="file"]:focus, +.ui.form .field.error input[type="url"]:focus { + background: #FFF6F6; + border-color: #E0B4B4; + color: #9F3A38; + box-shadow: none; +} + +/* Preserve Native Select Stylings */ +.ui.form .field.error select { + -webkit-appearance: menulist-button; +} + +/*------------------ + Input State + --------------------*/ + + +/* Transparent */ +.ui.form .field.error .transparent.input input, +.ui.form .field.error .transparent.input textarea, +.ui.form .field.error input.transparent, +.ui.form .field.error textarea.transparent { + background-color: #FFF6F6 !important; + color: #9F3A38 !important; +} + +/* Autofilled */ +.ui.form .error.error input:-webkit-autofill { + box-shadow: 0 0 0 100px #FFFAF0 inset !important; + border-color: #E0B4B4 !important; +} + +/* Placeholder */ +.ui.form .error ::-webkit-input-placeholder { + color: #e7bdbc; +} +.ui.form .error :-ms-input-placeholder { + color: #e7bdbc !important; +} +.ui.form .error ::-moz-placeholder { + color: #e7bdbc; +} +.ui.form .error :focus::-webkit-input-placeholder { + color: #da9796; +} +.ui.form .error :focus:-ms-input-placeholder { + color: #da9796 !important; +} +.ui.form .error :focus::-moz-placeholder { + color: #da9796; +} + +/*------------------ + Dropdown State + --------------------*/ + +.ui.form .fields.error .field .ui.dropdown, +.ui.form .fields.error .field .ui.dropdown .item, +.ui.form .field.error .ui.dropdown, +.ui.form .field.error .ui.dropdown .text, +.ui.form .field.error .ui.dropdown .item { + background: #FFF6F6; + color: #9F3A38; +} +.ui.form .fields.error .field .ui.dropdown, +.ui.form .field.error .ui.dropdown { + border-color: #E0B4B4 !important; +} +.ui.form .fields.error .field .ui.dropdown:hover, +.ui.form .field.error .ui.dropdown:hover { + border-color: #E0B4B4 !important; +} +.ui.form .fields.error .field .ui.dropdown:hover .menu, +.ui.form .field.error .ui.dropdown:hover .menu { + border-color: #E0B4B4; +} +.ui.form .fields.error .field .ui.multiple.selection.dropdown > .label, +.ui.form .field.error .ui.multiple.selection.dropdown > .label { + background-color: #EACBCB; + color: #9F3A38; +} + +/* Hover */ +.ui.form .fields.error .field .ui.dropdown .menu .item:hover, +.ui.form .field.error .ui.dropdown .menu .item:hover { + background-color: #FBE7E7; +} + +/* Selected */ +.ui.form .fields.error .field .ui.dropdown .menu .selected.item, +.ui.form .field.error .ui.dropdown .menu .selected.item { + background-color: #FBE7E7; +} + +/* Active */ +.ui.form .fields.error .field .ui.dropdown .menu .active.item, +.ui.form .field.error .ui.dropdown .menu .active.item { + background-color: #FDCFCF !important; +} + +/*-------------------- + Checkbox State + ---------------------*/ + +.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) label, +.ui.form .field.error .checkbox:not(.toggle):not(.slider) label, +.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) .box, +.ui.form .field.error .checkbox:not(.toggle):not(.slider) .box { + color: #9F3A38; +} +.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) label:before, +.ui.form .field.error .checkbox:not(.toggle):not(.slider) label:before, +.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) .box:before, +.ui.form .field.error .checkbox:not(.toggle):not(.slider) .box:before { + background: #FFF6F6; + border-color: #E0B4B4; +} +.ui.form .fields.error .field .checkbox label:after, +.ui.form .field.error .checkbox label:after, +.ui.form .fields.error .field .checkbox .box:after, +.ui.form .field.error .checkbox .box:after { + color: #9F3A38; +} + +/* On Form */ +.ui.form.info .info.message:not(:empty) { + display: block; +} +.ui.form.info .compact.info.message:not(:empty) { + display: inline-block; +} +.ui.form.info .icon.info.message:not(:empty) { + display: flex; +} + +/* On Field(s) */ +.ui.form .fields.info .info.message:not(:empty), +.ui.form .field.info .info.message:not(:empty) { + display: block; +} +.ui.form .fields.info .compact.info.message:not(:empty), +.ui.form .field.info .compact.info.message:not(:empty) { + display: inline-block; +} +.ui.form .fields.info .icon.info.message:not(:empty), +.ui.form .field.info .icon.info.message:not(:empty) { + display: flex; +} +.ui.ui.form .fields.info .field label, +.ui.ui.form .field.info label, +.ui.ui.form .fields.info .field .input, +.ui.ui.form .field.info .input { + color: #276F86; +} +.ui.form .fields.info .field .corner.label, +.ui.form .field.info .corner.label { + border-color: #276F86; + color: #FFFFFF; +} +.ui.form .fields.info .field textarea, +.ui.form .fields.info .field select, +.ui.form .fields.info .field input:not([type]), +.ui.form .fields.info .field input[type="date"], +.ui.form .fields.info .field input[type="datetime-local"], +.ui.form .fields.info .field input[type="email"], +.ui.form .fields.info .field input[type="number"], +.ui.form .fields.info .field input[type="password"], +.ui.form .fields.info .field input[type="search"], +.ui.form .fields.info .field input[type="tel"], +.ui.form .fields.info .field input[type="time"], +.ui.form .fields.info .field input[type="text"], +.ui.form .fields.info .field input[type="file"], +.ui.form .fields.info .field input[type="url"], +.ui.form .field.info textarea, +.ui.form .field.info select, +.ui.form .field.info input:not([type]), +.ui.form .field.info input[type="date"], +.ui.form .field.info input[type="datetime-local"], +.ui.form .field.info input[type="email"], +.ui.form .field.info input[type="number"], +.ui.form .field.info input[type="password"], +.ui.form .field.info input[type="search"], +.ui.form .field.info input[type="tel"], +.ui.form .field.info input[type="time"], +.ui.form .field.info input[type="text"], +.ui.form .field.info input[type="file"], +.ui.form .field.info input[type="url"] { + color: #276F86; + background: #F8FFFF; + border-color: #A9D5DE; + border-radius: ''; + box-shadow: none; +} +.ui.form .field.info textarea:focus, +.ui.form .field.info select:focus, +.ui.form .field.info input:not([type]):focus, +.ui.form .field.info input[type="date"]:focus, +.ui.form .field.info input[type="datetime-local"]:focus, +.ui.form .field.info input[type="email"]:focus, +.ui.form .field.info input[type="number"]:focus, +.ui.form .field.info input[type="password"]:focus, +.ui.form .field.info input[type="search"]:focus, +.ui.form .field.info input[type="tel"]:focus, +.ui.form .field.info input[type="time"]:focus, +.ui.form .field.info input[type="text"]:focus, +.ui.form .field.info input[type="file"]:focus, +.ui.form .field.info input[type="url"]:focus { + background: #F8FFFF; + border-color: #A9D5DE; + color: #276F86; + box-shadow: none; +} + +/* Preserve Native Select Stylings */ +.ui.form .field.info select { + -webkit-appearance: menulist-button; +} + +/*------------------ + Input State + --------------------*/ + + +/* Transparent */ +.ui.form .field.info .transparent.input input, +.ui.form .field.info .transparent.input textarea, +.ui.form .field.info input.transparent, +.ui.form .field.info textarea.transparent { + background-color: #F8FFFF !important; + color: #276F86 !important; +} + +/* Autofilled */ +.ui.form .info.info input:-webkit-autofill { + box-shadow: 0 0 0 100px #F0FAFF inset !important; + border-color: #b3e0e0 !important; +} + +/* Placeholder */ +.ui.form .info ::-webkit-input-placeholder { + color: #98cfe1; +} +.ui.form .info :-ms-input-placeholder { + color: #98cfe1 !important; +} +.ui.form .info ::-moz-placeholder { + color: #98cfe1; +} +.ui.form .info :focus::-webkit-input-placeholder { + color: #70bdd6; +} +.ui.form .info :focus:-ms-input-placeholder { + color: #70bdd6 !important; +} +.ui.form .info :focus::-moz-placeholder { + color: #70bdd6; +} + +/*------------------ + Dropdown State + --------------------*/ + +.ui.form .fields.info .field .ui.dropdown, +.ui.form .fields.info .field .ui.dropdown .item, +.ui.form .field.info .ui.dropdown, +.ui.form .field.info .ui.dropdown .text, +.ui.form .field.info .ui.dropdown .item { + background: #F8FFFF; + color: #276F86; +} +.ui.form .fields.info .field .ui.dropdown, +.ui.form .field.info .ui.dropdown { + border-color: #A9D5DE !important; +} +.ui.form .fields.info .field .ui.dropdown:hover, +.ui.form .field.info .ui.dropdown:hover { + border-color: #A9D5DE !important; +} +.ui.form .fields.info .field .ui.dropdown:hover .menu, +.ui.form .field.info .ui.dropdown:hover .menu { + border-color: #A9D5DE; +} +.ui.form .fields.info .field .ui.multiple.selection.dropdown > .label, +.ui.form .field.info .ui.multiple.selection.dropdown > .label { + background-color: #cce3ea; + color: #276F86; +} + +/* Hover */ +.ui.form .fields.info .field .ui.dropdown .menu .item:hover, +.ui.form .field.info .ui.dropdown .menu .item:hover { + background-color: #e9f2fb; +} + +/* Selected */ +.ui.form .fields.info .field .ui.dropdown .menu .selected.item, +.ui.form .field.info .ui.dropdown .menu .selected.item { + background-color: #e9f2fb; +} + +/* Active */ +.ui.form .fields.info .field .ui.dropdown .menu .active.item, +.ui.form .field.info .ui.dropdown .menu .active.item { + background-color: #cef1fd !important; +} + +/*-------------------- + Checkbox State + ---------------------*/ + +.ui.form .fields.info .field .checkbox:not(.toggle):not(.slider) label, +.ui.form .field.info .checkbox:not(.toggle):not(.slider) label, +.ui.form .fields.info .field .checkbox:not(.toggle):not(.slider) .box, +.ui.form .field.info .checkbox:not(.toggle):not(.slider) .box { + color: #276F86; +} +.ui.form .fields.info .field .checkbox:not(.toggle):not(.slider) label:before, +.ui.form .field.info .checkbox:not(.toggle):not(.slider) label:before, +.ui.form .fields.info .field .checkbox:not(.toggle):not(.slider) .box:before, +.ui.form .field.info .checkbox:not(.toggle):not(.slider) .box:before { + background: #F8FFFF; + border-color: #A9D5DE; +} +.ui.form .fields.info .field .checkbox label:after, +.ui.form .field.info .checkbox label:after, +.ui.form .fields.info .field .checkbox .box:after, +.ui.form .field.info .checkbox .box:after { + color: #276F86; +} + +/* On Form */ +.ui.form.success .success.message:not(:empty) { + display: block; +} +.ui.form.success .compact.success.message:not(:empty) { + display: inline-block; +} +.ui.form.success .icon.success.message:not(:empty) { + display: flex; +} + +/* On Field(s) */ +.ui.form .fields.success .success.message:not(:empty), +.ui.form .field.success .success.message:not(:empty) { + display: block; +} +.ui.form .fields.success .compact.success.message:not(:empty), +.ui.form .field.success .compact.success.message:not(:empty) { + display: inline-block; +} +.ui.form .fields.success .icon.success.message:not(:empty), +.ui.form .field.success .icon.success.message:not(:empty) { + display: flex; +} +.ui.ui.form .fields.success .field label, +.ui.ui.form .field.success label, +.ui.ui.form .fields.success .field .input, +.ui.ui.form .field.success .input { + color: #2C662D; +} +.ui.form .fields.success .field .corner.label, +.ui.form .field.success .corner.label { + border-color: #2C662D; + color: #FFFFFF; +} +.ui.form .fields.success .field textarea, +.ui.form .fields.success .field select, +.ui.form .fields.success .field input:not([type]), +.ui.form .fields.success .field input[type="date"], +.ui.form .fields.success .field input[type="datetime-local"], +.ui.form .fields.success .field input[type="email"], +.ui.form .fields.success .field input[type="number"], +.ui.form .fields.success .field input[type="password"], +.ui.form .fields.success .field input[type="search"], +.ui.form .fields.success .field input[type="tel"], +.ui.form .fields.success .field input[type="time"], +.ui.form .fields.success .field input[type="text"], +.ui.form .fields.success .field input[type="file"], +.ui.form .fields.success .field input[type="url"], +.ui.form .field.success textarea, +.ui.form .field.success select, +.ui.form .field.success input:not([type]), +.ui.form .field.success input[type="date"], +.ui.form .field.success input[type="datetime-local"], +.ui.form .field.success input[type="email"], +.ui.form .field.success input[type="number"], +.ui.form .field.success input[type="password"], +.ui.form .field.success input[type="search"], +.ui.form .field.success input[type="tel"], +.ui.form .field.success input[type="time"], +.ui.form .field.success input[type="text"], +.ui.form .field.success input[type="file"], +.ui.form .field.success input[type="url"] { + color: #2C662D; + background: #FCFFF5; + border-color: #A3C293; + border-radius: ''; + box-shadow: none; +} +.ui.form .field.success textarea:focus, +.ui.form .field.success select:focus, +.ui.form .field.success input:not([type]):focus, +.ui.form .field.success input[type="date"]:focus, +.ui.form .field.success input[type="datetime-local"]:focus, +.ui.form .field.success input[type="email"]:focus, +.ui.form .field.success input[type="number"]:focus, +.ui.form .field.success input[type="password"]:focus, +.ui.form .field.success input[type="search"]:focus, +.ui.form .field.success input[type="tel"]:focus, +.ui.form .field.success input[type="time"]:focus, +.ui.form .field.success input[type="text"]:focus, +.ui.form .field.success input[type="file"]:focus, +.ui.form .field.success input[type="url"]:focus { + background: #FCFFF5; + border-color: #A3C293; + color: #2C662D; + box-shadow: none; +} + +/* Preserve Native Select Stylings */ +.ui.form .field.success select { + -webkit-appearance: menulist-button; +} + +/*------------------ + Input State + --------------------*/ + + +/* Transparent */ +.ui.form .field.success .transparent.input input, +.ui.form .field.success .transparent.input textarea, +.ui.form .field.success input.transparent, +.ui.form .field.success textarea.transparent { + background-color: #FCFFF5 !important; + color: #2C662D !important; +} + +/* Autofilled */ +.ui.form .success.success input:-webkit-autofill { + box-shadow: 0 0 0 100px #F0FFF0 inset !important; + border-color: #bee0b3 !important; +} + +/* Placeholder */ +.ui.form .success ::-webkit-input-placeholder { + color: #8fcf90; +} +.ui.form .success :-ms-input-placeholder { + color: #8fcf90 !important; +} +.ui.form .success ::-moz-placeholder { + color: #8fcf90; +} +.ui.form .success :focus::-webkit-input-placeholder { + color: #6cbf6d; +} +.ui.form .success :focus:-ms-input-placeholder { + color: #6cbf6d !important; +} +.ui.form .success :focus::-moz-placeholder { + color: #6cbf6d; +} + +/*------------------ + Dropdown State + --------------------*/ + +.ui.form .fields.success .field .ui.dropdown, +.ui.form .fields.success .field .ui.dropdown .item, +.ui.form .field.success .ui.dropdown, +.ui.form .field.success .ui.dropdown .text, +.ui.form .field.success .ui.dropdown .item { + background: #FCFFF5; + color: #2C662D; +} +.ui.form .fields.success .field .ui.dropdown, +.ui.form .field.success .ui.dropdown { + border-color: #A3C293 !important; +} +.ui.form .fields.success .field .ui.dropdown:hover, +.ui.form .field.success .ui.dropdown:hover { + border-color: #A3C293 !important; +} +.ui.form .fields.success .field .ui.dropdown:hover .menu, +.ui.form .field.success .ui.dropdown:hover .menu { + border-color: #A3C293; +} +.ui.form .fields.success .field .ui.multiple.selection.dropdown > .label, +.ui.form .field.success .ui.multiple.selection.dropdown > .label { + background-color: #cceacc; + color: #2C662D; +} + +/* Hover */ +.ui.form .fields.success .field .ui.dropdown .menu .item:hover, +.ui.form .field.success .ui.dropdown .menu .item:hover { + background-color: #e9fbe9; +} + +/* Selected */ +.ui.form .fields.success .field .ui.dropdown .menu .selected.item, +.ui.form .field.success .ui.dropdown .menu .selected.item { + background-color: #e9fbe9; +} + +/* Active */ +.ui.form .fields.success .field .ui.dropdown .menu .active.item, +.ui.form .field.success .ui.dropdown .menu .active.item { + background-color: #dafdce !important; +} + +/*-------------------- + Checkbox State + ---------------------*/ + +.ui.form .fields.success .field .checkbox:not(.toggle):not(.slider) label, +.ui.form .field.success .checkbox:not(.toggle):not(.slider) label, +.ui.form .fields.success .field .checkbox:not(.toggle):not(.slider) .box, +.ui.form .field.success .checkbox:not(.toggle):not(.slider) .box { + color: #2C662D; +} +.ui.form .fields.success .field .checkbox:not(.toggle):not(.slider) label:before, +.ui.form .field.success .checkbox:not(.toggle):not(.slider) label:before, +.ui.form .fields.success .field .checkbox:not(.toggle):not(.slider) .box:before, +.ui.form .field.success .checkbox:not(.toggle):not(.slider) .box:before { + background: #FCFFF5; + border-color: #A3C293; +} +.ui.form .fields.success .field .checkbox label:after, +.ui.form .field.success .checkbox label:after, +.ui.form .fields.success .field .checkbox .box:after, +.ui.form .field.success .checkbox .box:after { + color: #2C662D; +} + +/* On Form */ +.ui.form.warning .warning.message:not(:empty) { + display: block; +} +.ui.form.warning .compact.warning.message:not(:empty) { + display: inline-block; +} +.ui.form.warning .icon.warning.message:not(:empty) { + display: flex; +} + +/* On Field(s) */ +.ui.form .fields.warning .warning.message:not(:empty), +.ui.form .field.warning .warning.message:not(:empty) { + display: block; +} +.ui.form .fields.warning .compact.warning.message:not(:empty), +.ui.form .field.warning .compact.warning.message:not(:empty) { + display: inline-block; +} +.ui.form .fields.warning .icon.warning.message:not(:empty), +.ui.form .field.warning .icon.warning.message:not(:empty) { + display: flex; +} +.ui.ui.form .fields.warning .field label, +.ui.ui.form .field.warning label, +.ui.ui.form .fields.warning .field .input, +.ui.ui.form .field.warning .input { + color: #573A08; +} +.ui.form .fields.warning .field .corner.label, +.ui.form .field.warning .corner.label { + border-color: #573A08; + color: #FFFFFF; +} +.ui.form .fields.warning .field textarea, +.ui.form .fields.warning .field select, +.ui.form .fields.warning .field input:not([type]), +.ui.form .fields.warning .field input[type="date"], +.ui.form .fields.warning .field input[type="datetime-local"], +.ui.form .fields.warning .field input[type="email"], +.ui.form .fields.warning .field input[type="number"], +.ui.form .fields.warning .field input[type="password"], +.ui.form .fields.warning .field input[type="search"], +.ui.form .fields.warning .field input[type="tel"], +.ui.form .fields.warning .field input[type="time"], +.ui.form .fields.warning .field input[type="text"], +.ui.form .fields.warning .field input[type="file"], +.ui.form .fields.warning .field input[type="url"], +.ui.form .field.warning textarea, +.ui.form .field.warning select, +.ui.form .field.warning input:not([type]), +.ui.form .field.warning input[type="date"], +.ui.form .field.warning input[type="datetime-local"], +.ui.form .field.warning input[type="email"], +.ui.form .field.warning input[type="number"], +.ui.form .field.warning input[type="password"], +.ui.form .field.warning input[type="search"], +.ui.form .field.warning input[type="tel"], +.ui.form .field.warning input[type="time"], +.ui.form .field.warning input[type="text"], +.ui.form .field.warning input[type="file"], +.ui.form .field.warning input[type="url"] { + color: #573A08; + background: #FFFAF3; + border-color: #C9BA9B; + border-radius: ''; + box-shadow: none; +} +.ui.form .field.warning textarea:focus, +.ui.form .field.warning select:focus, +.ui.form .field.warning input:not([type]):focus, +.ui.form .field.warning input[type="date"]:focus, +.ui.form .field.warning input[type="datetime-local"]:focus, +.ui.form .field.warning input[type="email"]:focus, +.ui.form .field.warning input[type="number"]:focus, +.ui.form .field.warning input[type="password"]:focus, +.ui.form .field.warning input[type="search"]:focus, +.ui.form .field.warning input[type="tel"]:focus, +.ui.form .field.warning input[type="time"]:focus, +.ui.form .field.warning input[type="text"]:focus, +.ui.form .field.warning input[type="file"]:focus, +.ui.form .field.warning input[type="url"]:focus { + background: #FFFAF3; + border-color: #C9BA9B; + color: #573A08; + box-shadow: none; +} + +/* Preserve Native Select Stylings */ +.ui.form .field.warning select { + -webkit-appearance: menulist-button; +} + +/*------------------ + Input State + --------------------*/ + + +/* Transparent */ +.ui.form .field.warning .transparent.input input, +.ui.form .field.warning .transparent.input textarea, +.ui.form .field.warning input.transparent, +.ui.form .field.warning textarea.transparent { + background-color: #FFFAF3 !important; + color: #573A08 !important; +} + +/* Autofilled */ +.ui.form .warning.warning input:-webkit-autofill { + box-shadow: 0 0 0 100px #FFFFe0 inset !important; + border-color: #e0e0b3 !important; +} + +/* Placeholder */ +.ui.form .warning ::-webkit-input-placeholder { + color: #edad3e; +} +.ui.form .warning :-ms-input-placeholder { + color: #edad3e !important; +} +.ui.form .warning ::-moz-placeholder { + color: #edad3e; +} +.ui.form .warning :focus::-webkit-input-placeholder { + color: #e39715; +} +.ui.form .warning :focus:-ms-input-placeholder { + color: #e39715 !important; +} +.ui.form .warning :focus::-moz-placeholder { + color: #e39715; +} + +/*------------------ + Dropdown State + --------------------*/ + +.ui.form .fields.warning .field .ui.dropdown, +.ui.form .fields.warning .field .ui.dropdown .item, +.ui.form .field.warning .ui.dropdown, +.ui.form .field.warning .ui.dropdown .text, +.ui.form .field.warning .ui.dropdown .item { + background: #FFFAF3; + color: #573A08; +} +.ui.form .fields.warning .field .ui.dropdown, +.ui.form .field.warning .ui.dropdown { + border-color: #C9BA9B !important; +} +.ui.form .fields.warning .field .ui.dropdown:hover, +.ui.form .field.warning .ui.dropdown:hover { + border-color: #C9BA9B !important; +} +.ui.form .fields.warning .field .ui.dropdown:hover .menu, +.ui.form .field.warning .ui.dropdown:hover .menu { + border-color: #C9BA9B; +} +.ui.form .fields.warning .field .ui.multiple.selection.dropdown > .label, +.ui.form .field.warning .ui.multiple.selection.dropdown > .label { + background-color: #eaeacc; + color: #573A08; +} + +/* Hover */ +.ui.form .fields.warning .field .ui.dropdown .menu .item:hover, +.ui.form .field.warning .ui.dropdown .menu .item:hover { + background-color: #fbfbe9; +} + +/* Selected */ +.ui.form .fields.warning .field .ui.dropdown .menu .selected.item, +.ui.form .field.warning .ui.dropdown .menu .selected.item { + background-color: #fbfbe9; +} + +/* Active */ +.ui.form .fields.warning .field .ui.dropdown .menu .active.item, +.ui.form .field.warning .ui.dropdown .menu .active.item { + background-color: #fdfdce !important; +} + +/*-------------------- + Checkbox State + ---------------------*/ + +.ui.form .fields.warning .field .checkbox:not(.toggle):not(.slider) label, +.ui.form .field.warning .checkbox:not(.toggle):not(.slider) label, +.ui.form .fields.warning .field .checkbox:not(.toggle):not(.slider) .box, +.ui.form .field.warning .checkbox:not(.toggle):not(.slider) .box { + color: #573A08; +} +.ui.form .fields.warning .field .checkbox:not(.toggle):not(.slider) label:before, +.ui.form .field.warning .checkbox:not(.toggle):not(.slider) label:before, +.ui.form .fields.warning .field .checkbox:not(.toggle):not(.slider) .box:before, +.ui.form .field.warning .checkbox:not(.toggle):not(.slider) .box:before { + background: #FFFAF3; + border-color: #C9BA9B; +} +.ui.form .fields.warning .field .checkbox label:after, +.ui.form .field.warning .checkbox label:after, +.ui.form .fields.warning .field .checkbox .box:after, +.ui.form .field.warning .checkbox .box:after { + color: #573A08; +} + +/*-------------------- + Disabled + ---------------------*/ + +.ui.form .disabled.fields .field, +.ui.form .disabled.field, +.ui.form .field :disabled { + pointer-events: none; + opacity: var(--opacity-disabled); +} +.ui.form .field.disabled > label, +.ui.form .fields.disabled > label { + opacity: var(--opacity-disabled); +} +.ui.form .field.disabled :disabled { + opacity: 1; +} + +/*-------------- + Loading + ---------------*/ + +.ui.loading.form { + position: relative; + cursor: default; + pointer-events: none; +} +.ui.loading.form:before { + position: absolute; + content: ''; + top: 0; + left: 0; + background: rgba(255, 255, 255, 0.8); + width: 100%; + height: 100%; + z-index: 100; +} +.ui.loading.form.segments:before { + border-radius: 0.28571429rem; +} +.ui.loading.form:after { + position: absolute; + content: ''; + top: 50%; + left: 50%; + margin: -1.5em 0 0 -1.5em; + width: 3em; + height: 3em; + animation: loader 0.6s infinite linear; + border: 0.2em solid #767676; + border-radius: 500rem; + box-shadow: 0 0 0 1px transparent; + visibility: visible; + z-index: 101; +} + + +/******************************* + Element Types +*******************************/ + + +/*-------------------- + Required Field + ---------------------*/ + +.ui.form .required.fields:not(.grouped) > .field > label:after, +.ui.form .required.fields.grouped > label:after, +.ui.form .required.field > label:after, +.ui.form .required.fields:not(.grouped) > .field > .checkbox:after, +.ui.form .required.field > .checkbox:after, +.ui.form label.required:after { + margin: -0.2em 0 0 0.2em; + content: '*'; + color: #DB2828; +} +.ui.form .required.fields:not(.grouped) > .field > label:after, +.ui.form .required.fields.grouped > label:after, +.ui.form .required.field > label:after, +.ui.form label.required:after { + display: inline-block; + vertical-align: top; +} +.ui.form .required.fields:not(.grouped) > .field > .checkbox:after, +.ui.form .required.field > .checkbox:after { + position: absolute; + top: 0; + left: 100%; +} + + +/******************************* + Variations +*******************************/ + + +/*-------------------- + Field Groups + ---------------------*/ + + +/* Grouped Vertically */ +.ui.form .grouped.fields { + display: block; + margin: 0 0 1em; +} +.ui.form .grouped.fields:last-child { + margin-bottom: 0; +} +.ui.form .grouped.fields > label { + margin: 0 0 0.28571429rem 0; + color: rgba(0, 0, 0, 0.87); + font-size: 0.92857143em; + font-weight: 500; + text-transform: none; +} +.ui.form .grouped.fields .field, +.ui.form .grouped.inline.fields .field { + display: block; + margin: 0.5em 0; + padding: 0; +} +.ui.form .grouped.inline.fields .ui.checkbox { + margin-bottom: 0.4em; +} + +/*-------------------- + Fields +---------------------*/ + + +/* Split fields */ +.ui.form .fields { + display: flex; + flex-direction: row; + margin: 0 -0.5em 1em; +} +.ui.form .fields > .field { + flex: 0 1 auto; + padding-left: 0.5em; + padding-right: 0.5em; +} +.ui.form .fields > .field:first-child { + border-left: none; + box-shadow: none; +} + +/* Other Combinations */ +.ui.form .two.fields > .fields, +.ui.form .two.fields > .field { + width: 50%; +} +.ui.form .three.fields > .fields, +.ui.form .three.fields > .field { + width: 33.33333333%; +} +.ui.form .four.fields > .fields, +.ui.form .four.fields > .field { + width: 25%; +} +.ui.form .five.fields > .fields, +.ui.form .five.fields > .field { + width: 20%; +} +.ui.form .six.fields > .fields, +.ui.form .six.fields > .field { + width: 16.66666667%; +} +.ui.form .seven.fields > .fields, +.ui.form .seven.fields > .field { + width: 14.28571429%; +} +.ui.form .eight.fields > .fields, +.ui.form .eight.fields > .field { + width: 12.5%; +} +.ui.form .nine.fields > .fields, +.ui.form .nine.fields > .field { + width: 11.11111111%; +} +.ui.form .ten.fields > .fields, +.ui.form .ten.fields > .field { + width: 10%; +} + +/* Swap to full width on mobile */ +@media only screen and (max-width: 767.98px) { + .ui.form .fields { + flex-wrap: wrap; + margin-bottom: 0; + } + .ui.form:not(.unstackable) .fields:not(.unstackable) > .fields, + .ui.form:not(.unstackable) .fields:not(.unstackable) > .field { + width: 100%; + margin: 0 0 1em; + } +} + +/* Sizing Combinations */ +.ui.form .fields .wide.field { + width: 6.25%; + padding-left: 0.5em; + padding-right: 0.5em; +} +.ui.form .one.wide.field { + width: 6.25%; +} +.ui.form .two.wide.field { + width: 12.5%; +} +.ui.form .three.wide.field { + width: 18.75%; +} +.ui.form .four.wide.field { + width: 25%; +} +.ui.form .five.wide.field { + width: 31.25%; +} +.ui.form .six.wide.field { + width: 37.5%; +} +.ui.form .seven.wide.field { + width: 43.75%; +} +.ui.form .eight.wide.field { + width: 50%; +} +.ui.form .nine.wide.field { + width: 56.25%; +} +.ui.form .ten.wide.field { + width: 62.5%; +} +.ui.form .eleven.wide.field { + width: 68.75%; +} +.ui.form .twelve.wide.field { + width: 75%; +} +.ui.form .thirteen.wide.field { + width: 81.25%; +} +.ui.form .fourteen.wide.field { + width: 87.5%; +} +.ui.form .fifteen.wide.field { + width: 93.75%; +} +.ui.form .sixteen.wide.field { + width: 100%; +} + +/*-------------------- + Inline Fields + ---------------------*/ + +.ui.form .inline.fields { + margin: 0 0 1em; + align-items: center; +} +.ui.form .inline.fields .field { + margin: 0; + padding: 0 1em 0 0; +} + +/* Inline Label */ +.ui.form .inline.fields > label, +.ui.form .inline.fields .field > label, +.ui.form .inline.fields .field > p, +.ui.form .inline.field > label, +.ui.form .inline.field > p { + display: inline-block; + width: auto; + margin-top: 0; + margin-bottom: 0; + vertical-align: baseline; + font-size: 0.92857143em; + font-weight: 500; + color: rgba(0, 0, 0, 0.87); + text-transform: none; +} + +/* Grouped Inline Label */ +.ui.form .inline.fields > label { + margin: 0.035714em 1em 0 0; +} + +/* Inline Input */ +.ui.form .inline.fields .field > input, +.ui.form .inline.fields .field > select, +.ui.form .inline.field > input, +.ui.form .inline.field > select { + display: inline-block; + width: auto; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + font-size: 1em; +} +.ui.form .inline.fields .field .calendar:not(.popup), +.ui.form .inline.field .calendar:not(.popup) { + display: inline-block; +} +.ui.form .inline.fields .field .calendar:not(.popup) > .input > input, +.ui.form .inline.field .calendar:not(.popup) > .input > input { + width: 13.11em; +} + +/* Label */ +.ui.form .inline.fields .field > :first-child, +.ui.form .inline.field > :first-child { + margin: 0 0.85714286em 0 0; +} +.ui.form .inline.fields .field > :only-child, +.ui.form .inline.field > :only-child { + margin: 0; +} + +/* Wide */ +.ui.form .inline.fields .wide.field { + display: flex; + align-items: center; +} +.ui.form .inline.fields .wide.field > input, +.ui.form .inline.fields .wide.field > select { + width: 100%; +} + +/*-------------------- + Sizes +---------------------*/ + +.ui.form, +.ui.form .field .dropdown, +.ui.form .field .dropdown .menu > .item { + font-size: 1rem; +} +.ui.mini.form, +.ui.mini.form .field .dropdown, +.ui.mini.form .field .dropdown .menu > .item { + font-size: 0.78571429rem; +} +.ui.tiny.form, +.ui.tiny.form .field .dropdown, +.ui.tiny.form .field .dropdown .menu > .item { + font-size: 0.85714286rem; +} +.ui.small.form, +.ui.small.form .field .dropdown, +.ui.small.form .field .dropdown .menu > .item { + font-size: 0.92857143rem; +} +.ui.large.form, +.ui.large.form .field .dropdown, +.ui.large.form .field .dropdown .menu > .item { + font-size: 1.14285714rem; +} +.ui.big.form, +.ui.big.form .field .dropdown, +.ui.big.form .field .dropdown .menu > .item { + font-size: 1.28571429rem; +} +.ui.huge.form, +.ui.huge.form .field .dropdown, +.ui.huge.form .field .dropdown .menu > .item { + font-size: 1.42857143rem; +} +.ui.massive.form, +.ui.massive.form .field .dropdown, +.ui.massive.form .field .dropdown .menu > .item { + font-size: 1.71428571rem; +} + + +/******************************* + Theme Overrides +*******************************/ + + + +/******************************* + Site Overrides +*******************************/ + diff --git a/web_src/fomantic/build/components/modal.css b/web_src/fomantic/build/components/modal.css new file mode 100644 index 0000000000..7da015cfd0 --- /dev/null +++ b/web_src/fomantic/build/components/modal.css @@ -0,0 +1,698 @@ +/*! + * # Fomantic-UI - Modal + * http://github.com/fomantic/Fomantic-UI/ + * + * + * Released under the MIT license + * http://opensource.org/licenses/MIT + * + */ + + +/******************************* + Modal +*******************************/ + +.ui.modal { + position: absolute; + display: none; + z-index: 1001; + text-align: left; + background: #FFFFFF; + border: none; + box-shadow: 1px 3px 3px 0 rgba(0, 0, 0, 0.2), 1px 3px 15px 2px rgba(0, 0, 0, 0.2); + transform-origin: 50% 25%; + flex: 0 0 auto; + border-radius: 0.28571429rem; + -webkit-user-select: text; + -moz-user-select: text; + user-select: text; + will-change: top, left, margin, transform, opacity; +} +.ui.modal > :first-child:not(.icon):not(.dimmer), +.ui.modal > i.icon:first-child + *, +.ui.modal > .dimmer:first-child + *:not(.icon), +.ui.modal > .dimmer:first-child + i.icon + * { + border-top-left-radius: 0.28571429rem; + border-top-right-radius: 0.28571429rem; +} +.ui.modal > :last-child { + border-bottom-left-radius: 0.28571429rem; + border-bottom-right-radius: 0.28571429rem; +} +.ui.modal > .ui.dimmer { + border-radius: inherit; +} + + +/******************************* + Content +*******************************/ + + +/*-------------- + Close +---------------*/ + +.ui.modal > .close { + cursor: pointer; + position: absolute; + top: -2.5rem; + right: -2.5rem; + z-index: 1; + opacity: 0.8; + font-size: 1.25em; + color: #FFFFFF; + width: 2.25rem; + height: 2.25rem; + padding: 0.625rem 0 0 0; +} +.ui.modal > .close:hover { + opacity: 1; +} + +/*-------------- + Header +---------------*/ + +.ui.modal > .header { + display: block; + font-family: var(--fonts-regular); + background: #FFFFFF; + margin: 0; + padding: 1.25rem 1.5rem; + box-shadow: none; + color: rgba(0, 0, 0, 0.85); + border-bottom: 1px solid rgba(34, 36, 38, 0.15); +} +.ui.modal > .header:not(.ui) { + font-size: 1.42857143rem; + line-height: 1.28571429em; + font-weight: 500; +} + +/*-------------- + Content +---------------*/ + +.ui.modal > .content { + display: block; + width: 100%; + font-size: 1em; + line-height: 1.4; + padding: 1.5rem; + background: #FFFFFF; +} +.ui.modal > .image.content { + display: flex; + flex-direction: row; +} + +/* Image */ +.ui.modal > .content > .image { + display: block; + flex: 0 1 auto; + width: ''; + align-self: start; + max-width: 100%; +} + +.ui.modal > [class*="stretched"] { + align-self: stretch; +} + +/* Description */ +.ui.modal > .content > .description { + display: block; + flex: 1 0 auto; + min-width: 0; + align-self: start; +} +.ui.modal > .content > i.icon + .description, +.ui.modal > .content > .image + .description { + flex: 0 1 auto; + min-width: ''; + width: auto; + padding-left: 2em; +} +/*rtl:ignore*/ +.ui.modal > .content > .image > i.icon { + margin: 0; + opacity: 1; + width: auto; + line-height: 1; + font-size: 8rem; +} + +/*-------------- + Actions +---------------*/ + +.ui.modal > .actions { + background: #F9FAFB; + padding: 1rem 1rem; + border-top: 1px solid rgba(34, 36, 38, 0.15); + text-align: right; +} +.ui.modal .actions > .button:not(.fluid) { + margin-left: 0.75em; +} +.ui.basic.modal > .actions { + border-top: none; +} + +/*------------------- + Responsive +--------------------*/ + + +/* Modal Width */ +@media only screen and (max-width: 767.98px) { + .ui.modal:not(.fullscreen) { + width: 95%; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 768px) { + .ui.modal:not(.fullscreen) { + width: 88%; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 992px) { + .ui.modal:not(.fullscreen) { + width: 850px; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 1200px) { + .ui.modal:not(.fullscreen) { + width: 900px; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 1920px) { + .ui.modal:not(.fullscreen) { + width: 950px; + margin: 0 0 0 0; + } +} + +/* Tablet and Mobile */ +@media only screen and (max-width: 991.98px) { + .ui.modal > .header { + padding-right: 2.25rem; + } + .ui.modal > .close { + top: 1.0535rem; + right: 1rem; + color: rgba(0, 0, 0, 0.87); + } +} + +/* Mobile */ +@media only screen and (max-width: 767.98px) { + .ui.modal > .header { + padding: 0.75rem 1rem !important; + padding-right: 2.25rem !important; + } + .ui.overlay.fullscreen.modal > .content.content.content { + min-height: calc(100vh - 8.1rem); + } + .ui.overlay.fullscreen.modal > .scrolling.content.content.content { + max-height: calc(100vh - 8.1rem); + } + .ui.modal > .content { + display: block; + padding: 1rem !important; + } + .ui.modal > .close { + top: 0.5rem !important; + right: 0.5rem !important; + } + /*rtl:ignore*/ + .ui.modal .image.content { + flex-direction: column; + } + .ui.modal > .content > .image { + display: block; + max-width: 100%; + margin: 0 auto !important; + text-align: center; + padding: 0 0 1rem !important; + } + .ui.modal > .content > .image > i.icon { + font-size: 5rem; + text-align: center; + } + /*rtl:ignore*/ + .ui.modal > .content > .description { + display: block; + width: 100% !important; + margin: 0 !important; + padding: 1rem 0 !important; + box-shadow: none; + } + +/* Let Buttons Stack */ + .ui.modal > .actions { + padding: 1rem 1rem 0rem !important; + } + .ui.modal .actions > .buttons, + .ui.modal .actions > .button { + margin-bottom: 1rem; + } +} + +/*-------------- + Coupling +---------------*/ + +.ui.inverted.dimmer > .ui.modal { + box-shadow: 1px 3px 10px 2px rgba(0, 0, 0, 0.2); +} + + +/******************************* + Types +*******************************/ + +.ui.basic.modal { + background-color: transparent; + border: none; + border-radius: 0; + box-shadow: none !important; + color: #FFFFFF; +} +.ui.basic.modal > .header, +.ui.basic.modal > .content, +.ui.basic.modal > .actions { + background-color: transparent; +} +.ui.basic.modal > .header { + color: #FFFFFF; + border-bottom: none; +} +.ui.basic.modal > .close { + top: 1rem; + right: 1.5rem; + color: #FFFFFF; +} +.ui.inverted.dimmer > .basic.modal { + color: rgba(0, 0, 0, 0.87); +} +.ui.inverted.dimmer > .ui.basic.modal > .header { + color: rgba(0, 0, 0, 0.85); +} + +/* Resort to margin positioning if legacy */ +.ui.legacy.legacy.modal, +.ui.legacy.legacy.page.dimmer > .ui.modal { + left: 50% !important; +} +.ui.legacy.legacy.modal:not(.aligned), +.ui.legacy.legacy.page.dimmer > .ui.modal:not(.aligned) { + top: 50%; +} +.ui.legacy.legacy.page.dimmer > .ui.scrolling.modal:not(.aligned), +.ui.page.dimmer > .ui.scrolling.legacy.legacy.modal:not(.aligned), +.ui.top.aligned.legacy.legacy.page.dimmer > .ui.modal:not(.aligned), +.ui.top.aligned.dimmer > .ui.legacy.legacy.modal:not(.aligned) { + top: auto; +} +.ui.legacy.overlay.fullscreen.modal { + margin-top: -2rem !important; +} + + +/******************************* + States +*******************************/ + +.ui.loading.modal { + display: block; + visibility: hidden; + z-index: -1; +} +.ui.active.modal { + display: block; +} + + +/******************************* + Variations +*******************************/ + + +/*-------------- + Aligned + ---------------*/ + +.modals.dimmer .ui.top.aligned.modal { + top: 5vh; +} +.modals.dimmer .ui.bottom.aligned.modal { + bottom: 5vh; +} +@media only screen and (max-width: 767.98px) { + .modals.dimmer .ui.top.aligned.modal { + top: 1rem; + } + .modals.dimmer .ui.bottom.aligned.modal { + bottom: 1rem; + } +} + +/*-------------- + Scrolling + ---------------*/ + + +/* Scrolling Dimmer */ +.scrolling.dimmable.dimmed { + overflow: hidden; +} +.scrolling.dimmable > .dimmer { + justify-content: flex-start; + position: fixed; +} +.scrolling.dimmable.dimmed > .dimmer { + overflow: auto; + -webkit-overflow-scrolling: touch; +} +.modals.dimmer .ui.scrolling.modal:not(.fullscreen) { + margin: 2rem auto; +} + +/* Fix for Firefox, Edge, IE11 */ +.modals.dimmer .ui.scrolling.modal:not([class*="overlay fullscreen"])::after { + content: '\00A0'; + position: absolute; + height: 2rem; +} + +/* Undetached Scrolling */ +.scrolling.undetached.dimmable.dimmed { + overflow: auto; + -webkit-overflow-scrolling: touch; +} +.scrolling.undetached.dimmable.dimmed > .dimmer { + overflow: hidden; +} +.scrolling.undetached.dimmable .ui.scrolling.modal:not(.fullscreen) { + position: absolute; + left: 50%; +} + +/* Scrolling Content */ +.ui.modal > .scrolling.content { + max-height: calc(80vh - 10rem); + overflow: auto; +} +.ui.overlay.fullscreen.modal > .content { + min-height: calc(100vh - 9.1rem); +} +.ui.overlay.fullscreen.modal > .scrolling.content { + max-height: calc(100vh - 9.1rem); +} + +/*-------------- + Full Screen + ---------------*/ + +.ui.fullscreen.modal { + width: 95%; + left: 2.5%; + margin: 1em auto; +} +.ui.overlay.fullscreen.modal { + width: 100%; + left: 0; + margin: 0 auto; + top: 0; + border-radius: 0; +} +.ui.modal > .close.inside + .header, +.ui.fullscreen.modal > .header { + padding-right: 2.25rem; +} +.ui.modal > .close.inside, +.ui.fullscreen.modal > .close { + top: 1.0535rem; + right: 1rem; + color: rgba(0, 0, 0, 0.87); +} +.ui.basic.fullscreen.modal > .close { + color: #FFFFFF; +} + +/*-------------- + Size +---------------*/ + +.ui.modal { + font-size: 1rem; +} +.ui.mini.modal > .header:not(.ui) { + font-size: 1.3em; +} +@media only screen and (max-width: 767.98px) { + .ui.mini.modal { + width: 95%; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 768px) { + .ui.mini.modal { + width: 35.2%; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 992px) { + .ui.mini.modal { + width: 340px; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 1200px) { + .ui.mini.modal { + width: 360px; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 1920px) { + .ui.mini.modal { + width: 380px; + margin: 0 0 0 0; + } +} +.ui.tiny.modal > .header:not(.ui) { + font-size: 1.3em; +} +@media only screen and (max-width: 767.98px) { + .ui.tiny.modal { + width: 95%; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 768px) { + .ui.tiny.modal { + width: 52.8%; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 992px) { + .ui.tiny.modal { + width: 510px; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 1200px) { + .ui.tiny.modal { + width: 540px; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 1920px) { + .ui.tiny.modal { + width: 570px; + margin: 0 0 0 0; + } +} +.ui.small.modal > .header:not(.ui) { + font-size: 1.3em; +} +@media only screen and (max-width: 767.98px) { + .ui.small.modal { + width: 95%; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 768px) { + .ui.small.modal { + width: 70.4%; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 992px) { + .ui.small.modal { + width: 680px; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 1200px) { + .ui.small.modal { + width: 720px; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 1920px) { + .ui.small.modal { + width: 760px; + margin: 0 0 0 0; + } +} +.ui.large.modal > .header:not(.ui) { + font-size: 1.6em; +} +@media only screen and (max-width: 767.98px) { + .ui.large.modal { + width: 95%; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 768px) { + .ui.large.modal { + width: 88%; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 992px) { + .ui.large.modal { + width: 1020px; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 1200px) { + .ui.large.modal { + width: 1080px; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 1920px) { + .ui.large.modal { + width: 1140px; + margin: 0 0 0 0; + } +} +.ui.big.modal > .header:not(.ui) { + font-size: 1.6em; +} +@media only screen and (max-width: 767.98px) { + .ui.big.modal { + width: 95%; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 768px) { + .ui.big.modal { + width: 88%; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 992px) { + .ui.big.modal { + width: 1190px; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 1200px) { + .ui.big.modal { + width: 1260px; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 1920px) { + .ui.big.modal { + width: 1330px; + margin: 0 0 0 0; + } +} +.ui.huge.modal > .header:not(.ui) { + font-size: 1.6em; +} +@media only screen and (max-width: 767.98px) { + .ui.huge.modal { + width: 95%; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 768px) { + .ui.huge.modal { + width: 88%; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 992px) { + .ui.huge.modal { + width: 1360px; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 1200px) { + .ui.huge.modal { + width: 1440px; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 1920px) { + .ui.huge.modal { + width: 1520px; + margin: 0 0 0 0; + } +} +.ui.massive.modal > .header:not(.ui) { + font-size: 1.8em; +} +@media only screen and (max-width: 767.98px) { + .ui.massive.modal { + width: 95%; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 768px) { + .ui.massive.modal { + width: 88%; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 992px) { + .ui.massive.modal { + width: 1530px; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 1200px) { + .ui.massive.modal { + width: 1620px; + margin: 0 0 0 0; + } +} +@media only screen and (min-width: 1920px) { + .ui.massive.modal { + width: 1710px; + margin: 0 0 0 0; + } +} + + +/******************************* + Theme Overrides +*******************************/ + + + +/******************************* + Site Overrides +*******************************/ + diff --git a/web_src/fomantic/build/components/modal.js b/web_src/fomantic/build/components/modal.js new file mode 100644 index 0000000000..3f578ccfcc --- /dev/null +++ b/web_src/fomantic/build/components/modal.js @@ -0,0 +1,1209 @@ +/*! + * # Fomantic-UI - Modal + * http://github.com/fomantic/Fomantic-UI/ + * + * + * Released under the MIT license + * http://opensource.org/licenses/MIT + * + */ + +;(function ($, window, document, undefined) { + +'use strict'; + +$.isFunction = $.isFunction || function(obj) { + return typeof obj === "function" && typeof obj.nodeType !== "number"; +}; + +window = (typeof window != 'undefined' && window.Math == Math) + ? window + : (typeof self != 'undefined' && self.Math == Math) + ? self + : Function('return this')() +; + +$.fn.modal = function(parameters) { + var + $allModules = $(this), + $window = $(window), + $document = $(document), + $body = $('body'), + + moduleSelector = $allModules.selector || '', + + time = new Date().getTime(), + performance = [], + + query = arguments[0], + methodInvoked = (typeof query == 'string'), + queryArguments = [].slice.call(arguments, 1), + + requestAnimationFrame = window.requestAnimationFrame + || window.mozRequestAnimationFrame + || window.webkitRequestAnimationFrame + || window.msRequestAnimationFrame + || function(callback) { setTimeout(callback, 0); }, + + returnedValue + ; + + $allModules + .each(function() { + var + settings = ( $.isPlainObject(parameters) ) + ? $.extend(true, {}, $.fn.modal.settings, parameters) + : $.extend({}, $.fn.modal.settings), + + selector = settings.selector, + className = settings.className, + namespace = settings.namespace, + error = settings.error, + + eventNamespace = '.' + namespace, + moduleNamespace = 'module-' + namespace, + + $module = $(this), + $context = $(settings.context), + $close = $module.find(selector.close), + + $allModals, + $otherModals, + $focusedElement, + $dimmable, + $dimmer, + + element = this, + instance = $module.data(moduleNamespace), + + ignoreRepeatedEvents = false, + + initialMouseDownInModal, + initialMouseDownInScrollbar, + initialBodyMargin = '', + tempBodyMargin = '', + + elementEventNamespace, + id, + observer, + module + ; + module = { + + initialize: function() { + module.cache = {}; + module.verbose('Initializing dimmer', $context); + + module.create.id(); + module.create.dimmer(); + + if ( settings.allowMultiple ) { + module.create.innerDimmer(); + } + if (!settings.centered){ + $module.addClass('top aligned'); + } + module.refreshModals(); + + module.bind.events(); + if(settings.observeChanges) { + module.observeChanges(); + } + module.instantiate(); + }, + + instantiate: function() { + module.verbose('Storing instance of modal'); + instance = module; + $module + .data(moduleNamespace, instance) + ; + }, + + create: { + dimmer: function() { + var + defaultSettings = { + debug : settings.debug, + dimmerName : 'modals' + }, + dimmerSettings = $.extend(true, defaultSettings, settings.dimmerSettings) + ; + if($.fn.dimmer === undefined) { + module.error(error.dimmer); + return; + } + module.debug('Creating dimmer'); + $dimmable = $context.dimmer(dimmerSettings); + if(settings.detachable) { + module.verbose('Modal is detachable, moving content into dimmer'); + $dimmable.dimmer('add content', $module); + } + else { + module.set.undetached(); + } + $dimmer = $dimmable.dimmer('get dimmer'); + }, + id: function() { + id = (Math.random().toString(16) + '000000000').substr(2, 8); + elementEventNamespace = '.' + id; + module.verbose('Creating unique id for element', id); + }, + innerDimmer: function() { + if ( $module.find(selector.dimmer).length == 0 ) { + $module.prepend('<div class="ui inverted dimmer"></div>'); + } + } + }, + + destroy: function() { + if (observer) { + observer.disconnect(); + } + module.verbose('Destroying previous modal'); + $module + .removeData(moduleNamespace) + .off(eventNamespace) + ; + $window.off(elementEventNamespace); + $dimmer.off(elementEventNamespace); + $close.off(eventNamespace); + $context.dimmer('destroy'); + }, + + observeChanges: function() { + if('MutationObserver' in window) { + observer = new MutationObserver(function(mutations) { + module.debug('DOM tree modified, refreshing'); + module.refresh(); + }); + observer.observe(element, { + childList : true, + subtree : true + }); + module.debug('Setting up mutation observer', observer); + } + }, + + refresh: function() { + module.remove.scrolling(); + module.cacheSizes(); + if(!module.can.useFlex()) { + module.set.modalOffset(); + } + module.set.screenHeight(); + module.set.type(); + }, + + refreshModals: function() { + $otherModals = $module.siblings(selector.modal); + $allModals = $otherModals.add($module); + }, + + attachEvents: function(selector, event) { + var + $toggle = $(selector) + ; + event = $.isFunction(module[event]) + ? module[event] + : module.toggle + ; + if($toggle.length > 0) { + module.debug('Attaching modal events to element', selector, event); + $toggle + .off(eventNamespace) + .on('click' + eventNamespace, event) + ; + } + else { + module.error(error.notFound, selector); + } + }, + + bind: { + events: function() { + module.verbose('Attaching events'); + $module + .on('click' + eventNamespace, selector.close, module.event.close) + .on('click' + eventNamespace, selector.approve, module.event.approve) + .on('click' + eventNamespace, selector.deny, module.event.deny) + ; + $window + .on('resize' + elementEventNamespace, module.event.resize) + ; + }, + scrollLock: function() { + // touch events default to passive, due to changes in chrome to optimize mobile perf + $dimmable.get(0).addEventListener('touchmove', module.event.preventScroll, { passive: false }); + } + }, + + unbind: { + scrollLock: function() { + $dimmable.get(0).removeEventListener('touchmove', module.event.preventScroll, { passive: false }); + } + }, + + get: { + id: function() { + return (Math.random().toString(16) + '000000000').substr(2, 8); + } + }, + + event: { + approve: function() { + if(ignoreRepeatedEvents || settings.onApprove.call(element, $(this)) === false) { + module.verbose('Approve callback returned false cancelling hide'); + return; + } + ignoreRepeatedEvents = true; + module.hide(function() { + ignoreRepeatedEvents = false; + }); + }, + preventScroll: function(event) { + if(event.target.className.indexOf('dimmer') !== -1) { + event.preventDefault(); + } + }, + deny: function() { + if(ignoreRepeatedEvents || settings.onDeny.call(element, $(this)) === false) { + module.verbose('Deny callback returned false cancelling hide'); + return; + } + ignoreRepeatedEvents = true; + module.hide(function() { + ignoreRepeatedEvents = false; + }); + }, + close: function() { + module.hide(); + }, + mousedown: function(event) { + var + $target = $(event.target), + isRtl = module.is.rtl(); + ; + initialMouseDownInModal = ($target.closest(selector.modal).length > 0); + if(initialMouseDownInModal) { + module.verbose('Mouse down event registered inside the modal'); + } + initialMouseDownInScrollbar = module.is.scrolling() && ((!isRtl && $(window).outerWidth() - settings.scrollbarWidth <= event.clientX) || (isRtl && settings.scrollbarWidth >= event.clientX)); + if(initialMouseDownInScrollbar) { + module.verbose('Mouse down event registered inside the scrollbar'); + } + }, + mouseup: function(event) { + if(!settings.closable) { + module.verbose('Dimmer clicked but closable setting is disabled'); + return; + } + if(initialMouseDownInModal) { + module.debug('Dimmer clicked but mouse down was initially registered inside the modal'); + return; + } + if(initialMouseDownInScrollbar){ + module.debug('Dimmer clicked but mouse down was initially registered inside the scrollbar'); + return; + } + var + $target = $(event.target), + isInModal = ($target.closest(selector.modal).length > 0), + isInDOM = $.contains(document.documentElement, event.target) + ; + if(!isInModal && isInDOM && module.is.active() && $module.hasClass(className.front) ) { + module.debug('Dimmer clicked, hiding all modals'); + if(settings.allowMultiple) { + if(!module.hideAll()) { + return; + } + } + else if(!module.hide()){ + return; + } + module.remove.clickaway(); + } + }, + debounce: function(method, delay) { + clearTimeout(module.timer); + module.timer = setTimeout(method, delay); + }, + keyboard: function(event) { + var + keyCode = event.which, + escapeKey = 27 + ; + if(keyCode == escapeKey) { + if(settings.closable) { + module.debug('Escape key pressed hiding modal'); + if ( $module.hasClass(className.front) ) { + module.hide(); + } + } + else { + module.debug('Escape key pressed, but closable is set to false'); + } + event.preventDefault(); + } + }, + resize: function() { + if( $dimmable.dimmer('is active') && ( module.is.animating() || module.is.active() ) ) { + requestAnimationFrame(module.refresh); + } + } + }, + + toggle: function() { + if( module.is.active() || module.is.animating() ) { + module.hide(); + } + else { + module.show(); + } + }, + + show: function(callback) { + callback = $.isFunction(callback) + ? callback + : function(){} + ; + module.refreshModals(); + module.set.dimmerSettings(); + module.set.dimmerStyles(); + + module.showModal(callback); + }, + + hide: function(callback) { + callback = $.isFunction(callback) + ? callback + : function(){} + ; + module.refreshModals(); + return module.hideModal(callback); + }, + + showModal: function(callback) { + callback = $.isFunction(callback) + ? callback + : function(){} + ; + if( module.is.animating() || !module.is.active() ) { + module.showDimmer(); + module.cacheSizes(); + module.set.bodyMargin(); + if(module.can.useFlex()) { + module.remove.legacy(); + } + else { + module.set.legacy(); + module.set.modalOffset(); + module.debug('Using non-flex legacy modal positioning.'); + } + module.set.screenHeight(); + module.set.type(); + module.set.clickaway(); + + if( !settings.allowMultiple && module.others.active() ) { + module.hideOthers(module.showModal); + } + else { + ignoreRepeatedEvents = false; + if( settings.allowMultiple ) { + if ( module.others.active() ) { + $otherModals.filter('.' + className.active).find(selector.dimmer).addClass('active'); + } + + if ( settings.detachable ) { + $module.detach().appendTo($dimmer); + } + } + settings.onShow.call(element); + if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { + module.debug('Showing modal with css animations'); + $module + .transition({ + debug : settings.debug, + animation : settings.transition + ' in', + queue : settings.queue, + duration : settings.duration, + useFailSafe : true, + onComplete : function() { + settings.onVisible.apply(element); + if(settings.keyboardShortcuts) { + module.add.keyboardShortcuts(); + } + module.save.focus(); + module.set.active(); + if(settings.autofocus) { + module.set.autofocus(); + } + callback(); + } + }) + ; + } + else { + module.error(error.noTransition); + } + } + } + else { + module.debug('Modal is already visible'); + } + }, + + hideModal: function(callback, keepDimmed, hideOthersToo) { + var + $previousModal = $otherModals.filter('.' + className.active).last() + ; + callback = $.isFunction(callback) + ? callback + : function(){} + ; + module.debug('Hiding modal'); + if(settings.onHide.call(element, $(this)) === false) { + module.verbose('Hide callback returned false cancelling hide'); + ignoreRepeatedEvents = false; + return false; + } + $module.fomanticExt.onModalBeforeHidden.call(element); // GITEA-PATCH: handle more UI updates before hidden + if( module.is.animating() || module.is.active() ) { + if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { + module.remove.active(); + $module + .transition({ + debug : settings.debug, + animation : settings.transition + ' out', + queue : settings.queue, + duration : settings.duration, + useFailSafe : true, + onStart : function() { + if(!module.others.active() && !module.others.animating() && !keepDimmed) { + module.hideDimmer(); + } + if( settings.keyboardShortcuts && !module.others.active() ) { + module.remove.keyboardShortcuts(); + } + }, + onComplete : function() { + module.unbind.scrollLock(); + if ( settings.allowMultiple ) { + $previousModal.addClass(className.front); + $module.removeClass(className.front); + + if ( hideOthersToo ) { + $allModals.find(selector.dimmer).removeClass('active'); + } + else { + $previousModal.find(selector.dimmer).removeClass('active'); + } + } + settings.onHidden.call(element); + module.remove.dimmerStyles(); + module.restore.focus(); + callback(); + } + }) + ; + } + else { + module.error(error.noTransition); + } + } + }, + + showDimmer: function() { + if($dimmable.dimmer('is animating') || !$dimmable.dimmer('is active') ) { + module.save.bodyMargin(); + module.debug('Showing dimmer'); + $dimmable.dimmer('show'); + } + else { + module.debug('Dimmer already visible'); + } + }, + + hideDimmer: function() { + if( $dimmable.dimmer('is animating') || ($dimmable.dimmer('is active')) ) { + module.unbind.scrollLock(); + $dimmable.dimmer('hide', function() { + module.restore.bodyMargin(); + module.remove.clickaway(); + module.remove.screenHeight(); + }); + } + else { + module.debug('Dimmer is not visible cannot hide'); + return; + } + }, + + hideAll: function(callback) { + var + $visibleModals = $allModals.filter('.' + className.active + ', .' + className.animating) + ; + callback = $.isFunction(callback) + ? callback + : function(){} + ; + if( $visibleModals.length > 0 ) { + module.debug('Hiding all visible modals'); + var hideOk = true; +//check in reverse order trying to hide most top displayed modal first + $($visibleModals.get().reverse()).each(function(index,element){ + if(hideOk){ + hideOk = $(element).modal('hide modal', callback, false, true); + } + }); + if(hideOk) { + module.hideDimmer(); + } + return hideOk; + } + }, + + hideOthers: function(callback) { + var + $visibleModals = $otherModals.filter('.' + className.active + ', .' + className.animating) + ; + callback = $.isFunction(callback) + ? callback + : function(){} + ; + if( $visibleModals.length > 0 ) { + module.debug('Hiding other modals', $otherModals); + $visibleModals + .modal('hide modal', callback, true) + ; + } + }, + + others: { + active: function() { + return ($otherModals.filter('.' + className.active).length > 0); + }, + animating: function() { + return ($otherModals.filter('.' + className.animating).length > 0); + } + }, + + + add: { + keyboardShortcuts: function() { + module.verbose('Adding keyboard shortcuts'); + $document + .on('keyup' + eventNamespace, module.event.keyboard) + ; + } + }, + + save: { + focus: function() { + var + $activeElement = $(document.activeElement), + inCurrentModal = $activeElement.closest($module).length > 0 + ; + if(!inCurrentModal) { + $focusedElement = $(document.activeElement).blur(); + } + }, + bodyMargin: function() { + initialBodyMargin = $body.css('margin-'+(module.can.leftBodyScrollbar() ? 'left':'right')); + var bodyMarginRightPixel = parseInt(initialBodyMargin.replace(/[^\d.]/g, '')), + bodyScrollbarWidth = window.innerWidth - document.documentElement.clientWidth; + tempBodyMargin = bodyMarginRightPixel + bodyScrollbarWidth; + } + }, + + restore: { + focus: function() { + if($focusedElement && $focusedElement.length > 0 && settings.restoreFocus) { + $focusedElement.focus(); + } + }, + bodyMargin: function() { + var position = module.can.leftBodyScrollbar() ? 'left':'right'; + $body.css('margin-'+position, initialBodyMargin); + $body.find(selector.bodyFixed.replace('right',position)).css('padding-'+position, initialBodyMargin); + } + }, + + remove: { + active: function() { + $module.removeClass(className.active); + }, + legacy: function() { + $module.removeClass(className.legacy); + }, + clickaway: function() { + if (!settings.detachable) { + $module + .off('mousedown' + elementEventNamespace) + ; + } + $dimmer + .off('mousedown' + elementEventNamespace) + ; + $dimmer + .off('mouseup' + elementEventNamespace) + ; + }, + dimmerStyles: function() { + $dimmer.removeClass(className.inverted); + $dimmable.removeClass(className.blurring); + }, + bodyStyle: function() { + if($body.attr('style') === '') { + module.verbose('Removing style attribute'); + $body.removeAttr('style'); + } + }, + screenHeight: function() { + module.debug('Removing page height'); + $body + .css('height', '') + ; + }, + keyboardShortcuts: function() { + module.verbose('Removing keyboard shortcuts'); + $document + .off('keyup' + eventNamespace) + ; + }, + scrolling: function() { + $dimmable.removeClass(className.scrolling); + $module.removeClass(className.scrolling); + } + }, + + cacheSizes: function() { + $module.addClass(className.loading); + var + scrollHeight = $module.prop('scrollHeight'), + modalWidth = $module.outerWidth(), + modalHeight = $module.outerHeight() + ; + if(module.cache.pageHeight === undefined || modalHeight !== 0) { + $.extend(module.cache, { + pageHeight : $(document).outerHeight(), + width : modalWidth, + height : modalHeight + settings.offset, + scrollHeight : scrollHeight + settings.offset, + contextHeight : (settings.context == 'body') + ? $(window).height() + : $dimmable.height(), + }); + module.cache.topOffset = -(module.cache.height / 2); + } + $module.removeClass(className.loading); + module.debug('Caching modal and container sizes', module.cache); + }, + + can: { + leftBodyScrollbar: function(){ + if(module.cache.leftBodyScrollbar === undefined) { + module.cache.leftBodyScrollbar = module.is.rtl() && ((module.is.iframe && !module.is.firefox()) || module.is.safari() || module.is.edge() || module.is.ie()); + } + return module.cache.leftBodyScrollbar; + }, + useFlex: function() { + if (settings.useFlex === 'auto') { + return settings.detachable && !module.is.ie(); + } + if(settings.useFlex && module.is.ie()) { + module.debug('useFlex true is not supported in IE'); + } else if(settings.useFlex && !settings.detachable) { + module.debug('useFlex true in combination with detachable false is not supported'); + } + return settings.useFlex; + }, + fit: function() { + var + contextHeight = module.cache.contextHeight, + verticalCenter = module.cache.contextHeight / 2, + topOffset = module.cache.topOffset, + scrollHeight = module.cache.scrollHeight, + height = module.cache.height, + paddingHeight = settings.padding, + startPosition = (verticalCenter + topOffset) + ; + return (scrollHeight > height) + ? (startPosition + scrollHeight + paddingHeight < contextHeight) + : (height + (paddingHeight * 2) < contextHeight) + ; + } + }, + + is: { + active: function() { + return $module.hasClass(className.active); + }, + ie: function() { + if(module.cache.isIE === undefined) { + var + isIE11 = (!(window.ActiveXObject) && 'ActiveXObject' in window), + isIE = ('ActiveXObject' in window) + ; + module.cache.isIE = (isIE11 || isIE); + } + return module.cache.isIE; + }, + animating: function() { + return $module.transition('is supported') + ? $module.transition('is animating') + : $module.is(':visible') + ; + }, + scrolling: function() { + return $dimmable.hasClass(className.scrolling); + }, + modernBrowser: function() { + // appName for IE11 reports 'Netscape' can no longer use + return !(window.ActiveXObject || 'ActiveXObject' in window); + }, + rtl: function() { + if(module.cache.isRTL === undefined) { + module.cache.isRTL = $body.attr('dir') === 'rtl' || $body.css('direction') === 'rtl'; + } + return module.cache.isRTL; + }, + safari: function() { + if(module.cache.isSafari === undefined) { + module.cache.isSafari = /constructor/i.test(window.HTMLElement) || !!window.ApplePaySession; + } + return module.cache.isSafari; + }, + edge: function(){ + if(module.cache.isEdge === undefined) { + module.cache.isEdge = !!window.setImmediate && !module.is.ie(); + } + return module.cache.isEdge; + }, + firefox: function(){ + if(module.cache.isFirefox === undefined) { + module.cache.isFirefox = !!window.InstallTrigger; + } + return module.cache.isFirefox; + }, + iframe: function() { + return !(self === top); + } + }, + + set: { + autofocus: function() { + var + $inputs = $module.find('[tabindex], :input').filter(':visible').filter(function() { + return $(this).closest('.disabled').length === 0; + }), + $autofocus = $inputs.filter('[autofocus]'), + $input = ($autofocus.length > 0) + ? $autofocus.first() + : $inputs.first() + ; + if($input.length > 0) { + $input.focus(); + } + }, + bodyMargin: function() { + var position = module.can.leftBodyScrollbar() ? 'left':'right'; + if(settings.detachable || module.can.fit()) { + $body.css('margin-'+position, tempBodyMargin + 'px'); + } + $body.find(selector.bodyFixed.replace('right',position)).css('padding-'+position, tempBodyMargin + 'px'); + }, + clickaway: function() { + if (!settings.detachable) { + $module + .on('mousedown' + elementEventNamespace, module.event.mousedown) + ; + } + $dimmer + .on('mousedown' + elementEventNamespace, module.event.mousedown) + ; + $dimmer + .on('mouseup' + elementEventNamespace, module.event.mouseup) + ; + }, + dimmerSettings: function() { + if($.fn.dimmer === undefined) { + module.error(error.dimmer); + return; + } + var + defaultSettings = { + debug : settings.debug, + dimmerName : 'modals', + closable : 'auto', + useFlex : module.can.useFlex(), + duration : { + show : settings.duration, + hide : settings.duration + } + }, + dimmerSettings = $.extend(true, defaultSettings, settings.dimmerSettings) + ; + if(settings.inverted) { + dimmerSettings.variation = (dimmerSettings.variation !== undefined) + ? dimmerSettings.variation + ' inverted' + : 'inverted' + ; + } + $context.dimmer('setting', dimmerSettings); + }, + dimmerStyles: function() { + if(settings.inverted) { + $dimmer.addClass(className.inverted); + } + else { + $dimmer.removeClass(className.inverted); + } + if(settings.blurring) { + $dimmable.addClass(className.blurring); + } + else { + $dimmable.removeClass(className.blurring); + } + }, + modalOffset: function() { + if (!settings.detachable) { + var canFit = module.can.fit(); + $module + .css({ + top: (!$module.hasClass('aligned') && canFit) + ? $(document).scrollTop() + (module.cache.contextHeight - module.cache.height) / 2 + : !canFit || $module.hasClass('top') + ? $(document).scrollTop() + settings.padding + : $(document).scrollTop() + (module.cache.contextHeight - module.cache.height - settings.padding), + marginLeft: -(module.cache.width / 2) + }) + ; + } else { + $module + .css({ + marginTop: (!$module.hasClass('aligned') && module.can.fit()) + ? -(module.cache.height / 2) + : settings.padding / 2, + marginLeft: -(module.cache.width / 2) + }) + ; + } + module.verbose('Setting modal offset for legacy mode'); + }, + screenHeight: function() { + if( module.can.fit() ) { + $body.css('height', ''); + } + else if(!$module.hasClass('bottom')) { + module.debug('Modal is taller than page content, resizing page height'); + $body + .css('height', module.cache.height + (settings.padding * 2) ) + ; + } + }, + active: function() { + $module.addClass(className.active + ' ' + className.front); + $otherModals.filter('.' + className.active).removeClass(className.front); + }, + scrolling: function() { + $dimmable.addClass(className.scrolling); + $module.addClass(className.scrolling); + module.unbind.scrollLock(); + }, + legacy: function() { + $module.addClass(className.legacy); + }, + type: function() { + if(module.can.fit()) { + module.verbose('Modal fits on screen'); + if(!module.others.active() && !module.others.animating()) { + module.remove.scrolling(); + module.bind.scrollLock(); + } + } + else if (!$module.hasClass('bottom')){ + module.verbose('Modal cannot fit on screen setting to scrolling'); + module.set.scrolling(); + } else { + module.verbose('Bottom aligned modal not fitting on screen is unsupported for scrolling'); + } + }, + undetached: function() { + $dimmable.addClass(className.undetached); + } + }, + + setting: function(name, value) { + module.debug('Changing setting', name, value); + if( $.isPlainObject(name) ) { + $.extend(true, settings, name); + } + else if(value !== undefined) { + if($.isPlainObject(settings[name])) { + $.extend(true, settings[name], value); + } + else { + settings[name] = value; + } + } + else { + return settings[name]; + } + }, + internal: function(name, value) { + if( $.isPlainObject(name) ) { + $.extend(true, module, name); + } + else if(value !== undefined) { + module[name] = value; + } + else { + return module[name]; + } + }, + debug: function() { + if(!settings.silent && settings.debug) { + if(settings.performance) { + module.performance.log(arguments); + } + else { + module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); + module.debug.apply(console, arguments); + } + } + }, + verbose: function() { + if(!settings.silent && settings.verbose && settings.debug) { + if(settings.performance) { + module.performance.log(arguments); + } + else { + module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); + module.verbose.apply(console, arguments); + } + } + }, + error: function() { + if(!settings.silent) { + module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); + module.error.apply(console, arguments); + } + }, + performance: { + log: function(message) { + var + currentTime, + executionTime, + previousTime + ; + if(settings.performance) { + currentTime = new Date().getTime(); + previousTime = time || currentTime; + executionTime = currentTime - previousTime; + time = currentTime; + performance.push({ + 'Name' : message[0], + 'Arguments' : [].slice.call(message, 1) || '', + 'Element' : element, + 'Execution Time' : executionTime + }); + } + clearTimeout(module.performance.timer); + module.performance.timer = setTimeout(module.performance.display, 500); + }, + display: function() { + var + title = settings.name + ':', + totalTime = 0 + ; + time = false; + clearTimeout(module.performance.timer); + $.each(performance, function(index, data) { + totalTime += data['Execution Time']; + }); + title += ' ' + totalTime + 'ms'; + if(moduleSelector) { + title += ' \'' + moduleSelector + '\''; + } + if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { + console.groupCollapsed(title); + if(console.table) { + console.table(performance); + } + else { + $.each(performance, function(index, data) { + console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); + }); + } + console.groupEnd(); + } + performance = []; + } + }, + invoke: function(query, passedArguments, context) { + var + object = instance, + maxDepth, + found, + response + ; + passedArguments = passedArguments || queryArguments; + context = element || context; + if(typeof query == 'string' && object !== undefined) { + query = query.split(/[\. ]/); + maxDepth = query.length - 1; + $.each(query, function(depth, value) { + var camelCaseValue = (depth != maxDepth) + ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) + : query + ; + if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { + object = object[camelCaseValue]; + } + else if( object[camelCaseValue] !== undefined ) { + found = object[camelCaseValue]; + return false; + } + else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { + object = object[value]; + } + else if( object[value] !== undefined ) { + found = object[value]; + return false; + } + else { + return false; + } + }); + } + if ( $.isFunction( found ) ) { + response = found.apply(context, passedArguments); + } + else if(found !== undefined) { + response = found; + } + if(Array.isArray(returnedValue)) { + returnedValue.push(response); + } + else if(returnedValue !== undefined) { + returnedValue = [returnedValue, response]; + } + else if(response !== undefined) { + returnedValue = response; + } + return found; + } + }; + + if(methodInvoked) { + if(instance === undefined) { + module.initialize(); + } + module.invoke(query); + } + else { + if(instance !== undefined) { + instance.invoke('destroy'); + } + module.initialize(); + } + }) + ; + + return (returnedValue !== undefined) + ? returnedValue + : this + ; +}; + +$.fn.modal.settings = { + + name : 'Modal', + namespace : 'modal', + + useFlex : 'auto', + offset : 0, + + silent : false, + debug : false, + verbose : false, + performance : true, + + observeChanges : false, + + allowMultiple : false, + detachable : true, + closable : true, + autofocus : true, + restoreFocus : true, + + inverted : false, + blurring : false, + + centered : true, + + dimmerSettings : { + closable : false, + useCSS : true + }, + + // whether to use keyboard shortcuts + keyboardShortcuts: true, + + context : 'body', + + queue : false, + duration : 500, + transition : 'scale', + + // padding with edge of page + padding : 50, + scrollbarWidth: 10, + + // called before show animation + onShow : function(){}, + + // called after show animation + onVisible : function(){}, + + // called before hide animation + onHide : function(){ return true; }, + + // called after hide animation + onHidden : function(){}, + + // called after approve selector match + onApprove : function(){ return true; }, + + // called after deny selector match + onDeny : function(){ return true; }, + + selector : { + close : '> .close', + approve : '.actions .positive, .actions .approve, .actions .ok', + deny : '.actions .negative, .actions .deny, .actions .cancel', + modal : '.ui.modal', + dimmer : '> .ui.dimmer', + bodyFixed: '> .ui.fixed.menu, > .ui.right.toast-container, > .ui.right.sidebar' + }, + error : { + dimmer : 'UI Dimmer, a required component is not included in this page', + method : 'The method you called is not defined.', + notFound : 'The element you specified could not be found' + }, + className : { + active : 'active', + animating : 'animating', + blurring : 'blurring', + inverted : 'inverted', + legacy : 'legacy', + loading : 'loading', + scrolling : 'scrolling', + undetached : 'undetached', + front : 'front' + } +}; + + +})( jQuery, window, document ); diff --git a/web_src/fomantic/build/components/search.css b/web_src/fomantic/build/components/search.css new file mode 100644 index 0000000000..b0a7b8db7e --- /dev/null +++ b/web_src/fomantic/build/components/search.css @@ -0,0 +1,520 @@ +/*! + * # Fomantic-UI - Search + * http://github.com/fomantic/Fomantic-UI/ + * + * + * Released under the MIT license + * http://opensource.org/licenses/MIT + * + */ + + +/******************************* + Search +*******************************/ + +.ui.search { + position: relative; +} +.ui.search > .prompt { + margin: 0; + outline: none; + -webkit-appearance: none; + -webkit-tap-highlight-color: rgba(255, 255, 255, 0); + text-shadow: none; + font-style: normal; + font-weight: normal; + line-height: 1.21428571em; + padding: 0.67857143em 1em; + font-size: 1em; + background: #FFFFFF; + border: 1px solid rgba(34, 36, 38, 0.15); + color: rgba(0, 0, 0, 0.87); + box-shadow: 0 0 0 0 transparent inset; + transition: background-color 0.1s ease, color 0.1s ease, box-shadow 0.1s ease, border-color 0.1s ease; +} +.ui.search .prompt { + border-radius: 500rem; +} + +/*-------------- + Icon +---------------*/ + +.ui.search .prompt ~ .search.icon { + cursor: pointer; +} + +/*-------------- + Results +---------------*/ + +.ui.search > .results { + display: none; + position: absolute; + top: 100%; + left: 0; + transform-origin: center top; + white-space: normal; + text-align: left; + text-transform: none; + background: #FFFFFF; + margin-top: 0.5em; + width: 18em; + border-radius: 0.28571429rem; + box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15); + border: 1px solid #D4D4D5; + z-index: 998; +} +.ui.search > .results > :first-child { + border-radius: 0.28571429rem 0.28571429rem 0 0; +} +.ui.search > .results > :last-child { + border-radius: 0 0 0.28571429rem 0.28571429rem; +} + +/*-------------- + Result +---------------*/ + +.ui.search > .results .result { + cursor: pointer; + display: block; + overflow: hidden; + font-size: 1em; + padding: 0.85714286em 1.14285714em; + color: rgba(0, 0, 0, 0.87); + line-height: 1.33; + border-bottom: 1px solid rgba(34, 36, 38, 0.1); +} +.ui.search > .results .result:last-child { + border-bottom: none !important; +} + +/* Image */ +.ui.search > .results .result .image { + float: right; + overflow: hidden; + background: none; + width: 5em; + height: 3em; + border-radius: 0.25em; +} +.ui.search > .results .result .image img { + display: block; + width: auto; + height: 100%; +} + +/*-------------- + Info +---------------*/ + +.ui.search > .results .result .image + .content { + margin: 0 6em 0 0; +} +.ui.search > .results .result .title { + margin: -0.14285714em 0 0; + font-family: var(--fonts-regular); + font-weight: 500; + font-size: 1em; + color: rgba(0, 0, 0, 0.85); +} +.ui.search > .results .result .description { + margin-top: 0; + font-size: 0.92857143em; + color: rgba(0, 0, 0, 0.4); +} +.ui.search > .results .result .price { + float: right; + color: #21BA45; +} + +/*-------------- + Message +---------------*/ + +.ui.search > .results > .message { + padding: 1em 1em; +} +.ui.search > .results > .message .header { + font-family: var(--fonts-regular); + font-size: 1rem; + font-weight: 500; + color: rgba(0, 0, 0, 0.87); +} +.ui.search > .results > .message .description { + margin-top: 0.25rem; + font-size: 1em; + color: rgba(0, 0, 0, 0.87); +} + +/* View All Results */ +.ui.search > .results > .action { + display: block; + border-top: none; + background: #F3F4F5; + padding: 0.92857143em 1em; + color: rgba(0, 0, 0, 0.87); + font-weight: 500; + text-align: center; +} + + +/******************************* + States +*******************************/ + + +/*-------------------- + Focus +---------------------*/ + +.ui.search > .prompt:focus { + border-color: rgba(34, 36, 38, 0.35); + background: #FFFFFF; + color: rgba(0, 0, 0, 0.95); +} + +/*-------------------- + Loading + ---------------------*/ + +.ui.loading.search .input > i.icon:before { + position: absolute; + content: ''; + top: 50%; + left: 50%; + margin: -0.64285714em 0 0 -0.64285714em; + width: 1.28571429em; + height: 1.28571429em; + border-radius: 500rem; + border: 0.2em solid rgba(0, 0, 0, 0.1); +} +.ui.loading.search .input > i.icon:after { + position: absolute; + content: ''; + top: 50%; + left: 50%; + margin: -0.64285714em 0 0 -0.64285714em; + width: 1.28571429em; + height: 1.28571429em; + animation: loader 0.6s infinite linear; + border: 0.2em solid #767676; + border-radius: 500rem; + box-shadow: 0 0 0 1px transparent; +} + +/*-------------- + Hover +---------------*/ + +.ui.search > .results .result:hover, +.ui.category.search > .results .category .result:hover { + background: #F9FAFB; +} +.ui.search .action:hover:not(div) { + background: #E0E0E0; +} + +/*-------------- + Active +---------------*/ + +.ui.category.search > .results .category.active { + background: #F3F4F5; +} +.ui.category.search > .results .category.active > .name { + color: rgba(0, 0, 0, 0.87); +} +.ui.search > .results .result.active, +.ui.category.search > .results .category .result.active { + position: relative; + border-left-color: rgba(34, 36, 38, 0.1); + background: #F3F4F5; + box-shadow: none; +} +.ui.search > .results .result.active .title { + color: rgba(0, 0, 0, 0.85); +} +.ui.search > .results .result.active .description { + color: rgba(0, 0, 0, 0.85); +} + +/*-------------------- + Disabled + ----------------------*/ + + +/* Disabled */ +.ui.disabled.search { + cursor: default; + pointer-events: none; + opacity: var(--opacity-disabled); +} + + +/******************************* + Types +*******************************/ + + +/*-------------- + Selection + ---------------*/ + +.ui.search.selection .prompt { + border-radius: 0.28571429rem; +} + +/* Remove input */ +.ui.search.selection > .icon.input > .remove.icon { + pointer-events: none; + position: absolute; + left: auto; + opacity: 0; + color: ''; + top: 0; + right: 0; + transition: color 0.1s ease, opacity 0.1s ease; +} +.ui.search.selection > .icon.input > .active.remove.icon { + cursor: pointer; + opacity: 0.8; + pointer-events: auto; +} +.ui.search.selection > .icon.input:not([class*="left icon"]) > .icon ~ .remove.icon { + right: 1.85714em; +} +.ui.search.selection > .icon.input > .remove.icon:hover { + opacity: 1; + color: #DB2828; +} + +/*-------------- + Category + ---------------*/ + +.ui.category.search .results { + width: 28em; +} +.ui.category.search .results.animating, +.ui.category.search .results.visible { + display: table; +} + +/* Category */ +.ui.category.search > .results .category { + display: table-row; + background: #F3F4F5; + box-shadow: none; + transition: background 0.1s ease, border-color 0.1s ease; +} + +/* Last Category */ +.ui.category.search > .results .category:last-child { + border-bottom: none; +} + +/* First / Last */ +.ui.category.search > .results .category:first-child .name + .result { + border-radius: 0 0.28571429rem 0 0; +} +.ui.category.search > .results .category:last-child .result:last-child { + border-radius: 0 0 0.28571429rem 0; +} + +/* Category Result Name */ +.ui.category.search > .results .category > .name { + display: table-cell; + text-overflow: ellipsis; + width: 100px; + white-space: nowrap; + background: transparent; + font-family: var(--fonts-regular); + font-size: 1em; + padding: 0.4em 1em; + font-weight: 500; + color: rgba(0, 0, 0, 0.4); + border-bottom: 1px solid rgba(34, 36, 38, 0.1); +} + +/* Category Result */ +.ui.category.search > .results .category .results { + display: table-cell; + background: #FFFFFF; + border-left: 1px solid rgba(34, 36, 38, 0.15); + border-bottom: 1px solid rgba(34, 36, 38, 0.1); +} +.ui.category.search > .results .category .result { + border-bottom: 1px solid rgba(34, 36, 38, 0.1); + transition: background 0.1s ease, border-color 0.1s ease; + padding: 0.85714286em 1.14285714em; +} + + +/******************************* + Variations +*******************************/ + + +/*------------------- + Scrolling + --------------------*/ + +.ui.scrolling.search > .results, +.ui.search.long > .results, +.ui.search.short > .results { + overflow-x: hidden; + overflow-y: auto; + backface-visibility: hidden; + -webkit-overflow-scrolling: touch; +} +@media only screen and (max-width: 767.98px) { + .ui.scrolling.search > .results { + max-height: 12.17714286em; + } +} +@media only screen and (min-width: 768px) { + .ui.scrolling.search > .results { + max-height: 18.26571429em; + } +} +@media only screen and (min-width: 992px) { + .ui.scrolling.search > .results { + max-height: 24.35428571em; + } +} +@media only screen and (min-width: 1920px) { + .ui.scrolling.search > .results { + max-height: 36.53142857em; + } +} +@media only screen and (max-width: 767.98px) { + .ui.search.short > .results { + max-height: 12.17714286em; + } + .ui.search[class*="very short"] > .results { + max-height: 9.13285714em; + } + .ui.search.long > .results { + max-height: 24.35428571em; + } + .ui.search[class*="very long"] > .results { + max-height: 36.53142857em; + } +} +@media only screen and (min-width: 768px) { + .ui.search.short > .results { + max-height: 18.26571429em; + } + .ui.search[class*="very short"] > .results { + max-height: 13.69928571em; + } + .ui.search.long > .results { + max-height: 36.53142857em; + } + .ui.search[class*="very long"] > .results { + max-height: 54.79714286em; + } +} +@media only screen and (min-width: 992px) { + .ui.search.short > .results { + max-height: 24.35428571em; + } + .ui.search[class*="very short"] > .results { + max-height: 18.26571429em; + } + .ui.search.long > .results { + max-height: 48.70857143em; + } + .ui.search[class*="very long"] > .results { + max-height: 73.06285714em; + } +} +@media only screen and (min-width: 1920px) { + .ui.search.short > .results { + max-height: 36.53142857em; + } + .ui.search[class*="very short"] > .results { + max-height: 27.39857143em; + } + .ui.search.long > .results { + max-height: 73.06285714em; + } + .ui.search[class*="very long"] > .results { + max-height: 109.59428571em; + } +} + +/*------------------- + Left / Right + --------------------*/ + +.ui[class*="left aligned"].search > .results { + right: auto; + left: 0; +} +.ui[class*="right aligned"].search > .results { + right: 0; + left: auto; +} + +/*-------------- + Fluid +---------------*/ + +.ui.fluid.search .results { + width: 100%; +} + +/*-------------- + Sizes +---------------*/ + +.ui.search { + font-size: 1em; +} +.ui.mini.search { + font-size: 0.78571429em; +} +.ui.tiny.search { + font-size: 0.85714286em; +} +.ui.small.search { + font-size: 0.92857143em; +} +.ui.large.search { + font-size: 1.14285714em; +} +.ui.big.search { + font-size: 1.28571429em; +} +.ui.huge.search { + font-size: 1.42857143em; +} +.ui.massive.search { + font-size: 1.71428571em; +} + +/*-------------- + Mobile +---------------*/ + +@media only screen and (max-width: 767.98px) { + .ui.search .results { + max-width: calc(100vw - 2rem); + } +} + + +/******************************* + Theme Overrides +*******************************/ + + + +/******************************* + Site Overrides +*******************************/ + diff --git a/web_src/fomantic/build/components/search.js b/web_src/fomantic/build/components/search.js new file mode 100644 index 0000000000..6f2c7ee699 --- /dev/null +++ b/web_src/fomantic/build/components/search.js @@ -0,0 +1,1565 @@ +/*! + * # Fomantic-UI - Search + * http://github.com/fomantic/Fomantic-UI/ + * + * + * Released under the MIT license + * http://opensource.org/licenses/MIT + * + */ + +;(function ($, window, document, undefined) { + +'use strict'; + +$.isFunction = $.isFunction || function(obj) { + return typeof obj === "function" && typeof obj.nodeType !== "number"; +}; + +window = (typeof window != 'undefined' && window.Math == Math) + ? window + : (typeof self != 'undefined' && self.Math == Math) + ? self + : Function('return this')() +; + +$.fn.search = function(parameters) { + var + $allModules = $(this), + moduleSelector = $allModules.selector || '', + + time = new Date().getTime(), + performance = [], + + query = arguments[0], + methodInvoked = (typeof query == 'string'), + queryArguments = [].slice.call(arguments, 1), + returnedValue + ; + $(this) + .each(function() { + var + settings = ( $.isPlainObject(parameters) ) + ? $.extend(true, {}, $.fn.search.settings, parameters) + : $.extend({}, $.fn.search.settings), + + className = settings.className, + metadata = settings.metadata, + regExp = settings.regExp, + fields = settings.fields, + selector = settings.selector, + error = settings.error, + namespace = settings.namespace, + + eventNamespace = '.' + namespace, + moduleNamespace = namespace + '-module', + + $module = $(this), + $prompt = $module.find(selector.prompt), + $searchButton = $module.find(selector.searchButton), + $results = $module.find(selector.results), + $result = $module.find(selector.result), + $category = $module.find(selector.category), + + element = this, + instance = $module.data(moduleNamespace), + + disabledBubbled = false, + resultsDismissed = false, + + module + ; + + module = { + + initialize: function() { + module.verbose('Initializing module'); + module.get.settings(); + module.determine.searchFields(); + module.bind.events(); + module.set.type(); + module.create.results(); + module.instantiate(); + }, + instantiate: function() { + module.verbose('Storing instance of module', module); + instance = module; + $module + .data(moduleNamespace, module) + ; + }, + destroy: function() { + module.verbose('Destroying instance'); + $module + .off(eventNamespace) + .removeData(moduleNamespace) + ; + }, + + refresh: function() { + module.debug('Refreshing selector cache'); + $prompt = $module.find(selector.prompt); + $searchButton = $module.find(selector.searchButton); + $category = $module.find(selector.category); + $results = $module.find(selector.results); + $result = $module.find(selector.result); + }, + + refreshResults: function() { + $results = $module.find(selector.results); + $result = $module.find(selector.result); + }, + + bind: { + events: function() { + module.verbose('Binding events to search'); + if(settings.automatic) { + $module + .on(module.get.inputEvent() + eventNamespace, selector.prompt, module.event.input) + ; + $prompt + .attr('autocomplete', 'off') + ; + } + $module + // prompt + .on('focus' + eventNamespace, selector.prompt, module.event.focus) + .on('blur' + eventNamespace, selector.prompt, module.event.blur) + .on('keydown' + eventNamespace, selector.prompt, module.handleKeyboard) + // search button + .on('click' + eventNamespace, selector.searchButton, module.query) + // results + .on('mousedown' + eventNamespace, selector.results, module.event.result.mousedown) + .on('mouseup' + eventNamespace, selector.results, module.event.result.mouseup) + .on('click' + eventNamespace, selector.result, module.event.result.click) + ; + } + }, + + determine: { + searchFields: function() { + // this makes sure $.extend does not add specified search fields to default fields + // this is the only setting which should not extend defaults + if(parameters && parameters.searchFields !== undefined) { + settings.searchFields = parameters.searchFields; + } + } + }, + + event: { + input: function() { + if(settings.searchDelay) { + clearTimeout(module.timer); + module.timer = setTimeout(function() { + if(module.is.focused()) { + module.query(); + } + }, settings.searchDelay); + } + else { + module.query(); + } + }, + focus: function() { + module.set.focus(); + if(settings.searchOnFocus && module.has.minimumCharacters() ) { + module.query(function() { + if(module.can.show() ) { + module.showResults(); + } + }); + } + }, + blur: function(event) { + var + pageLostFocus = (document.activeElement === this), + callback = function() { + module.cancel.query(); + module.remove.focus(); + module.timer = setTimeout(module.hideResults, settings.hideDelay); + } + ; + if(pageLostFocus) { + return; + } + resultsDismissed = false; + if(module.resultsClicked) { + module.debug('Determining if user action caused search to close'); + $module + .one('click.close' + eventNamespace, selector.results, function(event) { + if(module.is.inMessage(event) || disabledBubbled) { + $prompt.focus(); + return; + } + disabledBubbled = false; + if( !module.is.animating() && !module.is.hidden()) { + callback(); + } + }) + ; + } + else { + module.debug('Input blurred without user action, closing results'); + callback(); + } + }, + result: { + mousedown: function() { + module.resultsClicked = true; + }, + mouseup: function() { + module.resultsClicked = false; + }, + click: function(event) { + module.debug('Search result selected'); + var + $result = $(this), + $title = $result.find(selector.title).eq(0), + $link = $result.is('a[href]') + ? $result + : $result.find('a[href]').eq(0), + href = $link.attr('href') || false, + target = $link.attr('target') || false, + // title is used for result lookup + value = ($title.length > 0) + ? $title.text() + : false, + results = module.get.results(), + result = $result.data(metadata.result) || module.get.result(value, results) + ; + if(value) { + module.set.value(value); + } + if( $.isFunction(settings.onSelect) ) { + if(settings.onSelect.call(element, result, results) === false) { + module.debug('Custom onSelect callback cancelled default select action'); + disabledBubbled = true; + return; + } + } + module.hideResults(); + if(href) { + event.preventDefault(); + module.verbose('Opening search link found in result', $link); + if(target == '_blank' || event.ctrlKey) { + window.open(href); + } + else { + window.location.href = (href); + } + } + } + } + }, + ensureVisible: function ensureVisible($el) { + var elTop, elBottom, resultsScrollTop, resultsHeight; + + elTop = $el.position().top; + elBottom = elTop + $el.outerHeight(true); + + resultsScrollTop = $results.scrollTop(); + resultsHeight = $results.height() + parseInt($results.css('paddingTop'), 0) + + parseInt($results.css('paddingBottom'), 0); + + if (elTop < 0) { + $results.scrollTop(resultsScrollTop + elTop); + } + + else if (resultsHeight < elBottom) { + $results.scrollTop(resultsScrollTop + (elBottom - resultsHeight)); + } + }, + handleKeyboard: function(event) { + var + // force selector refresh + $result = $module.find(selector.result), + $category = $module.find(selector.category), + $activeResult = $result.filter('.' + className.active), + currentIndex = $result.index( $activeResult ), + resultSize = $result.length, + hasActiveResult = $activeResult.length > 0, + + keyCode = event.which, + keys = { + backspace : 8, + enter : 13, + escape : 27, + upArrow : 38, + downArrow : 40 + }, + newIndex + ; + // search shortcuts + if(keyCode == keys.escape) { + module.verbose('Escape key pressed, blurring search field'); + module.hideResults(); + resultsDismissed = true; + } + if( module.is.visible() ) { + if(keyCode == keys.enter) { + module.verbose('Enter key pressed, selecting active result'); + if( $result.filter('.' + className.active).length > 0 ) { + module.event.result.click.call($result.filter('.' + className.active), event); + event.preventDefault(); + return false; + } + } + else if(keyCode == keys.upArrow && hasActiveResult) { + module.verbose('Up key pressed, changing active result'); + newIndex = (currentIndex - 1 < 0) + ? currentIndex + : currentIndex - 1 + ; + $category + .removeClass(className.active) + ; + $result + .removeClass(className.active) + .eq(newIndex) + .addClass(className.active) + .closest($category) + .addClass(className.active) + ; + module.ensureVisible($result.eq(newIndex)); + event.preventDefault(); + } + else if(keyCode == keys.downArrow) { + module.verbose('Down key pressed, changing active result'); + newIndex = (currentIndex + 1 >= resultSize) + ? currentIndex + : currentIndex + 1 + ; + $category + .removeClass(className.active) + ; + $result + .removeClass(className.active) + .eq(newIndex) + .addClass(className.active) + .closest($category) + .addClass(className.active) + ; + module.ensureVisible($result.eq(newIndex)); + event.preventDefault(); + } + } + else { + // query shortcuts + if(keyCode == keys.enter) { + module.verbose('Enter key pressed, executing query'); + module.query(); + module.set.buttonPressed(); + $prompt.one('keyup', module.remove.buttonFocus); + } + } + }, + + setup: { + api: function(searchTerm, callback) { + var + apiSettings = { + debug : settings.debug, + on : false, + cache : settings.cache, + action : 'search', + urlData : { + query : searchTerm + }, + onSuccess : function(response) { + module.parse.response.call(element, response, searchTerm); + callback(); + }, + onFailure : function() { + module.displayMessage(error.serverError); + callback(); + }, + onAbort : function(response) { + }, + onError : module.error + } + ; + $.extend(true, apiSettings, settings.apiSettings); + module.verbose('Setting up API request', apiSettings); + $module.api(apiSettings); + } + }, + + can: { + useAPI: function() { + return $.fn.api !== undefined; + }, + show: function() { + return module.is.focused() && !module.is.visible() && !module.is.empty(); + }, + transition: function() { + return settings.transition && $.fn.transition !== undefined && $module.transition('is supported'); + } + }, + + is: { + animating: function() { + return $results.hasClass(className.animating); + }, + hidden: function() { + return $results.hasClass(className.hidden); + }, + inMessage: function(event) { + if(!event.target) { + return; + } + var + $target = $(event.target), + isInDOM = $.contains(document.documentElement, event.target) + ; + return (isInDOM && $target.closest(selector.message).length > 0); + }, + empty: function() { + return ($results.html() === ''); + }, + visible: function() { + return ($results.filter(':visible').length > 0); + }, + focused: function() { + return ($prompt.filter(':focus').length > 0); + } + }, + + get: { + settings: function() { + if($.isPlainObject(parameters) && parameters.searchFullText) { + settings.fullTextSearch = parameters.searchFullText; + module.error(settings.error.oldSearchSyntax, element); + } + if (settings.ignoreDiacritics && !String.prototype.normalize) { + settings.ignoreDiacritics = false; + module.error(error.noNormalize, element); + } + }, + inputEvent: function() { + var + prompt = $prompt[0], + inputEvent = (prompt !== undefined && prompt.oninput !== undefined) + ? 'input' + : (prompt !== undefined && prompt.onpropertychange !== undefined) + ? 'propertychange' + : 'keyup' + ; + return inputEvent; + }, + value: function() { + return $prompt.val(); + }, + results: function() { + var + results = $module.data(metadata.results) + ; + return results; + }, + result: function(value, results) { + var + result = false + ; + value = (value !== undefined) + ? value + : module.get.value() + ; + results = (results !== undefined) + ? results + : module.get.results() + ; + if(settings.type === 'category') { + module.debug('Finding result that matches', value); + $.each(results, function(index, category) { + if(Array.isArray(category.results)) { + result = module.search.object(value, category.results)[0]; + // don't continue searching if a result is found + if(result) { + return false; + } + } + }); + } + else { + module.debug('Finding result in results object', value); + result = module.search.object(value, results)[0]; + } + return result || false; + }, + }, + + select: { + firstResult: function() { + module.verbose('Selecting first result'); + $result.first().addClass(className.active); + } + }, + + set: { + focus: function() { + $module.addClass(className.focus); + }, + loading: function() { + $module.addClass(className.loading); + }, + value: function(value) { + module.verbose('Setting search input value', value); + $prompt + .val(value) + ; + }, + type: function(type) { + type = type || settings.type; + if(settings.type == 'category') { + $module.addClass(settings.type); + } + }, + buttonPressed: function() { + $searchButton.addClass(className.pressed); + } + }, + + remove: { + loading: function() { + $module.removeClass(className.loading); + }, + focus: function() { + $module.removeClass(className.focus); + }, + buttonPressed: function() { + $searchButton.removeClass(className.pressed); + }, + diacritics: function(text) { + return settings.ignoreDiacritics ? text.normalize('NFD').replace(/[\u0300-\u036f]/g, '') : text; + } + }, + + query: function(callback) { + callback = $.isFunction(callback) + ? callback + : function(){} + ; + var + searchTerm = module.get.value(), + cache = module.read.cache(searchTerm) + ; + callback = callback || function() {}; + if( module.has.minimumCharacters() ) { + if(cache) { + module.debug('Reading result from cache', searchTerm); + module.save.results(cache.results); + module.addResults(cache.html); + module.inject.id(cache.results); + callback(); + } + else { + module.debug('Querying for', searchTerm); + if($.isPlainObject(settings.source) || Array.isArray(settings.source)) { + module.search.local(searchTerm); + callback(); + } + else if( module.can.useAPI() ) { + module.search.remote(searchTerm, callback); + } + else { + module.error(error.source); + callback(); + } + } + settings.onSearchQuery.call(element, searchTerm); + } + else { + module.hideResults(); + } + }, + + search: { + local: function(searchTerm) { + var + results = module.search.object(searchTerm, settings.source), + searchHTML + ; + module.set.loading(); + module.save.results(results); + module.debug('Returned full local search results', results); + if(settings.maxResults > 0) { + module.debug('Using specified max results', results); + results = results.slice(0, settings.maxResults); + } + if(settings.type == 'category') { + results = module.create.categoryResults(results); + } + searchHTML = module.generateResults({ + results: results + }); + module.remove.loading(); + module.addResults(searchHTML); + module.inject.id(results); + module.write.cache(searchTerm, { + html : searchHTML, + results : results + }); + }, + remote: function(searchTerm, callback) { + callback = $.isFunction(callback) + ? callback + : function(){} + ; + if($module.api('is loading')) { + $module.api('abort'); + } + module.setup.api(searchTerm, callback); + $module + .api('query') + ; + }, + object: function(searchTerm, source, searchFields) { + searchTerm = module.remove.diacritics(String(searchTerm)); + var + results = [], + exactResults = [], + fuzzyResults = [], + searchExp = searchTerm.replace(regExp.escape, '\\$&'), + matchRegExp = new RegExp(regExp.beginsWith + searchExp, 'i'), + + // avoid duplicates when pushing results + addResult = function(array, result) { + var + notResult = ($.inArray(result, results) == -1), + notFuzzyResult = ($.inArray(result, fuzzyResults) == -1), + notExactResults = ($.inArray(result, exactResults) == -1) + ; + if(notResult && notFuzzyResult && notExactResults) { + array.push(result); + } + } + ; + source = source || settings.source; + searchFields = (searchFields !== undefined) + ? searchFields + : settings.searchFields + ; + + // search fields should be array to loop correctly + if(!Array.isArray(searchFields)) { + searchFields = [searchFields]; + } + + // exit conditions if no source + if(source === undefined || source === false) { + module.error(error.source); + return []; + } + // iterate through search fields looking for matches + $.each(searchFields, function(index, field) { + $.each(source, function(label, content) { + var + fieldExists = (typeof content[field] == 'string') || (typeof content[field] == 'number') + ; + if(fieldExists) { + var text; + if (typeof content[field] === 'string'){ + text = module.remove.diacritics(content[field]); + } else { + text = content[field].toString(); + } + if( text.search(matchRegExp) !== -1) { + // content starts with value (first in results) + addResult(results, content); + } + else if(settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text) ) { + // content fuzzy matches (last in results) + addResult(exactResults, content); + } + else if(settings.fullTextSearch == true && module.fuzzySearch(searchTerm, text) ) { + // content fuzzy matches (last in results) + addResult(fuzzyResults, content); + } + } + }); + }); + $.merge(exactResults, fuzzyResults); + $.merge(results, exactResults); + return results; + } + }, + exactSearch: function (query, term) { + query = query.toLowerCase(); + term = term.toLowerCase(); + return term.indexOf(query) > -1; + }, + fuzzySearch: function(query, term) { + var + termLength = term.length, + queryLength = query.length + ; + if(typeof query !== 'string') { + return false; + } + query = query.toLowerCase(); + term = term.toLowerCase(); + if(queryLength > termLength) { + return false; + } + if(queryLength === termLength) { + return (query === term); + } + search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) { + var + queryCharacter = query.charCodeAt(characterIndex) + ; + while(nextCharacterIndex < termLength) { + if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) { + continue search; + } + } + return false; + } + return true; + }, + + parse: { + response: function(response, searchTerm) { + if(Array.isArray(response)){ + var o={}; + o[fields.results]=response; + response = o; + } + var + searchHTML = module.generateResults(response) + ; + module.verbose('Parsing server response', response); + if(response !== undefined) { + if(searchTerm !== undefined && response[fields.results] !== undefined) { + module.addResults(searchHTML); + module.inject.id(response[fields.results]); + module.write.cache(searchTerm, { + html : searchHTML, + results : response[fields.results] + }); + module.save.results(response[fields.results]); + } + } + } + }, + + cancel: { + query: function() { + if( module.can.useAPI() ) { + $module.api('abort'); + } + } + }, + + has: { + minimumCharacters: function() { + var + searchTerm = module.get.value(), + numCharacters = searchTerm.length + ; + return (numCharacters >= settings.minCharacters); + }, + results: function() { + if($results.length === 0) { + return false; + } + var + html = $results.html() + ; + return html != ''; + } + }, + + clear: { + cache: function(value) { + var + cache = $module.data(metadata.cache) + ; + if(!value) { + module.debug('Clearing cache', value); + $module.removeData(metadata.cache); + } + else if(value && cache && cache[value]) { + module.debug('Removing value from cache', value); + delete cache[value]; + $module.data(metadata.cache, cache); + } + } + }, + + read: { + cache: function(name) { + var + cache = $module.data(metadata.cache) + ; + if(settings.cache) { + module.verbose('Checking cache for generated html for query', name); + return (typeof cache == 'object') && (cache[name] !== undefined) + ? cache[name] + : false + ; + } + return false; + } + }, + + create: { + categoryResults: function(results) { + var + categoryResults = {} + ; + $.each(results, function(index, result) { + if(!result.category) { + return; + } + if(categoryResults[result.category] === undefined) { + module.verbose('Creating new category of results', result.category); + categoryResults[result.category] = { + name : result.category, + results : [result] + }; + } + else { + categoryResults[result.category].results.push(result); + } + }); + return categoryResults; + }, + id: function(resultIndex, categoryIndex) { + var + resultID = (resultIndex + 1), // not zero indexed + letterID, + id + ; + if(categoryIndex !== undefined) { + // start char code for "A" + letterID = String.fromCharCode(97 + categoryIndex); + id = letterID + resultID; + module.verbose('Creating category result id', id); + } + else { + id = resultID; + module.verbose('Creating result id', id); + } + return id; + }, + results: function() { + if($results.length === 0) { + $results = $('<div />') + .addClass(className.results) + .appendTo($module) + ; + } + } + }, + + inject: { + result: function(result, resultIndex, categoryIndex) { + module.verbose('Injecting result into results'); + var + $selectedResult = (categoryIndex !== undefined) + ? $results + .children().eq(categoryIndex) + .children(selector.results) + .first() + .children(selector.result) + .eq(resultIndex) + : $results + .children(selector.result).eq(resultIndex) + ; + module.verbose('Injecting results metadata', $selectedResult); + $selectedResult + .data(metadata.result, result) + ; + }, + id: function(results) { + module.debug('Injecting unique ids into results'); + var + // since results may be object, we must use counters + categoryIndex = 0, + resultIndex = 0 + ; + if(settings.type === 'category') { + // iterate through each category result + $.each(results, function(index, category) { + if(category.results.length > 0){ + resultIndex = 0; + $.each(category.results, function(index, result) { + if(result.id === undefined) { + result.id = module.create.id(resultIndex, categoryIndex); + } + module.inject.result(result, resultIndex, categoryIndex); + resultIndex++; + }); + categoryIndex++; + } + }); + } + else { + // top level + $.each(results, function(index, result) { + if(result.id === undefined) { + result.id = module.create.id(resultIndex); + } + module.inject.result(result, resultIndex); + resultIndex++; + }); + } + return results; + } + }, + + save: { + results: function(results) { + module.verbose('Saving current search results to metadata', results); + $module.data(metadata.results, results); + } + }, + + write: { + cache: function(name, value) { + var + cache = ($module.data(metadata.cache) !== undefined) + ? $module.data(metadata.cache) + : {} + ; + if(settings.cache) { + module.verbose('Writing generated html to cache', name, value); + cache[name] = value; + $module + .data(metadata.cache, cache) + ; + } + } + }, + + addResults: function(html) { + if( $.isFunction(settings.onResultsAdd) ) { + if( settings.onResultsAdd.call($results, html) === false ) { + module.debug('onResultsAdd callback cancelled default action'); + return false; + } + } + if(html) { + $results + .html(html) + ; + module.refreshResults(); + if(settings.selectFirstResult) { + module.select.firstResult(); + } + module.showResults(); + } + else { + module.hideResults(function() { + $results.empty(); + }); + } + }, + + showResults: function(callback) { + callback = $.isFunction(callback) + ? callback + : function(){} + ; + if(resultsDismissed) { + return; + } + if(!module.is.visible() && module.has.results()) { + if( module.can.transition() ) { + module.debug('Showing results with css animations'); + $results + .transition({ + animation : settings.transition + ' in', + debug : settings.debug, + verbose : settings.verbose, + duration : settings.duration, + onShow : function() { + var $firstResult = $module.find(selector.result).eq(0); + if($firstResult.length > 0) { + module.ensureVisible($firstResult); + } + }, + onComplete : function() { + callback(); + }, + queue : true + }) + ; + } + else { + module.debug('Showing results with javascript'); + $results + .stop() + .fadeIn(settings.duration, settings.easing) + ; + } + settings.onResultsOpen.call($results); + } + }, + hideResults: function(callback) { + callback = $.isFunction(callback) + ? callback + : function(){} + ; + if( module.is.visible() ) { + if( module.can.transition() ) { + module.debug('Hiding results with css animations'); + $results + .transition({ + animation : settings.transition + ' out', + debug : settings.debug, + verbose : settings.verbose, + duration : settings.duration, + onComplete : function() { + callback(); + }, + queue : true + }) + ; + } + else { + module.debug('Hiding results with javascript'); + $results + .stop() + .fadeOut(settings.duration, settings.easing) + ; + } + settings.onResultsClose.call($results); + } + }, + + generateResults: function(response) { + module.debug('Generating html from response', response); + var + template = settings.templates[settings.type], + isProperObject = ($.isPlainObject(response[fields.results]) && !$.isEmptyObject(response[fields.results])), + isProperArray = (Array.isArray(response[fields.results]) && response[fields.results].length > 0), + html = '' + ; + if(isProperObject || isProperArray ) { + if(settings.maxResults > 0) { + if(isProperObject) { + if(settings.type == 'standard') { + module.error(error.maxResults); + } + } + else { + response[fields.results] = response[fields.results].slice(0, settings.maxResults); + } + } + if($.isFunction(template)) { + html = template(response, fields, settings.preserveHTML); + } + else { + module.error(error.noTemplate, false); + } + } + else if(settings.showNoResults) { + html = module.displayMessage(error.noResults, 'empty', error.noResultsHeader); + } + settings.onResults.call(element, response); + return html; + }, + + displayMessage: function(text, type, header) { + type = type || 'standard'; + module.debug('Displaying message', text, type, header); + module.addResults( settings.templates.message(text, type, header) ); + return settings.templates.message(text, type, header); + }, + + setting: function(name, value) { + if( $.isPlainObject(name) ) { + $.extend(true, settings, name); + } + else if(value !== undefined) { + settings[name] = value; + } + else { + return settings[name]; + } + }, + internal: function(name, value) { + if( $.isPlainObject(name) ) { + $.extend(true, module, name); + } + else if(value !== undefined) { + module[name] = value; + } + else { + return module[name]; + } + }, + debug: function() { + if(!settings.silent && settings.debug) { + if(settings.performance) { + module.performance.log(arguments); + } + else { + module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); + module.debug.apply(console, arguments); + } + } + }, + verbose: function() { + if(!settings.silent && settings.verbose && settings.debug) { + if(settings.performance) { + module.performance.log(arguments); + } + else { + module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); + module.verbose.apply(console, arguments); + } + } + }, + error: function() { + if(!settings.silent) { + module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); + module.error.apply(console, arguments); + } + }, + performance: { + log: function(message) { + var + currentTime, + executionTime, + previousTime + ; + if(settings.performance) { + currentTime = new Date().getTime(); + previousTime = time || currentTime; + executionTime = currentTime - previousTime; + time = currentTime; + performance.push({ + 'Name' : message[0], + 'Arguments' : [].slice.call(message, 1) || '', + 'Element' : element, + 'Execution Time' : executionTime + }); + } + clearTimeout(module.performance.timer); + module.performance.timer = setTimeout(module.performance.display, 500); + }, + display: function() { + var + title = settings.name + ':', + totalTime = 0 + ; + time = false; + clearTimeout(module.performance.timer); + $.each(performance, function(index, data) { + totalTime += data['Execution Time']; + }); + title += ' ' + totalTime + 'ms'; + if(moduleSelector) { + title += ' \'' + moduleSelector + '\''; + } + if($allModules.length > 1) { + title += ' ' + '(' + $allModules.length + ')'; + } + if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { + console.groupCollapsed(title); + if(console.table) { + console.table(performance); + } + else { + $.each(performance, function(index, data) { + console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); + }); + } + console.groupEnd(); + } + performance = []; + } + }, + invoke: function(query, passedArguments, context) { + var + object = instance, + maxDepth, + found, + response + ; + passedArguments = passedArguments || queryArguments; + context = element || context; + if(typeof query == 'string' && object !== undefined) { + query = query.split(/[\. ]/); + maxDepth = query.length - 1; + $.each(query, function(depth, value) { + var camelCaseValue = (depth != maxDepth) + ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) + : query + ; + if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { + object = object[camelCaseValue]; + } + else if( object[camelCaseValue] !== undefined ) { + found = object[camelCaseValue]; + return false; + } + else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { + object = object[value]; + } + else if( object[value] !== undefined ) { + found = object[value]; + return false; + } + else { + return false; + } + }); + } + if( $.isFunction( found ) ) { + response = found.apply(context, passedArguments); + } + else if(found !== undefined) { + response = found; + } + if(Array.isArray(returnedValue)) { + returnedValue.push(response); + } + else if(returnedValue !== undefined) { + returnedValue = [returnedValue, response]; + } + else if(response !== undefined) { + returnedValue = response; + } + return found; + } + }; + if(methodInvoked) { + if(instance === undefined) { + module.initialize(); + } + module.invoke(query); + } + else { + if(instance !== undefined) { + instance.invoke('destroy'); + } + module.initialize(); + } + + }) + ; + + return (returnedValue !== undefined) + ? returnedValue + : this + ; +}; + +$.fn.search.settings = { + + name : 'Search', + namespace : 'search', + + silent : false, + debug : false, + verbose : false, + performance : true, + + // template to use (specified in settings.templates) + type : 'standard', + + // minimum characters required to search + minCharacters : 1, + + // whether to select first result after searching automatically + selectFirstResult : false, + + // API config + apiSettings : false, + + // object to search + source : false, + + // Whether search should query current term on focus + searchOnFocus : true, + + // fields to search + searchFields : [ + 'id', + 'title', + 'description' + ], + + // field to display in standard results template + displayField : '', + + // search anywhere in value (set to 'exact' to require exact matches + fullTextSearch : 'exact', + + // match results also if they contain diacritics of the same base character (for example searching for "a" will also match "á" or "â" or "à", etc...) + ignoreDiacritics : false, + + // whether to add events to prompt automatically + automatic : true, + + // delay before hiding menu after blur + hideDelay : 0, + + // delay before searching + searchDelay : 200, + + // maximum results returned from search + maxResults : 7, + + // whether to store lookups in local cache + cache : true, + + // whether no results errors should be shown + showNoResults : true, + + // preserve possible html of resultset values + preserveHTML : true, + + // transition settings + transition : 'scale', + duration : 200, + easing : 'easeOutExpo', + + // callbacks + onSelect : false, + onResultsAdd : false, + + onSearchQuery : function(query){}, + onResults : function(response){}, + + onResultsOpen : function(){}, + onResultsClose : function(){}, + + className: { + animating : 'animating', + active : 'active', + empty : 'empty', + focus : 'focus', + hidden : 'hidden', + loading : 'loading', + results : 'results', + pressed : 'down' + }, + + error : { + source : 'Cannot search. No source used, and Semantic API module was not included', + noResultsHeader : 'No Results', + noResults : 'Your search returned no results', + logging : 'Error in debug logging, exiting.', + noEndpoint : 'No search endpoint was specified', + noTemplate : 'A valid template name was not specified.', + oldSearchSyntax : 'searchFullText setting has been renamed fullTextSearch for consistency, please adjust your settings.', + serverError : 'There was an issue querying the server.', + maxResults : 'Results must be an array to use maxResults setting', + method : 'The method you called is not defined.', + noNormalize : '"ignoreDiacritics" setting will be ignored. Browser does not support String().normalize(). You may consider including <https://cdn.jsdelivr.net/npm/unorm@1.4.1/lib/unorm.min.js> as a polyfill.' + }, + + metadata: { + cache : 'cache', + results : 'results', + result : 'result' + }, + + regExp: { + escape : /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, + beginsWith : '(?:\s|^)' + }, + + // maps api response attributes to internal representation + fields: { + categories : 'results', // array of categories (category view) + categoryName : 'name', // name of category (category view) + categoryResults : 'results', // array of results (category view) + description : 'description', // result description + image : 'image', // result image + price : 'price', // result price + results : 'results', // array of results (standard) + title : 'title', // result title + url : 'url', // result url + action : 'action', // "view more" object name + actionText : 'text', // "view more" text + actionURL : 'url' // "view more" url + }, + + selector : { + prompt : '.prompt', + searchButton : '.search.button', + results : '.results', + message : '.results > .message', + category : '.category', + result : '.result', + title : '.title, .name' + }, + + templates: { + escape: function(string, preserveHTML) { + if (preserveHTML){ + return string; + } + var + badChars = /[<>"'`]/g, + shouldEscape = /[&<>"'`]/, + escape = { + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" + }, + escapedChar = function(chr) { + return escape[chr]; + } + ; + if(shouldEscape.test(string)) { + string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); + return string.replace(badChars, escapedChar); + } + return string; + }, + message: function(message, type, header) { + var + html = '' + ; + if(message !== undefined && type !== undefined) { + html += '' + + '<div class="message ' + type + '">' + ; + if(header) { + html += '' + + '<div class="header">' + header + '</div>' + ; + } + html += ' <div class="description">' + message + '</div>'; + html += '</div>'; + } + return html; + }, + category: function(response, fields, preserveHTML) { + var + html = '', + escape = $.fn.search.settings.templates.escape + ; + if(response[fields.categoryResults] !== undefined) { + + // each category + $.each(response[fields.categoryResults], function(index, category) { + if(category[fields.results] !== undefined && category.results.length > 0) { + + html += '<div class="category">'; + + if(category[fields.categoryName] !== undefined) { + html += '<div class="name">' + escape(category[fields.categoryName], preserveHTML) + '</div>'; + } + + // each item inside category + html += '<div class="results">'; + $.each(category.results, function(index, result) { + if(result[fields.url]) { + html += '<a class="result" href="' + result[fields.url].replace(/"/g,"") + '">'; + } + else { + html += '<a class="result">'; + } + if(result[fields.image] !== undefined) { + html += '' + + '<div class="image">' + + ' <img src="' + result[fields.image].replace(/"/g,"") + '">' + + '</div>' + ; + } + html += '<div class="content">'; + if(result[fields.price] !== undefined) { + html += '<div class="price">' + escape(result[fields.price], preserveHTML) + '</div>'; + } + if(result[fields.title] !== undefined) { + html += '<div class="title">' + escape(result[fields.title], preserveHTML) + '</div>'; + } + if(result[fields.description] !== undefined) { + html += '<div class="description">' + escape(result[fields.description], preserveHTML) + '</div>'; + } + html += '' + + '</div>' + ; + html += '</a>'; + }); + html += '</div>'; + html += '' + + '</div>' + ; + } + }); + if(response[fields.action]) { + if(fields.actionURL === false) { + html += '' + + '<div class="action">' + + escape(response[fields.action][fields.actionText], preserveHTML) + + '</div>'; + } else { + html += '' + + '<a href="' + response[fields.action][fields.actionURL].replace(/"/g,"") + '" class="action">' + + escape(response[fields.action][fields.actionText], preserveHTML) + + '</a>'; + } + } + return html; + } + return false; + }, + standard: function(response, fields, preserveHTML) { + var + html = '', + escape = $.fn.search.settings.templates.escape + ; + if(response[fields.results] !== undefined) { + + // each result + $.each(response[fields.results], function(index, result) { + if(result[fields.url]) { + html += '<a class="result" href="' + result[fields.url].replace(/"/g,"") + '">'; + } + else { + html += '<a class="result">'; + } + if(result[fields.image] !== undefined) { + html += '' + + '<div class="image">' + + ' <img src="' + result[fields.image].replace(/"/g,"") + '">' + + '</div>' + ; + } + html += '<div class="content">'; + if(result[fields.price] !== undefined) { + html += '<div class="price">' + escape(result[fields.price], preserveHTML) + '</div>'; + } + if(result[fields.title] !== undefined) { + html += '<div class="title">' + escape(result[fields.title], preserveHTML) + '</div>'; + } + if(result[fields.description] !== undefined) { + html += '<div class="description">' + escape(result[fields.description], preserveHTML) + '</div>'; + } + html += '' + + '</div>' + ; + html += '</a>'; + }); + if(response[fields.action]) { + if(fields.actionURL === false) { + html += '' + + '<div class="action">' + + escape(response[fields.action][fields.actionText], preserveHTML) + + '</div>'; + } else { + html += '' + + '<a href="' + response[fields.action][fields.actionURL].replace(/"/g,"") + '" class="action">' + + escape(response[fields.action][fields.actionText], preserveHTML) + + '</a>'; + } + } + return html; + } + return false; + } + } +}; + +})( jQuery, window, document ); diff --git a/web_src/fomantic/build/fomantic.css b/web_src/fomantic/build/fomantic.css new file mode 100644 index 0000000000..a8fac9c9ba --- /dev/null +++ b/web_src/fomantic/build/fomantic.css @@ -0,0 +1,4 @@ +@import "./components/dropdown.css"; +@import "./components/form.css"; +@import "./components/modal.css"; +@import "./components/search.css"; diff --git a/web_src/fomantic/build/fomantic.js b/web_src/fomantic/build/fomantic.js new file mode 100644 index 0000000000..c903fd1633 --- /dev/null +++ b/web_src/fomantic/build/fomantic.js @@ -0,0 +1,6 @@ +import './components/api.js'; +import './components/dropdown.js'; +import './components/modal.js'; +import './components/search.js'; + +// Hard-forked from Fomantic UI 2.8.7, patches are commented with "GITEA-PATCH" diff --git a/web_src/fomantic/build/semantic.css b/web_src/fomantic/build/semantic.css deleted file mode 100644 index aef7a6bdbe..0000000000 --- a/web_src/fomantic/build/semantic.css +++ /dev/null @@ -1,5250 +0,0 @@ - /* - * # Fomantic UI - 2.8.7 - * https://github.com/fomantic/Fomantic-UI - * http://fomantic-ui.com/ - * - * Copyright 2014 Contributors - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ -/*! - * # Fomantic-UI - Dropdown - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -/******************************* - Dropdown -*******************************/ - -.ui.dropdown { - cursor: pointer; - position: relative; - display: inline-block; - outline: none; - text-align: left; - transition: box-shadow 0.1s ease, width 0.1s ease; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -} - -/******************************* - Content -*******************************/ - -/*-------------- - Menu ----------------*/ - -.ui.dropdown .menu { - cursor: auto; - position: absolute; - display: none; - outline: none; - top: 100%; - min-width: -moz-max-content; - min-width: max-content; - margin: 0; - padding: 0 0; - background: #FFFFFF; - font-size: 1em; - text-shadow: none; - text-align: left; - box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15); - border: 1px solid rgba(34, 36, 38, 0.15); - border-radius: 0.28571429rem; - transition: opacity 0.1s ease; - z-index: 11; - will-change: transform, opacity; -} - -.ui.dropdown .menu > * { - white-space: nowrap; -} - -/*-------------- - Hidden Input ----------------*/ - -.ui.dropdown > input:not(.search):first-child, -.ui.dropdown > select { - display: none !important; -} - -/*-------------- - Dropdown Icon ----------------*/ - -.ui.dropdown:not(.labeled) > .dropdown.icon { - position: relative; - width: auto; - font-size: 0.85714286em; - margin: 0 0 0 1em; -} - -.ui.dropdown .menu > .item .dropdown.icon { - width: auto; - float: right; - margin: 0em 0 0 1em; -} - -.ui.dropdown .menu > .item .dropdown.icon + .text { - margin-right: 1em; -} - -/*-------------- - Text ----------------*/ - -.ui.dropdown > .text { - display: inline-block; - transition: none; -} - -/*-------------- - Menu Item ----------------*/ - -.ui.dropdown .menu > .item { - position: relative; - cursor: pointer; - display: block; - border: none; - height: auto; - min-height: 2.57142857rem; - text-align: left; - border-top: none; - line-height: 1em; - font-size: 1rem; - color: rgba(0, 0, 0, 0.87); - padding: 0.78571429rem 1.14285714rem !important; - text-transform: none; - font-weight: normal; - box-shadow: none; - -webkit-touch-callout: none; -} - -.ui.dropdown .menu > .item:first-child { - border-top-width: 0; -} - -.ui.dropdown .menu > .item.vertical { - display: flex; - flex-direction: column-reverse; -} - -/*-------------- - Floated Content ----------------*/ - -.ui.dropdown > .text > [class*="right floated"], -.ui.dropdown .menu .item > [class*="right floated"] { - float: right !important; - margin-right: 0 !important; - margin-left: 1em !important; -} - -.ui.dropdown > .text > [class*="left floated"], -.ui.dropdown .menu .item > [class*="left floated"] { - float: left !important; - margin-left: 0 !important; - margin-right: 1em !important; -} - -.ui.dropdown .menu .item > i.icon.floated, -.ui.dropdown .menu .item > .flag.floated, -.ui.dropdown .menu .item > .image.floated, -.ui.dropdown .menu .item > img.floated { - margin-top: 0em; -} - -/*-------------- - Menu Divider ----------------*/ - -.ui.dropdown .menu > .header { - margin: 1rem 0 0.75rem; - padding: 0 1.14285714rem; - font-weight: 500; - text-transform: uppercase; -} - -.ui.dropdown .menu > .header:not(.ui) { - color: rgba(0, 0, 0, 0.85); - font-size: 0.78571429em; -} - -.ui.dropdown .menu > .divider { - border-top: 1px solid rgba(34, 36, 38, 0.1); - height: 0; - margin: 0.5em 0; -} - -.ui.dropdown .menu > .horizontal.divider { - border-top: none; -} - -.ui.dropdown.dropdown .menu > .input { - width: auto; - display: flex; - margin: 1.14285714rem 0.78571429rem; - min-width: 10rem; -} - -.ui.dropdown .menu > .header + .input { - margin-top: 0; -} - -.ui.dropdown .menu > .input:not(.transparent) input { - padding: 0.5em 1em; -} - -.ui.dropdown .menu > .input:not(.transparent) .button, -.ui.dropdown .menu > .input:not(.transparent) i.icon, -.ui.dropdown .menu > .input:not(.transparent) .label { - padding-top: 0.5em; - padding-bottom: 0.5em; -} - -/*----------------- - Item Description --------------------*/ - -.ui.dropdown > .text > .description, -.ui.dropdown .menu > .item > .description { - float: right; - margin: 0 0 0 1em; - color: rgba(0, 0, 0, 0.4); -} - -.ui.dropdown .menu > .item.vertical > .description { - margin: 0; -} - -/*----------------- - Item Text --------------------*/ - -.ui.dropdown .menu > .item.vertical > .text { - margin-bottom: 0.25em; -} - -/*----------------- - Message --------------------*/ - -.ui.dropdown .menu > .message { - padding: 0.78571429rem 1.14285714rem; - font-weight: normal; -} - -.ui.dropdown .menu > .message:not(.ui) { - color: rgba(0, 0, 0, 0.4); -} - -/*-------------- - Sub Menu ----------------*/ - -.ui.dropdown .menu .menu { - top: 0; - left: 100%; - right: auto; - margin: 0 -0.5em !important; - border-radius: 0.28571429rem !important; - z-index: 21 !important; -} - -/* Hide Arrow */ - -.ui.dropdown .menu .menu:after { - display: none; -} - -/*-------------- - Sub Elements ----------------*/ - -/* Icons / Flags / Labels / Image */ - -.ui.dropdown > .text > i.icon, -.ui.dropdown > .text > .label, -.ui.dropdown > .text > .flag, -.ui.dropdown > .text > img, -.ui.dropdown > .text > .image { - margin-top: 0em; -} - -.ui.dropdown .menu > .item > i.icon, -.ui.dropdown .menu > .item > .label, -.ui.dropdown .menu > .item > .flag, -.ui.dropdown .menu > .item > .image, -.ui.dropdown .menu > .item > img { - margin-top: 0em; -} - -.ui.dropdown > .text > i.icon, -.ui.dropdown > .text > .label, -.ui.dropdown > .text > .flag, -.ui.dropdown > .text > img, -.ui.dropdown > .text > .image, -.ui.dropdown .menu > .item > i.icon, -.ui.dropdown .menu > .item > .label, -.ui.dropdown .menu > .item > .flag, -.ui.dropdown .menu > .item > .image, -.ui.dropdown .menu > .item > img { - margin-left: 0; - float: none; - margin-right: 0.78571429rem; -} - -/*-------------- - Image ----------------*/ - -.ui.dropdown > .text > img, -.ui.dropdown > .text > .image:not(.icon), -.ui.dropdown .menu > .item > .image:not(.icon), -.ui.dropdown .menu > .item > img { - display: inline-block; - vertical-align: top; - width: auto; - margin-top: -0.5em; - margin-bottom: -0.5em; - max-height: 2em; -} - -/******************************* - Coupling -*******************************/ - -/*-------------- - Menu ----------------*/ - -/* Remove Menu Item Divider */ - -.ui.dropdown .ui.menu > .item:before, -.ui.menu .ui.dropdown .menu > .item:before { - display: none; -} - -/* Prevent Menu Item Border */ - -.ui.menu .ui.dropdown .menu .active.item { - border-left: none; -} - -/* Automatically float dropdown menu right on last menu item */ - -.ui.menu .right.menu .dropdown:last-child > .menu:not(.left), -.ui.menu .right.dropdown.item > .menu:not(.left), -.ui.buttons > .ui.dropdown:last-child > .menu:not(.left) { - left: auto; - right: 0; -} - -/*-------------- - Label - ---------------*/ - -/* Dropdown Menu */ - -.ui.label.dropdown .menu { - min-width: 100%; -} - -/*-------------- - Button - ---------------*/ - -/* No Margin On Icon Button */ - -.ui.dropdown.icon.button > .dropdown.icon { - margin: 0; -} - -.ui.button.dropdown .menu { - min-width: 100%; -} - -/******************************* - Types -*******************************/ - -select.ui.dropdown { - height: 38px; - padding: 0.5em; - border: 1px solid rgba(34, 36, 38, 0.15); - visibility: visible; -} - -/*-------------- - Selection - ---------------*/ - -/* Displays like a select box */ - -.ui.selection.dropdown { - cursor: pointer; - word-wrap: break-word; - line-height: 1em; - white-space: normal; - outline: 0; - transform: rotateZ(0deg); - min-width: 14em; - min-height: 2.71428571em; - background: #FFFFFF; - display: inline-block; - padding: 0.78571429em 3.2em 0.78571429em 1em; - color: rgba(0, 0, 0, 0.87); - box-shadow: none; - border: 1px solid rgba(34, 36, 38, 0.15); - border-radius: 0.28571429rem; - transition: box-shadow 0.1s ease, width 0.1s ease; -} - -.ui.selection.dropdown.visible, -.ui.selection.dropdown.active { - z-index: 10; -} - -.ui.selection.dropdown > .search.icon, -.ui.selection.dropdown > .delete.icon, -.ui.selection.dropdown > .dropdown.icon { - cursor: pointer; - position: absolute; - width: auto; - height: auto; - line-height: 1.21428571em; - top: 0.78571429em; - right: 1em; - z-index: 3; - margin: -0.78571429em; - padding: 0.91666667em; - opacity: 0.8; - transition: opacity 0.1s ease; -} - -/* Compact */ - -.ui.compact.selection.dropdown { - min-width: 0; -} - -/* Selection Menu */ - -.ui.selection.dropdown .menu { - overflow-x: hidden; - overflow-y: auto; - backface-visibility: hidden; - -webkit-overflow-scrolling: touch; - border-top-width: 0 !important; - width: auto; - outline: none; - margin: 0 -1px; - min-width: calc(100% + 2px); - width: calc(100% + 2px); - border-radius: 0 0 0.28571429rem 0.28571429rem; - box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15); - transition: opacity 0.1s ease; -} - -.ui.selection.dropdown .menu:after, -.ui.selection.dropdown .menu:before { - display: none; -} - -/*-------------- - Message - ---------------*/ - -.ui.selection.dropdown .menu > .message { - padding: 0.78571429rem 1.14285714rem; -} - -@media only screen and (max-width: 767.98px) { - .ui.selection.dropdown.short .menu { - max-height: 6.01071429rem; - } - - .ui.selection.dropdown[class*="very short"] .menu { - max-height: 4.00714286rem; - } - - .ui.selection.dropdown .menu { - max-height: 8.01428571rem; - } - - .ui.selection.dropdown.long .menu { - max-height: 16.02857143rem; - } - - .ui.selection.dropdown[class*="very long"] .menu { - max-height: 24.04285714rem; - } -} - -@media only screen and (min-width: 768px) { - .ui.selection.dropdown.short .menu { - max-height: 8.01428571rem; - } - - .ui.selection.dropdown[class*="very short"] .menu { - max-height: 5.34285714rem; - } - - .ui.selection.dropdown .menu { - max-height: 10.68571429rem; - } - - .ui.selection.dropdown.long .menu { - max-height: 21.37142857rem; - } - - .ui.selection.dropdown[class*="very long"] .menu { - max-height: 32.05714286rem; - } -} - -@media only screen and (min-width: 992px) { - .ui.selection.dropdown.short .menu { - max-height: 12.02142857rem; - } - - .ui.selection.dropdown[class*="very short"] .menu { - max-height: 8.01428571rem; - } - - .ui.selection.dropdown .menu { - max-height: 16.02857143rem; - } - - .ui.selection.dropdown.long .menu { - max-height: 32.05714286rem; - } - - .ui.selection.dropdown[class*="very long"] .menu { - max-height: 48.08571429rem; - } -} - -@media only screen and (min-width: 1920px) { - .ui.selection.dropdown.short .menu { - max-height: 16.02857143rem; - } - - .ui.selection.dropdown[class*="very short"] .menu { - max-height: 10.68571429rem; - } - - .ui.selection.dropdown .menu { - max-height: 21.37142857rem; - } - - .ui.selection.dropdown.long .menu { - max-height: 42.74285714rem; - } - - .ui.selection.dropdown[class*="very long"] .menu { - max-height: 64.11428571rem; - } -} - -/* Menu Item */ - -.ui.selection.dropdown .menu > .item { - border-top: 1px solid #FAFAFA; - padding: 0.78571429rem 1.14285714rem !important; - white-space: normal; - word-wrap: normal; -} - -/* User Item */ - -.ui.selection.dropdown .menu > .hidden.addition.item { - display: none; -} - -/* Hover */ - -.ui.selection.dropdown:hover { - border-color: rgba(34, 36, 38, 0.35); - box-shadow: none; -} - -/* Active */ - -.ui.selection.active.dropdown { - border-color: #96C8DA; - box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15); -} - -.ui.selection.active.dropdown .menu { - border-color: #96C8DA; - box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15); -} - -/* Focus */ - -.ui.selection.dropdown:focus { - border-color: #96C8DA; - box-shadow: none; -} - -.ui.selection.dropdown:focus .menu { - border-color: #96C8DA; - box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15); -} - -/* Visible */ - -.ui.selection.visible.dropdown > .text:not(.default) { - font-weight: normal; - color: rgba(0, 0, 0, 0.8); -} - -/* Visible Hover */ - -.ui.selection.active.dropdown:hover { - border-color: #96C8DA; - box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15); -} - -.ui.selection.active.dropdown:hover .menu { - border-color: #96C8DA; - box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15); -} - -/* Dropdown Icon */ - -.ui.active.selection.dropdown > .dropdown.icon, -.ui.visible.selection.dropdown > .dropdown.icon { - opacity: ''; - z-index: 3; -} - -/* Connecting Border */ - -.ui.active.selection.dropdown { - border-bottom-left-radius: 0 !important; - border-bottom-right-radius: 0 !important; -} - -/* Empty Connecting Border */ - -.ui.active.empty.selection.dropdown { - border-radius: 0.28571429rem !important; - box-shadow: none !important; -} - -.ui.active.empty.selection.dropdown .menu { - border: none !important; - box-shadow: none !important; -} - -/* CSS specific to iOS devices or firefox mobile only */ - -@supports (-webkit-touch-callout: none) or (-webkit-overflow-scrolling: touch) or (-moz-appearance:none) { -@media (-moz-touch-enabled), (pointer: coarse) { - .ui.dropdown .scrollhint.menu:not(.hidden):before { - animation: scrollhint 2s ease 2; - content: ''; - z-index: 15; - display: block; - position: absolute; - opacity: 0; - right: 0.25em; - top: 0; - height: 100%; - border-right: 0.25em solid; - border-left: 0; - -o-border-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0)) 1 100%; - border-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0)) 1 100%; - } - - .ui.inverted.dropdown .scrollhint.menu:not(.hidden):before { - -o-border-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.75), rgba(255, 255, 255, 0)) 1 100%; - border-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.75), rgba(255, 255, 255, 0)) 1 100%; - } - -@keyframes scrollhint { - 0% { - opacity: 1; - top: 100%; - } - - 100% { - opacity: 0; - top: 0; - } -} -} -} - -/*-------------- - Searchable - ---------------*/ - -/* Search Selection */ - -.ui.search.dropdown { - min-width: ''; -} - -/* Search Dropdown */ - -.ui.search.dropdown > input.search { - background: none transparent !important; - border: none !important; - box-shadow: none !important; - cursor: text; - top: 0; - left: 1px; - width: 100%; - outline: none; - -webkit-tap-highlight-color: rgba(255, 255, 255, 0); - padding: inherit; -} - -/* Text Layering */ - -.ui.search.dropdown > input.search { - position: absolute; - z-index: 2; -} - -.ui.search.dropdown > .text { - cursor: text; - position: relative; - left: 1px; - z-index: auto; -} - -/* Search Selection */ - -.ui.search.selection.dropdown > input.search { - line-height: 1.21428571em; - padding: 0.67857143em 3.2em 0.67857143em 1em; -} - -/* Used to size multi select input to character width */ - -.ui.search.selection.dropdown > span.sizer { - line-height: 1.21428571em; - padding: 0.67857143em 3.2em 0.67857143em 1em; - display: none; - white-space: pre; -} - -/* Active/Visible Search */ - -.ui.search.dropdown.active > input.search, -.ui.search.dropdown.visible > input.search { - cursor: auto; -} - -.ui.search.dropdown.active > .text, -.ui.search.dropdown.visible > .text { - pointer-events: none; -} - -/* Filtered Text */ - -.ui.active.search.dropdown input.search:focus + .text i.icon, -.ui.active.search.dropdown input.search:focus + .text .flag { - opacity: var(--opacity-disabled); -} - -.ui.active.search.dropdown input.search:focus + .text { - color: rgba(115, 115, 115, 0.87) !important; -} - -.ui.search.dropdown.button > span.sizer { - display: none; -} - -/* Search Menu */ - -.ui.search.dropdown .menu { - overflow-x: hidden; - overflow-y: auto; - backface-visibility: hidden; - -webkit-overflow-scrolling: touch; -} - -@media only screen and (max-width: 767.98px) { - .ui.search.dropdown .menu { - max-height: 8.01428571rem; - } -} - -@media only screen and (min-width: 768px) { - .ui.search.dropdown .menu { - max-height: 10.68571429rem; - } -} - -@media only screen and (min-width: 992px) { - .ui.search.dropdown .menu { - max-height: 16.02857143rem; - } -} - -@media only screen and (min-width: 1920px) { - .ui.search.dropdown .menu { - max-height: 21.37142857rem; - } -} - -/* Clearable Selection */ - -.ui.dropdown > .remove.icon { - cursor: pointer; - font-size: 0.85714286em; - margin: -0.78571429em; - padding: 0.91666667em; - right: 3em; - top: 0.78571429em; - position: absolute; - opacity: 0.6; - z-index: 3; -} - -.ui.clearable.dropdown .text, -.ui.clearable.dropdown a:last-of-type { - margin-right: 1.5em; -} - -.ui.dropdown select.noselection ~ .remove.icon, -.ui.dropdown input[value=''] ~ .remove.icon, -.ui.dropdown input:not([value]) ~ .remove.icon, -.ui.dropdown.loading > .remove.icon { - display: none; -} - -/*-------------- - Multiple - ---------------*/ - -/* Multiple Selection */ - -.ui.ui.multiple.dropdown { - padding: 0.22619048em 3.2em 0.22619048em 0.35714286em; -} - -.ui.multiple.dropdown .menu { - cursor: auto; -} - -/* Selection Label */ - -.ui.multiple.dropdown > .label { - display: inline-block; - white-space: normal; - font-size: 1em; - padding: 0.35714286em 0.78571429em; - margin: 0.14285714rem 0.28571429rem 0.14285714rem 0; - box-shadow: 0 0 0 1px rgba(34, 36, 38, 0.15) inset; -} - -/* Dropdown Icon */ - -.ui.multiple.dropdown .dropdown.icon { - margin: ''; - padding: ''; -} - -/* Text */ - -.ui.multiple.dropdown > .text { - position: static; - padding: 0; - max-width: 100%; - margin: 0.45238095em 0 0.45238095em 0.64285714em; - line-height: 1.21428571em; -} - -.ui.multiple.dropdown > .text.default { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.ui.multiple.dropdown > .label ~ input.search { - margin-left: 0.14285714em !important; -} - -.ui.multiple.dropdown > .label ~ .text { - display: none; -} - -.ui.multiple.dropdown > .label:not(.image) > img:not(.centered) { - margin-right: 0.78571429rem; -} - -.ui.multiple.dropdown > .label:not(.image) > img.ui:not(.avatar) { - margin-bottom: 0.39285714rem; -} - -.ui.multiple.dropdown > .image.label img { - margin: -0.35714286em 0.78571429em -0.35714286em -0.78571429em; - height: 1.71428571em; -} - -/*----------------- - Multiple Search - -----------------*/ - -/* Multiple Search Selection */ - -.ui.multiple.search.dropdown, -.ui.multiple.search.dropdown > input.search { - cursor: text; -} - -/* Prompt Text */ - -.ui.multiple.search.dropdown > .text { - display: inline-block; - position: absolute; - top: 0; - left: 0; - padding: inherit; - margin: 0.45238095em 0 0.45238095em 0.64285714em; - line-height: 1.21428571em; -} - -.ui.multiple.search.dropdown > .label ~ .text { - display: none; -} - -/* Search */ - -.ui.multiple.search.dropdown > input.search { - position: static; - padding: 0; - max-width: 100%; - margin: 0.45238095em 0 0.45238095em 0.64285714em; - width: 2.2em; - line-height: 1.21428571em; -} - -.ui.multiple.search.dropdown.button { - min-width: 14em; -} - -/*-------------- - Inline - ---------------*/ - -.ui.inline.dropdown { - cursor: pointer; - display: inline-block; - color: inherit; -} - -.ui.inline.dropdown .dropdown.icon { - margin: 0 0.21428571em 0 0.21428571em; - vertical-align: baseline; -} - -.ui.inline.dropdown > .text { - font-weight: 500; -} - -.ui.inline.dropdown .menu { - cursor: auto; - margin-top: 0.21428571em; - border-radius: 0.28571429rem; -} - -/******************************* - States -*******************************/ - -/*-------------------- - Active -----------------------*/ - -/* Menu Item Active */ - -.ui.dropdown .menu .active.item { - background: transparent; - font-weight: 500; - color: rgba(0, 0, 0, 0.95); - box-shadow: none; - z-index: 12; -} - -/*-------------------- - Hover -----------------------*/ - -/* Menu Item Hover */ - -.ui.dropdown .menu > .item:hover { - background: rgba(0, 0, 0, 0.05); - color: rgba(0, 0, 0, 0.95); - z-index: 13; -} - -/*-------------------- - Default Text -----------------------*/ - -.ui.dropdown:not(.button) > .default.text, -.ui.default.dropdown:not(.button) > .text { - color: rgba(191, 191, 191, 0.87); -} - -.ui.dropdown:not(.button) > input:focus ~ .default.text, -.ui.default.dropdown:not(.button) > input:focus ~ .text { - color: rgba(115, 115, 115, 0.87); -} - -/*-------------------- - Loading - ---------------------*/ - -.ui.loading.dropdown > i.icon { - height: 1em !important; -} - -.ui.loading.selection.dropdown > i.icon { - padding: 1.5em 1.28571429em !important; -} - -.ui.loading.dropdown > i.icon:before { - position: absolute; - content: ''; - top: 50%; - left: 50%; - margin: -0.64285714em 0 0 -0.64285714em; - width: 1.28571429em; - height: 1.28571429em; - border-radius: 500rem; - border: 0.2em solid rgba(0, 0, 0, 0.1); -} - -.ui.loading.dropdown > i.icon:after { - position: absolute; - content: ''; - top: 50%; - left: 50%; - box-shadow: 0 0 0 1px transparent; - margin: -0.64285714em 0 0 -0.64285714em; - width: 1.28571429em; - height: 1.28571429em; - animation: loader 0.6s infinite linear; - border: 0.2em solid #767676; - border-radius: 500rem; -} - -/* Coupling */ - -.ui.loading.dropdown.button > i.icon:before, -.ui.loading.dropdown.button > i.icon:after { - display: none; -} - -.ui.loading.dropdown > .text { - transition: none; -} - -/* Used To Check Position */ - -.ui.dropdown .loading.menu { - display: block; - visibility: hidden; - z-index: -1; -} - -.ui.dropdown > .loading.menu { - left: 0 !important; - right: auto !important; -} - -.ui.dropdown > .menu .loading.menu { - left: 100% !important; - right: auto !important; -} - -/*-------------------- - Keyboard Select -----------------------*/ - -/* Selected Item */ - -.ui.dropdown.selected, -.ui.dropdown .menu .selected.item { - background: rgba(0, 0, 0, 0.03); - color: rgba(0, 0, 0, 0.95); -} - -/*-------------------- - Search Filtered -----------------------*/ - -/* Filtered Item */ - -.ui.dropdown > .filtered.text { - visibility: hidden; -} - -.ui.dropdown .filtered.item { - display: none !important; -} - -/*-------------------- - States - ----------------------*/ - -.ui.dropdown.error, -.ui.dropdown.error > .text, -.ui.dropdown.error > .default.text { - color: #9F3A38; -} - -.ui.selection.dropdown.error { - background: #FFF6F6; - border-color: #E0B4B4; -} - -.ui.selection.dropdown.error:hover { - border-color: #E0B4B4; -} - -.ui.multiple.selection.error.dropdown > .label { - border-color: #E0B4B4; -} - -.ui.dropdown.error > .menu, -.ui.dropdown.error > .menu .menu { - border-color: #E0B4B4; -} - -.ui.dropdown.error > .menu > .item { - color: #9F3A38; -} - -/* Item Hover */ - -.ui.dropdown.error > .menu > .item:hover { - background-color: #FBE7E7; -} - -/* Item Active */ - -.ui.dropdown.error > .menu .active.item { - background-color: #FDCFCF; -} - -.ui.dropdown.info, -.ui.dropdown.info > .text, -.ui.dropdown.info > .default.text { - color: #276F86; -} - -.ui.selection.dropdown.info { - background: #F8FFFF; - border-color: #A9D5DE; -} - -.ui.selection.dropdown.info:hover { - border-color: #A9D5DE; -} - -.ui.multiple.selection.info.dropdown > .label { - border-color: #A9D5DE; -} - -.ui.dropdown.info > .menu, -.ui.dropdown.info > .menu .menu { - border-color: #A9D5DE; -} - -.ui.dropdown.info > .menu > .item { - color: #276F86; -} - -/* Item Hover */ - -.ui.dropdown.info > .menu > .item:hover { - background-color: #e9f2fb; -} - -/* Item Active */ - -.ui.dropdown.info > .menu .active.item { - background-color: #cef1fd; -} - -.ui.dropdown.success, -.ui.dropdown.success > .text, -.ui.dropdown.success > .default.text { - color: #2C662D; -} - -.ui.selection.dropdown.success { - background: #FCFFF5; - border-color: #A3C293; -} - -.ui.selection.dropdown.success:hover { - border-color: #A3C293; -} - -.ui.multiple.selection.success.dropdown > .label { - border-color: #A3C293; -} - -.ui.dropdown.success > .menu, -.ui.dropdown.success > .menu .menu { - border-color: #A3C293; -} - -.ui.dropdown.success > .menu > .item { - color: #2C662D; -} - -/* Item Hover */ - -.ui.dropdown.success > .menu > .item:hover { - background-color: #e9fbe9; -} - -/* Item Active */ - -.ui.dropdown.success > .menu .active.item { - background-color: #dafdce; -} - -.ui.dropdown.warning, -.ui.dropdown.warning > .text, -.ui.dropdown.warning > .default.text { - color: #573A08; -} - -.ui.selection.dropdown.warning { - background: #FFFAF3; - border-color: #C9BA9B; -} - -.ui.selection.dropdown.warning:hover { - border-color: #C9BA9B; -} - -.ui.multiple.selection.warning.dropdown > .label { - border-color: #C9BA9B; -} - -.ui.dropdown.warning > .menu, -.ui.dropdown.warning > .menu .menu { - border-color: #C9BA9B; -} - -.ui.dropdown.warning > .menu > .item { - color: #573A08; -} - -/* Item Hover */ - -.ui.dropdown.warning > .menu > .item:hover { - background-color: #fbfbe9; -} - -/* Item Active */ - -.ui.dropdown.warning > .menu .active.item { - background-color: #fdfdce; -} - -/*-------------------- - Clear -----------------------*/ - -.ui.dropdown > .clear.dropdown.icon { - opacity: 0.8; - transition: opacity 0.1s ease; -} - -.ui.dropdown > .clear.dropdown.icon:hover { - opacity: 1; -} - -/*-------------------- - Disabled - ----------------------*/ - -/* Disabled */ - -.ui.disabled.dropdown, -.ui.dropdown .menu > .disabled.item { - cursor: default; - pointer-events: none; - opacity: var(--opacity-disabled); -} - -/******************************* - Variations -*******************************/ - -/*-------------- - Direction ----------------*/ - -/* Flyout Direction */ - -.ui.dropdown .menu { - left: 0; -} - -/* Default Side (Right) */ - -.ui.dropdown .right.menu > .menu, -.ui.dropdown .menu .right.menu { - left: 100% !important; - right: auto !important; - border-radius: 0.28571429rem !important; -} - -/* Leftward Opening Menu */ - -.ui.dropdown > .left.menu { - left: auto !important; - right: 0 !important; -} - -.ui.dropdown > .left.menu .menu, -.ui.dropdown .menu .left.menu { - left: auto; - right: 100%; - margin: 0 -0.5em 0 0 !important; - border-radius: 0.28571429rem !important; -} - -.ui.dropdown .item .left.dropdown.icon, -.ui.dropdown .left.menu .item .dropdown.icon { - width: auto; - float: left; - margin: 0em 0 0 0; -} - -.ui.dropdown .item .left.dropdown.icon, -.ui.dropdown .left.menu .item .dropdown.icon { - width: auto; - float: left; - margin: 0em 0 0 0; -} - -.ui.dropdown .item .left.dropdown.icon + .text, -.ui.dropdown .left.menu .item .dropdown.icon + .text { - margin-left: 1em; - margin-right: 0; -} - -/*-------------- - Upward - ---------------*/ - -/* Upward Main Menu */ - -.ui.upward.dropdown > .menu { - top: auto; - bottom: 100%; - box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.08); - border-radius: 0.28571429rem 0.28571429rem 0 0; -} - -/* Upward Sub Menu */ - -.ui.dropdown .upward.menu { - top: auto !important; - bottom: 0 !important; -} - -/* Active Upward */ - -.ui.simple.upward.active.dropdown, -.ui.simple.upward.dropdown:hover { - border-radius: 0.28571429rem 0.28571429rem 0 0 !important; -} - -.ui.upward.dropdown.button:not(.pointing):not(.floating).active { - border-radius: 0.28571429rem 0.28571429rem 0 0; -} - -/* Selection */ - -.ui.upward.selection.dropdown .menu { - border-top-width: 1px !important; - border-bottom-width: 0 !important; - box-shadow: 0 -2px 3px 0 rgba(0, 0, 0, 0.08); -} - -.ui.upward.selection.dropdown:hover { - box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.05); -} - -/* Active Upward */ - -.ui.active.upward.selection.dropdown { - border-radius: 0 0 0.28571429rem 0.28571429rem !important; -} - -/* Visible Upward */ - -.ui.upward.selection.dropdown.visible { - box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.08); - border-radius: 0 0 0.28571429rem 0.28571429rem !important; -} - -/* Visible Hover Upward */ - -.ui.upward.active.selection.dropdown:hover { - box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.05); -} - -.ui.upward.active.selection.dropdown:hover .menu { - box-shadow: 0 -2px 3px 0 rgba(0, 0, 0, 0.08); -} - -/*-------------- - Scrolling - ---------------*/ - -/* Selection Menu */ - -.ui.scrolling.dropdown .menu, -.ui.dropdown .scrolling.menu { - overflow-x: hidden; - overflow-y: auto; -} - -.ui.scrolling.dropdown .menu { - overflow-x: hidden; - overflow-y: auto; - backface-visibility: hidden; - -webkit-overflow-scrolling: touch; - min-width: 100% !important; - width: auto !important; -} - -.ui.dropdown .scrolling.menu { - position: static; - overflow-y: auto; - border: none; - box-shadow: none !important; - border-radius: 0 !important; - margin: 0 !important; - min-width: 100% !important; - width: auto !important; - border-top: 1px solid rgba(34, 36, 38, 0.15); -} - -.ui.scrolling.dropdown .menu .item.item.item, -.ui.dropdown .scrolling.menu > .item.item.item { - border-top: none; -} - -.ui.scrolling.dropdown .menu .item:first-child, -.ui.dropdown .scrolling.menu .item:first-child { - border-top: none; -} - -.ui.dropdown > .animating.menu .scrolling.menu, -.ui.dropdown > .visible.menu .scrolling.menu { - display: block; -} - -/* Scrollbar in IE */ - -@media all and (-ms-high-contrast: none) { - .ui.scrolling.dropdown .menu, - .ui.dropdown .scrolling.menu { - min-width: calc(100% - 17px); - } -} - -@media only screen and (max-width: 767.98px) { - .ui.scrolling.dropdown .menu, - .ui.dropdown .scrolling.menu { - max-height: 10.28571429rem; - } -} - -@media only screen and (min-width: 768px) { - .ui.scrolling.dropdown .menu, - .ui.dropdown .scrolling.menu { - max-height: 15.42857143rem; - } -} - -@media only screen and (min-width: 992px) { - .ui.scrolling.dropdown .menu, - .ui.dropdown .scrolling.menu { - max-height: 20.57142857rem; - } -} - -@media only screen and (min-width: 1920px) { - .ui.scrolling.dropdown .menu, - .ui.dropdown .scrolling.menu { - max-height: 20.57142857rem; - } -} - -/*-------------- - Columnar ----------------*/ - -.ui.column.dropdown > .menu { - flex-wrap: wrap; -} - -.ui.dropdown[class*="two column"] > .menu > .item { - width: 50%; -} - -.ui.dropdown[class*="three column"] > .menu > .item { - width: 33%; -} - -.ui.dropdown[class*="four column"] > .menu > .item { - width: 25%; -} - -.ui.dropdown[class*="five column"] > .menu > .item { - width: 20%; -} - -/*-------------- - Simple - ---------------*/ - -/* Displays without javascript */ - -.ui.simple.dropdown .menu:before, -.ui.simple.dropdown .menu:after { - display: none; -} - -.ui.simple.dropdown .menu { - position: absolute; - /* IE hack to make dropdown icons appear inline */ - display: -ms-inline-flexbox !important; - display: block; - overflow: hidden; - top: -9999px; - opacity: 0; - width: 0; - height: 0; - transition: opacity 0.1s ease; - margin-top: 0 !important; -} - -.ui.simple.active.dropdown, -.ui.simple.dropdown:hover { - border-bottom-left-radius: 0 !important; - border-bottom-right-radius: 0 !important; -} - -.ui.simple.active.dropdown > .menu, -.ui.simple.dropdown:hover > .menu { - overflow: visible; - width: auto; - height: auto; - top: 100%; - opacity: 1; -} - -.ui.simple.dropdown > .menu > .item:active > .menu, -.ui.simple.dropdown .menu .item:hover > .menu { - overflow: visible; - width: auto; - height: auto; - top: 0 !important; - left: 100%; - opacity: 1; -} - -.ui.simple.dropdown > .menu > .item:active > .left.menu, -.ui.simple.dropdown .menu .item:hover > .left.menu, -.right.menu .ui.simple.dropdown > .menu > .item:active > .menu:not(.right), -.right.menu .ui.simple.dropdown > .menu .item:hover > .menu:not(.right) { - left: auto; - right: 100%; -} - -.ui.simple.disabled.dropdown:hover .menu { - display: none; - height: 0; - width: 0; - overflow: hidden; -} - -/* Visible */ - -.ui.simple.visible.dropdown > .menu { - display: block; -} - -/* Scrolling */ - -.ui.simple.scrolling.active.dropdown > .menu, -.ui.simple.scrolling.dropdown:hover > .menu { - overflow-x: hidden; - overflow-y: auto; -} - -/*-------------- - Fluid - ---------------*/ - -.ui.fluid.dropdown { - display: block; - width: 100% !important; - min-width: 0; -} - -.ui.fluid.dropdown > .dropdown.icon { - float: right; -} - -/*-------------- - Floating - ---------------*/ - -.ui.floating.dropdown .menu { - left: 0; - right: auto; - box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15) !important; - border-radius: 0.28571429rem !important; -} - -.ui.floating.dropdown > .menu { - border-radius: 0.28571429rem !important; -} - -.ui:not(.upward).floating.dropdown > .menu { - margin-top: 0.5em; -} - -.ui.upward.floating.dropdown > .menu { - margin-bottom: 0.5em; -} - -/*-------------- - Pointing - ---------------*/ - -.ui.pointing.dropdown > .menu { - top: 100%; - margin-top: 0.78571429rem; - border-radius: 0.28571429rem; -} - -.ui.pointing.dropdown > .menu:not(.hidden):after { - display: block; - position: absolute; - pointer-events: none; - content: ''; - visibility: visible; - transform: rotate(45deg); - width: 0.5em; - height: 0.5em; - box-shadow: -1px -1px 0 0 rgba(34, 36, 38, 0.15); - background: #FFFFFF; - z-index: 2; -} - -.ui.pointing.dropdown > .menu:not(.hidden):after { - top: -0.25em; - left: 50%; - margin: 0 0 0 -0.25em; -} - -/* Top Left Pointing */ - -.ui.top.left.pointing.dropdown > .menu { - top: 100%; - bottom: auto; - left: 0; - right: auto; - margin: 1em 0 0; -} - -.ui.top.left.pointing.dropdown > .menu { - top: 100%; - bottom: auto; - left: 0; - right: auto; - margin: 1em 0 0; -} - -.ui.top.left.pointing.dropdown > .menu:after { - top: -0.25em; - left: 1em; - right: auto; - margin: 0; - transform: rotate(45deg); -} - -/* Top Right Pointing */ - -.ui.top.right.pointing.dropdown > .menu { - top: 100%; - bottom: auto; - right: 0; - left: auto; - margin: 1em 0 0; -} - -.ui.top.pointing.dropdown > .left.menu:after, -.ui.top.right.pointing.dropdown > .menu:after { - top: -0.25em; - left: auto !important; - right: 1em !important; - margin: 0; - transform: rotate(45deg); -} - -/* Left Pointing */ - -.ui.left.pointing.dropdown > .menu { - top: 0; - left: 100%; - right: auto; - margin: 0 0 0 1em; -} - -.ui.left.pointing.dropdown > .menu:after { - top: 1em; - left: -0.25em; - margin: 0 0 0 0; - transform: rotate(-45deg); -} - -.ui.left:not(.top):not(.bottom).pointing.dropdown > .left.menu { - left: auto !important; - right: 100% !important; - margin: 0 1em 0 0; -} - -.ui.left:not(.top):not(.bottom).pointing.dropdown > .left.menu:after { - top: 1em; - left: auto; - right: -0.25em; - margin: 0 0 0 0; - transform: rotate(135deg); -} - -/* Right Pointing */ - -.ui.right.pointing.dropdown > .menu { - top: 0; - left: auto; - right: 100%; - margin: 0 1em 0 0; -} - -.ui.right.pointing.dropdown > .menu:after { - top: 1em; - left: auto; - right: -0.25em; - margin: 0 0 0 0; - transform: rotate(135deg); -} - -/* Bottom Pointing */ - -.ui.bottom.pointing.dropdown > .menu { - top: auto; - bottom: 100%; - left: 0; - right: auto; - margin: 0 0 1em; -} - -.ui.bottom.pointing.dropdown > .menu:after { - top: auto; - bottom: -0.25em; - right: auto; - margin: 0; - transform: rotate(-135deg); -} - -/* Reverse Sub-Menu Direction */ - -.ui.bottom.pointing.dropdown > .menu .menu { - top: auto !important; - bottom: 0 !important; -} - -/* Bottom Left */ - -.ui.bottom.left.pointing.dropdown > .menu { - left: 0; - right: auto; -} - -.ui.bottom.left.pointing.dropdown > .menu:after { - left: 1em; - right: auto; -} - -/* Bottom Right */ - -.ui.bottom.right.pointing.dropdown > .menu { - right: 0; - left: auto; -} - -.ui.bottom.right.pointing.dropdown > .menu:after { - left: auto; - right: 1em; -} - -/* Upward pointing */ - -.ui.pointing.upward.dropdown .menu, -.ui.top.pointing.upward.dropdown .menu { - top: auto !important; - bottom: 100% !important; - margin: 0 0 0.78571429rem; - border-radius: 0.28571429rem; -} - -.ui.pointing.upward.dropdown .menu:after, -.ui.top.pointing.upward.dropdown .menu:after { - top: 100% !important; - bottom: auto !important; - box-shadow: 1px 1px 0 0 rgba(34, 36, 38, 0.15); - margin: -0.25em 0 0; -} - -/* Right Pointing Upward */ - -.ui.right.pointing.upward.dropdown:not(.top):not(.bottom) .menu { - top: auto !important; - bottom: 0 !important; - margin: 0 1em 0 0; -} - -.ui.right.pointing.upward.dropdown:not(.top):not(.bottom) .menu:after { - top: auto !important; - bottom: 0 !important; - margin: 0 0 1em 0; - box-shadow: -1px -1px 0 0 rgba(34, 36, 38, 0.15); -} - -/* Left Pointing Upward */ - -.ui.left.pointing.upward.dropdown:not(.top):not(.bottom) .menu { - top: auto !important; - bottom: 0 !important; - margin: 0 0 0 1em; -} - -.ui.left.pointing.upward.dropdown:not(.top):not(.bottom) .menu:after { - top: auto !important; - bottom: 0 !important; - margin: 0 0 1em 0; - box-shadow: -1px -1px 0 0 rgba(34, 36, 38, 0.15); -} - -/*-------------------- - Sizes ----------------------*/ - -.ui.dropdown, -.ui.dropdown .menu > .item { - font-size: 1rem; -} - -.ui.mini.dropdown, -.ui.mini.dropdown .menu > .item { - font-size: 0.78571429rem; -} - -.ui.tiny.dropdown, -.ui.tiny.dropdown .menu > .item { - font-size: 0.85714286rem; -} - -.ui.small.dropdown, -.ui.small.dropdown .menu > .item { - font-size: 0.92857143rem; -} - -.ui.large.dropdown, -.ui.large.dropdown .menu > .item { - font-size: 1.14285714rem; -} - -.ui.big.dropdown, -.ui.big.dropdown .menu > .item { - font-size: 1.28571429rem; -} - -.ui.huge.dropdown, -.ui.huge.dropdown .menu > .item { - font-size: 1.42857143rem; -} - -.ui.massive.dropdown, -.ui.massive.dropdown .menu > .item { - font-size: 1.71428571rem; -} - -/******************************* - Theme Overrides -*******************************/ - -/* Dropdown Carets */ - -@font-face { - font-family: 'Dropdown'; - src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMggjB5AAAAC8AAAAYGNtYXAPfuIIAAABHAAAAExnYXNwAAAAEAAAAWgAAAAIZ2x5Zjo82LgAAAFwAAABVGhlYWQAQ88bAAACxAAAADZoaGVhAwcB6QAAAvwAAAAkaG10eAS4ABIAAAMgAAAAIGxvY2EBNgDeAAADQAAAABJtYXhwAAoAFgAAA1QAAAAgbmFtZVcZpu4AAAN0AAABRXBvc3QAAwAAAAAEvAAAACAAAwIAAZAABQAAAUwBZgAAAEcBTAFmAAAA9QAZAIQAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADw2gHg/+D/4AHgACAAAAABAAAAAAAAAAAAAAAgAAAAAAACAAAAAwAAABQAAwABAAAAFAAEADgAAAAKAAgAAgACAAEAIPDa//3//wAAAAAAIPDX//3//wAB/+MPLQADAAEAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAIABJQElABMAABM0NzY3BTYXFhUUDwEGJwYvASY1AAUGBwEACAUGBoAFCAcGgAUBEgcGBQEBAQcECQYHfwYBAQZ/BwYAAQAAAG4BJQESABMAADc0PwE2MzIfARYVFAcGIyEiJyY1AAWABgcIBYAGBgUI/wAHBgWABwaABQWABgcHBgUFBgcAAAABABIASQC3AW4AEwAANzQ/ATYXNhcWHQEUBwYnBi8BJjUSBoAFCAcFBgYFBwgFgAbbBwZ/BwEBBwQJ/wgEBwEBB38GBgAAAAABAAAASQClAW4AEwAANxE0NzYzMh8BFhUUDwEGIyInJjUABQYHCAWABgaABQgHBgVbAQAIBQYGgAUIBwWABgYFBwAAAAEAAAABAADZuaKOXw889QALAgAAAAAA0ABHWAAAAADQAEdYAAAAAAElAW4AAAAIAAIAAAAAAAAAAQAAAeD/4AAAAgAAAAAAASUAAQAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAABAAAAASUAAAElAAAAtwASALcAAAAAAAAACgAUAB4AQgBkAIgAqgAAAAEAAAAIABQAAQAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAOAK4AAQAAAAAAAQAOAAAAAQAAAAAAAgAOAEcAAQAAAAAAAwAOACQAAQAAAAAABAAOAFUAAQAAAAAABQAWAA4AAQAAAAAABgAHADIAAQAAAAAACgA0AGMAAwABBAkAAQAOAAAAAwABBAkAAgAOAEcAAwABBAkAAwAOACQAAwABBAkABAAOAFUAAwABBAkABQAWAA4AAwABBAkABgAOADkAAwABBAkACgA0AGMAaQBjAG8AbQBvAG8AbgBWAGUAcgBzAGkAbwBuACAAMQAuADAAaQBjAG8AbQBvAG8Abmljb21vb24AaQBjAG8AbQBvAG8AbgBSAGUAZwB1AGwAYQByAGkAYwBvAG0AbwBvAG4ARgBvAG4AdAAgAGcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAASQBjAG8ATQBvAG8AbgAuAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=) format('truetype'), url(data:application/font-woff;charset=utf-8;base64,d09GRk9UVE8AAAVwAAoAAAAABSgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABDRkYgAAAA9AAAAdkAAAHZLDXE/09TLzIAAALQAAAAYAAAAGAIIweQY21hcAAAAzAAAABMAAAATA9+4ghnYXNwAAADfAAAAAgAAAAIAAAAEGhlYWQAAAOEAAAANgAAADYAQ88baGhlYQAAA7wAAAAkAAAAJAMHAelobXR4AAAD4AAAACAAAAAgBLgAEm1heHAAAAQAAAAABgAAAAYACFAAbmFtZQAABAgAAAFFAAABRVcZpu5wb3N0AAAFUAAAACAAAAAgAAMAAAEABAQAAQEBCGljb21vb24AAQIAAQA6+BwC+BsD+BgEHgoAGVP/i4seCgAZU/+LiwwHi2v4lPh0BR0AAACIDx0AAACNER0AAAAJHQAAAdASAAkBAQgPERMWGyAlKmljb21vb25pY29tb29udTB1MXUyMHVGMEQ3dUYwRDh1RjBEOXVGMERBAAACAYkABgAIAgABAAQABwAKAA0AVgCfAOgBL/yUDvyUDvyUDvuUDvtvi/emFYuQjZCOjo+Pj42Qiwj3lIsFkIuQiY6Hj4iNhouGi4aJh4eHCPsU+xQFiIiGiYaLhouHjYeOCPsU9xQFiI+Jj4uQCA77b4v3FBWLkI2Pjo8I9xT3FAWPjo+NkIuQi5CJjogI9xT7FAWPh42Hi4aLhomHh4eIiIaJhosI+5SLBYaLh42HjoiPiY+LkAgO+92d928Vi5CNkI+OCPcU9xQFjo+QjZCLkIuPiY6Hj4iNhouGCIv7lAWLhomHh4iIh4eJhouGi4aNiI8I+xT3FAWHjomPi5AIDvvdi+YVi/eUBYuQjZCOjo+Pj42Qi5CLkImOhwj3FPsUBY+IjYaLhouGiYeHiAj7FPsUBYiHhomGi4aLh42Hj4iOiY+LkAgO+JQU+JQViwwKAAAAAAMCAAGQAAUAAAFMAWYAAABHAUwBZgAAAPUAGQCEAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAA8NoB4P/g/+AB4AAgAAAAAQAAAAAAAAAAAAAAIAAAAAAAAgAAAAMAAAAUAAMAAQAAABQABAA4AAAACgAIAAIAAgABACDw2v/9//8AAAAAACDw1//9//8AAf/jDy0AAwABAAAAAAAAAAAAAAABAAH//wAPAAEAAAABAAA5emozXw889QALAgAAAAAA0ABHWAAAAADQAEdYAAAAAAElAW4AAAAIAAIAAAAAAAAAAQAAAeD/4AAAAgAAAAAAASUAAQAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAABAAAAASUAAAElAAAAtwASALcAAAAAUAAACAAAAAAADgCuAAEAAAAAAAEADgAAAAEAAAAAAAIADgBHAAEAAAAAAAMADgAkAAEAAAAAAAQADgBVAAEAAAAAAAUAFgAOAAEAAAAAAAYABwAyAAEAAAAAAAoANABjAAMAAQQJAAEADgAAAAMAAQQJAAIADgBHAAMAAQQJAAMADgAkAAMAAQQJAAQADgBVAAMAAQQJAAUAFgAOAAMAAQQJAAYADgA5AAMAAQQJAAoANABjAGkAYwBvAG0AbwBvAG4AVgBlAHIAcwBpAG8AbgAgADEALgAwAGkAYwBvAG0AbwBvAG5pY29tb29uAGkAYwBvAG0AbwBvAG4AUgBlAGcAdQBsAGEAcgBpAGMAbwBtAG8AbwBuAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format('woff'); - font-weight: normal; - font-style: normal; -} - -.ui.dropdown > .dropdown.icon { - font-family: 'Dropdown'; - line-height: 1; - height: 1em; - width: 1.23em; - backface-visibility: hidden; - font-weight: normal; - font-style: normal; - text-align: center; -} - -.ui.dropdown > .dropdown.icon { - width: auto; -} - -.ui.dropdown > .dropdown.icon:before { - content: '\f0d7'; -} - -/* Sub Menu */ - -.ui.dropdown .menu .item .dropdown.icon:before { - content: '\f0da' ; -} - -.ui.dropdown .item .left.dropdown.icon:before, -.ui.dropdown .left.menu .item .dropdown.icon:before { - content: "\f0d9" ; -} - -/* Vertical Menu Dropdown */ - -.ui.vertical.menu .dropdown.item > .dropdown.icon:before { - content: "\f0da" ; -} - -/* Icons for Reference -.dropdown.down.icon { - content: "\f0d7"; -} -.dropdown.up.icon { - content: "\f0d8"; -} -.dropdown.left.icon { - content: "\f0d9"; -} -.dropdown.icon.icon { - content: "\f0da"; -} -*/ - -/******************************* - User Overrides -*******************************/ -/*! - * # Fomantic-UI - Form - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -/******************************* - Elements -*******************************/ - -/*-------------------- - Form ----------------------*/ - -.ui.form { - position: relative; - max-width: 100%; -} - -/*-------------------- - Content ----------------------*/ - -.ui.form > p { - margin: 1em 0; -} - -/*-------------------- - Field ----------------------*/ - -.ui.form .field { - clear: both; - margin: 0 0 1em; -} - -.ui.form .fields .fields, -.ui.form .field:last-child, -.ui.form .fields:last-child .field { - margin-bottom: 0; -} - -.ui.form .fields .field { - clear: both; - margin: 0; -} - -/*-------------------- - Labels ----------------------*/ - -.ui.form .field > label { - display: block; - margin: 0 0 0.28571429rem 0; - color: rgba(0, 0, 0, 0.87); - font-size: 0.92857143em; - font-weight: 500; - text-transform: none; -} - -/*-------------------- - Standard Inputs ----------------------*/ - -.ui.form textarea, -.ui.form input:not([type]), -.ui.form input[type="date"], -.ui.form input[type="datetime-local"], -.ui.form input[type="email"], -.ui.form input[type="number"], -.ui.form input[type="password"], -.ui.form input[type="search"], -.ui.form input[type="tel"], -.ui.form input[type="time"], -.ui.form input[type="text"], -.ui.form input[type="file"], -.ui.form input[type="url"] { - width: 100%; - vertical-align: top; -} - -/* Set max height on unusual input */ - -.ui.form ::-webkit-datetime-edit, -.ui.form ::-webkit-inner-spin-button { - height: 1.21428571em; -} - -.ui.form input:not([type]), -.ui.form input[type="date"], -.ui.form input[type="datetime-local"], -.ui.form input[type="email"], -.ui.form input[type="number"], -.ui.form input[type="password"], -.ui.form input[type="search"], -.ui.form input[type="tel"], -.ui.form input[type="time"], -.ui.form input[type="text"], -.ui.form input[type="file"], -.ui.form input[type="url"] { - font-family: var(--fonts-regular); - margin: 0; - outline: none; - -webkit-appearance: none; - -webkit-tap-highlight-color: rgba(255, 255, 255, 0); - line-height: 1.21428571em; - padding: 0.67857143em 1em; - font-size: 1em; - background: #FFFFFF; - border: 1px solid rgba(34, 36, 38, 0.15); - color: rgba(0, 0, 0, 0.87); - border-radius: 0.28571429rem; - box-shadow: 0 0 0 0 transparent inset; - transition: color 0.1s ease, border-color 0.1s ease; -} - -/* Text Area */ - -.ui.input textarea, -.ui.form textarea { - margin: 0; - -webkit-appearance: none; - -webkit-tap-highlight-color: rgba(255, 255, 255, 0); - padding: 0.78571429em 1em; - background: #FFFFFF; - border: 1px solid rgba(34, 36, 38, 0.15); - outline: none; - color: rgba(0, 0, 0, 0.87); - border-radius: 0.28571429rem; - box-shadow: 0 0 0 0 transparent inset; - transition: color 0.1s ease, border-color 0.1s ease; - font-size: 1em; - font-family: var(--fonts-regular); - line-height: 1.2857; - resize: vertical; -} - -.ui.form textarea:not([rows]) { - height: 12em; - min-height: 8em; - max-height: 24em; -} - -.ui.form textarea, -.ui.form input[type="checkbox"] { - vertical-align: top; -} - -/*-------------------- - Checkbox margin ----------------------*/ - -.ui.form .fields:not(.grouped):not(.inline) .field:not(:only-child) label + .ui.ui.checkbox { - margin-top: 0.7em; -} - -.ui.form .fields:not(.grouped):not(.inline) .field:not(:only-child) .ui.checkbox { - margin-top: 2.41428571em; -} - -.ui.form .fields:not(.grouped):not(.inline) .field:not(:only-child) .ui.toggle.checkbox { - margin-top: 2.21428571em; -} - -.ui.form .fields:not(.grouped):not(.inline) .field:not(:only-child) .ui.slider.checkbox { - margin-top: 2.61428571em; -} - -.ui.ui.form .field .fields .field:not(:only-child) .ui.checkbox { - margin-top: 0.6em; -} - -.ui.ui.form .field .fields .field:not(:only-child) .ui.toggle.checkbox { - margin-top: 0.5em; -} - -.ui.ui.form .field .fields .field:not(:only-child) .ui.slider.checkbox { - margin-top: 0.7em; -} - -/*-------------------------- - Input w/ attached Button ----------------------------*/ - -.ui.form input.attached { - width: auto; -} - -/*-------------------- - Basic Select ----------------------*/ - -.ui.form select { - display: block; - height: auto; - width: 100%; - background: #FFFFFF; - border: 1px solid rgba(34, 36, 38, 0.15); - border-radius: 0.28571429rem; - box-shadow: 0 0 0 0 transparent inset; - padding: 0.62em 1em; - color: rgba(0, 0, 0, 0.87); - transition: color 0.1s ease, border-color 0.1s ease; -} - -/*-------------------- - Dropdown ----------------------*/ - -/* Block */ - -.ui.form .field > .selection.dropdown { - min-width: auto; - width: 100%; -} - -.ui.form .field > .selection.dropdown > .dropdown.icon { - float: right; -} - -/* Inline */ - -.ui.form .inline.fields .field > .selection.dropdown, -.ui.form .inline.field > .selection.dropdown { - width: auto; -} - -.ui.form .inline.fields .field > .selection.dropdown > .dropdown.icon, -.ui.form .inline.field > .selection.dropdown > .dropdown.icon { - float: none; -} - -/*-------------------- - UI Input ----------------------*/ - -/* Block */ - -.ui.form .field .ui.input, -.ui.form .fields .field .ui.input, -.ui.form .wide.field .ui.input { - width: 100%; -} - -/* Inline */ - -.ui.form .inline.fields .field:not(.wide) .ui.input, -.ui.form .inline.field:not(.wide) .ui.input { - width: auto; - vertical-align: middle; -} - -/* Auto Input */ - -.ui.form .fields .field .ui.input input, -.ui.form .field .ui.input input { - width: auto; -} - -/* Full Width Input */ - -.ui.form .ten.fields .ui.input input, -.ui.form .nine.fields .ui.input input, -.ui.form .eight.fields .ui.input input, -.ui.form .seven.fields .ui.input input, -.ui.form .six.fields .ui.input input, -.ui.form .five.fields .ui.input input, -.ui.form .four.fields .ui.input input, -.ui.form .three.fields .ui.input input, -.ui.form .two.fields .ui.input input, -.ui.form .wide.field .ui.input input { - flex: 1 0 auto; - width: 0; -} - -/*-------------------- - Types of Messages ----------------------*/ - -.ui.form .error.message, -.ui.form .error.message:empty { - display: none; -} - -.ui.form .info.message, -.ui.form .info.message:empty { - display: none; -} - -.ui.form .success.message, -.ui.form .success.message:empty { - display: none; -} - -.ui.form .warning.message, -.ui.form .warning.message:empty { - display: none; -} - -/* Assumptions */ - -.ui.form .message:first-child { - margin-top: 0; -} - -/*-------------------- - Validation Prompt ----------------------*/ - -.ui.form .field .prompt.label { - white-space: normal; - background: #FFFFFF !important; - border: 1px solid #E0B4B4 !important; - color: #9F3A38 !important; -} - -.ui.form .inline.fields .field .prompt, -.ui.form .inline.field .prompt { - vertical-align: top; - margin: -0.25em 0 -0.5em 0.5em; -} - -.ui.form .inline.fields .field .prompt:before, -.ui.form .inline.field .prompt:before { - border-width: 0 0 1px 1px; - bottom: auto; - right: auto; - top: 50%; - left: 0; -} - -/******************************* - States -*******************************/ - -/*-------------------- - Autofilled ----------------------*/ - -.ui.form .field.field input:-webkit-autofill { - box-shadow: 0 0 0 100px #FFFFF0 inset !important; - border-color: #E5DFA1 !important; -} - -/* Focus */ - -.ui.form .field.field input:-webkit-autofill:focus { - box-shadow: 0 0 0 100px #FFFFF0 inset !important; - border-color: #D5C315 !important; -} - -/*-------------------- - Placeholder ----------------------*/ - -/* browsers require these rules separate */ - -.ui.form ::-webkit-input-placeholder { - color: rgba(191, 191, 191, 0.87); -} - -.ui.form :-ms-input-placeholder { - color: rgba(191, 191, 191, 0.87) !important; -} - -.ui.form ::-moz-placeholder { - color: rgba(191, 191, 191, 0.87); -} - -.ui.form :focus::-webkit-input-placeholder { - color: rgba(115, 115, 115, 0.87); -} - -.ui.form :focus:-ms-input-placeholder { - color: rgba(115, 115, 115, 0.87) !important; -} - -.ui.form :focus::-moz-placeholder { - color: rgba(115, 115, 115, 0.87); -} - -/*-------------------- - Focus ----------------------*/ - -.ui.form input:not([type]):focus, -.ui.form input[type="date"]:focus, -.ui.form input[type="datetime-local"]:focus, -.ui.form input[type="email"]:focus, -.ui.form input[type="number"]:focus, -.ui.form input[type="password"]:focus, -.ui.form input[type="search"]:focus, -.ui.form input[type="tel"]:focus, -.ui.form input[type="time"]:focus, -.ui.form input[type="text"]:focus, -.ui.form input[type="file"]:focus, -.ui.form input[type="url"]:focus { - color: rgba(0, 0, 0, 0.95); - border-color: #85B7D9; - border-radius: 0.28571429rem; - background: #FFFFFF; - box-shadow: 0 0 0 0 rgba(34, 36, 38, 0.35) inset; -} - -.ui.form .ui.action.input:not([class*="left action"]) input:not([type]):focus, -.ui.form .ui.action.input:not([class*="left action"]) input[type="date"]:focus, -.ui.form .ui.action.input:not([class*="left action"]) input[type="datetime-local"]:focus, -.ui.form .ui.action.input:not([class*="left action"]) input[type="email"]:focus, -.ui.form .ui.action.input:not([class*="left action"]) input[type="number"]:focus, -.ui.form .ui.action.input:not([class*="left action"]) input[type="password"]:focus, -.ui.form .ui.action.input:not([class*="left action"]) input[type="search"]:focus, -.ui.form .ui.action.input:not([class*="left action"]) input[type="tel"]:focus, -.ui.form .ui.action.input:not([class*="left action"]) input[type="time"]:focus, -.ui.form .ui.action.input:not([class*="left action"]) input[type="text"]:focus, -.ui.form .ui.action.input:not([class*="left action"]) input[type="file"]:focus, -.ui.form .ui.action.input:not([class*="left action"]) input[type="url"]:focus { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.ui.form .ui[class*="left action"].input input:not([type]), -.ui.form .ui[class*="left action"].input input[type="date"], -.ui.form .ui[class*="left action"].input input[type="datetime-local"], -.ui.form .ui[class*="left action"].input input[type="email"], -.ui.form .ui[class*="left action"].input input[type="number"], -.ui.form .ui[class*="left action"].input input[type="password"], -.ui.form .ui[class*="left action"].input input[type="search"], -.ui.form .ui[class*="left action"].input input[type="tel"], -.ui.form .ui[class*="left action"].input input[type="time"], -.ui.form .ui[class*="left action"].input input[type="text"], -.ui.form .ui[class*="left action"].input input[type="file"], -.ui.form .ui[class*="left action"].input input[type="url"] { - border-bottom-left-radius: 0; - border-top-left-radius: 0; -} - -.ui.form textarea:focus { - color: rgba(0, 0, 0, 0.95); - border-color: #85B7D9; - border-radius: 0.28571429rem; - background: #FFFFFF; - box-shadow: 0 0 0 0 rgba(34, 36, 38, 0.35) inset; - -webkit-appearance: none; -} - -/*-------------------- - States - ---------------------*/ - -/* On Form */ - -.ui.form.error .error.message:not(:empty) { - display: block; -} - -.ui.form.error .compact.error.message:not(:empty) { - display: inline-block; -} - -.ui.form.error .icon.error.message:not(:empty) { - display: flex; -} - -/* On Field(s) */ - -.ui.form .fields.error .error.message:not(:empty), -.ui.form .field.error .error.message:not(:empty) { - display: block; -} - -.ui.form .fields.error .compact.error.message:not(:empty), -.ui.form .field.error .compact.error.message:not(:empty) { - display: inline-block; -} - -.ui.form .fields.error .icon.error.message:not(:empty), -.ui.form .field.error .icon.error.message:not(:empty) { - display: flex; -} - -.ui.ui.form .fields.error .field label, -.ui.ui.form .field.error label, -.ui.ui.form .fields.error .field .input, -.ui.ui.form .field.error .input { - color: #9F3A38; -} - -.ui.form .fields.error .field .corner.label, -.ui.form .field.error .corner.label { - border-color: #9F3A38; - color: #FFFFFF; -} - -.ui.form .fields.error .field textarea, -.ui.form .fields.error .field select, -.ui.form .fields.error .field input:not([type]), -.ui.form .fields.error .field input[type="date"], -.ui.form .fields.error .field input[type="datetime-local"], -.ui.form .fields.error .field input[type="email"], -.ui.form .fields.error .field input[type="number"], -.ui.form .fields.error .field input[type="password"], -.ui.form .fields.error .field input[type="search"], -.ui.form .fields.error .field input[type="tel"], -.ui.form .fields.error .field input[type="time"], -.ui.form .fields.error .field input[type="text"], -.ui.form .fields.error .field input[type="file"], -.ui.form .fields.error .field input[type="url"], -.ui.form .field.error textarea, -.ui.form .field.error select, -.ui.form .field.error input:not([type]), -.ui.form .field.error input[type="date"], -.ui.form .field.error input[type="datetime-local"], -.ui.form .field.error input[type="email"], -.ui.form .field.error input[type="number"], -.ui.form .field.error input[type="password"], -.ui.form .field.error input[type="search"], -.ui.form .field.error input[type="tel"], -.ui.form .field.error input[type="time"], -.ui.form .field.error input[type="text"], -.ui.form .field.error input[type="file"], -.ui.form .field.error input[type="url"] { - color: #9F3A38; - background: #FFF6F6; - border-color: #E0B4B4; - border-radius: ''; - box-shadow: none; -} - -.ui.form .field.error textarea:focus, -.ui.form .field.error select:focus, -.ui.form .field.error input:not([type]):focus, -.ui.form .field.error input[type="date"]:focus, -.ui.form .field.error input[type="datetime-local"]:focus, -.ui.form .field.error input[type="email"]:focus, -.ui.form .field.error input[type="number"]:focus, -.ui.form .field.error input[type="password"]:focus, -.ui.form .field.error input[type="search"]:focus, -.ui.form .field.error input[type="tel"]:focus, -.ui.form .field.error input[type="time"]:focus, -.ui.form .field.error input[type="text"]:focus, -.ui.form .field.error input[type="file"]:focus, -.ui.form .field.error input[type="url"]:focus { - background: #FFF6F6; - border-color: #E0B4B4; - color: #9F3A38; - box-shadow: none; -} - -/* Preserve Native Select Stylings */ - -.ui.form .field.error select { - -webkit-appearance: menulist-button; -} - -/*------------------ - Input State - --------------------*/ - -/* Transparent */ - -.ui.form .field.error .transparent.input input, -.ui.form .field.error .transparent.input textarea, -.ui.form .field.error input.transparent, -.ui.form .field.error textarea.transparent { - background-color: #FFF6F6 !important; - color: #9F3A38 !important; -} - -/* Autofilled */ - -.ui.form .error.error input:-webkit-autofill { - box-shadow: 0 0 0 100px #FFFAF0 inset !important; - border-color: #E0B4B4 !important; -} - -/* Placeholder */ - -.ui.form .error ::-webkit-input-placeholder { - color: #e7bdbc; -} - -.ui.form .error :-ms-input-placeholder { - color: #e7bdbc !important; -} - -.ui.form .error ::-moz-placeholder { - color: #e7bdbc; -} - -.ui.form .error :focus::-webkit-input-placeholder { - color: #da9796; -} - -.ui.form .error :focus:-ms-input-placeholder { - color: #da9796 !important; -} - -.ui.form .error :focus::-moz-placeholder { - color: #da9796; -} - -/*------------------ - Dropdown State - --------------------*/ - -.ui.form .fields.error .field .ui.dropdown, -.ui.form .fields.error .field .ui.dropdown .item, -.ui.form .field.error .ui.dropdown, -.ui.form .field.error .ui.dropdown .text, -.ui.form .field.error .ui.dropdown .item { - background: #FFF6F6; - color: #9F3A38; -} - -.ui.form .fields.error .field .ui.dropdown, -.ui.form .field.error .ui.dropdown { - border-color: #E0B4B4 !important; -} - -.ui.form .fields.error .field .ui.dropdown:hover, -.ui.form .field.error .ui.dropdown:hover { - border-color: #E0B4B4 !important; -} - -.ui.form .fields.error .field .ui.dropdown:hover .menu, -.ui.form .field.error .ui.dropdown:hover .menu { - border-color: #E0B4B4; -} - -.ui.form .fields.error .field .ui.multiple.selection.dropdown > .label, -.ui.form .field.error .ui.multiple.selection.dropdown > .label { - background-color: #EACBCB; - color: #9F3A38; -} - -/* Hover */ - -.ui.form .fields.error .field .ui.dropdown .menu .item:hover, -.ui.form .field.error .ui.dropdown .menu .item:hover { - background-color: #FBE7E7; -} - -/* Selected */ - -.ui.form .fields.error .field .ui.dropdown .menu .selected.item, -.ui.form .field.error .ui.dropdown .menu .selected.item { - background-color: #FBE7E7; -} - -/* Active */ - -.ui.form .fields.error .field .ui.dropdown .menu .active.item, -.ui.form .field.error .ui.dropdown .menu .active.item { - background-color: #FDCFCF !important; -} - -/*-------------------- - Checkbox State - ---------------------*/ - -.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) label, -.ui.form .field.error .checkbox:not(.toggle):not(.slider) label, -.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) .box, -.ui.form .field.error .checkbox:not(.toggle):not(.slider) .box { - color: #9F3A38; -} - -.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) label:before, -.ui.form .field.error .checkbox:not(.toggle):not(.slider) label:before, -.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) .box:before, -.ui.form .field.error .checkbox:not(.toggle):not(.slider) .box:before { - background: #FFF6F6; - border-color: #E0B4B4; -} - -.ui.form .fields.error .field .checkbox label:after, -.ui.form .field.error .checkbox label:after, -.ui.form .fields.error .field .checkbox .box:after, -.ui.form .field.error .checkbox .box:after { - color: #9F3A38; -} - -/* On Form */ - -.ui.form.info .info.message:not(:empty) { - display: block; -} - -.ui.form.info .compact.info.message:not(:empty) { - display: inline-block; -} - -.ui.form.info .icon.info.message:not(:empty) { - display: flex; -} - -/* On Field(s) */ - -.ui.form .fields.info .info.message:not(:empty), -.ui.form .field.info .info.message:not(:empty) { - display: block; -} - -.ui.form .fields.info .compact.info.message:not(:empty), -.ui.form .field.info .compact.info.message:not(:empty) { - display: inline-block; -} - -.ui.form .fields.info .icon.info.message:not(:empty), -.ui.form .field.info .icon.info.message:not(:empty) { - display: flex; -} - -.ui.ui.form .fields.info .field label, -.ui.ui.form .field.info label, -.ui.ui.form .fields.info .field .input, -.ui.ui.form .field.info .input { - color: #276F86; -} - -.ui.form .fields.info .field .corner.label, -.ui.form .field.info .corner.label { - border-color: #276F86; - color: #FFFFFF; -} - -.ui.form .fields.info .field textarea, -.ui.form .fields.info .field select, -.ui.form .fields.info .field input:not([type]), -.ui.form .fields.info .field input[type="date"], -.ui.form .fields.info .field input[type="datetime-local"], -.ui.form .fields.info .field input[type="email"], -.ui.form .fields.info .field input[type="number"], -.ui.form .fields.info .field input[type="password"], -.ui.form .fields.info .field input[type="search"], -.ui.form .fields.info .field input[type="tel"], -.ui.form .fields.info .field input[type="time"], -.ui.form .fields.info .field input[type="text"], -.ui.form .fields.info .field input[type="file"], -.ui.form .fields.info .field input[type="url"], -.ui.form .field.info textarea, -.ui.form .field.info select, -.ui.form .field.info input:not([type]), -.ui.form .field.info input[type="date"], -.ui.form .field.info input[type="datetime-local"], -.ui.form .field.info input[type="email"], -.ui.form .field.info input[type="number"], -.ui.form .field.info input[type="password"], -.ui.form .field.info input[type="search"], -.ui.form .field.info input[type="tel"], -.ui.form .field.info input[type="time"], -.ui.form .field.info input[type="text"], -.ui.form .field.info input[type="file"], -.ui.form .field.info input[type="url"] { - color: #276F86; - background: #F8FFFF; - border-color: #A9D5DE; - border-radius: ''; - box-shadow: none; -} - -.ui.form .field.info textarea:focus, -.ui.form .field.info select:focus, -.ui.form .field.info input:not([type]):focus, -.ui.form .field.info input[type="date"]:focus, -.ui.form .field.info input[type="datetime-local"]:focus, -.ui.form .field.info input[type="email"]:focus, -.ui.form .field.info input[type="number"]:focus, -.ui.form .field.info input[type="password"]:focus, -.ui.form .field.info input[type="search"]:focus, -.ui.form .field.info input[type="tel"]:focus, -.ui.form .field.info input[type="time"]:focus, -.ui.form .field.info input[type="text"]:focus, -.ui.form .field.info input[type="file"]:focus, -.ui.form .field.info input[type="url"]:focus { - background: #F8FFFF; - border-color: #A9D5DE; - color: #276F86; - box-shadow: none; -} - -/* Preserve Native Select Stylings */ - -.ui.form .field.info select { - -webkit-appearance: menulist-button; -} - -/*------------------ - Input State - --------------------*/ - -/* Transparent */ - -.ui.form .field.info .transparent.input input, -.ui.form .field.info .transparent.input textarea, -.ui.form .field.info input.transparent, -.ui.form .field.info textarea.transparent { - background-color: #F8FFFF !important; - color: #276F86 !important; -} - -/* Autofilled */ - -.ui.form .info.info input:-webkit-autofill { - box-shadow: 0 0 0 100px #F0FAFF inset !important; - border-color: #b3e0e0 !important; -} - -/* Placeholder */ - -.ui.form .info ::-webkit-input-placeholder { - color: #98cfe1; -} - -.ui.form .info :-ms-input-placeholder { - color: #98cfe1 !important; -} - -.ui.form .info ::-moz-placeholder { - color: #98cfe1; -} - -.ui.form .info :focus::-webkit-input-placeholder { - color: #70bdd6; -} - -.ui.form .info :focus:-ms-input-placeholder { - color: #70bdd6 !important; -} - -.ui.form .info :focus::-moz-placeholder { - color: #70bdd6; -} - -/*------------------ - Dropdown State - --------------------*/ - -.ui.form .fields.info .field .ui.dropdown, -.ui.form .fields.info .field .ui.dropdown .item, -.ui.form .field.info .ui.dropdown, -.ui.form .field.info .ui.dropdown .text, -.ui.form .field.info .ui.dropdown .item { - background: #F8FFFF; - color: #276F86; -} - -.ui.form .fields.info .field .ui.dropdown, -.ui.form .field.info .ui.dropdown { - border-color: #A9D5DE !important; -} - -.ui.form .fields.info .field .ui.dropdown:hover, -.ui.form .field.info .ui.dropdown:hover { - border-color: #A9D5DE !important; -} - -.ui.form .fields.info .field .ui.dropdown:hover .menu, -.ui.form .field.info .ui.dropdown:hover .menu { - border-color: #A9D5DE; -} - -.ui.form .fields.info .field .ui.multiple.selection.dropdown > .label, -.ui.form .field.info .ui.multiple.selection.dropdown > .label { - background-color: #cce3ea; - color: #276F86; -} - -/* Hover */ - -.ui.form .fields.info .field .ui.dropdown .menu .item:hover, -.ui.form .field.info .ui.dropdown .menu .item:hover { - background-color: #e9f2fb; -} - -/* Selected */ - -.ui.form .fields.info .field .ui.dropdown .menu .selected.item, -.ui.form .field.info .ui.dropdown .menu .selected.item { - background-color: #e9f2fb; -} - -/* Active */ - -.ui.form .fields.info .field .ui.dropdown .menu .active.item, -.ui.form .field.info .ui.dropdown .menu .active.item { - background-color: #cef1fd !important; -} - -/*-------------------- - Checkbox State - ---------------------*/ - -.ui.form .fields.info .field .checkbox:not(.toggle):not(.slider) label, -.ui.form .field.info .checkbox:not(.toggle):not(.slider) label, -.ui.form .fields.info .field .checkbox:not(.toggle):not(.slider) .box, -.ui.form .field.info .checkbox:not(.toggle):not(.slider) .box { - color: #276F86; -} - -.ui.form .fields.info .field .checkbox:not(.toggle):not(.slider) label:before, -.ui.form .field.info .checkbox:not(.toggle):not(.slider) label:before, -.ui.form .fields.info .field .checkbox:not(.toggle):not(.slider) .box:before, -.ui.form .field.info .checkbox:not(.toggle):not(.slider) .box:before { - background: #F8FFFF; - border-color: #A9D5DE; -} - -.ui.form .fields.info .field .checkbox label:after, -.ui.form .field.info .checkbox label:after, -.ui.form .fields.info .field .checkbox .box:after, -.ui.form .field.info .checkbox .box:after { - color: #276F86; -} - -/* On Form */ - -.ui.form.success .success.message:not(:empty) { - display: block; -} - -.ui.form.success .compact.success.message:not(:empty) { - display: inline-block; -} - -.ui.form.success .icon.success.message:not(:empty) { - display: flex; -} - -/* On Field(s) */ - -.ui.form .fields.success .success.message:not(:empty), -.ui.form .field.success .success.message:not(:empty) { - display: block; -} - -.ui.form .fields.success .compact.success.message:not(:empty), -.ui.form .field.success .compact.success.message:not(:empty) { - display: inline-block; -} - -.ui.form .fields.success .icon.success.message:not(:empty), -.ui.form .field.success .icon.success.message:not(:empty) { - display: flex; -} - -.ui.ui.form .fields.success .field label, -.ui.ui.form .field.success label, -.ui.ui.form .fields.success .field .input, -.ui.ui.form .field.success .input { - color: #2C662D; -} - -.ui.form .fields.success .field .corner.label, -.ui.form .field.success .corner.label { - border-color: #2C662D; - color: #FFFFFF; -} - -.ui.form .fields.success .field textarea, -.ui.form .fields.success .field select, -.ui.form .fields.success .field input:not([type]), -.ui.form .fields.success .field input[type="date"], -.ui.form .fields.success .field input[type="datetime-local"], -.ui.form .fields.success .field input[type="email"], -.ui.form .fields.success .field input[type="number"], -.ui.form .fields.success .field input[type="password"], -.ui.form .fields.success .field input[type="search"], -.ui.form .fields.success .field input[type="tel"], -.ui.form .fields.success .field input[type="time"], -.ui.form .fields.success .field input[type="text"], -.ui.form .fields.success .field input[type="file"], -.ui.form .fields.success .field input[type="url"], -.ui.form .field.success textarea, -.ui.form .field.success select, -.ui.form .field.success input:not([type]), -.ui.form .field.success input[type="date"], -.ui.form .field.success input[type="datetime-local"], -.ui.form .field.success input[type="email"], -.ui.form .field.success input[type="number"], -.ui.form .field.success input[type="password"], -.ui.form .field.success input[type="search"], -.ui.form .field.success input[type="tel"], -.ui.form .field.success input[type="time"], -.ui.form .field.success input[type="text"], -.ui.form .field.success input[type="file"], -.ui.form .field.success input[type="url"] { - color: #2C662D; - background: #FCFFF5; - border-color: #A3C293; - border-radius: ''; - box-shadow: none; -} - -.ui.form .field.success textarea:focus, -.ui.form .field.success select:focus, -.ui.form .field.success input:not([type]):focus, -.ui.form .field.success input[type="date"]:focus, -.ui.form .field.success input[type="datetime-local"]:focus, -.ui.form .field.success input[type="email"]:focus, -.ui.form .field.success input[type="number"]:focus, -.ui.form .field.success input[type="password"]:focus, -.ui.form .field.success input[type="search"]:focus, -.ui.form .field.success input[type="tel"]:focus, -.ui.form .field.success input[type="time"]:focus, -.ui.form .field.success input[type="text"]:focus, -.ui.form .field.success input[type="file"]:focus, -.ui.form .field.success input[type="url"]:focus { - background: #FCFFF5; - border-color: #A3C293; - color: #2C662D; - box-shadow: none; -} - -/* Preserve Native Select Stylings */ - -.ui.form .field.success select { - -webkit-appearance: menulist-button; -} - -/*------------------ - Input State - --------------------*/ - -/* Transparent */ - -.ui.form .field.success .transparent.input input, -.ui.form .field.success .transparent.input textarea, -.ui.form .field.success input.transparent, -.ui.form .field.success textarea.transparent { - background-color: #FCFFF5 !important; - color: #2C662D !important; -} - -/* Autofilled */ - -.ui.form .success.success input:-webkit-autofill { - box-shadow: 0 0 0 100px #F0FFF0 inset !important; - border-color: #bee0b3 !important; -} - -/* Placeholder */ - -.ui.form .success ::-webkit-input-placeholder { - color: #8fcf90; -} - -.ui.form .success :-ms-input-placeholder { - color: #8fcf90 !important; -} - -.ui.form .success ::-moz-placeholder { - color: #8fcf90; -} - -.ui.form .success :focus::-webkit-input-placeholder { - color: #6cbf6d; -} - -.ui.form .success :focus:-ms-input-placeholder { - color: #6cbf6d !important; -} - -.ui.form .success :focus::-moz-placeholder { - color: #6cbf6d; -} - -/*------------------ - Dropdown State - --------------------*/ - -.ui.form .fields.success .field .ui.dropdown, -.ui.form .fields.success .field .ui.dropdown .item, -.ui.form .field.success .ui.dropdown, -.ui.form .field.success .ui.dropdown .text, -.ui.form .field.success .ui.dropdown .item { - background: #FCFFF5; - color: #2C662D; -} - -.ui.form .fields.success .field .ui.dropdown, -.ui.form .field.success .ui.dropdown { - border-color: #A3C293 !important; -} - -.ui.form .fields.success .field .ui.dropdown:hover, -.ui.form .field.success .ui.dropdown:hover { - border-color: #A3C293 !important; -} - -.ui.form .fields.success .field .ui.dropdown:hover .menu, -.ui.form .field.success .ui.dropdown:hover .menu { - border-color: #A3C293; -} - -.ui.form .fields.success .field .ui.multiple.selection.dropdown > .label, -.ui.form .field.success .ui.multiple.selection.dropdown > .label { - background-color: #cceacc; - color: #2C662D; -} - -/* Hover */ - -.ui.form .fields.success .field .ui.dropdown .menu .item:hover, -.ui.form .field.success .ui.dropdown .menu .item:hover { - background-color: #e9fbe9; -} - -/* Selected */ - -.ui.form .fields.success .field .ui.dropdown .menu .selected.item, -.ui.form .field.success .ui.dropdown .menu .selected.item { - background-color: #e9fbe9; -} - -/* Active */ - -.ui.form .fields.success .field .ui.dropdown .menu .active.item, -.ui.form .field.success .ui.dropdown .menu .active.item { - background-color: #dafdce !important; -} - -/*-------------------- - Checkbox State - ---------------------*/ - -.ui.form .fields.success .field .checkbox:not(.toggle):not(.slider) label, -.ui.form .field.success .checkbox:not(.toggle):not(.slider) label, -.ui.form .fields.success .field .checkbox:not(.toggle):not(.slider) .box, -.ui.form .field.success .checkbox:not(.toggle):not(.slider) .box { - color: #2C662D; -} - -.ui.form .fields.success .field .checkbox:not(.toggle):not(.slider) label:before, -.ui.form .field.success .checkbox:not(.toggle):not(.slider) label:before, -.ui.form .fields.success .field .checkbox:not(.toggle):not(.slider) .box:before, -.ui.form .field.success .checkbox:not(.toggle):not(.slider) .box:before { - background: #FCFFF5; - border-color: #A3C293; -} - -.ui.form .fields.success .field .checkbox label:after, -.ui.form .field.success .checkbox label:after, -.ui.form .fields.success .field .checkbox .box:after, -.ui.form .field.success .checkbox .box:after { - color: #2C662D; -} - -/* On Form */ - -.ui.form.warning .warning.message:not(:empty) { - display: block; -} - -.ui.form.warning .compact.warning.message:not(:empty) { - display: inline-block; -} - -.ui.form.warning .icon.warning.message:not(:empty) { - display: flex; -} - -/* On Field(s) */ - -.ui.form .fields.warning .warning.message:not(:empty), -.ui.form .field.warning .warning.message:not(:empty) { - display: block; -} - -.ui.form .fields.warning .compact.warning.message:not(:empty), -.ui.form .field.warning .compact.warning.message:not(:empty) { - display: inline-block; -} - -.ui.form .fields.warning .icon.warning.message:not(:empty), -.ui.form .field.warning .icon.warning.message:not(:empty) { - display: flex; -} - -.ui.ui.form .fields.warning .field label, -.ui.ui.form .field.warning label, -.ui.ui.form .fields.warning .field .input, -.ui.ui.form .field.warning .input { - color: #573A08; -} - -.ui.form .fields.warning .field .corner.label, -.ui.form .field.warning .corner.label { - border-color: #573A08; - color: #FFFFFF; -} - -.ui.form .fields.warning .field textarea, -.ui.form .fields.warning .field select, -.ui.form .fields.warning .field input:not([type]), -.ui.form .fields.warning .field input[type="date"], -.ui.form .fields.warning .field input[type="datetime-local"], -.ui.form .fields.warning .field input[type="email"], -.ui.form .fields.warning .field input[type="number"], -.ui.form .fields.warning .field input[type="password"], -.ui.form .fields.warning .field input[type="search"], -.ui.form .fields.warning .field input[type="tel"], -.ui.form .fields.warning .field input[type="time"], -.ui.form .fields.warning .field input[type="text"], -.ui.form .fields.warning .field input[type="file"], -.ui.form .fields.warning .field input[type="url"], -.ui.form .field.warning textarea, -.ui.form .field.warning select, -.ui.form .field.warning input:not([type]), -.ui.form .field.warning input[type="date"], -.ui.form .field.warning input[type="datetime-local"], -.ui.form .field.warning input[type="email"], -.ui.form .field.warning input[type="number"], -.ui.form .field.warning input[type="password"], -.ui.form .field.warning input[type="search"], -.ui.form .field.warning input[type="tel"], -.ui.form .field.warning input[type="time"], -.ui.form .field.warning input[type="text"], -.ui.form .field.warning input[type="file"], -.ui.form .field.warning input[type="url"] { - color: #573A08; - background: #FFFAF3; - border-color: #C9BA9B; - border-radius: ''; - box-shadow: none; -} - -.ui.form .field.warning textarea:focus, -.ui.form .field.warning select:focus, -.ui.form .field.warning input:not([type]):focus, -.ui.form .field.warning input[type="date"]:focus, -.ui.form .field.warning input[type="datetime-local"]:focus, -.ui.form .field.warning input[type="email"]:focus, -.ui.form .field.warning input[type="number"]:focus, -.ui.form .field.warning input[type="password"]:focus, -.ui.form .field.warning input[type="search"]:focus, -.ui.form .field.warning input[type="tel"]:focus, -.ui.form .field.warning input[type="time"]:focus, -.ui.form .field.warning input[type="text"]:focus, -.ui.form .field.warning input[type="file"]:focus, -.ui.form .field.warning input[type="url"]:focus { - background: #FFFAF3; - border-color: #C9BA9B; - color: #573A08; - box-shadow: none; -} - -/* Preserve Native Select Stylings */ - -.ui.form .field.warning select { - -webkit-appearance: menulist-button; -} - -/*------------------ - Input State - --------------------*/ - -/* Transparent */ - -.ui.form .field.warning .transparent.input input, -.ui.form .field.warning .transparent.input textarea, -.ui.form .field.warning input.transparent, -.ui.form .field.warning textarea.transparent { - background-color: #FFFAF3 !important; - color: #573A08 !important; -} - -/* Autofilled */ - -.ui.form .warning.warning input:-webkit-autofill { - box-shadow: 0 0 0 100px #FFFFe0 inset !important; - border-color: #e0e0b3 !important; -} - -/* Placeholder */ - -.ui.form .warning ::-webkit-input-placeholder { - color: #edad3e; -} - -.ui.form .warning :-ms-input-placeholder { - color: #edad3e !important; -} - -.ui.form .warning ::-moz-placeholder { - color: #edad3e; -} - -.ui.form .warning :focus::-webkit-input-placeholder { - color: #e39715; -} - -.ui.form .warning :focus:-ms-input-placeholder { - color: #e39715 !important; -} - -.ui.form .warning :focus::-moz-placeholder { - color: #e39715; -} - -/*------------------ - Dropdown State - --------------------*/ - -.ui.form .fields.warning .field .ui.dropdown, -.ui.form .fields.warning .field .ui.dropdown .item, -.ui.form .field.warning .ui.dropdown, -.ui.form .field.warning .ui.dropdown .text, -.ui.form .field.warning .ui.dropdown .item { - background: #FFFAF3; - color: #573A08; -} - -.ui.form .fields.warning .field .ui.dropdown, -.ui.form .field.warning .ui.dropdown { - border-color: #C9BA9B !important; -} - -.ui.form .fields.warning .field .ui.dropdown:hover, -.ui.form .field.warning .ui.dropdown:hover { - border-color: #C9BA9B !important; -} - -.ui.form .fields.warning .field .ui.dropdown:hover .menu, -.ui.form .field.warning .ui.dropdown:hover .menu { - border-color: #C9BA9B; -} - -.ui.form .fields.warning .field .ui.multiple.selection.dropdown > .label, -.ui.form .field.warning .ui.multiple.selection.dropdown > .label { - background-color: #eaeacc; - color: #573A08; -} - -/* Hover */ - -.ui.form .fields.warning .field .ui.dropdown .menu .item:hover, -.ui.form .field.warning .ui.dropdown .menu .item:hover { - background-color: #fbfbe9; -} - -/* Selected */ - -.ui.form .fields.warning .field .ui.dropdown .menu .selected.item, -.ui.form .field.warning .ui.dropdown .menu .selected.item { - background-color: #fbfbe9; -} - -/* Active */ - -.ui.form .fields.warning .field .ui.dropdown .menu .active.item, -.ui.form .field.warning .ui.dropdown .menu .active.item { - background-color: #fdfdce !important; -} - -/*-------------------- - Checkbox State - ---------------------*/ - -.ui.form .fields.warning .field .checkbox:not(.toggle):not(.slider) label, -.ui.form .field.warning .checkbox:not(.toggle):not(.slider) label, -.ui.form .fields.warning .field .checkbox:not(.toggle):not(.slider) .box, -.ui.form .field.warning .checkbox:not(.toggle):not(.slider) .box { - color: #573A08; -} - -.ui.form .fields.warning .field .checkbox:not(.toggle):not(.slider) label:before, -.ui.form .field.warning .checkbox:not(.toggle):not(.slider) label:before, -.ui.form .fields.warning .field .checkbox:not(.toggle):not(.slider) .box:before, -.ui.form .field.warning .checkbox:not(.toggle):not(.slider) .box:before { - background: #FFFAF3; - border-color: #C9BA9B; -} - -.ui.form .fields.warning .field .checkbox label:after, -.ui.form .field.warning .checkbox label:after, -.ui.form .fields.warning .field .checkbox .box:after, -.ui.form .field.warning .checkbox .box:after { - color: #573A08; -} - -/*-------------------- - Disabled - ---------------------*/ - -.ui.form .disabled.fields .field, -.ui.form .disabled.field, -.ui.form .field :disabled { - pointer-events: none; - opacity: var(--opacity-disabled); -} - -.ui.form .field.disabled > label, -.ui.form .fields.disabled > label { - opacity: var(--opacity-disabled); -} - -.ui.form .field.disabled :disabled { - opacity: 1; -} - -/*-------------- - Loading - ---------------*/ - -.ui.loading.form { - position: relative; - cursor: default; - pointer-events: none; -} - -.ui.loading.form:before { - position: absolute; - content: ''; - top: 0; - left: 0; - background: rgba(255, 255, 255, 0.8); - width: 100%; - height: 100%; - z-index: 100; -} - -.ui.loading.form.segments:before { - border-radius: 0.28571429rem; -} - -.ui.loading.form:after { - position: absolute; - content: ''; - top: 50%; - left: 50%; - margin: -1.5em 0 0 -1.5em; - width: 3em; - height: 3em; - animation: loader 0.6s infinite linear; - border: 0.2em solid #767676; - border-radius: 500rem; - box-shadow: 0 0 0 1px transparent; - visibility: visible; - z-index: 101; -} - -/******************************* - Element Types -*******************************/ - -/*-------------------- - Required Field - ---------------------*/ - -.ui.form .required.fields:not(.grouped) > .field > label:after, -.ui.form .required.fields.grouped > label:after, -.ui.form .required.field > label:after, -.ui.form .required.fields:not(.grouped) > .field > .checkbox:after, -.ui.form .required.field > .checkbox:after, -.ui.form label.required:after { - margin: -0.2em 0 0 0.2em; - content: '*'; - color: #DB2828; -} - -.ui.form .required.fields:not(.grouped) > .field > label:after, -.ui.form .required.fields.grouped > label:after, -.ui.form .required.field > label:after, -.ui.form label.required:after { - display: inline-block; - vertical-align: top; -} - -.ui.form .required.fields:not(.grouped) > .field > .checkbox:after, -.ui.form .required.field > .checkbox:after { - position: absolute; - top: 0; - left: 100%; -} - -/******************************* - Variations -*******************************/ - -/*-------------------- - Field Groups - ---------------------*/ - -/* Grouped Vertically */ - -.ui.form .grouped.fields { - display: block; - margin: 0 0 1em; -} - -.ui.form .grouped.fields:last-child { - margin-bottom: 0; -} - -.ui.form .grouped.fields > label { - margin: 0 0 0.28571429rem 0; - color: rgba(0, 0, 0, 0.87); - font-size: 0.92857143em; - font-weight: 500; - text-transform: none; -} - -.ui.form .grouped.fields .field, -.ui.form .grouped.inline.fields .field { - display: block; - margin: 0.5em 0; - padding: 0; -} - -.ui.form .grouped.inline.fields .ui.checkbox { - margin-bottom: 0.4em; -} - -/*-------------------- - Fields ----------------------*/ - -/* Split fields */ - -.ui.form .fields { - display: flex; - flex-direction: row; - margin: 0 -0.5em 1em; -} - -.ui.form .fields > .field { - flex: 0 1 auto; - padding-left: 0.5em; - padding-right: 0.5em; -} - -.ui.form .fields > .field:first-child { - border-left: none; - box-shadow: none; -} - -/* Other Combinations */ - -.ui.form .two.fields > .fields, -.ui.form .two.fields > .field { - width: 50%; -} - -.ui.form .three.fields > .fields, -.ui.form .three.fields > .field { - width: 33.33333333%; -} - -.ui.form .four.fields > .fields, -.ui.form .four.fields > .field { - width: 25%; -} - -.ui.form .five.fields > .fields, -.ui.form .five.fields > .field { - width: 20%; -} - -.ui.form .six.fields > .fields, -.ui.form .six.fields > .field { - width: 16.66666667%; -} - -.ui.form .seven.fields > .fields, -.ui.form .seven.fields > .field { - width: 14.28571429%; -} - -.ui.form .eight.fields > .fields, -.ui.form .eight.fields > .field { - width: 12.5%; -} - -.ui.form .nine.fields > .fields, -.ui.form .nine.fields > .field { - width: 11.11111111%; -} - -.ui.form .ten.fields > .fields, -.ui.form .ten.fields > .field { - width: 10%; -} - -/* Swap to full width on mobile */ - -@media only screen and (max-width: 767.98px) { - .ui.form .fields { - flex-wrap: wrap; - margin-bottom: 0; - } - - .ui.form:not(.unstackable) .fields:not(.unstackable) > .fields, - .ui.form:not(.unstackable) .fields:not(.unstackable) > .field { - width: 100%; - margin: 0 0 1em; - } -} - -/* Sizing Combinations */ - -.ui.form .fields .wide.field { - width: 6.25%; - padding-left: 0.5em; - padding-right: 0.5em; -} - -.ui.form .one.wide.field { - width: 6.25%; -} - -.ui.form .two.wide.field { - width: 12.5%; -} - -.ui.form .three.wide.field { - width: 18.75%; -} - -.ui.form .four.wide.field { - width: 25%; -} - -.ui.form .five.wide.field { - width: 31.25%; -} - -.ui.form .six.wide.field { - width: 37.5%; -} - -.ui.form .seven.wide.field { - width: 43.75%; -} - -.ui.form .eight.wide.field { - width: 50%; -} - -.ui.form .nine.wide.field { - width: 56.25%; -} - -.ui.form .ten.wide.field { - width: 62.5%; -} - -.ui.form .eleven.wide.field { - width: 68.75%; -} - -.ui.form .twelve.wide.field { - width: 75%; -} - -.ui.form .thirteen.wide.field { - width: 81.25%; -} - -.ui.form .fourteen.wide.field { - width: 87.5%; -} - -.ui.form .fifteen.wide.field { - width: 93.75%; -} - -.ui.form .sixteen.wide.field { - width: 100%; -} - -/*-------------------- - Equal Width ----------------------*/ - -.ui[class*="equal width"].form .fields > .field, -.ui.form [class*="equal width"].fields > .field { - width: 100%; - flex: 1 1 auto; -} - -/*-------------------- - Inline Fields - ---------------------*/ - -.ui.form .inline.fields { - margin: 0 0 1em; - align-items: center; -} - -.ui.form .inline.fields .field { - margin: 0; - padding: 0 1em 0 0; -} - -/* Inline Label */ - -.ui.form .inline.fields > label, -.ui.form .inline.fields .field > label, -.ui.form .inline.fields .field > p, -.ui.form .inline.field > label, -.ui.form .inline.field > p { - display: inline-block; - width: auto; - margin-top: 0; - margin-bottom: 0; - vertical-align: baseline; - font-size: 0.92857143em; - font-weight: 500; - color: rgba(0, 0, 0, 0.87); - text-transform: none; -} - -/* Grouped Inline Label */ - -.ui.form .inline.fields > label { - margin: 0.035714em 1em 0 0; -} - -/* Inline Input */ - -.ui.form .inline.fields .field > input, -.ui.form .inline.fields .field > select, -.ui.form .inline.field > input, -.ui.form .inline.field > select { - display: inline-block; - width: auto; - margin-top: 0; - margin-bottom: 0; - vertical-align: middle; - font-size: 1em; -} - -.ui.form .inline.fields .field .calendar:not(.popup), -.ui.form .inline.field .calendar:not(.popup) { - display: inline-block; -} - -.ui.form .inline.fields .field .calendar:not(.popup) > .input > input, -.ui.form .inline.field .calendar:not(.popup) > .input > input { - width: 13.11em; -} - -/* Label */ - -.ui.form .inline.fields .field > :first-child, -.ui.form .inline.field > :first-child { - margin: 0 0.85714286em 0 0; -} - -.ui.form .inline.fields .field > :only-child, -.ui.form .inline.field > :only-child { - margin: 0; -} - -/* Wide */ - -.ui.form .inline.fields .wide.field { - display: flex; - align-items: center; -} - -.ui.form .inline.fields .wide.field > input, -.ui.form .inline.fields .wide.field > select { - width: 100%; -} - -/*-------------------- - Sizes ----------------------*/ - -.ui.form, -.ui.form .field .dropdown, -.ui.form .field .dropdown .menu > .item { - font-size: 1rem; -} - -.ui.mini.form, -.ui.mini.form .field .dropdown, -.ui.mini.form .field .dropdown .menu > .item { - font-size: 0.78571429rem; -} - -.ui.tiny.form, -.ui.tiny.form .field .dropdown, -.ui.tiny.form .field .dropdown .menu > .item { - font-size: 0.85714286rem; -} - -.ui.small.form, -.ui.small.form .field .dropdown, -.ui.small.form .field .dropdown .menu > .item { - font-size: 0.92857143rem; -} - -.ui.large.form, -.ui.large.form .field .dropdown, -.ui.large.form .field .dropdown .menu > .item { - font-size: 1.14285714rem; -} - -.ui.big.form, -.ui.big.form .field .dropdown, -.ui.big.form .field .dropdown .menu > .item { - font-size: 1.28571429rem; -} - -.ui.huge.form, -.ui.huge.form .field .dropdown, -.ui.huge.form .field .dropdown .menu > .item { - font-size: 1.42857143rem; -} - -.ui.massive.form, -.ui.massive.form .field .dropdown, -.ui.massive.form .field .dropdown .menu > .item { - font-size: 1.71428571rem; -} - -/******************************* - Theme Overrides -*******************************/ - -/******************************* - Site Overrides -*******************************/ -/*! - * # Fomantic-UI - Modal - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -/******************************* - Modal -*******************************/ - -.ui.modal { - position: absolute; - display: none; - z-index: 1001; - text-align: left; - background: #FFFFFF; - border: none; - box-shadow: 1px 3px 3px 0 rgba(0, 0, 0, 0.2), 1px 3px 15px 2px rgba(0, 0, 0, 0.2); - transform-origin: 50% 25%; - flex: 0 0 auto; - border-radius: 0.28571429rem; - -webkit-user-select: text; - -moz-user-select: text; - user-select: text; - will-change: top, left, margin, transform, opacity; -} - -.ui.modal > :first-child:not(.icon):not(.dimmer), -.ui.modal > i.icon:first-child + *, -.ui.modal > .dimmer:first-child + *:not(.icon), -.ui.modal > .dimmer:first-child + i.icon + * { - border-top-left-radius: 0.28571429rem; - border-top-right-radius: 0.28571429rem; -} - -.ui.modal > :last-child { - border-bottom-left-radius: 0.28571429rem; - border-bottom-right-radius: 0.28571429rem; -} - -.ui.modal > .ui.dimmer { - border-radius: inherit; -} - -/******************************* - Content -*******************************/ - -/*-------------- - Close ----------------*/ - -.ui.modal > .close { - cursor: pointer; - position: absolute; - top: -2.5rem; - right: -2.5rem; - z-index: 1; - opacity: 0.8; - font-size: 1.25em; - color: #FFFFFF; - width: 2.25rem; - height: 2.25rem; - padding: 0.625rem 0 0 0; -} - -.ui.modal > .close:hover { - opacity: 1; -} - -/*-------------- - Header ----------------*/ - -.ui.modal > .header { - display: block; - font-family: var(--fonts-regular); - background: #FFFFFF; - margin: 0; - padding: 1.25rem 1.5rem; - box-shadow: none; - color: rgba(0, 0, 0, 0.85); - border-bottom: 1px solid rgba(34, 36, 38, 0.15); -} - -.ui.modal > .header:not(.ui) { - font-size: 1.42857143rem; - line-height: 1.28571429em; - font-weight: 500; -} - -/*-------------- - Content ----------------*/ - -.ui.modal > .content { - display: block; - width: 100%; - font-size: 1em; - line-height: 1.4; - padding: 1.5rem; - background: #FFFFFF; -} - -.ui.modal > .image.content { - display: flex; - flex-direction: row; -} - -/* Image */ - -.ui.modal > .content > .image { - display: block; - flex: 0 1 auto; - width: ''; - align-self: start; - max-width: 100%; -} - -.ui.modal > [class*="top aligned"] { - align-self: start; -} - -.ui.modal > [class*="middle aligned"] { - align-self: center; -} - -.ui.modal > [class*="stretched"] { - align-self: stretch; -} - -/* Description */ - -.ui.modal > .content > .description { - display: block; - flex: 1 0 auto; - min-width: 0; - align-self: start; -} - -.ui.modal > .content > i.icon + .description, -.ui.modal > .content > .image + .description { - flex: 0 1 auto; - min-width: ''; - width: auto; - padding-left: 2em; -} - -/*rtl:ignore*/ - -.ui.modal > .content > .image > i.icon { - margin: 0; - opacity: 1; - width: auto; - line-height: 1; - font-size: 8rem; -} - -/*-------------- - Actions ----------------*/ - -.ui.modal > .actions { - background: #F9FAFB; - padding: 1rem 1rem; - border-top: 1px solid rgba(34, 36, 38, 0.15); - text-align: right; -} - -.ui.modal .actions > .button:not(.fluid) { - margin-left: 0.75em; -} - -.ui.basic.modal > .actions { - border-top: none; -} - -/*------------------- - Responsive ---------------------*/ - -/* Modal Width */ - -@media only screen and (max-width: 767.98px) { - .ui.modal:not(.fullscreen) { - width: 95%; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 768px) { - .ui.modal:not(.fullscreen) { - width: 88%; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 992px) { - .ui.modal:not(.fullscreen) { - width: 850px; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 1200px) { - .ui.modal:not(.fullscreen) { - width: 900px; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 1920px) { - .ui.modal:not(.fullscreen) { - width: 950px; - margin: 0 0 0 0; - } -} - -/* Tablet and Mobile */ - -@media only screen and (max-width: 991.98px) { - .ui.modal > .header { - padding-right: 2.25rem; - } - - .ui.modal > .close { - top: 1.0535rem; - right: 1rem; - color: rgba(0, 0, 0, 0.87); - } -} - -/* Mobile */ - -@media only screen and (max-width: 767.98px) { - .ui.modal > .header { - padding: 0.75rem 1rem !important; - padding-right: 2.25rem !important; - } - - .ui.overlay.fullscreen.modal > .content.content.content { - min-height: calc(100vh - 8.1rem); - } - - .ui.overlay.fullscreen.modal > .scrolling.content.content.content { - max-height: calc(100vh - 8.1rem); - } - - .ui.modal > .content { - display: block; - padding: 1rem !important; - } - - .ui.modal > .close { - top: 0.5rem !important; - right: 0.5rem !important; - } - - /*rtl:ignore*/ - - .ui.modal .image.content { - flex-direction: column; - } - - .ui.modal > .content > .image { - display: block; - max-width: 100%; - margin: 0 auto !important; - text-align: center; - padding: 0 0 1rem !important; - } - - .ui.modal > .content > .image > i.icon { - font-size: 5rem; - text-align: center; - } - - /*rtl:ignore*/ - - .ui.modal > .content > .description { - display: block; - width: 100% !important; - margin: 0 !important; - padding: 1rem 0 !important; - box-shadow: none; - } - - /* Let Buttons Stack */ - - .ui.modal > .actions { - padding: 1rem 1rem 0rem !important; - } - - .ui.modal .actions > .buttons, - .ui.modal .actions > .button { - margin-bottom: 1rem; - } -} - -/*-------------- - Coupling ----------------*/ - -.ui.inverted.dimmer > .ui.modal { - box-shadow: 1px 3px 10px 2px rgba(0, 0, 0, 0.2); -} - -/******************************* - Types -*******************************/ - -.ui.basic.modal { - background-color: transparent; - border: none; - border-radius: 0; - box-shadow: none !important; - color: #FFFFFF; -} - -.ui.basic.modal > .header, -.ui.basic.modal > .content, -.ui.basic.modal > .actions { - background-color: transparent; -} - -.ui.basic.modal > .header { - color: #FFFFFF; - border-bottom: none; -} - -.ui.basic.modal > .close { - top: 1rem; - right: 1.5rem; - color: #FFFFFF; -} - -.ui.inverted.dimmer > .basic.modal { - color: rgba(0, 0, 0, 0.87); -} - -.ui.inverted.dimmer > .ui.basic.modal > .header { - color: rgba(0, 0, 0, 0.85); -} - -/* Resort to margin positioning if legacy */ - -.ui.legacy.legacy.modal, -.ui.legacy.legacy.page.dimmer > .ui.modal { - left: 50% !important; -} - -.ui.legacy.legacy.modal:not(.aligned), -.ui.legacy.legacy.page.dimmer > .ui.modal:not(.aligned) { - top: 50%; -} - -.ui.legacy.legacy.page.dimmer > .ui.scrolling.modal:not(.aligned), -.ui.page.dimmer > .ui.scrolling.legacy.legacy.modal:not(.aligned), -.ui.top.aligned.legacy.legacy.page.dimmer > .ui.modal:not(.aligned), -.ui.top.aligned.dimmer > .ui.legacy.legacy.modal:not(.aligned) { - top: auto; -} - -.ui.legacy.overlay.fullscreen.modal { - margin-top: -2rem !important; -} - -/******************************* - States -*******************************/ - -.ui.loading.modal { - display: block; - visibility: hidden; - z-index: -1; -} - -.ui.active.modal { - display: block; -} - -/******************************* - Variations -*******************************/ - -/*-------------- - Aligned - ---------------*/ - -.modals.dimmer .ui.top.aligned.modal { - top: 5vh; -} - -.modals.dimmer .ui.bottom.aligned.modal { - bottom: 5vh; -} - -@media only screen and (max-width: 767.98px) { - .modals.dimmer .ui.top.aligned.modal { - top: 1rem; - } - - .modals.dimmer .ui.bottom.aligned.modal { - bottom: 1rem; - } -} - -/*-------------- - Scrolling - ---------------*/ - -/* Scrolling Dimmer */ - -.scrolling.dimmable.dimmed { - overflow: hidden; -} - -.scrolling.dimmable > .dimmer { - justify-content: flex-start; - position: fixed; -} - -.scrolling.dimmable.dimmed > .dimmer { - overflow: auto; - -webkit-overflow-scrolling: touch; -} - -.modals.dimmer .ui.scrolling.modal:not(.fullscreen) { - margin: 2rem auto; -} - -/* Fix for Firefox, Edge, IE11 */ - -.modals.dimmer .ui.scrolling.modal:not([class*="overlay fullscreen"])::after { - content: '\00A0'; - position: absolute; - height: 2rem; -} - -/* Undetached Scrolling */ - -.scrolling.undetached.dimmable.dimmed { - overflow: auto; - -webkit-overflow-scrolling: touch; -} - -.scrolling.undetached.dimmable.dimmed > .dimmer { - overflow: hidden; -} - -.scrolling.undetached.dimmable .ui.scrolling.modal:not(.fullscreen) { - position: absolute; - left: 50%; -} - -/* Scrolling Content */ - -.ui.modal > .scrolling.content { - max-height: calc(80vh - 10rem); - overflow: auto; -} - -.ui.overlay.fullscreen.modal > .content { - min-height: calc(100vh - 9.1rem); -} - -.ui.overlay.fullscreen.modal > .scrolling.content { - max-height: calc(100vh - 9.1rem); -} - -/*-------------- - Full Screen - ---------------*/ - -.ui.fullscreen.modal { - width: 95%; - left: 2.5%; - margin: 1em auto; -} - -.ui.overlay.fullscreen.modal { - width: 100%; - left: 0; - margin: 0 auto; - top: 0; - border-radius: 0; -} - -.ui.modal > .close.inside + .header, -.ui.fullscreen.modal > .header { - padding-right: 2.25rem; -} - -.ui.modal > .close.inside, -.ui.fullscreen.modal > .close { - top: 1.0535rem; - right: 1rem; - color: rgba(0, 0, 0, 0.87); -} - -.ui.basic.fullscreen.modal > .close { - color: #FFFFFF; -} - -/*-------------- - Size ----------------*/ - -.ui.modal { - font-size: 1rem; -} - -.ui.mini.modal > .header:not(.ui) { - font-size: 1.3em; -} - -@media only screen and (max-width: 767.98px) { - .ui.mini.modal { - width: 95%; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 768px) { - .ui.mini.modal { - width: 35.2%; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 992px) { - .ui.mini.modal { - width: 340px; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 1200px) { - .ui.mini.modal { - width: 360px; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 1920px) { - .ui.mini.modal { - width: 380px; - margin: 0 0 0 0; - } -} - -.ui.tiny.modal > .header:not(.ui) { - font-size: 1.3em; -} - -@media only screen and (max-width: 767.98px) { - .ui.tiny.modal { - width: 95%; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 768px) { - .ui.tiny.modal { - width: 52.8%; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 992px) { - .ui.tiny.modal { - width: 510px; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 1200px) { - .ui.tiny.modal { - width: 540px; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 1920px) { - .ui.tiny.modal { - width: 570px; - margin: 0 0 0 0; - } -} - -.ui.small.modal > .header:not(.ui) { - font-size: 1.3em; -} - -@media only screen and (max-width: 767.98px) { - .ui.small.modal { - width: 95%; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 768px) { - .ui.small.modal { - width: 70.4%; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 992px) { - .ui.small.modal { - width: 680px; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 1200px) { - .ui.small.modal { - width: 720px; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 1920px) { - .ui.small.modal { - width: 760px; - margin: 0 0 0 0; - } -} - -.ui.large.modal > .header:not(.ui) { - font-size: 1.6em; -} - -@media only screen and (max-width: 767.98px) { - .ui.large.modal { - width: 95%; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 768px) { - .ui.large.modal { - width: 88%; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 992px) { - .ui.large.modal { - width: 1020px; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 1200px) { - .ui.large.modal { - width: 1080px; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 1920px) { - .ui.large.modal { - width: 1140px; - margin: 0 0 0 0; - } -} - -.ui.big.modal > .header:not(.ui) { - font-size: 1.6em; -} - -@media only screen and (max-width: 767.98px) { - .ui.big.modal { - width: 95%; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 768px) { - .ui.big.modal { - width: 88%; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 992px) { - .ui.big.modal { - width: 1190px; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 1200px) { - .ui.big.modal { - width: 1260px; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 1920px) { - .ui.big.modal { - width: 1330px; - margin: 0 0 0 0; - } -} - -.ui.huge.modal > .header:not(.ui) { - font-size: 1.6em; -} - -@media only screen and (max-width: 767.98px) { - .ui.huge.modal { - width: 95%; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 768px) { - .ui.huge.modal { - width: 88%; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 992px) { - .ui.huge.modal { - width: 1360px; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 1200px) { - .ui.huge.modal { - width: 1440px; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 1920px) { - .ui.huge.modal { - width: 1520px; - margin: 0 0 0 0; - } -} - -.ui.massive.modal > .header:not(.ui) { - font-size: 1.8em; -} - -@media only screen and (max-width: 767.98px) { - .ui.massive.modal { - width: 95%; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 768px) { - .ui.massive.modal { - width: 88%; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 992px) { - .ui.massive.modal { - width: 1530px; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 1200px) { - .ui.massive.modal { - width: 1620px; - margin: 0 0 0 0; - } -} - -@media only screen and (min-width: 1920px) { - .ui.massive.modal { - width: 1710px; - margin: 0 0 0 0; - } -} - -/******************************* - Theme Overrides -*******************************/ - -/******************************* - Site Overrides -*******************************/ -/*! - * # Fomantic-UI - Search - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -/******************************* - Search -*******************************/ - -.ui.search { - position: relative; -} - -.ui.search > .prompt { - margin: 0; - outline: none; - -webkit-appearance: none; - -webkit-tap-highlight-color: rgba(255, 255, 255, 0); - text-shadow: none; - font-style: normal; - font-weight: normal; - line-height: 1.21428571em; - padding: 0.67857143em 1em; - font-size: 1em; - background: #FFFFFF; - border: 1px solid rgba(34, 36, 38, 0.15); - color: rgba(0, 0, 0, 0.87); - box-shadow: 0 0 0 0 transparent inset; - transition: background-color 0.1s ease, color 0.1s ease, box-shadow 0.1s ease, border-color 0.1s ease; -} - -.ui.search .prompt { - border-radius: 500rem; -} - -/*-------------- - Icon ----------------*/ - -.ui.search .prompt ~ .search.icon { - cursor: pointer; -} - -/*-------------- - Results ----------------*/ - -.ui.search > .results { - display: none; - position: absolute; - top: 100%; - left: 0; - transform-origin: center top; - white-space: normal; - text-align: left; - text-transform: none; - background: #FFFFFF; - margin-top: 0.5em; - width: 18em; - border-radius: 0.28571429rem; - box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15); - border: 1px solid #D4D4D5; - z-index: 998; -} - -.ui.search > .results > :first-child { - border-radius: 0.28571429rem 0.28571429rem 0 0; -} - -.ui.search > .results > :last-child { - border-radius: 0 0 0.28571429rem 0.28571429rem; -} - -/*-------------- - Result ----------------*/ - -.ui.search > .results .result { - cursor: pointer; - display: block; - overflow: hidden; - font-size: 1em; - padding: 0.85714286em 1.14285714em; - color: rgba(0, 0, 0, 0.87); - line-height: 1.33; - border-bottom: 1px solid rgba(34, 36, 38, 0.1); -} - -.ui.search > .results .result:last-child { - border-bottom: none !important; -} - -/* Image */ - -.ui.search > .results .result .image { - float: right; - overflow: hidden; - background: none; - width: 5em; - height: 3em; - border-radius: 0.25em; -} - -.ui.search > .results .result .image img { - display: block; - width: auto; - height: 100%; -} - -/*-------------- - Info ----------------*/ - -.ui.search > .results .result .image + .content { - margin: 0 6em 0 0; -} - -.ui.search > .results .result .title { - margin: -0.14285714em 0 0; - font-family: var(--fonts-regular); - font-weight: 500; - font-size: 1em; - color: rgba(0, 0, 0, 0.85); -} - -.ui.search > .results .result .description { - margin-top: 0; - font-size: 0.92857143em; - color: rgba(0, 0, 0, 0.4); -} - -.ui.search > .results .result .price { - float: right; - color: #21BA45; -} - -/*-------------- - Message ----------------*/ - -.ui.search > .results > .message { - padding: 1em 1em; -} - -.ui.search > .results > .message .header { - font-family: var(--fonts-regular); - font-size: 1rem; - font-weight: 500; - color: rgba(0, 0, 0, 0.87); -} - -.ui.search > .results > .message .description { - margin-top: 0.25rem; - font-size: 1em; - color: rgba(0, 0, 0, 0.87); -} - -/* View All Results */ - -.ui.search > .results > .action { - display: block; - border-top: none; - background: #F3F4F5; - padding: 0.92857143em 1em; - color: rgba(0, 0, 0, 0.87); - font-weight: 500; - text-align: center; -} - -/******************************* - States -*******************************/ - -/*-------------------- - Focus ----------------------*/ - -.ui.search > .prompt:focus { - border-color: rgba(34, 36, 38, 0.35); - background: #FFFFFF; - color: rgba(0, 0, 0, 0.95); -} - -/*-------------------- - Loading - ---------------------*/ - -.ui.loading.search .input > i.icon:before { - position: absolute; - content: ''; - top: 50%; - left: 50%; - margin: -0.64285714em 0 0 -0.64285714em; - width: 1.28571429em; - height: 1.28571429em; - border-radius: 500rem; - border: 0.2em solid rgba(0, 0, 0, 0.1); -} - -.ui.loading.search .input > i.icon:after { - position: absolute; - content: ''; - top: 50%; - left: 50%; - margin: -0.64285714em 0 0 -0.64285714em; - width: 1.28571429em; - height: 1.28571429em; - animation: loader 0.6s infinite linear; - border: 0.2em solid #767676; - border-radius: 500rem; - box-shadow: 0 0 0 1px transparent; -} - -/*-------------- - Hover ----------------*/ - -.ui.search > .results .result:hover, -.ui.category.search > .results .category .result:hover { - background: #F9FAFB; -} - -.ui.search .action:hover:not(div) { - background: #E0E0E0; -} - -/*-------------- - Active ----------------*/ - -.ui.category.search > .results .category.active { - background: #F3F4F5; -} - -.ui.category.search > .results .category.active > .name { - color: rgba(0, 0, 0, 0.87); -} - -.ui.search > .results .result.active, -.ui.category.search > .results .category .result.active { - position: relative; - border-left-color: rgba(34, 36, 38, 0.1); - background: #F3F4F5; - box-shadow: none; -} - -.ui.search > .results .result.active .title { - color: rgba(0, 0, 0, 0.85); -} - -.ui.search > .results .result.active .description { - color: rgba(0, 0, 0, 0.85); -} - -/*-------------------- - Disabled - ----------------------*/ - -/* Disabled */ - -.ui.disabled.search { - cursor: default; - pointer-events: none; - opacity: var(--opacity-disabled); -} - -/******************************* - Types -*******************************/ - -/*-------------- - Selection - ---------------*/ - -.ui.search.selection .prompt { - border-radius: 0.28571429rem; -} - -/* Remove input */ - -.ui.search.selection > .icon.input > .remove.icon { - pointer-events: none; - position: absolute; - left: auto; - opacity: 0; - color: ''; - top: 0; - right: 0; - transition: color 0.1s ease, opacity 0.1s ease; -} - -.ui.search.selection > .icon.input > .active.remove.icon { - cursor: pointer; - opacity: 0.8; - pointer-events: auto; -} - -.ui.search.selection > .icon.input:not([class*="left icon"]) > .icon ~ .remove.icon { - right: 1.85714em; -} - -.ui.search.selection > .icon.input > .remove.icon:hover { - opacity: 1; - color: #DB2828; -} - -/*-------------- - Category - ---------------*/ - -.ui.category.search .results { - width: 28em; -} - -.ui.category.search .results.animating, -.ui.category.search .results.visible { - display: table; -} - -/* Category */ - -.ui.category.search > .results .category { - display: table-row; - background: #F3F4F5; - box-shadow: none; - transition: background 0.1s ease, border-color 0.1s ease; -} - -/* Last Category */ - -.ui.category.search > .results .category:last-child { - border-bottom: none; -} - -/* First / Last */ - -.ui.category.search > .results .category:first-child .name + .result { - border-radius: 0 0.28571429rem 0 0; -} - -.ui.category.search > .results .category:last-child .result:last-child { - border-radius: 0 0 0.28571429rem 0; -} - -/* Category Result Name */ - -.ui.category.search > .results .category > .name { - display: table-cell; - text-overflow: ellipsis; - width: 100px; - white-space: nowrap; - background: transparent; - font-family: var(--fonts-regular); - font-size: 1em; - padding: 0.4em 1em; - font-weight: 500; - color: rgba(0, 0, 0, 0.4); - border-bottom: 1px solid rgba(34, 36, 38, 0.1); -} - -/* Category Result */ - -.ui.category.search > .results .category .results { - display: table-cell; - background: #FFFFFF; - border-left: 1px solid rgba(34, 36, 38, 0.15); - border-bottom: 1px solid rgba(34, 36, 38, 0.1); -} - -.ui.category.search > .results .category .result { - border-bottom: 1px solid rgba(34, 36, 38, 0.1); - transition: background 0.1s ease, border-color 0.1s ease; - padding: 0.85714286em 1.14285714em; -} - -/******************************* - Variations -*******************************/ - -/*------------------- - Scrolling - --------------------*/ - -.ui.scrolling.search > .results, -.ui.search.long > .results, -.ui.search.short > .results { - overflow-x: hidden; - overflow-y: auto; - backface-visibility: hidden; - -webkit-overflow-scrolling: touch; -} - -@media only screen and (max-width: 767.98px) { - .ui.scrolling.search > .results { - max-height: 12.17714286em; - } -} - -@media only screen and (min-width: 768px) { - .ui.scrolling.search > .results { - max-height: 18.26571429em; - } -} - -@media only screen and (min-width: 992px) { - .ui.scrolling.search > .results { - max-height: 24.35428571em; - } -} - -@media only screen and (min-width: 1920px) { - .ui.scrolling.search > .results { - max-height: 36.53142857em; - } -} - -@media only screen and (max-width: 767.98px) { - .ui.search.short > .results { - max-height: 12.17714286em; - } - - .ui.search[class*="very short"] > .results { - max-height: 9.13285714em; - } - - .ui.search.long > .results { - max-height: 24.35428571em; - } - - .ui.search[class*="very long"] > .results { - max-height: 36.53142857em; - } -} - -@media only screen and (min-width: 768px) { - .ui.search.short > .results { - max-height: 18.26571429em; - } - - .ui.search[class*="very short"] > .results { - max-height: 13.69928571em; - } - - .ui.search.long > .results { - max-height: 36.53142857em; - } - - .ui.search[class*="very long"] > .results { - max-height: 54.79714286em; - } -} - -@media only screen and (min-width: 992px) { - .ui.search.short > .results { - max-height: 24.35428571em; - } - - .ui.search[class*="very short"] > .results { - max-height: 18.26571429em; - } - - .ui.search.long > .results { - max-height: 48.70857143em; - } - - .ui.search[class*="very long"] > .results { - max-height: 73.06285714em; - } -} - -@media only screen and (min-width: 1920px) { - .ui.search.short > .results { - max-height: 36.53142857em; - } - - .ui.search[class*="very short"] > .results { - max-height: 27.39857143em; - } - - .ui.search.long > .results { - max-height: 73.06285714em; - } - - .ui.search[class*="very long"] > .results { - max-height: 109.59428571em; - } -} - -/*------------------- - Left / Right - --------------------*/ - -.ui[class*="left aligned"].search > .results { - right: auto; - left: 0; -} - -.ui[class*="right aligned"].search > .results { - right: 0; - left: auto; -} - -/*-------------- - Fluid ----------------*/ - -.ui.fluid.search .results { - width: 100%; -} - -/*-------------- - Sizes ----------------*/ - -.ui.search { - font-size: 1em; -} - -.ui.mini.search { - font-size: 0.78571429em; -} - -.ui.tiny.search { - font-size: 0.85714286em; -} - -.ui.small.search { - font-size: 0.92857143em; -} - -.ui.large.search { - font-size: 1.14285714em; -} - -.ui.big.search { - font-size: 1.28571429em; -} - -.ui.huge.search { - font-size: 1.42857143em; -} - -.ui.massive.search { - font-size: 1.71428571em; -} - -/*-------------- - Mobile ----------------*/ - -@media only screen and (max-width: 767.98px) { - .ui.search .results { - max-width: calc(100vw - 2rem); - } -} - -/******************************* - Theme Overrides -*******************************/ - -/******************************* - Site Overrides -*******************************/ -/*! - * # Fomantic-UI - Tab - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -/******************************* - UI Tabs -*******************************/ - -.ui.tab { - display: none; -} - -/******************************* - States -*******************************/ - -/*-------------------- - Active ----------------------*/ - -.ui.tab.active, -.ui.tab.open { - display: block; -} - -/*-------------------- - Loading - ---------------------*/ - -.ui.tab.loading { - position: relative; - overflow: hidden; - display: block; - min-height: 250px; -} - -.ui.tab.loading * { - position: relative !important; - left: -10000px !important; -} - -.ui.tab.loading:before, -.ui.tab.loading.segment:before { - position: absolute; - content: ''; - top: 50%; - left: 50%; - margin: -1.25em 0 0 -1.25em; - width: 2.5em; - height: 2.5em; - border-radius: 500rem; - border: 0.2em solid rgba(0, 0, 0, 0.1); -} - -.ui.tab.loading:after, -.ui.tab.loading.segment:after { - position: absolute; - content: ''; - top: 50%; - left: 50%; - margin: -1.25em 0 0 -1.25em; - width: 2.5em; - height: 2.5em; - animation: loader 0.6s infinite linear; - border: 0.2em solid #767676; - border-radius: 500rem; - box-shadow: 0 0 0 1px transparent; -} - -/******************************* - Tab Overrides -*******************************/ - -/******************************* - User Overrides -*******************************/
\ No newline at end of file diff --git a/web_src/fomantic/build/semantic.js b/web_src/fomantic/build/semantic.js deleted file mode 100644 index 1297216a31..0000000000 --- a/web_src/fomantic/build/semantic.js +++ /dev/null @@ -1,11238 +0,0 @@ - /* - * # Fomantic UI - 2.8.7 - * https://github.com/fomantic/Fomantic-UI - * http://fomantic-ui.com/ - * - * Copyright 2014 Contributors - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ -/*! - * # Fomantic-UI - API - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -;(function ($, window, document, undefined) { - -'use strict'; - -$.isWindow = $.isWindow || function(obj) { - return obj != null && obj === obj.window; -}; - - window = (typeof window != 'undefined' && window.Math == Math) - ? window - : (typeof self != 'undefined' && self.Math == Math) - ? self - : Function('return this')() -; - -$.api = $.fn.api = function(parameters) { - - var - // use window context if none specified - $allModules = $.isFunction(this) - ? $(window) - : $(this), - moduleSelector = $allModules.selector || '', - time = new Date().getTime(), - performance = [], - - query = arguments[0], - methodInvoked = (typeof query == 'string'), - queryArguments = [].slice.call(arguments, 1), - - returnedValue - ; - - $allModules - .each(function() { - var - settings = ( $.isPlainObject(parameters) ) - ? $.extend(true, {}, $.fn.api.settings, parameters) - : $.extend({}, $.fn.api.settings), - - // internal aliases - namespace = settings.namespace, - metadata = settings.metadata, - selector = settings.selector, - error = settings.error, - className = settings.className, - - // define namespaces for modules - eventNamespace = '.' + namespace, - moduleNamespace = 'module-' + namespace, - - // element that creates request - $module = $(this), - $form = $module.closest(selector.form), - - // context used for state - $context = (settings.stateContext) - ? $(settings.stateContext) - : $module, - - // request details - ajaxSettings, - requestSettings, - url, - data, - requestStartTime, - - // standard module - element = this, - context = $context[0], - instance = $module.data(moduleNamespace), - module - ; - - module = { - - initialize: function() { - if(!methodInvoked) { - module.bind.events(); - } - module.instantiate(); - }, - - instantiate: function() { - module.verbose('Storing instance of module', module); - instance = module; - $module - .data(moduleNamespace, instance) - ; - }, - - destroy: function() { - module.verbose('Destroying previous module for', element); - $module - .removeData(moduleNamespace) - .off(eventNamespace) - ; - }, - - bind: { - events: function() { - var - triggerEvent = module.get.event() - ; - if( triggerEvent ) { - module.verbose('Attaching API events to element', triggerEvent); - $module - .on(triggerEvent + eventNamespace, module.event.trigger) - ; - } - else if(settings.on == 'now') { - module.debug('Querying API endpoint immediately'); - module.query(); - } - } - }, - - decode: { - json: function(response) { - if(response !== undefined && typeof response == 'string') { - try { - response = JSON.parse(response); - } - catch(e) { - // isnt json string - } - } - return response; - } - }, - - read: { - cachedResponse: function(url) { - var - response - ; - if(window.Storage === undefined) { - module.error(error.noStorage); - return; - } - response = sessionStorage.getItem(url); - module.debug('Using cached response', url, response); - response = module.decode.json(response); - return response; - } - }, - write: { - cachedResponse: function(url, response) { - if(response && response === '') { - module.debug('Response empty, not caching', response); - return; - } - if(window.Storage === undefined) { - module.error(error.noStorage); - return; - } - if( $.isPlainObject(response) ) { - response = JSON.stringify(response); - } - sessionStorage.setItem(url, response); - module.verbose('Storing cached response for url', url, response); - } - }, - - query: function() { - - if(module.is.disabled()) { - module.debug('Element is disabled API request aborted'); - return; - } - - if(module.is.loading()) { - if(settings.interruptRequests) { - module.debug('Interrupting previous request'); - module.abort(); - } - else { - module.debug('Cancelling request, previous request is still pending'); - return; - } - } - - // pass element metadata to url (value, text) - if(settings.defaultData) { - $.extend(true, settings.urlData, module.get.defaultData()); - } - - // Add form content - if(settings.serializeForm) { - settings.data = module.add.formData(settings.data); - } - - // call beforesend and get any settings changes - requestSettings = module.get.settings(); - - // check if before send cancelled request - if(requestSettings === false) { - module.cancelled = true; - module.error(error.beforeSend); - return; - } - else { - module.cancelled = false; - } - - // get url - url = module.get.templatedURL(); - - if(!url && !module.is.mocked()) { - module.error(error.missingURL); - return; - } - - // replace variables - url = module.add.urlData( url ); - // missing url parameters - if( !url && !module.is.mocked()) { - return; - } - - requestSettings.url = settings.base + url; - - // look for jQuery ajax parameters in settings - ajaxSettings = $.extend(true, {}, settings, { - type : settings.method || settings.type, - data : data, - url : settings.base + url, - beforeSend : settings.beforeXHR, - success : function() {}, - failure : function() {}, - complete : function() {} - }); - - module.debug('Querying URL', ajaxSettings.url); - module.verbose('Using AJAX settings', ajaxSettings); - if(settings.cache === 'local' && module.read.cachedResponse(url)) { - module.debug('Response returned from local cache'); - module.request = module.create.request(); - module.request.resolveWith(context, [ module.read.cachedResponse(url) ]); - return; - } - - if( !settings.throttle ) { - module.debug('Sending request', data, ajaxSettings.method); - module.send.request(); - } - else { - if(!settings.throttleFirstRequest && !module.timer) { - module.debug('Sending request', data, ajaxSettings.method); - module.send.request(); - module.timer = setTimeout(function(){}, settings.throttle); - } - else { - module.debug('Throttling request', settings.throttle); - clearTimeout(module.timer); - module.timer = setTimeout(function() { - if(module.timer) { - delete module.timer; - } - module.debug('Sending throttled request', data, ajaxSettings.method); - module.send.request(); - }, settings.throttle); - } - } - - }, - - should: { - removeError: function() { - return ( settings.hideError === true || (settings.hideError === 'auto' && !module.is.form()) ); - } - }, - - is: { - disabled: function() { - return ($module.filter(selector.disabled).length > 0); - }, - expectingJSON: function() { - return settings.dataType === 'json' || settings.dataType === 'jsonp'; - }, - form: function() { - return $module.is('form') || $context.is('form'); - }, - mocked: function() { - return (settings.mockResponse || settings.mockResponseAsync || settings.response || settings.responseAsync); - }, - input: function() { - return $module.is('input'); - }, - loading: function() { - return (module.request) - ? (module.request.state() == 'pending') - : false - ; - }, - abortedRequest: function(xhr) { - if(xhr && xhr.readyState !== undefined && xhr.readyState === 0) { - module.verbose('XHR request determined to be aborted'); - return true; - } - else { - module.verbose('XHR request was not aborted'); - return false; - } - }, - validResponse: function(response) { - if( (!module.is.expectingJSON()) || !$.isFunction(settings.successTest) ) { - module.verbose('Response is not JSON, skipping validation', settings.successTest, response); - return true; - } - module.debug('Checking JSON returned success', settings.successTest, response); - if( settings.successTest(response) ) { - module.debug('Response passed success test', response); - return true; - } - else { - module.debug('Response failed success test', response); - return false; - } - } - }, - - was: { - cancelled: function() { - return (module.cancelled || false); - }, - succesful: function() { - module.verbose('This behavior will be deleted due to typo. Use "was successful" instead.'); - return module.was.successful(); - }, - successful: function() { - return (module.request && module.request.state() == 'resolved'); - }, - failure: function() { - return (module.request && module.request.state() == 'rejected'); - }, - complete: function() { - return (module.request && (module.request.state() == 'resolved' || module.request.state() == 'rejected') ); - } - }, - - add: { - urlData: function(url, urlData) { - var - requiredVariables, - optionalVariables - ; - if(url) { - requiredVariables = url.match(settings.regExp.required); - optionalVariables = url.match(settings.regExp.optional); - urlData = urlData || settings.urlData; - if(requiredVariables) { - module.debug('Looking for required URL variables', requiredVariables); - $.each(requiredVariables, function(index, templatedString) { - var - // allow legacy {$var} style - variable = (templatedString.indexOf('$') !== -1) - ? templatedString.substr(2, templatedString.length - 3) - : templatedString.substr(1, templatedString.length - 2), - value = ($.isPlainObject(urlData) && urlData[variable] !== undefined) - ? urlData[variable] - : ($module.data(variable) !== undefined) - ? $module.data(variable) - : ($context.data(variable) !== undefined) - ? $context.data(variable) - : urlData[variable] - ; - // remove value - if(value === undefined) { - module.error(error.requiredParameter, variable, url); - url = false; - return false; - } - else { - module.verbose('Found required variable', variable, value); - value = (settings.encodeParameters) - ? module.get.urlEncodedValue(value) - : value - ; - url = url.replace(templatedString, value); - } - }); - } - if(optionalVariables) { - module.debug('Looking for optional URL variables', requiredVariables); - $.each(optionalVariables, function(index, templatedString) { - var - // allow legacy {/$var} style - variable = (templatedString.indexOf('$') !== -1) - ? templatedString.substr(3, templatedString.length - 4) - : templatedString.substr(2, templatedString.length - 3), - value = ($.isPlainObject(urlData) && urlData[variable] !== undefined) - ? urlData[variable] - : ($module.data(variable) !== undefined) - ? $module.data(variable) - : ($context.data(variable) !== undefined) - ? $context.data(variable) - : urlData[variable] - ; - // optional replacement - if(value !== undefined) { - module.verbose('Optional variable Found', variable, value); - url = url.replace(templatedString, value); - } - else { - module.verbose('Optional variable not found', variable); - // remove preceding slash if set - if(url.indexOf('/' + templatedString) !== -1) { - url = url.replace('/' + templatedString, ''); - } - else { - url = url.replace(templatedString, ''); - } - } - }); - } - } - return url; - }, - formData: function(data) { - var - canSerialize = ($.fn.serializeObject !== undefined), - formData = (canSerialize) - ? $form.serializeObject() - : $form.serialize(), - hasOtherData - ; - data = data || settings.data; - hasOtherData = $.isPlainObject(data); - - if(hasOtherData) { - if(canSerialize) { - module.debug('Extending existing data with form data', data, formData); - data = $.extend(true, {}, data, formData); - } - else { - module.error(error.missingSerialize); - module.debug('Cant extend data. Replacing data with form data', data, formData); - data = formData; - } - } - else { - module.debug('Adding form data', formData); - data = formData; - } - return data; - } - }, - - send: { - request: function() { - module.set.loading(); - module.request = module.create.request(); - if( module.is.mocked() ) { - module.mockedXHR = module.create.mockedXHR(); - } - else { - module.xhr = module.create.xhr(); - } - settings.onRequest.call(context, module.request, module.xhr); - } - }, - - event: { - trigger: function(event) { - module.query(); - if(event.type == 'submit' || event.type == 'click') { - event.preventDefault(); - } - }, - xhr: { - always: function() { - // nothing special - }, - done: function(response, textStatus, xhr) { - var - context = this, - elapsedTime = (new Date().getTime() - requestStartTime), - timeLeft = (settings.loadingDuration - elapsedTime), - translatedResponse = ( $.isFunction(settings.onResponse) ) - ? module.is.expectingJSON() && !settings.rawResponse - ? settings.onResponse.call(context, $.extend(true, {}, response)) - : settings.onResponse.call(context, response) - : false - ; - timeLeft = (timeLeft > 0) - ? timeLeft - : 0 - ; - if(translatedResponse) { - module.debug('Modified API response in onResponse callback', settings.onResponse, translatedResponse, response); - response = translatedResponse; - } - if(timeLeft > 0) { - module.debug('Response completed early delaying state change by', timeLeft); - } - setTimeout(function() { - if( module.is.validResponse(response) ) { - module.request.resolveWith(context, [response, xhr]); - } - else { - module.request.rejectWith(context, [xhr, 'invalid']); - } - }, timeLeft); - }, - fail: function(xhr, status, httpMessage) { - var - context = this, - elapsedTime = (new Date().getTime() - requestStartTime), - timeLeft = (settings.loadingDuration - elapsedTime) - ; - timeLeft = (timeLeft > 0) - ? timeLeft - : 0 - ; - if(timeLeft > 0) { - module.debug('Response completed early delaying state change by', timeLeft); - } - setTimeout(function() { - if( module.is.abortedRequest(xhr) ) { - module.request.rejectWith(context, [xhr, 'aborted', httpMessage]); - } - else { - module.request.rejectWith(context, [xhr, 'error', status, httpMessage]); - } - }, timeLeft); - } - }, - request: { - done: function(response, xhr) { - module.debug('Successful API Response', response); - if(settings.cache === 'local' && url) { - module.write.cachedResponse(url, response); - module.debug('Saving server response locally', module.cache); - } - settings.onSuccess.call(context, response, $module, xhr); - }, - complete: function(firstParameter, secondParameter) { - var - xhr, - response - ; - // have to guess callback parameters based on request success - if( module.was.successful() ) { - response = firstParameter; - xhr = secondParameter; - } - else { - xhr = firstParameter; - response = module.get.responseFromXHR(xhr); - } - module.remove.loading(); - settings.onComplete.call(context, response, $module, xhr); - }, - fail: function(xhr, status, httpMessage) { - var - // pull response from xhr if available - response = module.get.responseFromXHR(xhr), - errorMessage = module.get.errorFromRequest(response, status, httpMessage) - ; - if(status == 'aborted') { - module.debug('XHR Aborted (Most likely caused by page navigation or CORS Policy)', status, httpMessage); - settings.onAbort.call(context, status, $module, xhr); - return true; - } - else if(status == 'invalid') { - module.debug('JSON did not pass success test. A server-side error has most likely occurred', response); - } - else if(status == 'error') { - if(xhr !== undefined) { - module.debug('XHR produced a server error', status, httpMessage); - // make sure we have an error to display to console - if( (xhr.status < 200 || xhr.status >= 300) && httpMessage !== undefined && httpMessage !== '') { - module.error(error.statusMessage + httpMessage, ajaxSettings.url); - } - settings.onError.call(context, errorMessage, $module, xhr); - } - } - - if(settings.errorDuration && status !== 'aborted') { - module.debug('Adding error state'); - module.set.error(); - if( module.should.removeError() ) { - setTimeout(module.remove.error, settings.errorDuration); - } - } - module.debug('API Request failed', errorMessage, xhr); - settings.onFailure.call(context, response, $module, xhr); - } - } - }, - - create: { - - request: function() { - // api request promise - return $.Deferred() - .always(module.event.request.complete) - .done(module.event.request.done) - .fail(module.event.request.fail) - ; - }, - - mockedXHR: function () { - var - // xhr does not simulate these properties of xhr but must return them - textStatus = false, - status = false, - httpMessage = false, - responder = settings.mockResponse || settings.response, - asyncResponder = settings.mockResponseAsync || settings.responseAsync, - asyncCallback, - response, - mockedXHR - ; - - mockedXHR = $.Deferred() - .always(module.event.xhr.complete) - .done(module.event.xhr.done) - .fail(module.event.xhr.fail) - ; - - if(responder) { - if( $.isFunction(responder) ) { - module.debug('Using specified synchronous callback', responder); - response = responder.call(context, requestSettings); - } - else { - module.debug('Using settings specified response', responder); - response = responder; - } - // simulating response - mockedXHR.resolveWith(context, [ response, textStatus, { responseText: response }]); - } - else if( $.isFunction(asyncResponder) ) { - asyncCallback = function(response) { - module.debug('Async callback returned response', response); - - if(response) { - mockedXHR.resolveWith(context, [ response, textStatus, { responseText: response }]); - } - else { - mockedXHR.rejectWith(context, [{ responseText: response }, status, httpMessage]); - } - }; - module.debug('Using specified async response callback', asyncResponder); - asyncResponder.call(context, requestSettings, asyncCallback); - } - return mockedXHR; - }, - - xhr: function() { - var - xhr - ; - // ajax request promise - xhr = $.ajax(ajaxSettings) - .always(module.event.xhr.always) - .done(module.event.xhr.done) - .fail(module.event.xhr.fail) - ; - module.verbose('Created server request', xhr, ajaxSettings); - return xhr; - } - }, - - set: { - error: function() { - module.verbose('Adding error state to element', $context); - $context.addClass(className.error); - }, - loading: function() { - module.verbose('Adding loading state to element', $context); - $context.addClass(className.loading); - requestStartTime = new Date().getTime(); - } - }, - - remove: { - error: function() { - module.verbose('Removing error state from element', $context); - $context.removeClass(className.error); - }, - loading: function() { - module.verbose('Removing loading state from element', $context); - $context.removeClass(className.loading); - } - }, - - get: { - responseFromXHR: function(xhr) { - return $.isPlainObject(xhr) - ? (module.is.expectingJSON()) - ? module.decode.json(xhr.responseText) - : xhr.responseText - : false - ; - }, - errorFromRequest: function(response, status, httpMessage) { - return ($.isPlainObject(response) && response.error !== undefined) - ? response.error // use json error message - : (settings.error[status] !== undefined) // use server error message - ? settings.error[status] - : httpMessage - ; - }, - request: function() { - return module.request || false; - }, - xhr: function() { - return module.xhr || false; - }, - settings: function() { - var - runSettings - ; - runSettings = settings.beforeSend.call($module, settings); - if(runSettings) { - if(runSettings.success !== undefined) { - module.debug('Legacy success callback detected', runSettings); - module.error(error.legacyParameters, runSettings.success); - runSettings.onSuccess = runSettings.success; - } - if(runSettings.failure !== undefined) { - module.debug('Legacy failure callback detected', runSettings); - module.error(error.legacyParameters, runSettings.failure); - runSettings.onFailure = runSettings.failure; - } - if(runSettings.complete !== undefined) { - module.debug('Legacy complete callback detected', runSettings); - module.error(error.legacyParameters, runSettings.complete); - runSettings.onComplete = runSettings.complete; - } - } - if(runSettings === undefined) { - module.error(error.noReturnedValue); - } - if(runSettings === false) { - return runSettings; - } - return (runSettings !== undefined) - ? $.extend(true, {}, runSettings) - : $.extend(true, {}, settings) - ; - }, - urlEncodedValue: function(value) { - var - decodedValue = window.decodeURIComponent(value), - encodedValue = window.encodeURIComponent(value), - alreadyEncoded = (decodedValue !== value) - ; - if(alreadyEncoded) { - module.debug('URL value is already encoded, avoiding double encoding', value); - return value; - } - module.verbose('Encoding value using encodeURIComponent', value, encodedValue); - return encodedValue; - }, - defaultData: function() { - var - data = {} - ; - if( !$.isWindow(element) ) { - if( module.is.input() ) { - data.value = $module.val(); - } - else if( module.is.form() ) { - - } - else { - data.text = $module.text(); - } - } - return data; - }, - event: function() { - if( $.isWindow(element) || settings.on == 'now' ) { - module.debug('API called without element, no events attached'); - return false; - } - else if(settings.on == 'auto') { - if( $module.is('input') ) { - return (element.oninput !== undefined) - ? 'input' - : (element.onpropertychange !== undefined) - ? 'propertychange' - : 'keyup' - ; - } - else if( $module.is('form') ) { - return 'submit'; - } - else { - return 'click'; - } - } - else { - return settings.on; - } - }, - templatedURL: function(action) { - action = action || $module.data(metadata.action) || settings.action || false; - url = $module.data(metadata.url) || settings.url || false; - if(url) { - module.debug('Using specified url', url); - return url; - } - if(action) { - module.debug('Looking up url for action', action, settings.api); - if(settings.api[action] === undefined && !module.is.mocked()) { - module.error(error.missingAction, settings.action, settings.api); - return; - } - url = settings.api[action]; - } - else if( module.is.form() ) { - url = $module.attr('action') || $context.attr('action') || false; - module.debug('No url or action specified, defaulting to form action', url); - } - return url; - } - }, - - abort: function() { - var - xhr = module.get.xhr() - ; - if( xhr && xhr.state() !== 'resolved') { - module.debug('Cancelling API request'); - xhr.abort(); - } - }, - - // reset state - reset: function() { - module.remove.error(); - module.remove.loading(); - }, - - setting: function(name, value) { - module.debug('Changing setting', name, value); - if( $.isPlainObject(name) ) { - $.extend(true, settings, name); - } - else if(value !== undefined) { - if($.isPlainObject(settings[name])) { - $.extend(true, settings[name], value); - } - else { - settings[name] = value; - } - } - else { - return settings[name]; - } - }, - internal: function(name, value) { - if( $.isPlainObject(name) ) { - $.extend(true, module, name); - } - else if(value !== undefined) { - module[name] = value; - } - else { - return module[name]; - } - }, - debug: function() { - if(!settings.silent && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.debug.apply(console, arguments); - } - } - }, - verbose: function() { - if(!settings.silent && settings.verbose && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.verbose.apply(console, arguments); - } - } - }, - error: function() { - if(!settings.silent) { - module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); - module.error.apply(console, arguments); - } - }, - performance: { - log: function(message) { - var - currentTime, - executionTime, - previousTime - ; - if(settings.performance) { - currentTime = new Date().getTime(); - previousTime = time || currentTime; - executionTime = currentTime - previousTime; - time = currentTime; - performance.push({ - 'Name' : message[0], - 'Arguments' : [].slice.call(message, 1) || '', - //'Element' : element, - 'Execution Time' : executionTime - }); - } - clearTimeout(module.performance.timer); - module.performance.timer = setTimeout(module.performance.display, 500); - }, - display: function() { - var - title = settings.name + ':', - totalTime = 0 - ; - time = false; - clearTimeout(module.performance.timer); - $.each(performance, function(index, data) { - totalTime += data['Execution Time']; - }); - title += ' ' + totalTime + 'ms'; - if(moduleSelector) { - title += ' \'' + moduleSelector + '\''; - } - if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { - console.groupCollapsed(title); - if(console.table) { - console.table(performance); - } - else { - $.each(performance, function(index, data) { - console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); - }); - } - console.groupEnd(); - } - performance = []; - } - }, - invoke: function(query, passedArguments, context) { - var - object = instance, - maxDepth, - found, - response - ; - passedArguments = passedArguments || queryArguments; - context = element || context; - if(typeof query == 'string' && object !== undefined) { - query = query.split(/[\. ]/); - maxDepth = query.length - 1; - $.each(query, function(depth, value) { - var camelCaseValue = (depth != maxDepth) - ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) - : query - ; - if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { - object = object[camelCaseValue]; - } - else if( object[camelCaseValue] !== undefined ) { - found = object[camelCaseValue]; - return false; - } - else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { - object = object[value]; - } - else if( object[value] !== undefined ) { - found = object[value]; - return false; - } - else { - module.error(error.method, query); - return false; - } - }); - } - if ( $.isFunction( found ) ) { - response = found.apply(context, passedArguments); - } - else if(found !== undefined) { - response = found; - } - if(Array.isArray(returnedValue)) { - returnedValue.push(response); - } - else if(returnedValue !== undefined) { - returnedValue = [returnedValue, response]; - } - else if(response !== undefined) { - returnedValue = response; - } - return found; - } - }; - - if(methodInvoked) { - if(instance === undefined) { - module.initialize(); - } - module.invoke(query); - } - else { - if(instance !== undefined) { - instance.invoke('destroy'); - } - module.initialize(); - } - }) - ; - - return (returnedValue !== undefined) - ? returnedValue - : this - ; -}; - -$.api.settings = { - - name : 'API', - namespace : 'api', - - debug : false, - verbose : false, - performance : true, - - // object containing all templates endpoints - api : {}, - - // whether to cache responses - cache : true, - - // whether new requests should abort previous requests - interruptRequests : true, - - // event binding - on : 'auto', - - // context for applying state classes - stateContext : false, - - // duration for loading state - loadingDuration : 0, - - // whether to hide errors after a period of time - hideError : 'auto', - - // duration for error state - errorDuration : 2000, - - // whether parameters should be encoded with encodeURIComponent - encodeParameters : true, - - // API action to use - action : false, - - // templated URL to use - url : false, - - // base URL to apply to all endpoints - base : '', - - // data that will - urlData : {}, - - // whether to add default data to url data - defaultData : true, - - // whether to serialize closest form - serializeForm : false, - - // how long to wait before request should occur - throttle : 0, - - // whether to throttle first request or only repeated - throttleFirstRequest : true, - - // standard ajax settings - method : 'get', - data : {}, - dataType : 'json', - - // mock response - mockResponse : false, - mockResponseAsync : false, - - // aliases for mock - response : false, - responseAsync : false, - -// whether onResponse should work with response value without force converting into an object - rawResponse : false, - - // callbacks before request - beforeSend : function(settings) { return settings; }, - beforeXHR : function(xhr) {}, - onRequest : function(promise, xhr) {}, - - // after request - onResponse : false, // function(response) { }, - - // response was successful, if JSON passed validation - onSuccess : function(response, $module) {}, - - // request finished without aborting - onComplete : function(response, $module) {}, - - // failed JSON success test - onFailure : function(response, $module) {}, - - // server error - onError : function(errorMessage, $module) {}, - - // request aborted - onAbort : function(errorMessage, $module) {}, - - successTest : false, - - // errors - error : { - beforeSend : 'The before send function has aborted the request', - error : 'There was an error with your request', - exitConditions : 'API Request Aborted. Exit conditions met', - JSONParse : 'JSON could not be parsed during error handling', - legacyParameters : 'You are using legacy API success callback names', - method : 'The method you called is not defined', - missingAction : 'API action used but no url was defined', - missingSerialize : 'jquery-serialize-object is required to add form data to an existing data object', - missingURL : 'No URL specified for api event', - noReturnedValue : 'The beforeSend callback must return a settings object, beforeSend ignored.', - noStorage : 'Caching responses locally requires session storage', - parseError : 'There was an error parsing your request', - requiredParameter : 'Missing a required URL parameter: ', - statusMessage : 'Server gave an error: ', - timeout : 'Your request timed out' - }, - - regExp : { - required : /\{\$*[A-z0-9]+\}/g, - optional : /\{\/\$*[A-z0-9]+\}/g, - }, - - className: { - loading : 'loading', - error : 'error' - }, - - selector: { - disabled : '.disabled', - form : 'form' - }, - - metadata: { - action : 'action', - url : 'url' - } -}; - - - -})( jQuery, window, document ); - -/*! - * # Fomantic-UI - Dropdown - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -;(function ($, window, document, undefined) { - -'use strict'; - -$.isFunction = $.isFunction || function(obj) { - return typeof obj === "function" && typeof obj.nodeType !== "number"; -}; - -window = (typeof window != 'undefined' && window.Math == Math) - ? window - : (typeof self != 'undefined' && self.Math == Math) - ? self - : Function('return this')() -; - -$.fn.dropdown = function(parameters) { - var - $allModules = $(this), - $document = $(document), - - moduleSelector = $allModules.selector || '', - - hasTouch = ('ontouchstart' in document.documentElement), - clickEvent = "click", unstableClickEvent = hasTouch - ? 'touchstart' - : 'click', - - time = new Date().getTime(), - performance = [], - - query = arguments[0], - methodInvoked = (typeof query == 'string'), - queryArguments = [].slice.call(arguments, 1), - returnedValue - ; - - $allModules - .each(function(elementIndex) { - var - settings = ( $.isPlainObject(parameters) ) - ? $.extend(true, {}, $.fn.dropdown.settings, parameters) - : $.extend({}, $.fn.dropdown.settings), - - className = settings.className, - message = settings.message, - fields = settings.fields, - keys = settings.keys, - metadata = settings.metadata, - namespace = settings.namespace, - regExp = settings.regExp, - selector = settings.selector, - error = settings.error, - templates = settings.templates, - - eventNamespace = '.' + namespace, - moduleNamespace = 'module-' + namespace, - - $module = $(this), - $context = $(settings.context), - $text = $module.find(selector.text), - $search = $module.find(selector.search), - $sizer = $module.find(selector.sizer), - $input = $module.find(selector.input), - $icon = $module.find(selector.icon), - $clear = $module.find(selector.clearIcon), - - $combo = ($module.prev().find(selector.text).length > 0) - ? $module.prev().find(selector.text) - : $module.prev(), - - $menu = $module.children(selector.menu), - $item = $menu.find(selector.item), - $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(), - - activated = false, - itemActivated = false, - internalChange = false, - iconClicked = false, - element = this, - instance = $module.data(moduleNamespace), - - selectActionActive, - initialLoad, - pageLostFocus, - willRefocus, - elementNamespace, - id, - selectObserver, - menuObserver, - classObserver, - module - ; - - module = { - - initialize: function() { - module.debug('Initializing dropdown', settings); - - if( module.is.alreadySetup() ) { - module.setup.reference(); - } - else { - if (settings.ignoreDiacritics && !String.prototype.normalize) { - settings.ignoreDiacritics = false; - module.error(error.noNormalize, element); - } - - module.setup.layout(); - - if(settings.values) { - module.set.initialLoad(); - module.change.values(settings.values); - module.remove.initialLoad(); - } - - module.refreshData(); - - module.save.defaults(); - module.restore.selected(); - - module.create.id(); - module.bind.events(); - - module.observeChanges(); - module.instantiate(); - } - - }, - - instantiate: function() { - module.verbose('Storing instance of dropdown', module); - instance = module; - $module - .data(moduleNamespace, module) - ; - }, - - destroy: function() { - module.verbose('Destroying previous dropdown', $module); - module.remove.tabbable(); - module.remove.active(); - $menu.transition('stop all'); - $menu.removeClass(className.visible).addClass(className.hidden); - $module - .off(eventNamespace) - .removeData(moduleNamespace) - ; - $menu - .off(eventNamespace) - ; - $document - .off(elementNamespace) - ; - module.disconnect.menuObserver(); - module.disconnect.selectObserver(); - module.disconnect.classObserver(); - }, - - observeChanges: function() { - if('MutationObserver' in window) { - selectObserver = new MutationObserver(module.event.select.mutation); - menuObserver = new MutationObserver(module.event.menu.mutation); - classObserver = new MutationObserver(module.event.class.mutation); - module.debug('Setting up mutation observer', selectObserver, menuObserver, classObserver); - module.observe.select(); - module.observe.menu(); - module.observe.class(); - } - }, - - disconnect: { - menuObserver: function() { - if(menuObserver) { - menuObserver.disconnect(); - } - }, - selectObserver: function() { - if(selectObserver) { - selectObserver.disconnect(); - } - }, - classObserver: function() { - if(classObserver) { - classObserver.disconnect(); - } - } - }, - observe: { - select: function() { - if(module.has.input() && selectObserver) { - selectObserver.observe($module[0], { - childList : true, - subtree : true - }); - } - }, - menu: function() { - if(module.has.menu() && menuObserver) { - menuObserver.observe($menu[0], { - childList : true, - subtree : true - }); - } - }, - class: function() { - if(module.has.search() && classObserver) { - classObserver.observe($module[0], { - attributes : true - }); - } - } - }, - - create: { - id: function() { - id = (Math.random().toString(16) + '000000000').substr(2, 8); - elementNamespace = '.' + id; - module.verbose('Creating unique id for element', id); - }, - userChoice: function(values) { - var - $userChoices, - $userChoice, - isUserValue, - html - ; - values = values || module.get.userValues(); - if(!values) { - return false; - } - values = Array.isArray(values) - ? values - : [values] - ; - $.each(values, function(index, value) { - if(module.get.item(value) === false) { - html = settings.templates.addition( module.add.variables(message.addResult, value) ); - $userChoice = $('<div />') - .html(html) - .attr('data-' + metadata.value, value) - .attr('data-' + metadata.text, value) - .addClass(className.addition) - .addClass(className.item) - ; - if(settings.hideAdditions) { - $userChoice.addClass(className.hidden); - } - $userChoices = ($userChoices === undefined) - ? $userChoice - : $userChoices.add($userChoice) - ; - module.verbose('Creating user choices for value', value, $userChoice); - } - }); - return $userChoices; - }, - userLabels: function(value) { - var - userValues = module.get.userValues() - ; - if(userValues) { - module.debug('Adding user labels', userValues); - $.each(userValues, function(index, value) { - module.verbose('Adding custom user value'); - module.add.label(value, value); - }); - } - }, - menu: function() { - $menu = $('<div />') - .addClass(className.menu) - .appendTo($module) - ; - }, - sizer: function() { - $sizer = $('<span />') - .addClass(className.sizer) - .insertAfter($search) - ; - } - }, - - search: function(query) { - query = (query !== undefined) - ? query - : module.get.query() - ; - module.verbose('Searching for query', query); - if(module.has.minCharacters(query)) { - module.filter(query); - } - else { - module.hide(null,true); - } - }, - - select: { - firstUnfiltered: function() { - module.verbose('Selecting first non-filtered element'); - module.remove.selectedItem(); - $item - .not(selector.unselectable) - .not(selector.addition + selector.hidden) - .eq(0) - .addClass(className.selected) - ; - }, - nextAvailable: function($selected) { - $selected = $selected.eq(0); - var - $nextAvailable = $selected.nextAll(selector.item).not(selector.unselectable).eq(0), - $prevAvailable = $selected.prevAll(selector.item).not(selector.unselectable).eq(0), - hasNext = ($nextAvailable.length > 0) - ; - if(hasNext) { - module.verbose('Moving selection to', $nextAvailable); - $nextAvailable.addClass(className.selected); - } - else { - module.verbose('Moving selection to', $prevAvailable); - $prevAvailable.addClass(className.selected); - } - } - }, - - setup: { - api: function() { - var - apiSettings = { - debug : settings.debug, - urlData : { - value : module.get.value(), - query : module.get.query() - }, - on : false - } - ; - module.verbose('First request, initializing API'); - $module - .api(apiSettings) - ; - }, - layout: function() { - if( $module.is('select') ) { - module.setup.select(); - module.setup.returnedObject(); - } - if( !module.has.menu() ) { - module.create.menu(); - } - if ( module.is.selection() && module.is.clearable() && !module.has.clearItem() ) { - module.verbose('Adding clear icon'); - $clear = $('<i />') - .addClass('remove icon') - .insertBefore($text) - ; - } - if( module.is.search() && !module.has.search() ) { - module.verbose('Adding search input'); - $search = $('<input />') - .addClass(className.search) - .prop('autocomplete', 'off') - .insertBefore($text) - ; - } - if( module.is.multiple() && module.is.searchSelection() && !module.has.sizer()) { - module.create.sizer(); - } - if(settings.allowTab) { - module.set.tabbable(); - } - }, - select: function() { - var - selectValues = module.get.selectValues() - ; - module.debug('Dropdown initialized on a select', selectValues); - if( $module.is('select') ) { - $input = $module; - } - // see if select is placed correctly already - if($input.parent(selector.dropdown).length > 0) { - module.debug('UI dropdown already exists. Creating dropdown menu only'); - $module = $input.closest(selector.dropdown); - if( !module.has.menu() ) { - module.create.menu(); - } - $menu = $module.children(selector.menu); - module.setup.menu(selectValues); - } - else { - module.debug('Creating entire dropdown from select'); - $module = $('<div />') - .attr('class', $input.attr('class') ) - .addClass(className.selection) - .addClass(className.dropdown) - .html( templates.dropdown(selectValues, fields, settings.preserveHTML, settings.className) ) - .insertBefore($input) - ; - if($input.hasClass(className.multiple) && $input.prop('multiple') === false) { - module.error(error.missingMultiple); - $input.prop('multiple', true); - } - if($input.is('[multiple]')) { - module.set.multiple(); - } - if ($input.prop('disabled')) { - module.debug('Disabling dropdown'); - $module.addClass(className.disabled); - } - $input - .removeAttr('required') - .removeAttr('class') - .detach() - .prependTo($module) - ; - } - module.refresh(); - }, - menu: function(values) { - $menu.html( templates.menu(values, fields,settings.preserveHTML,settings.className)); - $item = $menu.find(selector.item); - $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); - }, - reference: function() { - module.debug('Dropdown behavior was called on select, replacing with closest dropdown'); - // replace module reference - $module = $module.parent(selector.dropdown); - instance = $module.data(moduleNamespace); - element = $module.get(0); - module.refresh(); - module.setup.returnedObject(); - }, - returnedObject: function() { - var - $firstModules = $allModules.slice(0, elementIndex), - $lastModules = $allModules.slice(elementIndex + 1) - ; - // adjust all modules to use correct reference - $allModules = $firstModules.add($module).add($lastModules); - } - }, - - refresh: function() { - module.refreshSelectors(); - module.refreshData(); - }, - - refreshItems: function() { - $item = $menu.find(selector.item); - $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); - }, - - refreshSelectors: function() { - module.verbose('Refreshing selector cache'); - $text = $module.find(selector.text); - $search = $module.find(selector.search); - $input = $module.find(selector.input); - $icon = $module.find(selector.icon); - $combo = ($module.prev().find(selector.text).length > 0) - ? $module.prev().find(selector.text) - : $module.prev() - ; - $menu = $module.children(selector.menu); - $item = $menu.find(selector.item); - $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); - }, - - refreshData: function() { - module.verbose('Refreshing cached metadata'); - $item - .removeData(metadata.text) - .removeData(metadata.value) - ; - }, - - clearData: function() { - module.verbose('Clearing metadata'); - $item - .removeData(metadata.text) - .removeData(metadata.value) - ; - $module - .removeData(metadata.defaultText) - .removeData(metadata.defaultValue) - .removeData(metadata.placeholderText) - ; - }, - - toggle: function() { - module.verbose('Toggling menu visibility'); - if( !module.is.active() ) { - module.show(); - } - else { - module.hide(); - } - }, - - show: function(callback, preventFocus) { - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if(!module.can.show() && module.is.remote()) { - module.debug('No API results retrieved, searching before show'); - module.queryRemote(module.get.query(), module.show); - } - if( module.can.show() && !module.is.active() ) { - module.debug('Showing dropdown'); - if(module.has.message() && !(module.has.maxSelections() || module.has.allResultsFiltered()) ) { - module.remove.message(); - } - if(module.is.allFiltered()) { - return true; - } - if(settings.onShow.call(element) !== false) { - module.animate.show(function() { - if( module.can.click() ) { - module.bind.intent(); - } - if(module.has.search() && !preventFocus) { - module.focusSearch(); - } - module.set.visible(); - callback.call(element); - }); - } - } - }, - - hide: function(callback, preventBlur) { - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if( module.is.active() && !module.is.animatingOutward() ) { - module.debug('Hiding dropdown'); - if(settings.onHide.call(element) !== false) { - module.animate.hide(function() { - module.remove.visible(); - // hidding search focus - if ( module.is.focusedOnSearch() && preventBlur !== true ) { - $search.blur(); - } - callback.call(element); - }); - } - } else if( module.can.click() ) { - module.unbind.intent(); - } - iconClicked = false; - }, - - hideOthers: function() { - module.verbose('Finding other dropdowns to hide'); - $allModules - .not($module) - .has(selector.menu + '.' + className.visible) - .dropdown('hide') - ; - }, - - hideMenu: function() { - module.verbose('Hiding menu instantaneously'); - module.remove.active(); - module.remove.visible(); - $menu.transition('hide'); - }, - - hideSubMenus: function() { - var - $subMenus = $menu.children(selector.item).find(selector.menu) - ; - module.verbose('Hiding sub menus', $subMenus); - $subMenus.transition('hide'); - }, - - bind: { - events: function() { - module.bind.keyboardEvents(); - module.bind.inputEvents(); - module.bind.mouseEvents(); - }, - keyboardEvents: function() { - module.verbose('Binding keyboard events'); - $module - .on('keydown' + eventNamespace, module.event.keydown) - ; - if( module.has.search() ) { - $module - .on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input) - ; - } - if( module.is.multiple() ) { - $document - .on('keydown' + elementNamespace, module.event.document.keydown) - ; - } - }, - inputEvents: function() { - module.verbose('Binding input change events'); - $module - .on('change' + eventNamespace, selector.input, module.event.change) - ; - }, - mouseEvents: function() { - module.verbose('Binding mouse events'); - if(module.is.multiple()) { - $module - .on(clickEvent + eventNamespace, selector.label, module.event.label.click) - .on(clickEvent + eventNamespace, selector.remove, module.event.remove.click) - ; - } - if( module.is.searchSelection() ) { - $module - .on('mousedown' + eventNamespace, module.event.mousedown) - .on('mouseup' + eventNamespace, module.event.mouseup) - .on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown) - .on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup) - .on(clickEvent + eventNamespace, selector.icon, module.event.icon.click) - .on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click) - .on('focus' + eventNamespace, selector.search, module.event.search.focus) - .on(clickEvent + eventNamespace, selector.search, module.event.search.focus) - .on('blur' + eventNamespace, selector.search, module.event.search.blur) - .on(clickEvent + eventNamespace, selector.text, module.event.text.focus) - ; - if(module.is.multiple()) { - $module - .on(clickEvent + eventNamespace, module.event.click) - ; - } - } - else { - if(settings.on == 'click') { - $module - .on(clickEvent + eventNamespace, selector.icon, module.event.icon.click) - .on(clickEvent + eventNamespace, module.event.test.toggle) - ; - } - else if(settings.on == 'hover') { - $module - .on('mouseenter' + eventNamespace, module.delay.show) - .on('mouseleave' + eventNamespace, module.delay.hide) - ; - } - else { - $module - .on(settings.on + eventNamespace, module.toggle) - ; - } - $module - .on('mousedown' + eventNamespace, module.event.mousedown) - .on('mouseup' + eventNamespace, module.event.mouseup) - .on('focus' + eventNamespace, module.event.focus) - .on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click) - ; - if(module.has.menuSearch() ) { - $module - .on('blur' + eventNamespace, selector.search, module.event.search.blur) - ; - } - else { - $module - .on('blur' + eventNamespace, module.event.blur) - ; - } - } - $menu - .on((hasTouch ? 'touchstart' : 'mouseenter') + eventNamespace, selector.item, module.event.item.mouseenter) - .on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave) - .on('click' + eventNamespace, selector.item, module.event.item.click) - ; - }, - intent: function() { - module.verbose('Binding hide intent event to document'); - if(hasTouch) { - $document - .on('touchstart' + elementNamespace, module.event.test.touch) - .on('touchmove' + elementNamespace, module.event.test.touch) - ; - } - $document - .on(clickEvent + elementNamespace, module.event.test.hide) - ; - } - }, - - unbind: { - intent: function() { - module.verbose('Removing hide intent event from document'); - if(hasTouch) { - $document - .off('touchstart' + elementNamespace) - .off('touchmove' + elementNamespace) - ; - } - $document - .off(clickEvent + elementNamespace) - ; - } - }, - - filter: function(query) { - var - searchTerm = (query !== undefined) - ? query - : module.get.query(), - afterFiltered = function() { - if(module.is.multiple()) { - module.filterActive(); - } - if(query || (!query && module.get.activeItem().length == 0)) { - module.select.firstUnfiltered(); - } - if( module.has.allResultsFiltered() ) { - if( settings.onNoResults.call(element, searchTerm) ) { - if(settings.allowAdditions) { - if(settings.hideAdditions) { - module.verbose('User addition with no menu, setting empty style'); - module.set.empty(); - module.hideMenu(); - } - } - else { - module.verbose('All items filtered, showing message', searchTerm); - module.add.message(message.noResults); - } - } - else { - module.verbose('All items filtered, hiding dropdown', searchTerm); - module.hideMenu(); - } - } - else { - module.remove.empty(); - module.remove.message(); - } - if(settings.allowAdditions) { - module.add.userSuggestion(module.escape.htmlEntities(query)); - } - if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) { - module.show(); - } - } - ; - if(settings.useLabels && module.has.maxSelections()) { - return; - } - if(settings.apiSettings) { - if( module.can.useAPI() ) { - module.queryRemote(searchTerm, function() { - if(settings.filterRemoteData) { - module.filterItems(searchTerm); - } - var preSelected = $input.val(); - if(!Array.isArray(preSelected)) { - preSelected = preSelected && preSelected!=="" ? preSelected.split(settings.delimiter) : []; - } - $.each(preSelected,function(index,value){ - $item.filter('[data-value="'+value+'"]') - .addClass(className.filtered) - ; - }); - afterFiltered(); - }); - } - else { - module.error(error.noAPI); - } - } - else { - module.filterItems(searchTerm); - afterFiltered(); - } - }, - - queryRemote: function(query, callback) { - var - apiSettings = { - errorDuration : false, - cache : 'local', - throttle : settings.throttle, - urlData : { - query: query - }, - onError: function() { - module.add.message(message.serverError); - callback(); - }, - onFailure: function() { - module.add.message(message.serverError); - callback(); - }, - onSuccess : function(response) { - var - values = response[fields.remoteValues] - ; - if (!Array.isArray(values)){ - values = []; - } - module.remove.message(); - var menuConfig = {}; - menuConfig[fields.values] = values; - module.setup.menu(menuConfig); - - if(values.length===0 && !settings.allowAdditions) { - module.add.message(message.noResults); - } - callback(); - } - } - ; - if( !$module.api('get request') ) { - module.setup.api(); - } - apiSettings = $.extend(true, {}, apiSettings, settings.apiSettings); - $module - .api('setting', apiSettings) - .api('query') - ; - }, - - filterItems: function(query) { - var - searchTerm = module.remove.diacritics(query !== undefined - ? query - : module.get.query() - ), - results = null, - escapedTerm = module.escape.string(searchTerm), - regExpFlags = (settings.ignoreSearchCase ? 'i' : '') + 'gm', - beginsWithRegExp = new RegExp('^' + escapedTerm, regExpFlags) - ; - // avoid loop if we're matching nothing - if( module.has.query() ) { - results = []; - - module.verbose('Searching for matching values', searchTerm); - $item - .each(function(){ - var - $choice = $(this), - text, - value - ; - if($choice.hasClass(className.unfilterable)) { - results.push(this); - return true; - } - if(settings.match === 'both' || settings.match === 'text') { - text = module.remove.diacritics(String(module.get.choiceText($choice, false))); - if(text.search(beginsWithRegExp) !== -1) { - results.push(this); - return true; - } - else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text)) { - results.push(this); - return true; - } - else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, text)) { - results.push(this); - return true; - } - } - if(settings.match === 'both' || settings.match === 'value') { - value = module.remove.diacritics(String(module.get.choiceValue($choice, text))); - if(value.search(beginsWithRegExp) !== -1) { - results.push(this); - return true; - } - else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, value)) { - results.push(this); - return true; - } - else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, value)) { - results.push(this); - return true; - } - } - }) - ; - } - module.debug('Showing only matched items', searchTerm); - module.remove.filteredItem(); - if(results) { - $item - .not(results) - .addClass(className.filtered) - ; - } - - if(!module.has.query()) { - $divider - .removeClass(className.hidden); - } else if(settings.hideDividers === true) { - $divider - .addClass(className.hidden); - } else if(settings.hideDividers === 'empty') { - $divider - .removeClass(className.hidden) - .filter(function() { - // First find the last divider in this divider group - // Dividers which are direct siblings are considered a group - var lastDivider = $(this).nextUntil(selector.item); - - return (lastDivider.length ? lastDivider : $(this)) - // Count all non-filtered items until the next divider (or end of the dropdown) - .nextUntil(selector.divider) - .filter(selector.item + ":not(." + className.filtered + ")") - // Hide divider if no items are found - .length === 0; - }) - .addClass(className.hidden); - } - }, - - fuzzySearch: function(query, term) { - var - termLength = term.length, - queryLength = query.length - ; - query = (settings.ignoreSearchCase ? query.toLowerCase() : query); - term = (settings.ignoreSearchCase ? term.toLowerCase() : term); - if(queryLength > termLength) { - return false; - } - if(queryLength === termLength) { - return (query === term); - } - search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) { - var - queryCharacter = query.charCodeAt(characterIndex) - ; - while(nextCharacterIndex < termLength) { - if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) { - continue search; - } - } - return false; - } - return true; - }, - exactSearch: function (query, term) { - query = (settings.ignoreSearchCase ? query.toLowerCase() : query); - term = (settings.ignoreSearchCase ? term.toLowerCase() : term); - return term.indexOf(query) > -1; - - }, - filterActive: function() { - if(settings.useLabels) { - $item.filter('.' + className.active) - .addClass(className.filtered) - ; - } - }, - - focusSearch: function(skipHandler) { - if( module.has.search() && !module.is.focusedOnSearch() ) { - if(skipHandler) { - $module.off('focus' + eventNamespace, selector.search); - $search.focus(); - $module.on('focus' + eventNamespace, selector.search, module.event.search.focus); - } - else { - $search.focus(); - } - } - }, - - blurSearch: function() { - if( module.has.search() ) { - $search.blur(); - } - }, - - forceSelection: function() { - var - $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0), - $activeItem = $item.not(className.filtered).filter('.' + className.active).eq(0), - $selectedItem = ($currentlySelected.length > 0) - ? $currentlySelected - : $activeItem, - hasSelected = ($selectedItem.length > 0) - ; - if(settings.allowAdditions || (hasSelected && !module.is.multiple())) { - module.debug('Forcing partial selection to selected item', $selectedItem); - module.event.item.click.call($selectedItem, {}, true); - } - else { - module.remove.searchTerm(); - } - }, - - change: { - values: function(values) { - if(!settings.allowAdditions) { - module.clear(); - } - module.debug('Creating dropdown with specified values', values); - var menuConfig = {}; - menuConfig[fields.values] = values; - module.setup.menu(menuConfig); - $.each(values, function(index, item) { - if(item.selected == true) { - module.debug('Setting initial selection to', item[fields.value]); - module.set.selected(item[fields.value]); - if(!module.is.multiple()) { - return false; - } - } - }); - - if(module.has.selectInput()) { - module.disconnect.selectObserver(); - $input.html(''); - $input.append('<option disabled selected value></option>'); - $.each(values, function(index, item) { - var - value = settings.templates.deQuote(item[fields.value]), - name = settings.templates.escape( - item[fields.name] || '', - settings.preserveHTML - ) - ; - $input.append('<option value="' + value + '">' + name + '</option>'); - }); - module.observe.select(); - } - } - }, - - event: { - change: function() { - if(!internalChange) { - module.debug('Input changed, updating selection'); - module.set.selected(); - } - }, - focus: function() { - if(settings.showOnFocus && !activated && module.is.hidden() && !pageLostFocus) { - module.show(); - } - }, - blur: function(event) { - pageLostFocus = (document.activeElement === this); - if(!activated && !pageLostFocus) { - module.remove.activeLabel(); - module.hide(); - } - }, - mousedown: function() { - if(module.is.searchSelection()) { - // prevent menu hiding on immediate re-focus - willRefocus = true; - } - else { - // prevents focus callback from occurring on mousedown - activated = true; - } - }, - mouseup: function() { - if(module.is.searchSelection()) { - // prevent menu hiding on immediate re-focus - willRefocus = false; - } - else { - activated = false; - } - }, - click: function(event) { - var - $target = $(event.target) - ; - // focus search - if($target.is($module)) { - if(!module.is.focusedOnSearch()) { - module.focusSearch(); - } - else { - module.show(); - } - } - }, - search: { - focus: function(event) { - activated = true; - if(module.is.multiple()) { - module.remove.activeLabel(); - } - if(settings.showOnFocus || (event.type !== 'focus' && event.type !== 'focusin')) { - module.search(); - } - }, - blur: function(event) { - pageLostFocus = (document.activeElement === this); - if(module.is.searchSelection() && !willRefocus) { - if(!itemActivated && !pageLostFocus) { - if(settings.forceSelection) { - module.forceSelection(); - } else if(!settings.allowAdditions){ - module.remove.searchTerm(); - } - module.hide(); - } - } - willRefocus = false; - } - }, - clearIcon: { - click: function(event) { - module.clear(); - if(module.is.searchSelection()) { - module.remove.searchTerm(); - } - module.hide(); - event.stopPropagation(); - } - }, - icon: { - click: function(event) { - iconClicked=true; - if(module.has.search()) { - if(!module.is.active()) { - if(settings.showOnFocus){ - module.focusSearch(); - } else { - module.toggle(); - } - } else { - module.blurSearch(); - } - } else { - module.toggle(); - } - } - }, - text: { - focus: function(event) { - activated = true; - module.focusSearch(); - } - }, - input: function(event) { - if(module.is.multiple() || module.is.searchSelection()) { - module.set.filtered(); - } - clearTimeout(module.timer); - module.timer = setTimeout(module.search, settings.delay.search); - }, - label: { - click: function(event) { - var - $label = $(this), - $labels = $module.find(selector.label), - $activeLabels = $labels.filter('.' + className.active), - $nextActive = $label.nextAll('.' + className.active), - $prevActive = $label.prevAll('.' + className.active), - $range = ($nextActive.length > 0) - ? $label.nextUntil($nextActive).add($activeLabels).add($label) - : $label.prevUntil($prevActive).add($activeLabels).add($label) - ; - if(event.shiftKey) { - $activeLabels.removeClass(className.active); - $range.addClass(className.active); - } - else if(event.ctrlKey) { - $label.toggleClass(className.active); - } - else { - $activeLabels.removeClass(className.active); - $label.addClass(className.active); - } - settings.onLabelSelect.apply(this, $labels.filter('.' + className.active)); - } - }, - remove: { - click: function() { - var - $label = $(this).parent() - ; - if( $label.hasClass(className.active) ) { - // remove all selected labels - module.remove.activeLabels(); - } - else { - // remove this label only - module.remove.activeLabels( $label ); - } - } - }, - test: { - toggle: function(event) { - var - toggleBehavior = (module.is.multiple()) - ? module.show - : module.toggle - ; - if(module.is.bubbledLabelClick(event) || module.is.bubbledIconClick(event)) { - return; - } - if( module.determine.eventOnElement(event, toggleBehavior) ) { - event.preventDefault(); - } - }, - touch: function(event) { - module.determine.eventOnElement(event, function() { - if(event.type == 'touchstart') { - module.timer = setTimeout(function() { - module.hide(); - }, settings.delay.touch); - } - else if(event.type == 'touchmove') { - clearTimeout(module.timer); - } - }); - event.stopPropagation(); - }, - hide: function(event) { - if(module.determine.eventInModule(event, module.hide)){ - if(element.id && $(event.target).attr('for') === element.id){ - event.preventDefault(); - } - } - } - }, - class: { - mutation: function(mutations) { - mutations.forEach(function(mutation) { - if(mutation.attributeName === "class") { - module.check.disabled(); - } - }); - } - }, - select: { - mutation: function(mutations) { - module.debug('<select> modified, recreating menu'); - if(module.is.selectMutation(mutations)) { - module.disconnect.selectObserver(); - module.refresh(); - module.setup.select(); - module.set.selected(); - module.observe.select(); - } - } - }, - menu: { - mutation: function(mutations) { - var - mutation = mutations[0], - $addedNode = mutation.addedNodes - ? $(mutation.addedNodes[0]) - : $(false), - $removedNode = mutation.removedNodes - ? $(mutation.removedNodes[0]) - : $(false), - $changedNodes = $addedNode.add($removedNode), - isUserAddition = $changedNodes.is(selector.addition) || $changedNodes.closest(selector.addition).length > 0, - isMessage = $changedNodes.is(selector.message) || $changedNodes.closest(selector.message).length > 0 - ; - if(isUserAddition || isMessage) { - module.debug('Updating item selector cache'); - module.refreshItems(); - } - else { - module.debug('Menu modified, updating selector cache'); - module.refresh(); - } - }, - mousedown: function() { - itemActivated = true; - }, - mouseup: function() { - itemActivated = false; - } - }, - item: { - mouseenter: function(event) { - var - $target = $(event.target), - $item = $(this), - $subMenu = $item.children(selector.menu), - $otherMenus = $item.siblings(selector.item).children(selector.menu), - hasSubMenu = ($subMenu.length > 0), - isBubbledEvent = ($subMenu.find($target).length > 0) - ; - if( !isBubbledEvent && hasSubMenu ) { - clearTimeout(module.itemTimer); - module.itemTimer = setTimeout(function() { - module.verbose('Showing sub-menu', $subMenu); - $.each($otherMenus, function() { - module.animate.hide(false, $(this)); - }); - module.animate.show(false, $subMenu); - }, settings.delay.show); - event.preventDefault(); - } - }, - mouseleave: function(event) { - var - $subMenu = $(this).children(selector.menu) - ; - if($subMenu.length > 0) { - clearTimeout(module.itemTimer); - module.itemTimer = setTimeout(function() { - module.verbose('Hiding sub-menu', $subMenu); - module.animate.hide(false, $subMenu); - }, settings.delay.hide); - } - }, - click: function (event, skipRefocus) { - var - $choice = $(this), - $target = (event) - ? $(event.target) - : $(''), - $subMenu = $choice.find(selector.menu), - text = module.get.choiceText($choice), - value = module.get.choiceValue($choice, text), - hasSubMenu = ($subMenu.length > 0), - isBubbledEvent = ($subMenu.find($target).length > 0) - ; - // prevents IE11 bug where menu receives focus even though `tabindex=-1` - if (document.activeElement.tagName.toLowerCase() !== 'input') { - $(document.activeElement).blur(); - } - if(!isBubbledEvent && (!hasSubMenu || settings.allowCategorySelection)) { - if(module.is.searchSelection()) { - if(settings.allowAdditions) { - module.remove.userAddition(); - } - module.remove.searchTerm(); - if(!module.is.focusedOnSearch() && !(skipRefocus == true)) { - module.focusSearch(true); - } - } - if(!settings.useLabels) { - module.remove.filteredItem(); - module.set.scrollPosition($choice); - } - module.determine.selectAction.call(this, text, value); - } - } - }, - - document: { - // label selection should occur even when element has no focus - keydown: function(event) { - var - pressedKey = event.which, - isShortcutKey = module.is.inObject(pressedKey, keys) - ; - if(isShortcutKey) { - var - $label = $module.find(selector.label), - $activeLabel = $label.filter('.' + className.active), - activeValue = $activeLabel.data(metadata.value), - labelIndex = $label.index($activeLabel), - labelCount = $label.length, - hasActiveLabel = ($activeLabel.length > 0), - hasMultipleActive = ($activeLabel.length > 1), - isFirstLabel = (labelIndex === 0), - isLastLabel = (labelIndex + 1 == labelCount), - isSearch = module.is.searchSelection(), - isFocusedOnSearch = module.is.focusedOnSearch(), - isFocused = module.is.focused(), - caretAtStart = (isFocusedOnSearch && module.get.caretPosition(false) === 0), - isSelectedSearch = (caretAtStart && module.get.caretPosition(true) !== 0), - $nextLabel - ; - if(isSearch && !hasActiveLabel && !isFocusedOnSearch) { - return; - } - - if(pressedKey == keys.leftArrow) { - // activate previous label - if((isFocused || caretAtStart) && !hasActiveLabel) { - module.verbose('Selecting previous label'); - $label.last().addClass(className.active); - } - else if(hasActiveLabel) { - if(!event.shiftKey) { - module.verbose('Selecting previous label'); - $label.removeClass(className.active); - } - else { - module.verbose('Adding previous label to selection'); - } - if(isFirstLabel && !hasMultipleActive) { - $activeLabel.addClass(className.active); - } - else { - $activeLabel.prev(selector.siblingLabel) - .addClass(className.active) - .end() - ; - } - event.preventDefault(); - } - } - else if(pressedKey == keys.rightArrow) { - // activate first label - if(isFocused && !hasActiveLabel) { - $label.first().addClass(className.active); - } - // activate next label - if(hasActiveLabel) { - if(!event.shiftKey) { - module.verbose('Selecting next label'); - $label.removeClass(className.active); - } - else { - module.verbose('Adding next label to selection'); - } - if(isLastLabel) { - if(isSearch) { - if(!isFocusedOnSearch) { - module.focusSearch(); - } - else { - $label.removeClass(className.active); - } - } - else if(hasMultipleActive) { - $activeLabel.next(selector.siblingLabel).addClass(className.active); - } - else { - $activeLabel.addClass(className.active); - } - } - else { - $activeLabel.next(selector.siblingLabel).addClass(className.active); - } - event.preventDefault(); - } - } - else if(pressedKey == keys.deleteKey || pressedKey == keys.backspace) { - if(hasActiveLabel) { - module.verbose('Removing active labels'); - if(isLastLabel) { - if(isSearch && !isFocusedOnSearch) { - module.focusSearch(); - } - } - $activeLabel.last().next(selector.siblingLabel).addClass(className.active); - module.remove.activeLabels($activeLabel); - event.preventDefault(); - } - else if(caretAtStart && !isSelectedSearch && !hasActiveLabel && pressedKey == keys.backspace) { - module.verbose('Removing last label on input backspace'); - $activeLabel = $label.last().addClass(className.active); - module.remove.activeLabels($activeLabel); - } - } - else { - $activeLabel.removeClass(className.active); - } - } - } - }, - - keydown: function(event) { - var - pressedKey = event.which, - isShortcutKey = module.is.inObject(pressedKey, keys) - ; - if(isShortcutKey) { - var - $currentlySelected = $item.not(selector.unselectable).filter('.' + className.selected).eq(0), - $activeItem = $menu.children('.' + className.active).eq(0), - $selectedItem = ($currentlySelected.length > 0) - ? $currentlySelected - : $activeItem, - $visibleItems = ($selectedItem.length > 0) - ? $selectedItem.siblings(':not(.' + className.filtered +')').addBack() - : $menu.children(':not(.' + className.filtered +')'), - $subMenu = $selectedItem.children(selector.menu), - $parentMenu = $selectedItem.closest(selector.menu), - inVisibleMenu = ($parentMenu.hasClass(className.visible) || $parentMenu.hasClass(className.animating) || $parentMenu.parent(selector.menu).length > 0), - hasSubMenu = ($subMenu.length> 0), - hasSelectedItem = ($selectedItem.length > 0), - selectedIsSelectable = ($selectedItem.not(selector.unselectable).length > 0), - delimiterPressed = (pressedKey == keys.delimiter && settings.allowAdditions && module.is.multiple()), - isAdditionWithoutMenu = (settings.allowAdditions && settings.hideAdditions && (pressedKey == keys.enter || delimiterPressed) && selectedIsSelectable), - $nextItem, - isSubMenuItem, - newIndex - ; - // allow selection with menu closed - if(isAdditionWithoutMenu) { - module.verbose('Selecting item from keyboard shortcut', $selectedItem); - module.event.item.click.call($selectedItem, event); - if(module.is.searchSelection()) { - module.remove.searchTerm(); - } - if(module.is.multiple()){ - event.preventDefault(); - } - } - - // visible menu keyboard shortcuts - if( module.is.visible() ) { - - // enter (select or open sub-menu) - if(pressedKey == keys.enter || delimiterPressed) { - if(pressedKey == keys.enter && hasSelectedItem && hasSubMenu && !settings.allowCategorySelection) { - module.verbose('Pressed enter on unselectable category, opening sub menu'); - pressedKey = keys.rightArrow; - } - else if(selectedIsSelectable) { - module.verbose('Selecting item from keyboard shortcut', $selectedItem); - module.event.item.click.call($selectedItem, event); - if(module.is.searchSelection()) { - module.remove.searchTerm(); - if(module.is.multiple()) { - $search.focus(); - } - } - } - event.preventDefault(); - } - - // sub-menu actions - if(hasSelectedItem) { - - if(pressedKey == keys.leftArrow) { - - isSubMenuItem = ($parentMenu[0] !== $menu[0]); - - if(isSubMenuItem) { - module.verbose('Left key pressed, closing sub-menu'); - module.animate.hide(false, $parentMenu); - $selectedItem - .removeClass(className.selected) - ; - $parentMenu - .closest(selector.item) - .addClass(className.selected) - ; - event.preventDefault(); - } - } - - // right arrow (show sub-menu) - if(pressedKey == keys.rightArrow) { - if(hasSubMenu) { - module.verbose('Right key pressed, opening sub-menu'); - module.animate.show(false, $subMenu); - $selectedItem - .removeClass(className.selected) - ; - $subMenu - .find(selector.item).eq(0) - .addClass(className.selected) - ; - event.preventDefault(); - } - } - } - - // up arrow (traverse menu up) - if(pressedKey == keys.upArrow) { - $nextItem = (hasSelectedItem && inVisibleMenu) - ? $selectedItem.prevAll(selector.item + ':not(' + selector.unselectable + ')').eq(0) - : $item.eq(0) - ; - if($visibleItems.index( $nextItem ) < 0) { - module.verbose('Up key pressed but reached top of current menu'); - event.preventDefault(); - return; - } - else { - module.verbose('Up key pressed, changing active item'); - $selectedItem - .removeClass(className.selected) - ; - $nextItem - .addClass(className.selected) - ; - module.set.scrollPosition($nextItem); - if(settings.selectOnKeydown && module.is.single()) { - module.set.selectedItem($nextItem); - } - } - event.preventDefault(); - } - - // down arrow (traverse menu down) - if(pressedKey == keys.downArrow) { - $nextItem = (hasSelectedItem && inVisibleMenu) - ? $nextItem = $selectedItem.nextAll(selector.item + ':not(' + selector.unselectable + ')').eq(0) - : $item.eq(0) - ; - if($nextItem.length === 0) { - module.verbose('Down key pressed but reached bottom of current menu'); - event.preventDefault(); - return; - } - else { - module.verbose('Down key pressed, changing active item'); - $item - .removeClass(className.selected) - ; - $nextItem - .addClass(className.selected) - ; - module.set.scrollPosition($nextItem); - if(settings.selectOnKeydown && module.is.single()) { - module.set.selectedItem($nextItem); - } - } - event.preventDefault(); - } - - // page down (show next page) - if(pressedKey == keys.pageUp) { - module.scrollPage('up'); - event.preventDefault(); - } - if(pressedKey == keys.pageDown) { - module.scrollPage('down'); - event.preventDefault(); - } - - // escape (close menu) - if(pressedKey == keys.escape) { - module.verbose('Escape key pressed, closing dropdown'); - module.hide(); - } - - } - else { - // delimiter key - if(delimiterPressed) { - event.preventDefault(); - } - // down arrow (open menu) - if(pressedKey == keys.downArrow && !module.is.visible()) { - module.verbose('Down key pressed, showing dropdown'); - module.show(); - event.preventDefault(); - } - } - } - else { - if( !module.has.search() ) { - module.set.selectedLetter( String.fromCharCode(pressedKey) ); - } - } - } - }, - - trigger: { - change: function() { - var - inputElement = $input[0] - ; - if(inputElement) { - var events = document.createEvent('HTMLEvents'); - module.verbose('Triggering native change event'); - events.initEvent('change', true, false); - inputElement.dispatchEvent(events); - } - } - }, - - determine: { - selectAction: function(text, value) { - selectActionActive = true; - module.verbose('Determining action', settings.action); - if( $.isFunction( module.action[settings.action] ) ) { - module.verbose('Triggering preset action', settings.action, text, value); - module.action[ settings.action ].call(element, text, value, this); - } - else if( $.isFunction(settings.action) ) { - module.verbose('Triggering user action', settings.action, text, value); - settings.action.call(element, text, value, this); - } - else { - module.error(error.action, settings.action); - } - selectActionActive = false; - }, - eventInModule: function(event, callback) { - var - $target = $(event.target), - inDocument = ($target.closest(document.documentElement).length > 0), - inModule = ($target.closest($module).length > 0) - ; - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if(inDocument && !inModule) { - module.verbose('Triggering event', callback); - callback(); - return true; - } - else { - module.verbose('Event occurred in dropdown, canceling callback'); - return false; - } - }, - eventOnElement: function(event, callback) { - var - $target = $(event.target), - $label = $target.closest(selector.siblingLabel), - inVisibleDOM = document.body.contains(event.target), - notOnLabel = ($module.find($label).length === 0 || !(module.is.multiple() && settings.useLabels)), - notInMenu = ($target.closest($menu).length === 0) - ; - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if(inVisibleDOM && notOnLabel && notInMenu) { - module.verbose('Triggering event', callback); - callback(); - return true; - } - else { - module.verbose('Event occurred in dropdown menu, canceling callback'); - return false; - } - } - }, - - action: { - - nothing: function() {}, - - activate: function(text, value, element) { - value = (value !== undefined) - ? value - : text - ; - if( module.can.activate( $(element) ) ) { - module.set.selected(value, $(element)); - if(!module.is.multiple()) { - module.hideAndClear(); - } - } - }, - - select: function(text, value, element) { - value = (value !== undefined) - ? value - : text - ; - if( module.can.activate( $(element) ) ) { - module.set.value(value, text, $(element)); - if(!module.is.multiple()) { - module.hideAndClear(); - } - } - }, - - combo: function(text, value, element) { - value = (value !== undefined) - ? value - : text - ; - module.set.selected(value, $(element)); - module.hideAndClear(); - }, - - hide: function(text, value, element) { - module.set.value(value, text, $(element)); - module.hideAndClear(); - } - - }, - - get: { - id: function() { - return id; - }, - defaultText: function() { - return $module.data(metadata.defaultText); - }, - defaultValue: function() { - return $module.data(metadata.defaultValue); - }, - placeholderText: function() { - if(settings.placeholder != 'auto' && typeof settings.placeholder == 'string') { - return settings.placeholder; - } - return $module.data(metadata.placeholderText) || ''; - }, - text: function() { - return settings.preserveHTML ? $text.html() : $text.text(); - }, - query: function() { - return String($search.val()).trim(); - }, - searchWidth: function(value) { - value = (value !== undefined) - ? value - : $search.val() - ; - $sizer.text(value); - // prevent rounding issues - return Math.ceil( $sizer.width() + 1); - }, - selectionCount: function() { - var - values = module.get.values(), - count - ; - count = ( module.is.multiple() ) - ? Array.isArray(values) - ? values.length - : 0 - : (module.get.value() !== '') - ? 1 - : 0 - ; - return count; - }, - transition: function($subMenu) { - return (settings.transition == 'auto') - ? module.is.upward($subMenu) - ? 'slide up' - : 'slide down' - : settings.transition - ; - }, - userValues: function() { - var - values = module.get.values() - ; - if(!values) { - return false; - } - values = Array.isArray(values) - ? values - : [values] - ; - return $.grep(values, function(value) { - return (module.get.item(value) === false); - }); - }, - uniqueArray: function(array) { - return $.grep(array, function (value, index) { - return $.inArray(value, array) === index; - }); - }, - caretPosition: function(returnEndPos) { - var - input = $search.get(0), - range, - rangeLength - ; - if(returnEndPos && 'selectionEnd' in input){ - return input.selectionEnd; - } - else if(!returnEndPos && 'selectionStart' in input) { - return input.selectionStart; - } - if (document.selection) { - input.focus(); - range = document.selection.createRange(); - rangeLength = range.text.length; - if(returnEndPos) { - return rangeLength; - } - range.moveStart('character', -input.value.length); - return range.text.length - rangeLength; - } - }, - value: function() { - var - value = ($input.length > 0) - ? $input.val() - : $module.data(metadata.value), - isEmptyMultiselect = (Array.isArray(value) && value.length === 1 && value[0] === '') - ; - // prevents placeholder element from being selected when multiple - return (value === undefined || isEmptyMultiselect) - ? '' - : value - ; - }, - values: function() { - var - value = module.get.value() - ; - if(value === '') { - return ''; - } - return ( !module.has.selectInput() && module.is.multiple() ) - ? (typeof value == 'string') // delimited string - ? module.escape.htmlEntities(value).split(settings.delimiter) - : '' - : value - ; - }, - remoteValues: function() { - var - values = module.get.values(), - remoteValues = false - ; - if(values) { - if(typeof values == 'string') { - values = [values]; - } - $.each(values, function(index, value) { - var - name = module.read.remoteData(value) - ; - module.verbose('Restoring value from session data', name, value); - if(name) { - if(!remoteValues) { - remoteValues = {}; - } - remoteValues[value] = name; - } - }); - } - return remoteValues; - }, - choiceText: function($choice, preserveHTML) { - preserveHTML = (preserveHTML !== undefined) - ? preserveHTML - : settings.preserveHTML - ; - if($choice) { - if($choice.find(selector.menu).length > 0) { - module.verbose('Retrieving text of element with sub-menu'); - $choice = $choice.clone(); - $choice.find(selector.menu).remove(); - $choice.find(selector.menuIcon).remove(); - } - return ($choice.data(metadata.text) !== undefined) - ? $choice.data(metadata.text) - : (preserveHTML) - ? $choice.html().trim() - : $choice.text().trim() - ; - } - }, - choiceValue: function($choice, choiceText) { - choiceText = choiceText || module.get.choiceText($choice); - if(!$choice) { - return false; - } - return ($choice.data(metadata.value) !== undefined) - ? String( $choice.data(metadata.value) ) - : (typeof choiceText === 'string') - ? String( - settings.ignoreSearchCase - ? choiceText.toLowerCase() - : choiceText - ).trim() - : String(choiceText) - ; - }, - inputEvent: function() { - var - input = $search[0] - ; - if(input) { - return (input.oninput !== undefined) - ? 'input' - : (input.onpropertychange !== undefined) - ? 'propertychange' - : 'keyup' - ; - } - return false; - }, - selectValues: function() { - var - select = {}, - oldGroup = [], - values = [] - ; - $module - .find('option') - .each(function() { - var - $option = $(this), - name = $option.html(), - disabled = $option.attr('disabled'), - value = ( $option.attr('value') !== undefined ) - ? $option.attr('value') - : name, - text = ( $option.data(metadata.text) !== undefined ) - ? $option.data(metadata.text) - : name, - group = $option.parent('optgroup') - ; - if(settings.placeholder === 'auto' && value === '') { - select.placeholder = name; - } - else { - if(group.length !== oldGroup.length || group[0] !== oldGroup[0]) { - values.push({ - type: 'header', - divider: settings.headerDivider, - name: group.attr('label') || '' - }); - oldGroup = group; - } - values.push({ - name : name, - value : value, - text : text, - disabled : disabled - }); - } - }) - ; - if(settings.placeholder && settings.placeholder !== 'auto') { - module.debug('Setting placeholder value to', settings.placeholder); - select.placeholder = settings.placeholder; - } - if(settings.sortSelect) { - if(settings.sortSelect === true) { - values.sort(function(a, b) { - return a.name.localeCompare(b.name); - }); - } else if(settings.sortSelect === 'natural') { - values.sort(function(a, b) { - return (a.name.toLowerCase().localeCompare(b.name.toLowerCase())); - }); - } else if($.isFunction(settings.sortSelect)) { - values.sort(settings.sortSelect); - } - select[fields.values] = values; - module.debug('Retrieved and sorted values from select', select); - } - else { - select[fields.values] = values; - module.debug('Retrieved values from select', select); - } - return select; - }, - activeItem: function() { - return $item.filter('.' + className.active); - }, - selectedItem: function() { - var - $selectedItem = $item.not(selector.unselectable).filter('.' + className.selected) - ; - return ($selectedItem.length > 0) - ? $selectedItem - : $item.eq(0) - ; - }, - itemWithAdditions: function(value) { - var - $items = module.get.item(value), - $userItems = module.create.userChoice(value), - hasUserItems = ($userItems && $userItems.length > 0) - ; - if(hasUserItems) { - $items = ($items.length > 0) - ? $items.add($userItems) - : $userItems - ; - } - return $items; - }, - item: function(value, strict) { - var - $selectedItem = false, - shouldSearch, - isMultiple - ; - value = (value !== undefined) - ? value - : ( module.get.values() !== undefined) - ? module.get.values() - : module.get.text() - ; - isMultiple = (module.is.multiple() && Array.isArray(value)); - shouldSearch = (isMultiple) - ? (value.length > 0) - : (value !== undefined && value !== null) - ; - strict = (value === '' || value === false || value === true) - ? true - : strict || false - ; - if(shouldSearch) { - $item - .each(function() { - var - $choice = $(this), - optionText = module.get.choiceText($choice), - optionValue = module.get.choiceValue($choice, optionText) - ; - // safe early exit - if(optionValue === null || optionValue === undefined) { - return; - } - if(isMultiple) { - if($.inArray(module.escape.htmlEntities(String(optionValue)), value.map(function(v){return String(v);})) !== -1) { - $selectedItem = ($selectedItem) - ? $selectedItem.add($choice) - : $choice - ; - } - } - else if(strict) { - module.verbose('Ambiguous dropdown value using strict type check', $choice, value); - if( optionValue === value) { - $selectedItem = $choice; - return true; - } - } - else { - if(settings.ignoreCase) { - optionValue = optionValue.toLowerCase(); - value = value.toLowerCase(); - } - if(module.escape.htmlEntities(String(optionValue)) === module.escape.htmlEntities(String(value))) { - module.verbose('Found select item by value', optionValue, value); - $selectedItem = $choice; - return true; - } - } - }) - ; - } - return $selectedItem; - } - }, - - check: { - maxSelections: function(selectionCount) { - if(settings.maxSelections) { - selectionCount = (selectionCount !== undefined) - ? selectionCount - : module.get.selectionCount() - ; - if(selectionCount >= settings.maxSelections) { - module.debug('Maximum selection count reached'); - if(settings.useLabels) { - $item.addClass(className.filtered); - module.add.message(message.maxSelections); - } - return true; - } - else { - module.verbose('No longer at maximum selection count'); - module.remove.message(); - module.remove.filteredItem(); - if(module.is.searchSelection()) { - module.filterItems(); - } - return false; - } - } - return true; - }, - disabled: function(){ - $search.attr('tabindex',module.is.disabled() ? -1 : 0); - } - }, - - restore: { - defaults: function(preventChangeTrigger) { - module.clear(preventChangeTrigger); - module.restore.defaultText(); - module.restore.defaultValue(); - }, - defaultText: function() { - var - defaultText = module.get.defaultText(), - placeholderText = module.get.placeholderText - ; - if(defaultText === placeholderText) { - module.debug('Restoring default placeholder text', defaultText); - module.set.placeholderText(defaultText); - } - else { - module.debug('Restoring default text', defaultText); - module.set.text(defaultText); - } - }, - placeholderText: function() { - module.set.placeholderText(); - }, - defaultValue: function() { - var - defaultValue = module.get.defaultValue() - ; - if(defaultValue !== undefined) { - module.debug('Restoring default value', defaultValue); - if(defaultValue !== '') { - module.set.value(defaultValue); - module.set.selected(); - } - else { - module.remove.activeItem(); - module.remove.selectedItem(); - } - } - }, - labels: function() { - if(settings.allowAdditions) { - if(!settings.useLabels) { - module.error(error.labels); - settings.useLabels = true; - } - module.debug('Restoring selected values'); - module.create.userLabels(); - } - module.check.maxSelections(); - }, - selected: function() { - module.restore.values(); - if(module.is.multiple()) { - module.debug('Restoring previously selected values and labels'); - module.restore.labels(); - } - else { - module.debug('Restoring previously selected values'); - } - }, - values: function() { - // prevents callbacks from occurring on initial load - module.set.initialLoad(); - if(settings.apiSettings && settings.saveRemoteData && module.get.remoteValues()) { - module.restore.remoteValues(); - } - else { - module.set.selected(); - } - var value = module.get.value(); - if(value && value !== '' && !(Array.isArray(value) && value.length === 0)) { - $input.removeClass(className.noselection); - } else { - $input.addClass(className.noselection); - } - module.remove.initialLoad(); - }, - remoteValues: function() { - var - values = module.get.remoteValues() - ; - module.debug('Recreating selected from session data', values); - if(values) { - if( module.is.single() ) { - $.each(values, function(value, name) { - module.set.text(name); - }); - } - else { - $.each(values, function(value, name) { - module.add.label(value, name); - }); - } - } - } - }, - - read: { - remoteData: function(value) { - var - name - ; - if(window.Storage === undefined) { - module.error(error.noStorage); - return; - } - name = sessionStorage.getItem(value); - return (name !== undefined) - ? name - : false - ; - } - }, - - save: { - defaults: function() { - module.save.defaultText(); - module.save.placeholderText(); - module.save.defaultValue(); - }, - defaultValue: function() { - var - value = module.get.value() - ; - module.verbose('Saving default value as', value); - $module.data(metadata.defaultValue, value); - }, - defaultText: function() { - var - text = module.get.text() - ; - module.verbose('Saving default text as', text); - $module.data(metadata.defaultText, text); - }, - placeholderText: function() { - var - text - ; - if(settings.placeholder !== false && $text.hasClass(className.placeholder)) { - text = module.get.text(); - module.verbose('Saving placeholder text as', text); - $module.data(metadata.placeholderText, text); - } - }, - remoteData: function(name, value) { - if(window.Storage === undefined) { - module.error(error.noStorage); - return; - } - module.verbose('Saving remote data to session storage', value, name); - sessionStorage.setItem(value, name); - } - }, - - clear: function(preventChangeTrigger) { - if(module.is.multiple() && settings.useLabels) { - module.remove.labels(); - } - else { - module.remove.activeItem(); - module.remove.selectedItem(); - module.remove.filteredItem(); - } - module.set.placeholderText(); - module.clearValue(preventChangeTrigger); - }, - - clearValue: function(preventChangeTrigger) { - module.set.value('', null, null, preventChangeTrigger); - }, - - scrollPage: function(direction, $selectedItem) { - var - $currentItem = $selectedItem || module.get.selectedItem(), - $menu = $currentItem.closest(selector.menu), - menuHeight = $menu.outerHeight(), - currentScroll = $menu.scrollTop(), - itemHeight = $item.eq(0).outerHeight(), - itemsPerPage = Math.floor(menuHeight / itemHeight), - maxScroll = $menu.prop('scrollHeight'), - newScroll = (direction == 'up') - ? currentScroll - (itemHeight * itemsPerPage) - : currentScroll + (itemHeight * itemsPerPage), - $selectableItem = $item.not(selector.unselectable), - isWithinRange, - $nextSelectedItem, - elementIndex - ; - elementIndex = (direction == 'up') - ? $selectableItem.index($currentItem) - itemsPerPage - : $selectableItem.index($currentItem) + itemsPerPage - ; - isWithinRange = (direction == 'up') - ? (elementIndex >= 0) - : (elementIndex < $selectableItem.length) - ; - $nextSelectedItem = (isWithinRange) - ? $selectableItem.eq(elementIndex) - : (direction == 'up') - ? $selectableItem.first() - : $selectableItem.last() - ; - if($nextSelectedItem.length > 0) { - module.debug('Scrolling page', direction, $nextSelectedItem); - $currentItem - .removeClass(className.selected) - ; - $nextSelectedItem - .addClass(className.selected) - ; - if(settings.selectOnKeydown && module.is.single()) { - module.set.selectedItem($nextSelectedItem); - } - $menu - .scrollTop(newScroll) - ; - } - }, - - set: { - filtered: function() { - var - isMultiple = module.is.multiple(), - isSearch = module.is.searchSelection(), - isSearchMultiple = (isMultiple && isSearch), - searchValue = (isSearch) - ? module.get.query() - : '', - hasSearchValue = (typeof searchValue === 'string' && searchValue.length > 0), - searchWidth = module.get.searchWidth(), - valueIsSet = searchValue !== '' - ; - if(isMultiple && hasSearchValue) { - module.verbose('Adjusting input width', searchWidth, settings.glyphWidth); - $search.css('width', searchWidth); - } - if(hasSearchValue || (isSearchMultiple && valueIsSet)) { - module.verbose('Hiding placeholder text'); - $text.addClass(className.filtered); - } - else if(!isMultiple || (isSearchMultiple && !valueIsSet)) { - module.verbose('Showing placeholder text'); - $text.removeClass(className.filtered); - } - }, - empty: function() { - $module.addClass(className.empty); - }, - loading: function() { - $module.addClass(className.loading); - }, - placeholderText: function(text) { - text = text || module.get.placeholderText(); - module.debug('Setting placeholder text', text); - module.set.text(text); - $text.addClass(className.placeholder); - }, - tabbable: function() { - if( module.is.searchSelection() ) { - module.debug('Added tabindex to searchable dropdown'); - $search - .val('') - ; - module.check.disabled(); - $menu - .attr('tabindex', -1) - ; - } - else { - module.debug('Added tabindex to dropdown'); - if( $module.attr('tabindex') === undefined) { - $module - .attr('tabindex', 0) - ; - $menu - .attr('tabindex', -1) - ; - } - } - }, - initialLoad: function() { - module.verbose('Setting initial load'); - initialLoad = true; - }, - activeItem: function($item) { - if( settings.allowAdditions && $item.filter(selector.addition).length > 0 ) { - $item.addClass(className.filtered); - } - else { - $item.addClass(className.active); - } - }, - partialSearch: function(text) { - var - length = module.get.query().length - ; - $search.val( text.substr(0, length)); - }, - scrollPosition: function($item, forceScroll) { - var - edgeTolerance = 5, - $menu, - hasActive, - offset, - itemHeight, - itemOffset, - menuOffset, - menuScroll, - menuHeight, - abovePage, - belowPage - ; - - $item = $item || module.get.selectedItem(); - $menu = $item.closest(selector.menu); - hasActive = ($item && $item.length > 0); - forceScroll = (forceScroll !== undefined) - ? forceScroll - : false - ; - if(module.get.activeItem().length === 0){ - forceScroll = false; - } - if($item && $menu.length > 0 && hasActive) { - itemOffset = $item.position().top; - - $menu.addClass(className.loading); - menuScroll = $menu.scrollTop(); - menuOffset = $menu.offset().top; - itemOffset = $item.offset().top; - offset = menuScroll - menuOffset + itemOffset; - if(!forceScroll) { - menuHeight = $menu.height(); - belowPage = menuScroll + menuHeight < (offset + edgeTolerance); - abovePage = ((offset - edgeTolerance) < menuScroll); - } - module.debug('Scrolling to active item', offset); - if(forceScroll || abovePage || belowPage) { - $menu.scrollTop(offset); - } - $menu.removeClass(className.loading); - } - }, - text: function(text) { - if(settings.action === 'combo') { - module.debug('Changing combo button text', text, $combo); - if(settings.preserveHTML) { - $combo.html(text); - } - else { - $combo.text(text); - } - } - else if(settings.action === 'activate') { - if(text !== module.get.placeholderText()) { - $text.removeClass(className.placeholder); - } - module.debug('Changing text', text, $text); - $text - .removeClass(className.filtered) - ; - if(settings.preserveHTML) { - $text.html(text); - } - else { - $text.text(text); - } - } - }, - selectedItem: function($item) { - var - value = module.get.choiceValue($item), - searchText = module.get.choiceText($item, false), - text = module.get.choiceText($item, true) - ; - module.debug('Setting user selection to item', $item); - module.remove.activeItem(); - module.set.partialSearch(searchText); - module.set.activeItem($item); - module.set.selected(value, $item); - module.set.text(text); - }, - selectedLetter: function(letter) { - var - $selectedItem = $item.filter('.' + className.selected), - alreadySelectedLetter = $selectedItem.length > 0 && module.has.firstLetter($selectedItem, letter), - $nextValue = false, - $nextItem - ; - // check next of same letter - if(alreadySelectedLetter) { - $nextItem = $selectedItem.nextAll($item).eq(0); - if( module.has.firstLetter($nextItem, letter) ) { - $nextValue = $nextItem; - } - } - // check all values - if(!$nextValue) { - $item - .each(function(){ - if(module.has.firstLetter($(this), letter)) { - $nextValue = $(this); - return false; - } - }) - ; - } - // set next value - if($nextValue) { - module.verbose('Scrolling to next value with letter', letter); - module.set.scrollPosition($nextValue); - $selectedItem.removeClass(className.selected); - $nextValue.addClass(className.selected); - if(settings.selectOnKeydown && module.is.single()) { - module.set.selectedItem($nextValue); - } - } - }, - direction: function($menu) { - if(settings.direction == 'auto') { - // reset position, remove upward if it's base menu - if (!$menu) { - module.remove.upward(); - } else if (module.is.upward($menu)) { - //we need make sure when make assertion openDownward for $menu, $menu does not have upward class - module.remove.upward($menu); - } - - if(module.can.openDownward($menu)) { - module.remove.upward($menu); - } - else { - module.set.upward($menu); - } - if(!module.is.leftward($menu) && !module.can.openRightward($menu)) { - module.set.leftward($menu); - } - } - else if(settings.direction == 'upward') { - module.set.upward($menu); - } - }, - upward: function($currentMenu) { - var $element = $currentMenu || $module; - $element.addClass(className.upward); - }, - leftward: function($currentMenu) { - var $element = $currentMenu || $menu; - $element.addClass(className.leftward); - }, - value: function(value, text, $selected, preventChangeTrigger) { - if(value !== undefined && value !== '' && !(Array.isArray(value) && value.length === 0)) { - $input.removeClass(className.noselection); - } else { - $input.addClass(className.noselection); - } - var - escapedValue = module.escape.value(value), - hasInput = ($input.length > 0), - currentValue = module.get.values(), - stringValue = (value !== undefined) - ? String(value) - : value, - newValue - ; - if(hasInput) { - if(!settings.allowReselection && stringValue == currentValue) { - module.verbose('Skipping value update already same value', value, currentValue); - if(!module.is.initialLoad()) { - return; - } - } - - if( module.is.single() && module.has.selectInput() && module.can.extendSelect() ) { - module.debug('Adding user option', value); - module.add.optionValue(value); - } - module.debug('Updating input value', escapedValue, currentValue); - internalChange = true; - $input - .val(escapedValue) - ; - if(settings.fireOnInit === false && module.is.initialLoad()) { - module.debug('Input native change event ignored on initial load'); - } - else if(preventChangeTrigger !== true) { - module.trigger.change(); - } - internalChange = false; - } - else { - module.verbose('Storing value in metadata', escapedValue, $input); - if(escapedValue !== currentValue) { - $module.data(metadata.value, stringValue); - } - } - if(settings.fireOnInit === false && module.is.initialLoad()) { - module.verbose('No callback on initial load', settings.onChange); - } - else if(preventChangeTrigger !== true) { - settings.onChange.call(element, value, text, $selected); - } - }, - active: function() { - $module - .addClass(className.active) - ; - }, - multiple: function() { - $module.addClass(className.multiple); - }, - visible: function() { - $module.addClass(className.visible); - }, - exactly: function(value, $selectedItem) { - module.debug('Setting selected to exact values'); - module.clear(); - module.set.selected(value, $selectedItem); - }, - selected: function(value, $selectedItem) { - var - isMultiple = module.is.multiple() - ; - $selectedItem = (settings.allowAdditions) - ? $selectedItem || module.get.itemWithAdditions(value) - : $selectedItem || module.get.item(value) - ; - if(!$selectedItem) { - return; - } - module.debug('Setting selected menu item to', $selectedItem); - if(module.is.multiple()) { - module.remove.searchWidth(); - } - if(module.is.single()) { - module.remove.activeItem(); - module.remove.selectedItem(); - } - else if(settings.useLabels) { - module.remove.selectedItem(); - } - // select each item - $selectedItem - .each(function() { - var - $selected = $(this), - selectedText = module.get.choiceText($selected), - selectedValue = module.get.choiceValue($selected, selectedText), - - isFiltered = $selected.hasClass(className.filtered), - isActive = $selected.hasClass(className.active), - isUserValue = $selected.hasClass(className.addition), - shouldAnimate = (isMultiple && $selectedItem.length == 1) - ; - if(isMultiple) { - if(!isActive || isUserValue) { - if(settings.apiSettings && settings.saveRemoteData) { - module.save.remoteData(selectedText, selectedValue); - } - if(settings.useLabels) { - module.add.label(selectedValue, selectedText, shouldAnimate); - module.add.value(selectedValue, selectedText, $selected); - module.set.activeItem($selected); - module.filterActive(); - module.select.nextAvailable($selectedItem); - } - else { - module.add.value(selectedValue, selectedText, $selected); - module.set.text(module.add.variables(message.count)); - module.set.activeItem($selected); - } - } - else if(!isFiltered && (settings.useLabels || selectActionActive)) { - module.debug('Selected active value, removing label'); - module.remove.selected(selectedValue); - } - } - else { - if(settings.apiSettings && settings.saveRemoteData) { - module.save.remoteData(selectedText, selectedValue); - } - module.set.text(selectedText); - module.set.value(selectedValue, selectedText, $selected); - $selected - .addClass(className.active) - .addClass(className.selected) - ; - } - }) - ; - module.remove.searchTerm(); - } - }, - - add: { - label: function(value, text, shouldAnimate) { - var - $next = module.is.searchSelection() - ? $search - : $text, - escapedValue = module.escape.value(value), - $label - ; - if(settings.ignoreCase) { - escapedValue = escapedValue.toLowerCase(); - } - $label = $('<a />') - .addClass(className.label) - .attr('data-' + metadata.value, escapedValue) - .html(templates.label(escapedValue, text, settings.preserveHTML, settings.className)) - ; - $label = settings.onLabelCreate.call($label, escapedValue, text); - - if(module.has.label(value)) { - module.debug('User selection already exists, skipping', escapedValue); - return; - } - if(settings.label.variation) { - $label.addClass(settings.label.variation); - } - if(shouldAnimate === true) { - module.debug('Animating in label', $label); - $label - .addClass(className.hidden) - .insertBefore($next) - .transition({ - animation : settings.label.transition, - debug : settings.debug, - verbose : settings.verbose, - duration : settings.label.duration - }) - ; - } - else { - module.debug('Adding selection label', $label); - $label - .insertBefore($next) - ; - } - }, - message: function(message) { - var - $message = $menu.children(selector.message), - html = settings.templates.message(module.add.variables(message)) - ; - if($message.length > 0) { - $message - .html(html) - ; - } - else { - $message = $('<div/>') - .html(html) - .addClass(className.message) - .appendTo($menu) - ; - } - }, - optionValue: function(value) { - var - escapedValue = module.escape.value(value), - $option = $input.find('option[value="' + module.escape.string(escapedValue) + '"]'), - hasOption = ($option.length > 0) - ; - if(hasOption) { - return; - } - // temporarily disconnect observer - module.disconnect.selectObserver(); - if( module.is.single() ) { - module.verbose('Removing previous user addition'); - $input.find('option.' + className.addition).remove(); - } - $('<option/>') - .prop('value', escapedValue) - .addClass(className.addition) - .html(value) - .appendTo($input) - ; - module.verbose('Adding user addition as an <option>', value); - module.observe.select(); - }, - userSuggestion: function(value) { - var - $addition = $menu.children(selector.addition), - $existingItem = module.get.item(value), - alreadyHasValue = $existingItem && $existingItem.not(selector.addition).length, - hasUserSuggestion = $addition.length > 0, - html - ; - if(settings.useLabels && module.has.maxSelections()) { - return; - } - if(value === '' || alreadyHasValue) { - $addition.remove(); - return; - } - if(hasUserSuggestion) { - $addition - .data(metadata.value, value) - .data(metadata.text, value) - .attr('data-' + metadata.value, value) - .attr('data-' + metadata.text, value) - .removeClass(className.filtered) - ; - if(!settings.hideAdditions) { - html = settings.templates.addition( module.add.variables(message.addResult, value) ); - $addition - .html(html) - ; - } - module.verbose('Replacing user suggestion with new value', $addition); - } - else { - $addition = module.create.userChoice(value); - $addition - .prependTo($menu) - ; - module.verbose('Adding item choice to menu corresponding with user choice addition', $addition); - } - if(!settings.hideAdditions || module.is.allFiltered()) { - $addition - .addClass(className.selected) - .siblings() - .removeClass(className.selected) - ; - } - module.refreshItems(); - }, - variables: function(message, term) { - var - hasCount = (message.search('{count}') !== -1), - hasMaxCount = (message.search('{maxCount}') !== -1), - hasTerm = (message.search('{term}') !== -1), - count, - query - ; - module.verbose('Adding templated variables to message', message); - if(hasCount) { - count = module.get.selectionCount(); - message = message.replace('{count}', count); - } - if(hasMaxCount) { - count = module.get.selectionCount(); - message = message.replace('{maxCount}', settings.maxSelections); - } - if(hasTerm) { - query = term || module.get.query(); - message = message.replace('{term}', query); - } - return message; - }, - value: function(addedValue, addedText, $selectedItem) { - var - currentValue = module.get.values(), - newValue - ; - if(module.has.value(addedValue)) { - module.debug('Value already selected'); - return; - } - if(addedValue === '') { - module.debug('Cannot select blank values from multiselect'); - return; - } - // extend current array - if(Array.isArray(currentValue)) { - newValue = currentValue.concat([addedValue]); - newValue = module.get.uniqueArray(newValue); - } - else { - newValue = [addedValue]; - } - // add values - if( module.has.selectInput() ) { - if(module.can.extendSelect()) { - module.debug('Adding value to select', addedValue, newValue, $input); - module.add.optionValue(addedValue); - } - } - else { - newValue = newValue.join(settings.delimiter); - module.debug('Setting hidden input to delimited value', newValue, $input); - } - - if(settings.fireOnInit === false && module.is.initialLoad()) { - module.verbose('Skipping onadd callback on initial load', settings.onAdd); - } - else { - settings.onAdd.call(element, addedValue, addedText, $selectedItem); - } - module.set.value(newValue, addedText, $selectedItem); - module.check.maxSelections(); - }, - }, - - remove: { - active: function() { - $module.removeClass(className.active); - }, - activeLabel: function() { - $module.find(selector.label).removeClass(className.active); - }, - empty: function() { - $module.removeClass(className.empty); - }, - loading: function() { - $module.removeClass(className.loading); - }, - initialLoad: function() { - initialLoad = false; - }, - upward: function($currentMenu) { - var $element = $currentMenu || $module; - $element.removeClass(className.upward); - }, - leftward: function($currentMenu) { - var $element = $currentMenu || $menu; - $element.removeClass(className.leftward); - }, - visible: function() { - $module.removeClass(className.visible); - }, - activeItem: function() { - $item.removeClass(className.active); - }, - filteredItem: function() { - if(settings.useLabels && module.has.maxSelections() ) { - return; - } - if(settings.useLabels && module.is.multiple()) { - $item.not('.' + className.active).removeClass(className.filtered); - } - else { - $item.removeClass(className.filtered); - } - if(settings.hideDividers) { - $divider.removeClass(className.hidden); - } - module.remove.empty(); - }, - optionValue: function(value) { - var - escapedValue = module.escape.value(value), - $option = $input.find('option[value="' + module.escape.string(escapedValue) + '"]'), - hasOption = ($option.length > 0) - ; - if(!hasOption || !$option.hasClass(className.addition)) { - return; - } - // temporarily disconnect observer - if(selectObserver) { - selectObserver.disconnect(); - module.verbose('Temporarily disconnecting mutation observer'); - } - $option.remove(); - module.verbose('Removing user addition as an <option>', escapedValue); - if(selectObserver) { - selectObserver.observe($input[0], { - childList : true, - subtree : true - }); - } - }, - message: function() { - $menu.children(selector.message).remove(); - }, - searchWidth: function() { - $search.css('width', ''); - }, - searchTerm: function() { - module.verbose('Cleared search term'); - $search.val(''); - module.set.filtered(); - }, - userAddition: function() { - $item.filter(selector.addition).remove(); - }, - selected: function(value, $selectedItem) { - $selectedItem = (settings.allowAdditions) - ? $selectedItem || module.get.itemWithAdditions(value) - : $selectedItem || module.get.item(value) - ; - - if(!$selectedItem) { - return false; - } - - $selectedItem - .each(function() { - var - $selected = $(this), - selectedText = module.get.choiceText($selected), - selectedValue = module.get.choiceValue($selected, selectedText) - ; - if(module.is.multiple()) { - if(settings.useLabels) { - module.remove.value(selectedValue, selectedText, $selected); - module.remove.label(selectedValue); - } - else { - module.remove.value(selectedValue, selectedText, $selected); - if(module.get.selectionCount() === 0) { - module.set.placeholderText(); - } - else { - module.set.text(module.add.variables(message.count)); - } - } - } - else { - module.remove.value(selectedValue, selectedText, $selected); - } - $selected - .removeClass(className.filtered) - .removeClass(className.active) - ; - if(settings.useLabels) { - $selected.removeClass(className.selected); - } - }) - ; - }, - selectedItem: function() { - $item.removeClass(className.selected); - }, - value: function(removedValue, removedText, $removedItem) { - var - values = module.get.values(), - newValue - ; - removedValue = module.escape.htmlEntities(removedValue); - if( module.has.selectInput() ) { - module.verbose('Input is <select> removing selected option', removedValue); - newValue = module.remove.arrayValue(removedValue, values); - module.remove.optionValue(removedValue); - } - else { - module.verbose('Removing from delimited values', removedValue); - newValue = module.remove.arrayValue(removedValue, values); - newValue = newValue.join(settings.delimiter); - } - if(settings.fireOnInit === false && module.is.initialLoad()) { - module.verbose('No callback on initial load', settings.onRemove); - } - else { - settings.onRemove.call(element, removedValue, removedText, $removedItem); - } - module.set.value(newValue, removedText, $removedItem); - module.check.maxSelections(); - }, - arrayValue: function(removedValue, values) { - if( !Array.isArray(values) ) { - values = [values]; - } - values = $.grep(values, function(value){ - return (removedValue != value); - }); - module.verbose('Removed value from delimited string', removedValue, values); - return values; - }, - label: function(value, shouldAnimate) { - var - $labels = $module.find(selector.label), - $removedLabel = $labels.filter('[data-' + metadata.value + '="' + module.escape.string(settings.ignoreCase ? value.toLowerCase() : value) +'"]') - ; - module.verbose('Removing label', $removedLabel); - $removedLabel.remove(); - }, - activeLabels: function($activeLabels) { - $activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active); - module.verbose('Removing active label selections', $activeLabels); - module.remove.labels($activeLabels); - }, - labels: function($labels) { - $labels = $labels || $module.find(selector.label); - module.verbose('Removing labels', $labels); - $labels - .each(function(){ - var - $label = $(this), - value = $label.data(metadata.value), - stringValue = (value !== undefined) - ? String(value) - : value, - isUserValue = module.is.userValue(stringValue) - ; - if(settings.onLabelRemove.call($label, value) === false) { - module.debug('Label remove callback cancelled removal'); - return; - } - module.remove.message(); - if(isUserValue) { - module.remove.value(stringValue); - module.remove.label(stringValue); - } - else { - // selected will also remove label - module.remove.selected(stringValue); - } - }) - ; - }, - tabbable: function() { - if( module.is.searchSelection() ) { - module.debug('Searchable dropdown initialized'); - $search - .removeAttr('tabindex') - ; - $menu - .removeAttr('tabindex') - ; - } - else { - module.debug('Simple selection dropdown initialized'); - $module - .removeAttr('tabindex') - ; - $menu - .removeAttr('tabindex') - ; - } - }, - diacritics: function(text) { - return settings.ignoreDiacritics ? text.normalize('NFD').replace(/[\u0300-\u036f]/g, '') : text; - } - }, - - has: { - menuSearch: function() { - return (module.has.search() && $search.closest($menu).length > 0); - }, - clearItem: function() { - return ($clear.length > 0); - }, - search: function() { - return ($search.length > 0); - }, - sizer: function() { - return ($sizer.length > 0); - }, - selectInput: function() { - return ( $input.is('select') ); - }, - minCharacters: function(searchTerm) { - if(settings.minCharacters && !iconClicked) { - searchTerm = (searchTerm !== undefined) - ? String(searchTerm) - : String(module.get.query()) - ; - return (searchTerm.length >= settings.minCharacters); - } - iconClicked=false; - return true; - }, - firstLetter: function($item, letter) { - var - text, - firstLetter - ; - if(!$item || $item.length === 0 || typeof letter !== 'string') { - return false; - } - text = module.get.choiceText($item, false); - letter = letter.toLowerCase(); - firstLetter = String(text).charAt(0).toLowerCase(); - return (letter == firstLetter); - }, - input: function() { - return ($input.length > 0); - }, - items: function() { - return ($item.length > 0); - }, - menu: function() { - return ($menu.length > 0); - }, - message: function() { - return ($menu.children(selector.message).length !== 0); - }, - label: function(value) { - var - escapedValue = module.escape.value(value), - $labels = $module.find(selector.label) - ; - if(settings.ignoreCase) { - escapedValue = escapedValue.toLowerCase(); - } - return ($labels.filter('[data-' + metadata.value + '="' + module.escape.string(escapedValue) +'"]').length > 0); - }, - maxSelections: function() { - return (settings.maxSelections && module.get.selectionCount() >= settings.maxSelections); - }, - allResultsFiltered: function() { - var - $normalResults = $item.not(selector.addition) - ; - return ($normalResults.filter(selector.unselectable).length === $normalResults.length); - }, - userSuggestion: function() { - return ($menu.children(selector.addition).length > 0); - }, - query: function() { - return (module.get.query() !== ''); - }, - value: function(value) { - return (settings.ignoreCase) - ? module.has.valueIgnoringCase(value) - : module.has.valueMatchingCase(value) - ; - }, - valueMatchingCase: function(value) { - var - values = module.get.values(), - hasValue = Array.isArray(values) - ? values && ($.inArray(value, values) !== -1) - : (values == value) - ; - return (hasValue) - ? true - : false - ; - }, - valueIgnoringCase: function(value) { - var - values = module.get.values(), - hasValue = false - ; - if(!Array.isArray(values)) { - values = [values]; - } - $.each(values, function(index, existingValue) { - if(String(value).toLowerCase() == String(existingValue).toLowerCase()) { - hasValue = true; - return false; - } - }); - return hasValue; - } - }, - - is: { - active: function() { - return $module.hasClass(className.active); - }, - animatingInward: function() { - return $menu.transition('is inward'); - }, - animatingOutward: function() { - return $menu.transition('is outward'); - }, - bubbledLabelClick: function(event) { - return $(event.target).is('select, input') && $module.closest('label').length > 0; - }, - bubbledIconClick: function(event) { - return $(event.target).closest($icon).length > 0; - }, - alreadySetup: function() { - return ($module.is('select') && $module.parent(selector.dropdown).data(moduleNamespace) !== undefined && $module.prev().length === 0); - }, - animating: function($subMenu) { - return ($subMenu) - ? $subMenu.transition && $subMenu.transition('is animating') - : $menu.transition && $menu.transition('is animating') - ; - }, - leftward: function($subMenu) { - var $selectedMenu = $subMenu || $menu; - return $selectedMenu.hasClass(className.leftward); - }, - clearable: function() { - return ($module.hasClass(className.clearable) || settings.clearable); - }, - disabled: function() { - return $module.hasClass(className.disabled); - }, - focused: function() { - return (document.activeElement === $module[0]); - }, - focusedOnSearch: function() { - return (document.activeElement === $search[0]); - }, - allFiltered: function() { - return( (module.is.multiple() || module.has.search()) && !(settings.hideAdditions == false && module.has.userSuggestion()) && !module.has.message() && module.has.allResultsFiltered() ); - }, - hidden: function($subMenu) { - return !module.is.visible($subMenu); - }, - initialLoad: function() { - return initialLoad; - }, - inObject: function(needle, object) { - var - found = false - ; - $.each(object, function(index, property) { - if(property == needle) { - found = true; - return true; - } - }); - return found; - }, - multiple: function() { - return $module.hasClass(className.multiple); - }, - remote: function() { - return settings.apiSettings && module.can.useAPI(); - }, - single: function() { - return !module.is.multiple(); - }, - selectMutation: function(mutations) { - var - selectChanged = false - ; - $.each(mutations, function(index, mutation) { - if($(mutation.target).is('select') || $(mutation.addedNodes).is('select')) { - selectChanged = true; - return false; - } - }); - return selectChanged; - }, - search: function() { - return $module.hasClass(className.search); - }, - searchSelection: function() { - return ( module.has.search() && $search.parent(selector.dropdown).length === 1 ); - }, - selection: function() { - return $module.hasClass(className.selection); - }, - userValue: function(value) { - return ($.inArray(value, module.get.userValues()) !== -1); - }, - upward: function($menu) { - var $element = $menu || $module; - return $element.hasClass(className.upward); - }, - visible: function($subMenu) { - return ($subMenu) - ? $subMenu.hasClass(className.visible) - : $menu.hasClass(className.visible) - ; - }, - verticallyScrollableContext: function() { - var - overflowY = ($context.get(0) !== window) - ? $context.css('overflow-y') - : false - ; - return (overflowY == 'auto' || overflowY == 'scroll'); - }, - horizontallyScrollableContext: function() { - var - overflowX = ($context.get(0) !== window) - ? $context.css('overflow-X') - : false - ; - return (overflowX == 'auto' || overflowX == 'scroll'); - } - }, - - can: { - activate: function($item) { - if(settings.useLabels) { - return true; - } - if(!module.has.maxSelections()) { - return true; - } - if(module.has.maxSelections() && $item.hasClass(className.active)) { - return true; - } - return false; - }, - openDownward: function($subMenu) { - var - $currentMenu = $subMenu || $menu, - canOpenDownward = true, - onScreen = {}, - calculations - ; - $currentMenu - .addClass(className.loading) - ; - calculations = { - context: { - offset : ($context.get(0) === window) - ? { top: 0, left: 0} - : $context.offset(), - scrollTop : $context.scrollTop(), - height : $context.outerHeight() - }, - menu : { - offset: $currentMenu.offset(), - height: $currentMenu.outerHeight() - } - }; - if(module.is.verticallyScrollableContext()) { - calculations.menu.offset.top += calculations.context.scrollTop; - } - onScreen = { - above : (calculations.context.scrollTop) <= calculations.menu.offset.top - calculations.context.offset.top - calculations.menu.height, - below : (calculations.context.scrollTop + calculations.context.height) >= calculations.menu.offset.top - calculations.context.offset.top + calculations.menu.height - }; - if(onScreen.below) { - module.verbose('Dropdown can fit in context downward', onScreen); - canOpenDownward = true; - } - else if(!onScreen.below && !onScreen.above) { - module.verbose('Dropdown cannot fit in either direction, favoring downward', onScreen); - canOpenDownward = true; - } - else { - module.verbose('Dropdown cannot fit below, opening upward', onScreen); - canOpenDownward = false; - } - $currentMenu.removeClass(className.loading); - return canOpenDownward; - }, - openRightward: function($subMenu) { - var - $currentMenu = $subMenu || $menu, - canOpenRightward = true, - isOffscreenRight = false, - calculations - ; - $currentMenu - .addClass(className.loading) - ; - calculations = { - context: { - offset : ($context.get(0) === window) - ? { top: 0, left: 0} - : $context.offset(), - scrollLeft : $context.scrollLeft(), - width : $context.outerWidth() - }, - menu: { - offset : $currentMenu.offset(), - width : $currentMenu.outerWidth() - } - }; - if(module.is.horizontallyScrollableContext()) { - calculations.menu.offset.left += calculations.context.scrollLeft; - } - isOffscreenRight = (calculations.menu.offset.left - calculations.context.offset.left + calculations.menu.width >= calculations.context.scrollLeft + calculations.context.width); - if(isOffscreenRight) { - module.verbose('Dropdown cannot fit in context rightward', isOffscreenRight); - canOpenRightward = false; - } - $currentMenu.removeClass(className.loading); - return canOpenRightward; - }, - click: function() { - return (hasTouch || settings.on == 'click'); - }, - extendSelect: function() { - return settings.allowAdditions || settings.apiSettings; - }, - show: function() { - return !module.is.disabled() && (module.has.items() || module.has.message()); - }, - useAPI: function() { - return $.fn.api !== undefined; - } - }, - - animate: { - show: function(callback, $subMenu) { - var - $currentMenu = $subMenu || $menu, - start = ($subMenu) - ? function() {} - : function() { - module.hideSubMenus(); - module.hideOthers(); - module.set.active(); - }, - transition - ; - callback = $.isFunction(callback) - ? callback - : function(){} - ; - module.verbose('Doing menu show animation', $currentMenu); - module.set.direction($subMenu); - transition = module.get.transition($subMenu); - if( module.is.selection() ) { - module.set.scrollPosition(module.get.selectedItem(), true); - } - if( module.is.hidden($currentMenu) || module.is.animating($currentMenu) ) { - var displayType = $module.hasClass('column') ? 'flex' : false; - if(transition == 'none') { - start(); - $currentMenu.transition({ - displayType: displayType - }).transition('show'); - callback.call(element); - } - else if($.fn.transition !== undefined && $module.transition('is supported')) { - $currentMenu - .transition({ - animation : transition + ' in', - debug : settings.debug, - verbose : settings.verbose, - duration : settings.duration, - queue : true, - onStart : start, - displayType: displayType, - onComplete : function() { - callback.call(element); - } - }) - ; - } - else { - module.error(error.noTransition, transition); - } - } - }, - hide: function(callback, $subMenu) { - var - $currentMenu = $subMenu || $menu, - start = ($subMenu) - ? function() {} - : function() { - if( module.can.click() ) { - module.unbind.intent(); - } - module.remove.active(); - }, - transition = module.get.transition($subMenu) - ; - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if( module.is.visible($currentMenu) || module.is.animating($currentMenu) ) { - module.verbose('Doing menu hide animation', $currentMenu); - - if(transition == 'none') { - start(); - $currentMenu.transition('hide'); - callback.call(element); - } - else if($.fn.transition !== undefined && $module.transition('is supported')) { - $currentMenu - .transition({ - animation : transition + ' out', - duration : settings.duration, - debug : settings.debug, - verbose : settings.verbose, - queue : false, - onStart : start, - onComplete : function() { - callback.call(element); - } - }) - ; - } - else { - module.error(error.transition); - } - } - } - }, - - hideAndClear: function() { - module.remove.searchTerm(); - if( module.has.maxSelections() ) { - return; - } - if(module.has.search()) { - module.hide(function() { - module.remove.filteredItem(); - }); - } - else { - module.hide(); - } - }, - - delay: { - show: function() { - module.verbose('Delaying show event to ensure user intent'); - clearTimeout(module.timer); - module.timer = setTimeout(module.show, settings.delay.show); - }, - hide: function() { - module.verbose('Delaying hide event to ensure user intent'); - clearTimeout(module.timer); - module.timer = setTimeout(module.hide, settings.delay.hide); - } - }, - - escape: { - value: function(value) { - var - multipleValues = Array.isArray(value), - stringValue = (typeof value === 'string'), - isUnparsable = (!stringValue && !multipleValues), - hasQuotes = (stringValue && value.search(regExp.quote) !== -1), - values = [] - ; - if(isUnparsable || !hasQuotes) { - return value; - } - module.debug('Encoding quote values for use in select', value); - if(multipleValues) { - $.each(value, function(index, value){ - values.push(value.replace(regExp.quote, '"')); - }); - return values; - } - return value.replace(regExp.quote, '"'); - }, - string: function(text) { - text = String(text); - return text.replace(regExp.escape, '\\$&'); - }, - htmlEntities: function(string) { - var - badChars = /[<>"'`]/g, - shouldEscape = /[&<>"'`]/, - escape = { - "<": "<", - ">": ">", - '"': """, - "'": "'", - "`": "`" - }, - escapedChar = function(chr) { - return escape[chr]; - } - ; - if(shouldEscape.test(string)) { - string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); - return string.replace(badChars, escapedChar); - } - return string; - } - }, - - setting: function(name, value) { - module.debug('Changing setting', name, value); - if( $.isPlainObject(name) ) { - $.extend(true, settings, name); - } - else if(value !== undefined) { - if($.isPlainObject(settings[name])) { - $.extend(true, settings[name], value); - } - else { - settings[name] = value; - } - } - else { - return settings[name]; - } - }, - internal: function(name, value) { - if( $.isPlainObject(name) ) { - $.extend(true, module, name); - } - else if(value !== undefined) { - module[name] = value; - } - else { - return module[name]; - } - }, - debug: function() { - if(!settings.silent && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.debug.apply(console, arguments); - } - } - }, - verbose: function() { - if(!settings.silent && settings.verbose && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.verbose.apply(console, arguments); - } - } - }, - error: function() { - if(!settings.silent) { - module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); - module.error.apply(console, arguments); - } - }, - performance: { - log: function(message) { - var - currentTime, - executionTime, - previousTime - ; - if(settings.performance) { - currentTime = new Date().getTime(); - previousTime = time || currentTime; - executionTime = currentTime - previousTime; - time = currentTime; - performance.push({ - 'Name' : message[0], - 'Arguments' : [].slice.call(message, 1) || '', - 'Element' : element, - 'Execution Time' : executionTime - }); - } - clearTimeout(module.performance.timer); - module.performance.timer = setTimeout(module.performance.display, 500); - }, - display: function() { - var - title = settings.name + ':', - totalTime = 0 - ; - time = false; - clearTimeout(module.performance.timer); - $.each(performance, function(index, data) { - totalTime += data['Execution Time']; - }); - title += ' ' + totalTime + 'ms'; - if(moduleSelector) { - title += ' \'' + moduleSelector + '\''; - } - if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { - console.groupCollapsed(title); - if(console.table) { - console.table(performance); - } - else { - $.each(performance, function(index, data) { - console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); - }); - } - console.groupEnd(); - } - performance = []; - } - }, - invoke: function(query, passedArguments, context) { - var - object = instance, - maxDepth, - found, - response - ; - passedArguments = passedArguments || queryArguments; - context = element || context; - if(typeof query == 'string' && object !== undefined) { - query = query.split(/[\. ]/); - maxDepth = query.length - 1; - $.each(query, function(depth, value) { - var camelCaseValue = (depth != maxDepth) - ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) - : query - ; - if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { - object = object[camelCaseValue]; - } - else if( object[camelCaseValue] !== undefined ) { - found = object[camelCaseValue]; - return false; - } - else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { - object = object[value]; - } - else if( object[value] !== undefined ) { - found = object[value]; - return false; - } - else { - module.error(error.method, query); - return false; - } - }); - } - if ( $.isFunction( found ) ) { - response = found.apply(context, passedArguments); - } - else if(found !== undefined) { - response = found; - } - if(Array.isArray(returnedValue)) { - returnedValue.push(response); - } - else if(returnedValue !== undefined) { - returnedValue = [returnedValue, response]; - } - else if(response !== undefined) { - returnedValue = response; - } - return found; - } - }; - - if(methodInvoked) { - if(instance === undefined) { - module.initialize(); - } - module.invoke(query); - } - else { - if(instance !== undefined) { - instance.invoke('destroy'); - } - module.initialize(); - } - }) - ; - return (returnedValue !== undefined) - ? returnedValue - : $allModules - ; -}; - -$.fn.dropdown.settings = { - - silent : false, - debug : false, - verbose : false, - performance : true, - - on : 'click', // what event should show menu action on item selection - action : 'activate', // action on item selection (nothing, activate, select, combo, hide, function(){}) - - values : false, // specify values to use for dropdown - - clearable : false, // whether the value of the dropdown can be cleared - - apiSettings : false, - selectOnKeydown : true, // Whether selection should occur automatically when keyboard shortcuts used - minCharacters : 0, // Minimum characters required to trigger API call - - filterRemoteData : false, // Whether API results should be filtered after being returned for query term - saveRemoteData : true, // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh - - throttle : 200, // How long to wait after last user input to search remotely - - context : window, // Context to use when determining if on screen - direction : 'auto', // Whether dropdown should always open in one direction - keepOnScreen : true, // Whether dropdown should check whether it is on screen before showing - - match : 'both', // what to match against with search selection (both, text, or label) - fullTextSearch : false, // search anywhere in value (set to 'exact' to require exact matches) - ignoreDiacritics : false, // match results also if they contain diacritics of the same base character (for example searching for "a" will also match "á" or "â" or "à", etc...) - hideDividers : false, // Whether to hide any divider elements (specified in selector.divider) that are sibling to any items when searched (set to true will hide all dividers, set to 'empty' will hide them when they are not followed by a visible item) - - placeholder : 'auto', // whether to convert blank <select> values to placeholder text - preserveHTML : true, // preserve html when selecting value - sortSelect : false, // sort selection on init - - forceSelection : true, // force a choice on blur with search selection - - allowAdditions : false, // whether multiple select should allow user added values - ignoreCase : false, // whether to consider case sensitivity when creating labels - ignoreSearchCase : true, // whether to consider case sensitivity when filtering items - hideAdditions : true, // whether or not to hide special message prompting a user they can enter a value - - maxSelections : false, // When set to a number limits the number of selections to this count - useLabels : true, // whether multiple select should filter currently active selections from choices - delimiter : ',', // when multiselect uses normal <input> the values will be delimited with this character - - showOnFocus : true, // show menu on focus - allowReselection : false, // whether current value should trigger callbacks when reselected - allowTab : true, // add tabindex to element - allowCategorySelection : false, // allow elements with sub-menus to be selected - - fireOnInit : false, // Whether callbacks should fire when initializing dropdown values - - transition : 'auto', // auto transition will slide down or up based on direction - duration : 200, // duration of transition - - glyphWidth : 1.037, // widest glyph width in em (W is 1.037 em) used to calculate multiselect input width - - headerDivider : true, // whether option headers should have an additional divider line underneath when converted from <select> <optgroup> - - // label settings on multi-select - label: { - transition : 'scale', - duration : 200, - variation : false - }, - - // delay before event - delay : { - hide : 300, - show : 200, - search : 20, - touch : 50 - }, - - /* Callbacks */ - onChange : function(value, text, $selected){}, - onAdd : function(value, text, $selected){}, - onRemove : function(value, text, $selected){}, - - onLabelSelect : function($selectedLabels){}, - onLabelCreate : function(value, text) { return $(this); }, - onLabelRemove : function(value) { return true; }, - onNoResults : function(searchTerm) { return true; }, - onShow : function(){}, - onHide : function(){}, - - /* Component */ - name : 'Dropdown', - namespace : 'dropdown', - - message: { - addResult : 'Add <b>{term}</b>', - count : '{count} selected', - maxSelections : 'Max {maxCount} selections', - noResults : 'No results found.', - serverError : 'There was an error contacting the server' - }, - - error : { - action : 'You called a dropdown action that was not defined', - alreadySetup : 'Once a select has been initialized behaviors must be called on the created ui dropdown', - labels : 'Allowing user additions currently requires the use of labels.', - missingMultiple : '<select> requires multiple property to be set to correctly preserve multiple values', - method : 'The method you called is not defined.', - noAPI : 'The API module is required to load resources remotely', - noStorage : 'Saving remote data requires session storage', - noTransition : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>', - noNormalize : '"ignoreDiacritics" setting will be ignored. Browser does not support String().normalize(). You may consider including <https://cdn.jsdelivr.net/npm/unorm@1.4.1/lib/unorm.min.js> as a polyfill.' - }, - - regExp : { - escape : /[-[\]{}()*+?.,\\^$|#\s:=@]/g, - quote : /"/g - }, - - metadata : { - defaultText : 'defaultText', - defaultValue : 'defaultValue', - placeholderText : 'placeholder', - text : 'text', - value : 'value' - }, - - // property names for remote query - fields: { - remoteValues : 'results', // grouping for api results - values : 'values', // grouping for all dropdown values - disabled : 'disabled', // whether value should be disabled - name : 'name', // displayed dropdown text - value : 'value', // actual dropdown value - text : 'text', // displayed text when selected - type : 'type', // type of dropdown element - image : 'image', // optional image path - imageClass : 'imageClass', // optional individual class for image - icon : 'icon', // optional icon name - iconClass : 'iconClass', // optional individual class for icon (for example to use flag instead) - class : 'class', // optional individual class for item/header - divider : 'divider' // optional divider append for group headers - }, - - keys : { - backspace : 8, - delimiter : 188, // comma - deleteKey : 46, - enter : 13, - escape : 27, - pageUp : 33, - pageDown : 34, - leftArrow : 37, - upArrow : 38, - rightArrow : 39, - downArrow : 40 - }, - - selector : { - addition : '.addition', - divider : '.divider, .header', - dropdown : '.ui.dropdown', - hidden : '.hidden', - icon : '> .dropdown.icon', - input : '> input[type="hidden"], > select', - item : '.item', - label : '> .label', - remove : '> .label > .delete.icon', - siblingLabel : '.label', - menu : '.menu', - message : '.message', - menuIcon : '.dropdown.icon', - search : 'input.search, .menu > .search > input, .menu input.search', - sizer : '> span.sizer', - text : '> .text:not(.icon)', - unselectable : '.disabled, .filtered', - clearIcon : '> .remove.icon' - }, - - className : { - active : 'active', - addition : 'addition', - animating : 'animating', - disabled : 'disabled', - empty : 'empty', - dropdown : 'ui dropdown', - filtered : 'filtered', - hidden : 'hidden transition', - icon : 'icon', - image : 'image', - item : 'item', - label : 'ui label', - loading : 'loading', - menu : 'menu', - message : 'message', - multiple : 'multiple', - placeholder : 'default', - sizer : 'sizer', - search : 'search', - selected : 'selected', - selection : 'selection', - upward : 'upward', - leftward : 'left', - visible : 'visible', - clearable : 'clearable', - noselection : 'noselection', - delete : 'delete', - header : 'header', - divider : 'divider', - groupIcon : '', - unfilterable : 'unfilterable' - } - -}; - -/* Templates */ -$.fn.dropdown.settings.templates = { - deQuote: function(string) { - return String(string).replace(/"/g,""); - }, - escape: function(string, preserveHTML) { - if (preserveHTML){ - return string; - } - var - badChars = /[<>"'`]/g, - shouldEscape = /[&<>"'`]/, - escape = { - "<": "<", - ">": ">", - '"': """, - "'": "'", - "`": "`" - }, - escapedChar = function(chr) { - return escape[chr]; - } - ; - if(shouldEscape.test(string)) { - string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); - return string.replace(badChars, escapedChar); - } - return string; - }, - // generates dropdown from select values - dropdown: function(select, fields, preserveHTML, className) { - var - placeholder = select.placeholder || false, - html = '', - escape = $.fn.dropdown.settings.templates.escape - ; - html += '<i class="dropdown icon"></i>'; - if(placeholder) { - html += '<div class="default text">' + escape(placeholder,preserveHTML) + '</div>'; - } - else { - html += '<div class="text"></div>'; - } - html += '<div class="'+className.menu+'">'; - html += $.fn.dropdown.settings.templates.menu(select, fields, preserveHTML,className); - html += '</div>'; - return html; - }, - - // generates just menu from select - menu: function(response, fields, preserveHTML, className) { - var - values = response[fields.values] || [], - html = '', - escape = $.fn.dropdown.settings.templates.escape, - deQuote = $.fn.dropdown.settings.templates.deQuote - ; - $.each(values, function(index, option) { - var - itemType = (option[fields.type]) - ? option[fields.type] - : 'item' - ; - - if( itemType === 'item' ) { - var - maybeText = (option[fields.text]) - ? ' data-text="' + deQuote(option[fields.text]) + '"' - : '', - maybeDisabled = (option[fields.disabled]) - ? className.disabled+' ' - : '' - ; - html += '<div class="'+ maybeDisabled + (option[fields.class] ? deQuote(option[fields.class]) : className.item)+'" data-value="' + deQuote(option[fields.value]) + '"' + maybeText + '>'; - if(option[fields.image]) { - html += '<img class="'+(option[fields.imageClass] ? deQuote(option[fields.imageClass]) : className.image)+'" src="' + deQuote(option[fields.image]) + '">'; - } - if(option[fields.icon]) { - html += '<i class="'+deQuote(option[fields.icon])+' '+(option[fields.iconClass] ? deQuote(option[fields.iconClass]) : className.icon)+'"></i>'; - } - html += escape(option[fields.name] || '', preserveHTML); - html += '</div>'; - } else if (itemType === 'header') { - var groupName = escape(option[fields.name] || '', preserveHTML), - groupIcon = option[fields.icon] ? deQuote(option[fields.icon]) : className.groupIcon - ; - if(groupName !== '' || groupIcon !== '') { - html += '<div class="' + (option[fields.class] ? deQuote(option[fields.class]) : className.header) + '">'; - if (groupIcon !== '') { - html += '<i class="' + groupIcon + ' ' + (option[fields.iconClass] ? deQuote(option[fields.iconClass]) : className.icon) + '"></i>'; - } - html += groupName; - html += '</div>'; - } - if(option[fields.divider]){ - html += '<div class="'+className.divider+'"></div>'; - } - } - }); - return html; - }, - - // generates label for multiselect - label: function(value, text, preserveHTML, className) { - var - escape = $.fn.dropdown.settings.templates.escape; - return escape(text,preserveHTML) + '<i class="'+className.delete+' icon"></i>'; - }, - - - // generates messages like "No results" - message: function(message) { - return message; - }, - - // generates user addition to selection menu - addition: function(choice) { - return choice; - } - -}; - -})( jQuery, window, document ); - -/*! - * # Fomantic-UI - Form Validation - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -;(function ($, window, document, undefined) { - -'use strict'; - -$.isFunction = $.isFunction || function(obj) { - return typeof obj === "function" && typeof obj.nodeType !== "number"; -}; - -window = (typeof window != 'undefined' && window.Math == Math) - ? window - : (typeof self != 'undefined' && self.Math == Math) - ? self - : Function('return this')() -; - -$.fn.form = function(parameters) { - var - $allModules = $(this), - moduleSelector = $allModules.selector || '', - - time = new Date().getTime(), - performance = [], - - query = arguments[0], - legacyParameters = arguments[1], - methodInvoked = (typeof query == 'string'), - queryArguments = [].slice.call(arguments, 1), - returnedValue - ; - $allModules - .each(function() { - var - $module = $(this), - element = this, - - formErrors = [], - keyHeldDown = false, - - // set at run-time - $field, - $group, - $message, - $prompt, - $submit, - $clear, - $reset, - - settings, - validation, - - metadata, - selector, - className, - regExp, - error, - - namespace, - moduleNamespace, - eventNamespace, - - submitting = false, - dirty = false, - history = ['clean', 'clean'], - - instance, - module - ; - - module = { - - initialize: function() { - - // settings grabbed at run time - module.get.settings(); - if(methodInvoked) { - if(instance === undefined) { - module.instantiate(); - } - module.invoke(query); - } - else { - if(instance !== undefined) { - instance.invoke('destroy'); - } - module.verbose('Initializing form validation', $module, settings); - module.bindEvents(); - module.set.defaults(); - if (settings.autoCheckRequired) { - module.set.autoCheck(); - } - module.instantiate(); - } - }, - - instantiate: function() { - module.verbose('Storing instance of module', module); - instance = module; - $module - .data(moduleNamespace, module) - ; - }, - - destroy: function() { - module.verbose('Destroying previous module', instance); - module.removeEvents(); - $module - .removeData(moduleNamespace) - ; - }, - - refresh: function() { - module.verbose('Refreshing selector cache'); - $field = $module.find(selector.field); - $group = $module.find(selector.group); - $message = $module.find(selector.message); - $prompt = $module.find(selector.prompt); - - $submit = $module.find(selector.submit); - $clear = $module.find(selector.clear); - $reset = $module.find(selector.reset); - }, - - submit: function() { - module.verbose('Submitting form', $module); - submitting = true; - $module.submit(); - }, - - attachEvents: function(selector, action) { - action = action || 'submit'; - $(selector).on('click' + eventNamespace, function(event) { - module[action](); - event.preventDefault(); - }); - }, - - bindEvents: function() { - module.verbose('Attaching form events'); - $module - .on('submit' + eventNamespace, module.validate.form) - .on('blur' + eventNamespace, selector.field, module.event.field.blur) - .on('click' + eventNamespace, selector.submit, module.submit) - .on('click' + eventNamespace, selector.reset, module.reset) - .on('click' + eventNamespace, selector.clear, module.clear) - ; - if(settings.keyboardShortcuts) { - $module.on('keydown' + eventNamespace, selector.field, module.event.field.keydown); - } - $field.each(function(index, el) { - var - $input = $(el), - type = $input.prop('type'), - inputEvent = module.get.changeEvent(type, $input) - ; - $input.on(inputEvent + eventNamespace, module.event.field.change); - }); - - // Dirty events - if (settings.preventLeaving) { - $(window).on('beforeunload' + eventNamespace, module.event.beforeUnload); - } - - $field.on('change click keyup keydown blur', function(e) { - $(this).triggerHandler(e.type + ".dirty"); - }); - - $field.on('change.dirty click.dirty keyup.dirty keydown.dirty blur.dirty', module.determine.isDirty); - - $module.on('dirty' + eventNamespace, function(e) { - settings.onDirty.call(); - }); - - $module.on('clean' + eventNamespace, function(e) { - settings.onClean.call(); - }) - }, - - clear: function() { - $field.each(function (index, el) { - var - $field = $(el), - $element = $field.parent(), - $fieldGroup = $field.closest($group), - $prompt = $fieldGroup.find(selector.prompt), - $calendar = $field.closest(selector.uiCalendar), - defaultValue = $field.data(metadata.defaultValue) || '', - isCheckbox = $element.is(selector.uiCheckbox), - isDropdown = $element.is(selector.uiDropdown) && module.can.useElement('dropdown'), - isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), - isErrored = $fieldGroup.hasClass(className.error) - ; - if(isErrored) { - module.verbose('Resetting error on field', $fieldGroup); - $fieldGroup.removeClass(className.error); - $prompt.remove(); - } - if(isDropdown) { - module.verbose('Resetting dropdown value', $element, defaultValue); - $element.dropdown('clear', true); - } - else if(isCheckbox) { - $field.prop('checked', false); - } - else if (isCalendar) { - $calendar.calendar('clear'); - } - else { - module.verbose('Resetting field value', $field, defaultValue); - $field.val(''); - } - }); - module.remove.states(); - }, - - reset: function() { - $field.each(function (index, el) { - var - $field = $(el), - $element = $field.parent(), - $fieldGroup = $field.closest($group), - $calendar = $field.closest(selector.uiCalendar), - $prompt = $fieldGroup.find(selector.prompt), - defaultValue = $field.data(metadata.defaultValue), - isCheckbox = $element.is(selector.uiCheckbox), - isDropdown = $element.is(selector.uiDropdown) && module.can.useElement('dropdown'), - isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), - isErrored = $fieldGroup.hasClass(className.error) - ; - if(defaultValue === undefined) { - return; - } - if(isErrored) { - module.verbose('Resetting error on field', $fieldGroup); - $fieldGroup.removeClass(className.error); - $prompt.remove(); - } - if(isDropdown) { - module.verbose('Resetting dropdown value', $element, defaultValue); - $element.dropdown('restore defaults', true); - } - else if(isCheckbox) { - module.verbose('Resetting checkbox value', $element, defaultValue); - $field.prop('checked', defaultValue); - } - else if (isCalendar) { - $calendar.calendar('set date', defaultValue); - } - else { - module.verbose('Resetting field value', $field, defaultValue); - $field.val(defaultValue); - } - }); - module.remove.states(); - }, - - determine: { - isValid: function() { - var - allValid = true - ; - $.each(validation, function(fieldName, field) { - if( !( module.validate.field(field, fieldName, true) ) ) { - allValid = false; - } - }); - return allValid; - }, - isDirty: function(e) { - var formIsDirty = false; - - $field.each(function(index, el) { - var - $el = $(el), - isCheckbox = ($el.filter(selector.checkbox).length > 0), - isDirty - ; - - if (isCheckbox) { - isDirty = module.is.checkboxDirty($el); - } else { - isDirty = module.is.fieldDirty($el); - } - - $el.data(settings.metadata.isDirty, isDirty); - - formIsDirty |= isDirty; - }); - - if (formIsDirty) { - module.set.dirty(); - } else { - module.set.clean(); - } - - if (e && e.namespace === 'dirty') { - e.stopImmediatePropagation(); - e.preventDefault(); - } - } - }, - - is: { - bracketedRule: function(rule) { - return (rule.type && rule.type.match(settings.regExp.bracket)); - }, - shorthandFields: function(fields) { - var - fieldKeys = Object.keys(fields), - firstRule = fields[fieldKeys[0]] - ; - return module.is.shorthandRules(firstRule); - }, - // duck type rule test - shorthandRules: function(rules) { - return (typeof rules == 'string' || Array.isArray(rules)); - }, - empty: function($field) { - if(!$field || $field.length === 0) { - return true; - } - else if($field.is(selector.checkbox)) { - return !$field.is(':checked'); - } - else { - return module.is.blank($field); - } - }, - blank: function($field) { - return String($field.val()).trim() === ''; - }, - valid: function(field, showErrors) { - var - allValid = true - ; - if(field) { - module.verbose('Checking if field is valid', field); - return module.validate.field(validation[field], field, !!showErrors); - } - else { - module.verbose('Checking if form is valid'); - $.each(validation, function(fieldName, field) { - if( !module.is.valid(fieldName, showErrors) ) { - allValid = false; - } - }); - return allValid; - } - }, - dirty: function() { - return dirty; - }, - clean: function() { - return !dirty; - }, - fieldDirty: function($el) { - var initialValue = $el.data(metadata.defaultValue); - // Explicitly check for null/undefined here as value may be `false`, so ($el.data(dataInitialValue) || '') would not work - if (initialValue == null) { initialValue = ''; } - else if(Array.isArray(initialValue)) { - initialValue = initialValue.toString(); - } - var currentValue = $el.val(); - if (currentValue == null) { currentValue = ''; } - // multiple select values are returned as arrays which are never equal, so do string conversion first - else if(Array.isArray(currentValue)) { - currentValue = currentValue.toString(); - } - // Boolean values can be encoded as "true/false" or "True/False" depending on underlying frameworks so we need a case insensitive comparison - var boolRegex = /^(true|false)$/i; - var isBoolValue = boolRegex.test(initialValue) && boolRegex.test(currentValue); - if (isBoolValue) { - var regex = new RegExp("^" + initialValue + "$", "i"); - return !regex.test(currentValue); - } - - return currentValue !== initialValue; - }, - checkboxDirty: function($el) { - var initialValue = $el.data(metadata.defaultValue); - var currentValue = $el.is(":checked"); - - return initialValue !== currentValue; - }, - justDirty: function() { - return (history[0] === 'dirty'); - }, - justClean: function() { - return (history[0] === 'clean'); - } - }, - - removeEvents: function() { - $module.off(eventNamespace); - $field.off(eventNamespace); - $submit.off(eventNamespace); - $field.off(eventNamespace); - }, - - event: { - field: { - keydown: function(event) { - var - $field = $(this), - key = event.which, - isInput = $field.is(selector.input), - isCheckbox = $field.is(selector.checkbox), - isInDropdown = ($field.closest(selector.uiDropdown).length > 0), - keyCode = { - enter : 13, - escape : 27 - } - ; - if( key == keyCode.escape) { - module.verbose('Escape key pressed blurring field'); - $field - .blur() - ; - } - if(!event.ctrlKey && key == keyCode.enter && isInput && !isInDropdown && !isCheckbox) { - if(!keyHeldDown) { - $field.one('keyup' + eventNamespace, module.event.field.keyup); - module.submit(); - module.debug('Enter pressed on input submitting form'); - } - keyHeldDown = true; - } - }, - keyup: function() { - keyHeldDown = false; - }, - blur: function(event) { - var - $field = $(this), - $fieldGroup = $field.closest($group), - validationRules = module.get.validation($field) - ; - if( $fieldGroup.hasClass(className.error) ) { - module.debug('Revalidating field', $field, validationRules); - if(validationRules) { - module.validate.field( validationRules ); - } - } - else if(settings.on == 'blur') { - if(validationRules) { - module.validate.field( validationRules ); - } - } - }, - change: function(event) { - var - $field = $(this), - $fieldGroup = $field.closest($group), - validationRules = module.get.validation($field) - ; - if(validationRules && (settings.on == 'change' || ( $fieldGroup.hasClass(className.error) && settings.revalidate) )) { - clearTimeout(module.timer); - module.timer = setTimeout(function() { - module.debug('Revalidating field', $field, module.get.validation($field)); - module.validate.field( validationRules ); - if(!settings.inline) { - module.validate.form(false,true); - } - }, settings.delay); - } - } - }, - beforeUnload: function(event) { - if (module.is.dirty() && !submitting) { - var event = event || window.event; - - // For modern browsers - if (event) { - event.returnValue = settings.text.leavingMessage; - } - - // For olders... - return settings.text.leavingMessage; - } - } - - }, - - get: { - ancillaryValue: function(rule) { - if(!rule.type || (!rule.value && !module.is.bracketedRule(rule))) { - return false; - } - return (rule.value !== undefined) - ? rule.value - : rule.type.match(settings.regExp.bracket)[1] + '' - ; - }, - ruleName: function(rule) { - if( module.is.bracketedRule(rule) ) { - return rule.type.replace(rule.type.match(settings.regExp.bracket)[0], ''); - } - return rule.type; - }, - changeEvent: function(type, $input) { - if(type == 'checkbox' || type == 'radio' || type == 'hidden' || $input.is('select')) { - return 'change'; - } - else { - return module.get.inputEvent(); - } - }, - inputEvent: function() { - return (document.createElement('input').oninput !== undefined) - ? 'input' - : (document.createElement('input').onpropertychange !== undefined) - ? 'propertychange' - : 'keyup' - ; - }, - fieldsFromShorthand: function(fields) { - var - fullFields = {} - ; - $.each(fields, function(name, rules) { - if(typeof rules == 'string') { - rules = [rules]; - } - fullFields[name] = { - rules: [] - }; - $.each(rules, function(index, rule) { - fullFields[name].rules.push({ type: rule }); - }); - }); - return fullFields; - }, - prompt: function(rule, field) { - var - ruleName = module.get.ruleName(rule), - ancillary = module.get.ancillaryValue(rule), - $field = module.get.field(field.identifier), - value = $field.val(), - prompt = $.isFunction(rule.prompt) - ? rule.prompt(value) - : rule.prompt || settings.prompt[ruleName] || settings.text.unspecifiedRule, - requiresValue = (prompt.search('{value}') !== -1), - requiresName = (prompt.search('{name}') !== -1), - $label, - name - ; - if(requiresValue) { - prompt = prompt.replace(/\{value\}/g, $field.val()); - } - if(requiresName) { - $label = $field.closest(selector.group).find('label').eq(0); - name = ($label.length == 1) - ? $label.text() - : $field.prop('placeholder') || settings.text.unspecifiedField - ; - prompt = prompt.replace(/\{name\}/g, name); - } - prompt = prompt.replace(/\{identifier\}/g, field.identifier); - prompt = prompt.replace(/\{ruleValue\}/g, ancillary); - if(!rule.prompt) { - module.verbose('Using default validation prompt for type', prompt, ruleName); - } - return prompt; - }, - settings: function() { - if($.isPlainObject(parameters)) { - var - keys = Object.keys(parameters), - isLegacySettings = (keys.length > 0) - ? (parameters[keys[0]].identifier !== undefined && parameters[keys[0]].rules !== undefined) - : false - ; - if(isLegacySettings) { - // 1.x (ducktyped) - settings = $.extend(true, {}, $.fn.form.settings, legacyParameters); - validation = $.extend({}, $.fn.form.settings.defaults, parameters); - module.error(settings.error.oldSyntax, element); - module.verbose('Extending settings from legacy parameters', validation, settings); - } - else { - // 2.x - if(parameters.fields && module.is.shorthandFields(parameters.fields)) { - parameters.fields = module.get.fieldsFromShorthand(parameters.fields); - } - settings = $.extend(true, {}, $.fn.form.settings, parameters); - validation = $.extend({}, $.fn.form.settings.defaults, settings.fields); - module.verbose('Extending settings', validation, settings); - } - } - else { - settings = $.fn.form.settings; - validation = $.fn.form.settings.defaults; - module.verbose('Using default form validation', validation, settings); - } - - // shorthand - namespace = settings.namespace; - metadata = settings.metadata; - selector = settings.selector; - className = settings.className; - regExp = settings.regExp; - error = settings.error; - moduleNamespace = 'module-' + namespace; - eventNamespace = '.' + namespace; - - // grab instance - instance = $module.data(moduleNamespace); - - // refresh selector cache - module.refresh(); - }, - field: function(identifier) { - module.verbose('Finding field with identifier', identifier); - identifier = module.escape.string(identifier); - var t; - if((t=$field.filter('#' + identifier)).length > 0 ) { - return t; - } - if((t=$field.filter('[name="' + identifier +'"]')).length > 0 ) { - return t; - } - if((t=$field.filter('[name="' + identifier +'[]"]')).length > 0 ) { - return t; - } - if((t=$field.filter('[data-' + metadata.validate + '="'+ identifier +'"]')).length > 0 ) { - return t; - } - return $('<input/>'); - }, - fields: function(fields) { - var - $fields = $() - ; - $.each(fields, function(index, name) { - $fields = $fields.add( module.get.field(name) ); - }); - return $fields; - }, - validation: function($field) { - var - fieldValidation, - identifier - ; - if(!validation) { - return false; - } - $.each(validation, function(fieldName, field) { - identifier = field.identifier || fieldName; - $.each(module.get.field(identifier), function(index, groupField) { - if(groupField == $field[0]) { - field.identifier = identifier; - fieldValidation = field; - return false; - } - }); - }); - return fieldValidation || false; - }, - value: function (field) { - var - fields = [], - results - ; - fields.push(field); - results = module.get.values.call(element, fields); - return results[field]; - }, - values: function (fields) { - var - $fields = Array.isArray(fields) - ? module.get.fields(fields) - : $field, - values = {} - ; - $fields.each(function(index, field) { - var - $field = $(field), - $calendar = $field.closest(selector.uiCalendar), - name = $field.prop('name'), - value = $field.val(), - isCheckbox = $field.is(selector.checkbox), - isRadio = $field.is(selector.radio), - isMultiple = (name.indexOf('[]') !== -1), - isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), - isChecked = (isCheckbox) - ? $field.is(':checked') - : false - ; - if(name) { - if(isMultiple) { - name = name.replace('[]', ''); - if(!values[name]) { - values[name] = []; - } - if(isCheckbox) { - if(isChecked) { - values[name].push(value || true); - } - else { - values[name].push(false); - } - } - else { - values[name].push(value); - } - } - else { - if(isRadio) { - if(values[name] === undefined || values[name] === false) { - values[name] = (isChecked) - ? value || true - : false - ; - } - } - else if(isCheckbox) { - if(isChecked) { - values[name] = value || true; - } - else { - values[name] = false; - } - } - else if(isCalendar) { - var date = $calendar.calendar('get date'); - - if (date !== null) { - if (settings.dateHandling == 'date') { - values[name] = date; - } else if(settings.dateHandling == 'input') { - values[name] = $calendar.calendar('get input date') - } else if (settings.dateHandling == 'formatter') { - var type = $calendar.calendar('setting', 'type'); - - switch(type) { - case 'date': - values[name] = settings.formatter.date(date); - break; - - case 'datetime': - values[name] = settings.formatter.datetime(date); - break; - - case 'time': - values[name] = settings.formatter.time(date); - break; - - case 'month': - values[name] = settings.formatter.month(date); - break; - - case 'year': - values[name] = settings.formatter.year(date); - break; - - default: - module.debug('Wrong calendar mode', $calendar, type); - values[name] = ''; - } - } - } else { - values[name] = ''; - } - } else { - values[name] = value; - } - } - } - }); - return values; - }, - dirtyFields: function() { - return $field.filter(function(index, e) { - return $(e).data(metadata.isDirty); - }); - } - }, - - has: { - - field: function(identifier) { - module.verbose('Checking for existence of a field with identifier', identifier); - identifier = module.escape.string(identifier); - if(typeof identifier !== 'string') { - module.error(error.identifier, identifier); - } - if($field.filter('#' + identifier).length > 0 ) { - return true; - } - else if( $field.filter('[name="' + identifier +'"]').length > 0 ) { - return true; - } - else if( $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]').length > 0 ) { - return true; - } - return false; - } - - }, - - can: { - useElement: function(element){ - if ($.fn[element] !== undefined) { - return true; - } - module.error(error.noElement.replace('{element}',element)); - return false; - } - }, - - escape: { - string: function(text) { - text = String(text); - return text.replace(regExp.escape, '\\$&'); - } - }, - - add: { - // alias - rule: function(name, rules) { - module.add.field(name, rules); - }, - field: function(name, rules) { - // Validation should have at least a standard format - if(validation[name] === undefined || validation[name].rules === undefined) { - validation[name] = { - rules: [] - }; - } - var - newValidation = { - rules: [] - } - ; - if(module.is.shorthandRules(rules)) { - rules = Array.isArray(rules) - ? rules - : [rules] - ; - $.each(rules, function(_index, rule) { - newValidation.rules.push({ type: rule }); - }); - } - else { - newValidation.rules = rules.rules; - } - // For each new rule, check if there's not already one with the same type - $.each(newValidation.rules, function (_index, rule) { - if ($.grep(validation[name].rules, function(item){ return item.type == rule.type; }).length == 0) { - validation[name].rules.push(rule); - } - }); - module.debug('Adding rules', newValidation.rules, validation); - }, - fields: function(fields) { - var - newValidation - ; - if(fields && module.is.shorthandFields(fields)) { - newValidation = module.get.fieldsFromShorthand(fields); - } - else { - newValidation = fields; - } - validation = $.extend({}, validation, newValidation); - }, - prompt: function(identifier, errors, internal) { - var - $field = module.get.field(identifier), - $fieldGroup = $field.closest($group), - $prompt = $fieldGroup.children(selector.prompt), - promptExists = ($prompt.length !== 0) - ; - errors = (typeof errors == 'string') - ? [errors] - : errors - ; - module.verbose('Adding field error state', identifier); - if(!internal) { - $fieldGroup - .addClass(className.error) - ; - } - if(settings.inline) { - if(!promptExists) { - $prompt = settings.templates.prompt(errors, className.label); - $prompt - .appendTo($fieldGroup) - ; - } - $prompt - .html(errors[0]) - ; - if(!promptExists) { - if(settings.transition && module.can.useElement('transition') && $module.transition('is supported')) { - module.verbose('Displaying error with css transition', settings.transition); - $prompt.transition(settings.transition + ' in', settings.duration); - } - else { - module.verbose('Displaying error with fallback javascript animation'); - $prompt - .fadeIn(settings.duration) - ; - } - } - else { - module.verbose('Inline errors are disabled, no inline error added', identifier); - } - } - }, - errors: function(errors) { - module.debug('Adding form error messages', errors); - module.set.error(); - $message - .html( settings.templates.error(errors) ) - ; - } - }, - - remove: { - errors: function() { - module.debug('Removing form error messages'); - $message.empty(); - }, - states: function() { - $module.removeClass(className.error).removeClass(className.success); - if(!settings.inline) { - module.remove.errors(); - } - module.determine.isDirty(); - }, - rule: function(field, rule) { - var - rules = Array.isArray(rule) - ? rule - : [rule] - ; - if(validation[field] === undefined || !Array.isArray(validation[field].rules)) { - return; - } - if(rule === undefined) { - module.debug('Removed all rules'); - validation[field].rules = []; - return; - } - $.each(validation[field].rules, function(index, rule) { - if(rule && rules.indexOf(rule.type) !== -1) { - module.debug('Removed rule', rule.type); - validation[field].rules.splice(index, 1); - } - }); - }, - field: function(field) { - var - fields = Array.isArray(field) - ? field - : [field] - ; - $.each(fields, function(index, field) { - module.remove.rule(field); - }); - }, - // alias - rules: function(field, rules) { - if(Array.isArray(field)) { - $.each(field, function(index, field) { - module.remove.rule(field, rules); - }); - } - else { - module.remove.rule(field, rules); - } - }, - fields: function(fields) { - module.remove.field(fields); - }, - prompt: function(identifier) { - var - $field = module.get.field(identifier), - $fieldGroup = $field.closest($group), - $prompt = $fieldGroup.children(selector.prompt) - ; - $fieldGroup - .removeClass(className.error) - ; - if(settings.inline && $prompt.is(':visible')) { - module.verbose('Removing prompt for field', identifier); - if(settings.transition && module.can.useElement('transition') && $module.transition('is supported')) { - $prompt.transition(settings.transition + ' out', settings.duration, function() { - $prompt.remove(); - }); - } - else { - $prompt - .fadeOut(settings.duration, function(){ - $prompt.remove(); - }) - ; - } - } - } - }, - - set: { - success: function() { - $module - .removeClass(className.error) - .addClass(className.success) - ; - }, - defaults: function () { - $field.each(function (index, el) { - var - $el = $(el), - $parent = $el.parent(), - isCheckbox = ($el.filter(selector.checkbox).length > 0), - isDropdown = $parent.is(selector.uiDropdown) && module.can.useElement('dropdown'), - $calendar = $el.closest(selector.uiCalendar), - isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), - value = (isCheckbox) - ? $el.is(':checked') - : $el.val() - ; - if (isDropdown) { - $parent.dropdown('save defaults'); - } - else if (isCalendar) { - $calendar.calendar('refresh'); - } - $el.data(metadata.defaultValue, value); - $el.data(metadata.isDirty, false); - }); - }, - error: function() { - $module - .removeClass(className.success) - .addClass(className.error) - ; - }, - value: function (field, value) { - var - fields = {} - ; - fields[field] = value; - return module.set.values.call(element, fields); - }, - values: function (fields) { - if($.isEmptyObject(fields)) { - return; - } - $.each(fields, function(key, value) { - var - $field = module.get.field(key), - $element = $field.parent(), - $calendar = $field.closest(selector.uiCalendar), - isMultiple = Array.isArray(value), - isCheckbox = $element.is(selector.uiCheckbox) && module.can.useElement('checkbox'), - isDropdown = $element.is(selector.uiDropdown) && module.can.useElement('dropdown'), - isRadio = ($field.is(selector.radio) && isCheckbox), - isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), - fieldExists = ($field.length > 0), - $multipleField - ; - if(fieldExists) { - if(isMultiple && isCheckbox) { - module.verbose('Selecting multiple', value, $field); - $element.checkbox('uncheck'); - $.each(value, function(index, value) { - $multipleField = $field.filter('[value="' + value + '"]'); - $element = $multipleField.parent(); - if($multipleField.length > 0) { - $element.checkbox('check'); - } - }); - } - else if(isRadio) { - module.verbose('Selecting radio value', value, $field); - $field.filter('[value="' + value + '"]') - .parent(selector.uiCheckbox) - .checkbox('check') - ; - } - else if(isCheckbox) { - module.verbose('Setting checkbox value', value, $element); - if(value === true || value === 1) { - $element.checkbox('check'); - } - else { - $element.checkbox('uncheck'); - } - } - else if(isDropdown) { - module.verbose('Setting dropdown value', value, $element); - $element.dropdown('set selected', value); - } - else if (isCalendar) { - $calendar.calendar('set date',value); - } - else { - module.verbose('Setting field value', value, $field); - $field.val(value); - } - } - }); - }, - dirty: function() { - module.verbose('Setting state dirty'); - dirty = true; - history[0] = history[1]; - history[1] = 'dirty'; - - if (module.is.justClean()) { - $module.trigger('dirty'); - } - }, - clean: function() { - module.verbose('Setting state clean'); - dirty = false; - history[0] = history[1]; - history[1] = 'clean'; - - if (module.is.justDirty()) { - $module.trigger('clean'); - } - }, - asClean: function() { - module.set.defaults(); - module.set.clean(); - }, - asDirty: function() { - module.set.defaults(); - module.set.dirty(); - }, - autoCheck: function() { - module.debug('Enabling auto check on required fields'); - $field.each(function (_index, el) { - var - $el = $(el), - $elGroup = $(el).closest($group), - isCheckbox = ($el.filter(selector.checkbox).length > 0), - isRequired = $el.prop('required') || $elGroup.hasClass(className.required) || $elGroup.parent().hasClass(className.required), - isDisabled = $el.is(':disabled') || $elGroup.hasClass(className.disabled) || $elGroup.parent().hasClass(className.disabled), - validation = module.get.validation($el), - hasEmptyRule = validation - ? $.grep(validation.rules, function(rule) { return rule.type == "empty" }) !== 0 - : false, - identifier = validation.identifier || $el.attr('id') || $el.attr('name') || $el.data(metadata.validate) - ; - if (isRequired && !isDisabled && !hasEmptyRule && identifier !== undefined) { - if (isCheckbox) { - module.verbose("Adding 'checked' rule on field", identifier); - module.add.rule(identifier, "checked"); - } else { - module.verbose("Adding 'empty' rule on field", identifier); - module.add.rule(identifier, "empty"); - } - } - }); - } - }, - - validate: { - - form: function(event, ignoreCallbacks) { - var values = module.get.values(); - - // input keydown event will fire submit repeatedly by browser default - if(keyHeldDown) { - return false; - } - - // reset errors - formErrors = []; - if( module.determine.isValid() ) { - module.debug('Form has no validation errors, submitting'); - module.set.success(); - if(!settings.inline) { - module.remove.errors(); - } - if(ignoreCallbacks !== true) { - return settings.onSuccess.call(element, event, values); - } - } - else { - module.debug('Form has errors'); - submitting = false; - module.set.error(); - if(!settings.inline) { - module.add.errors(formErrors); - } - // prevent ajax submit - if(event && $module.data('moduleApi') !== undefined) { - event.stopImmediatePropagation(); - } - if(ignoreCallbacks !== true) { - return settings.onFailure.call(element, formErrors, values); - } - } - }, - - // takes a validation object and returns whether field passes validation - field: function(field, fieldName, showErrors) { - showErrors = (showErrors !== undefined) - ? showErrors - : true - ; - if(typeof field == 'string') { - module.verbose('Validating field', field); - fieldName = field; - field = validation[field]; - } - var - identifier = field.identifier || fieldName, - $field = module.get.field(identifier), - $dependsField = (field.depends) - ? module.get.field(field.depends) - : false, - fieldValid = true, - fieldErrors = [] - ; - if(!field.identifier) { - module.debug('Using field name as identifier', identifier); - field.identifier = identifier; - } - var isDisabled = !$field.filter(':not(:disabled)').length; - if(isDisabled) { - module.debug('Field is disabled. Skipping', identifier); - } - else if(field.optional && module.is.blank($field)){ - module.debug('Field is optional and blank. Skipping', identifier); - } - else if(field.depends && module.is.empty($dependsField)) { - module.debug('Field depends on another value that is not present or empty. Skipping', $dependsField); - } - else if(field.rules !== undefined) { - if(showErrors) { - $field.closest($group).removeClass(className.error); - } - $.each(field.rules, function(index, rule) { - if( module.has.field(identifier)) { - var invalidFields = module.validate.rule(field, rule,true) || []; - if (invalidFields.length>0){ - module.debug('Field is invalid', identifier, rule.type); - fieldErrors.push(module.get.prompt(rule, field)); - fieldValid = false; - if(showErrors){ - $(invalidFields).closest($group).addClass(className.error); - } - } - } - }); - } - if(fieldValid) { - if(showErrors) { - module.remove.prompt(identifier, fieldErrors); - settings.onValid.call($field); - } - } - else { - if(showErrors) { - formErrors = formErrors.concat(fieldErrors); - module.add.prompt(identifier, fieldErrors, true); - settings.onInvalid.call($field, fieldErrors); - } - return false; - } - return true; - }, - - // takes validation rule and returns whether field passes rule - rule: function(field, rule, internal) { - var - $field = module.get.field(field.identifier), - ancillary = module.get.ancillaryValue(rule), - ruleName = module.get.ruleName(rule), - ruleFunction = settings.rules[ruleName], - invalidFields = [], - isCheckbox = $field.is(selector.checkbox), - isValid = function(field){ - var value = (isCheckbox ? $(field).filter(':checked').val() : $(field).val()); - // cast to string avoiding encoding special values - value = (value === undefined || value === '' || value === null) - ? '' - : (settings.shouldTrim) ? String(value + '').trim() : String(value + '') - ; - return ruleFunction.call(field, value, ancillary, $module); - } - ; - if( !$.isFunction(ruleFunction) ) { - module.error(error.noRule, ruleName); - return; - } - if(isCheckbox) { - if (!isValid($field)) { - invalidFields = $field; - } - } else { - $.each($field, function (index, field) { - if (!isValid(field)) { - invalidFields.push(field); - } - }); - } - return internal ? invalidFields : !(invalidFields.length>0); - } - }, - - setting: function(name, value) { - if( $.isPlainObject(name) ) { - $.extend(true, settings, name); - } - else if(value !== undefined) { - settings[name] = value; - } - else { - return settings[name]; - } - }, - internal: function(name, value) { - if( $.isPlainObject(name) ) { - $.extend(true, module, name); - } - else if(value !== undefined) { - module[name] = value; - } - else { - return module[name]; - } - }, - debug: function() { - if(!settings.silent && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.debug.apply(console, arguments); - } - } - }, - verbose: function() { - if(!settings.silent && settings.verbose && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.verbose.apply(console, arguments); - } - } - }, - error: function() { - if(!settings.silent) { - module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); - module.error.apply(console, arguments); - } - }, - performance: { - log: function(message) { - var - currentTime, - executionTime, - previousTime - ; - if(settings.performance) { - currentTime = new Date().getTime(); - previousTime = time || currentTime; - executionTime = currentTime - previousTime; - time = currentTime; - performance.push({ - 'Name' : message[0], - 'Arguments' : [].slice.call(message, 1) || '', - 'Element' : element, - 'Execution Time' : executionTime - }); - } - clearTimeout(module.performance.timer); - module.performance.timer = setTimeout(module.performance.display, 500); - }, - display: function() { - var - title = settings.name + ':', - totalTime = 0 - ; - time = false; - clearTimeout(module.performance.timer); - $.each(performance, function(index, data) { - totalTime += data['Execution Time']; - }); - title += ' ' + totalTime + 'ms'; - if(moduleSelector) { - title += ' \'' + moduleSelector + '\''; - } - if($allModules.length > 1) { - title += ' ' + '(' + $allModules.length + ')'; - } - if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { - console.groupCollapsed(title); - if(console.table) { - console.table(performance); - } - else { - $.each(performance, function(index, data) { - console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); - }); - } - console.groupEnd(); - } - performance = []; - } - }, - invoke: function(query, passedArguments, context) { - var - object = instance, - maxDepth, - found, - response - ; - passedArguments = passedArguments || queryArguments; - context = element || context; - if(typeof query == 'string' && object !== undefined) { - query = query.split(/[\. ]/); - maxDepth = query.length - 1; - $.each(query, function(depth, value) { - var camelCaseValue = (depth != maxDepth) - ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) - : query - ; - if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { - object = object[camelCaseValue]; - } - else if( object[camelCaseValue] !== undefined ) { - found = object[camelCaseValue]; - return false; - } - else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { - object = object[value]; - } - else if( object[value] !== undefined ) { - found = object[value]; - return false; - } - else { - return false; - } - }); - } - if( $.isFunction( found ) ) { - response = found.apply(context, passedArguments); - } - else if(found !== undefined) { - response = found; - } - if(Array.isArray(returnedValue)) { - returnedValue.push(response); - } - else if(returnedValue !== undefined) { - returnedValue = [returnedValue, response]; - } - else if(response !== undefined) { - returnedValue = response; - } - return found; - } - }; - module.initialize(); - }) - ; - - return (returnedValue !== undefined) - ? returnedValue - : this - ; -}; - -$.fn.form.settings = { - - name : 'Form', - namespace : 'form', - - debug : false, - verbose : false, - performance : true, - - fields : false, - - keyboardShortcuts : true, - on : 'submit', - inline : false, - - delay : 200, - revalidate : true, - shouldTrim : true, - - transition : 'scale', - duration : 200, - - autoCheckRequired : false, - preventLeaving : false, - dateHandling : 'date', // 'date', 'input', 'formatter' - - onValid : function() {}, - onInvalid : function() {}, - onSuccess : function() { return true; }, - onFailure : function() { return false; }, - onDirty : function() {}, - onClean : function() {}, - - metadata : { - defaultValue : 'default', - validate : 'validate', - isDirty : 'isDirty' - }, - - regExp: { - htmlID : /^[a-zA-Z][\w:.-]*$/g, - bracket : /\[(.*)\]/i, - decimal : /^\d+\.?\d*$/, - email : /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i, - escape : /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|:,=@]/g, - flags : /^\/(.*)\/(.*)?/, - integer : /^\-?\d+$/, - number : /^\-?\d*(\.\d+)?$/, - url : /(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/i - }, - - text: { - unspecifiedRule : 'Please enter a valid value', - unspecifiedField : 'This field', - leavingMessage : 'There are unsaved changes on this page which will be discarded if you continue.' - }, - - prompt: { - empty : '{name} must have a value', - checked : '{name} must be checked', - email : '{name} must be a valid e-mail', - url : '{name} must be a valid url', - regExp : '{name} is not formatted correctly', - integer : '{name} must be an integer', - decimal : '{name} must be a decimal number', - number : '{name} must be set to a number', - is : '{name} must be "{ruleValue}"', - isExactly : '{name} must be exactly "{ruleValue}"', - not : '{name} cannot be set to "{ruleValue}"', - notExactly : '{name} cannot be set to exactly "{ruleValue}"', - contain : '{name} must contain "{ruleValue}"', - containExactly : '{name} must contain exactly "{ruleValue}"', - doesntContain : '{name} cannot contain "{ruleValue}"', - doesntContainExactly : '{name} cannot contain exactly "{ruleValue}"', - minLength : '{name} must be at least {ruleValue} characters', - length : '{name} must be at least {ruleValue} characters', - exactLength : '{name} must be exactly {ruleValue} characters', - maxLength : '{name} cannot be longer than {ruleValue} characters', - match : '{name} must match {ruleValue} field', - different : '{name} must have a different value than {ruleValue} field', - creditCard : '{name} must be a valid credit card number', - minCount : '{name} must have at least {ruleValue} choices', - exactCount : '{name} must have exactly {ruleValue} choices', - maxCount : '{name} must have {ruleValue} or less choices' - }, - - selector : { - checkbox : 'input[type="checkbox"], input[type="radio"]', - clear : '.clear', - field : 'input:not(.search), textarea, select', - group : '.field', - input : 'input', - message : '.error.message', - prompt : '.prompt.label', - radio : 'input[type="radio"]', - reset : '.reset:not([type="reset"])', - submit : '.submit:not([type="submit"])', - uiCheckbox : '.ui.checkbox', - uiDropdown : '.ui.dropdown', - uiCalendar : '.ui.calendar' - }, - - className : { - error : 'error', - label : 'ui basic red pointing prompt label', - pressed : 'down', - success : 'success', - required : 'required', - disabled : 'disabled' - }, - - error: { - identifier : 'You must specify a string identifier for each field', - method : 'The method you called is not defined.', - noRule : 'There is no rule matching the one you specified', - oldSyntax : 'Starting in 2.0 forms now only take a single settings object. Validation settings converted to new syntax automatically.', - noElement : 'This module requires ui {element}' - }, - - templates: { - - // template that produces error message - error: function(errors) { - var - html = '<ul class="list">' - ; - $.each(errors, function(index, value) { - html += '<li>' + value + '</li>'; - }); - html += '</ul>'; - return $(html); - }, - - // template that produces label - prompt: function(errors, labelClasses) { - return $('<div/>') - .addClass(labelClasses) - .html(errors[0]) - ; - } - }, - - formatter: { - date: function(date) { - return Intl.DateTimeFormat('en-GB').format(date); - }, - datetime: function(date) { - return Intl.DateTimeFormat('en-GB', { - year: "numeric", - month: "2-digit", - day: "2-digit", - hour: '2-digit', - minute: '2-digit', - second: '2-digit' - }).format(date); - }, - time: function(date) { - return Intl.DateTimeFormat('en-GB', { - hour: '2-digit', - minute: '2-digit', - second: '2-digit' - }).format(date); - }, - month: function(date) { - return Intl.DateTimeFormat('en-GB', { - month: '2-digit', - year: 'numeric' - }).format(date); - }, - year: function(date) { - return Intl.DateTimeFormat('en-GB', { - year: 'numeric' - }).format(date); - } - }, - - rules: { - - // is not empty or blank string - empty: function(value) { - return !(value === undefined || '' === value || Array.isArray(value) && value.length === 0); - }, - - // checkbox checked - checked: function() { - return ($(this).filter(':checked').length > 0); - }, - - // is most likely an email - email: function(value){ - return $.fn.form.settings.regExp.email.test(value); - }, - - // value is most likely url - url: function(value) { - return $.fn.form.settings.regExp.url.test(value); - }, - - // matches specified regExp - regExp: function(value, regExp) { - if(regExp instanceof RegExp) { - return value.match(regExp); - } - var - regExpParts = regExp.match($.fn.form.settings.regExp.flags), - flags - ; - // regular expression specified as /baz/gi (flags) - if(regExpParts) { - regExp = (regExpParts.length >= 2) - ? regExpParts[1] - : regExp - ; - flags = (regExpParts.length >= 3) - ? regExpParts[2] - : '' - ; - } - return value.match( new RegExp(regExp, flags) ); - }, - - // is valid integer or matches range - integer: function(value, range) { - var - intRegExp = $.fn.form.settings.regExp.integer, - min, - max, - parts - ; - if( !range || ['', '..'].indexOf(range) !== -1) { - // do nothing - } - else if(range.indexOf('..') == -1) { - if(intRegExp.test(range)) { - min = max = range - 0; - } - } - else { - parts = range.split('..', 2); - if(intRegExp.test(parts[0])) { - min = parts[0] - 0; - } - if(intRegExp.test(parts[1])) { - max = parts[1] - 0; - } - } - return ( - intRegExp.test(value) && - (min === undefined || value >= min) && - (max === undefined || value <= max) - ); - }, - - // is valid number (with decimal) - decimal: function(value) { - return $.fn.form.settings.regExp.decimal.test(value); - }, - - // is valid number - number: function(value) { - return $.fn.form.settings.regExp.number.test(value); - }, - - // is value (case insensitive) - is: function(value, text) { - text = (typeof text == 'string') - ? text.toLowerCase() - : text - ; - value = (typeof value == 'string') - ? value.toLowerCase() - : value - ; - return (value == text); - }, - - // is value - isExactly: function(value, text) { - return (value == text); - }, - - // value is not another value (case insensitive) - not: function(value, notValue) { - value = (typeof value == 'string') - ? value.toLowerCase() - : value - ; - notValue = (typeof notValue == 'string') - ? notValue.toLowerCase() - : notValue - ; - return (value != notValue); - }, - - // value is not another value (case sensitive) - notExactly: function(value, notValue) { - return (value != notValue); - }, - - // value contains text (insensitive) - contains: function(value, text) { - // escape regex characters - text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); - return (value.search( new RegExp(text, 'i') ) !== -1); - }, - - // value contains text (case sensitive) - containsExactly: function(value, text) { - // escape regex characters - text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); - return (value.search( new RegExp(text) ) !== -1); - }, - - // value contains text (insensitive) - doesntContain: function(value, text) { - // escape regex characters - text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); - return (value.search( new RegExp(text, 'i') ) === -1); - }, - - // value contains text (case sensitive) - doesntContainExactly: function(value, text) { - // escape regex characters - text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); - return (value.search( new RegExp(text) ) === -1); - }, - - // is at least string length - minLength: function(value, requiredLength) { - return (value !== undefined) - ? (value.length >= requiredLength) - : false - ; - }, - - // see rls notes for 2.0.6 (this is a duplicate of minLength) - length: function(value, requiredLength) { - return (value !== undefined) - ? (value.length >= requiredLength) - : false - ; - }, - - // is exactly length - exactLength: function(value, requiredLength) { - return (value !== undefined) - ? (value.length == requiredLength) - : false - ; - }, - - // is less than length - maxLength: function(value, maxLength) { - return (value !== undefined) - ? (value.length <= maxLength) - : false - ; - }, - - // matches another field - match: function(value, identifier, $module) { - var - matchingValue, - matchingElement - ; - if((matchingElement = $module.find('[data-validate="'+ identifier +'"]')).length > 0 ) { - matchingValue = matchingElement.val(); - } - else if((matchingElement = $module.find('#' + identifier)).length > 0) { - matchingValue = matchingElement.val(); - } - else if((matchingElement = $module.find('[name="' + identifier +'"]')).length > 0) { - matchingValue = matchingElement.val(); - } - else if((matchingElement = $module.find('[name="' + identifier +'[]"]')).length > 0 ) { - matchingValue = matchingElement; - } - return (matchingValue !== undefined) - ? ( value.toString() == matchingValue.toString() ) - : false - ; - }, - - // different than another field - different: function(value, identifier, $module) { - // use either id or name of field - var - matchingValue, - matchingElement - ; - if((matchingElement = $module.find('[data-validate="'+ identifier +'"]')).length > 0 ) { - matchingValue = matchingElement.val(); - } - else if((matchingElement = $module.find('#' + identifier)).length > 0) { - matchingValue = matchingElement.val(); - } - else if((matchingElement = $module.find('[name="' + identifier +'"]')).length > 0) { - matchingValue = matchingElement.val(); - } - else if((matchingElement = $module.find('[name="' + identifier +'[]"]')).length > 0 ) { - matchingValue = matchingElement; - } - return (matchingValue !== undefined) - ? ( value.toString() !== matchingValue.toString() ) - : false - ; - }, - - creditCard: function(cardNumber, cardTypes) { - var - cards = { - visa: { - pattern : /^4/, - length : [16] - }, - amex: { - pattern : /^3[47]/, - length : [15] - }, - mastercard: { - pattern : /^5[1-5]/, - length : [16] - }, - discover: { - pattern : /^(6011|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5]|64[4-9])|65)/, - length : [16] - }, - unionPay: { - pattern : /^(62|88)/, - length : [16, 17, 18, 19] - }, - jcb: { - pattern : /^35(2[89]|[3-8][0-9])/, - length : [16] - }, - maestro: { - pattern : /^(5018|5020|5038|6304|6759|676[1-3])/, - length : [12, 13, 14, 15, 16, 17, 18, 19] - }, - dinersClub: { - pattern : /^(30[0-5]|^36)/, - length : [14] - }, - laser: { - pattern : /^(6304|670[69]|6771)/, - length : [16, 17, 18, 19] - }, - visaElectron: { - pattern : /^(4026|417500|4508|4844|491(3|7))/, - length : [16] - } - }, - valid = {}, - validCard = false, - requiredTypes = (typeof cardTypes == 'string') - ? cardTypes.split(',') - : false, - unionPay, - validation - ; - - if(typeof cardNumber !== 'string' || cardNumber.length === 0) { - return; - } - - // allow dashes in card - cardNumber = cardNumber.replace(/[\-]/g, ''); - - // verify card types - if(requiredTypes) { - $.each(requiredTypes, function(index, type){ - // verify each card type - validation = cards[type]; - if(validation) { - valid = { - length : ($.inArray(cardNumber.length, validation.length) !== -1), - pattern : (cardNumber.search(validation.pattern) !== -1) - }; - if(valid.length && valid.pattern) { - validCard = true; - } - } - }); - - if(!validCard) { - return false; - } - } - - // skip luhn for UnionPay - unionPay = { - number : ($.inArray(cardNumber.length, cards.unionPay.length) !== -1), - pattern : (cardNumber.search(cards.unionPay.pattern) !== -1) - }; - if(unionPay.number && unionPay.pattern) { - return true; - } - - // verify luhn, adapted from <https://gist.github.com/2134376> - var - length = cardNumber.length, - multiple = 0, - producedValue = [ - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - [0, 2, 4, 6, 8, 1, 3, 5, 7, 9] - ], - sum = 0 - ; - while (length--) { - sum += producedValue[multiple][parseInt(cardNumber.charAt(length), 10)]; - multiple ^= 1; - } - return (sum % 10 === 0 && sum > 0); - }, - - minCount: function(value, minCount) { - if(minCount == 0) { - return true; - } - if(minCount == 1) { - return (value !== ''); - } - return (value.split(',').length >= minCount); - }, - - exactCount: function(value, exactCount) { - if(exactCount == 0) { - return (value === ''); - } - if(exactCount == 1) { - return (value !== '' && value.search(',') === -1); - } - return (value.split(',').length == exactCount); - }, - - maxCount: function(value, maxCount) { - if(maxCount == 0) { - return false; - } - if(maxCount == 1) { - return (value.search(',') === -1); - } - return (value.split(',').length <= maxCount); - } - } - -}; - -})( jQuery, window, document ); - -/*! - * # Fomantic-UI - Modal - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -;(function ($, window, document, undefined) { - -'use strict'; - -$.isFunction = $.isFunction || function(obj) { - return typeof obj === "function" && typeof obj.nodeType !== "number"; -}; - -window = (typeof window != 'undefined' && window.Math == Math) - ? window - : (typeof self != 'undefined' && self.Math == Math) - ? self - : Function('return this')() -; - -$.fn.modal = function(parameters) { - var - $allModules = $(this), - $window = $(window), - $document = $(document), - $body = $('body'), - - moduleSelector = $allModules.selector || '', - - time = new Date().getTime(), - performance = [], - - query = arguments[0], - methodInvoked = (typeof query == 'string'), - queryArguments = [].slice.call(arguments, 1), - - requestAnimationFrame = window.requestAnimationFrame - || window.mozRequestAnimationFrame - || window.webkitRequestAnimationFrame - || window.msRequestAnimationFrame - || function(callback) { setTimeout(callback, 0); }, - - returnedValue - ; - - $allModules - .each(function() { - var - settings = ( $.isPlainObject(parameters) ) - ? $.extend(true, {}, $.fn.modal.settings, parameters) - : $.extend({}, $.fn.modal.settings), - - selector = settings.selector, - className = settings.className, - namespace = settings.namespace, - error = settings.error, - - eventNamespace = '.' + namespace, - moduleNamespace = 'module-' + namespace, - - $module = $(this), - $context = $(settings.context), - $close = $module.find(selector.close), - - $allModals, - $otherModals, - $focusedElement, - $dimmable, - $dimmer, - - element = this, - instance = $module.data(moduleNamespace), - - ignoreRepeatedEvents = false, - - initialMouseDownInModal, - initialMouseDownInScrollbar, - initialBodyMargin = '', - tempBodyMargin = '', - - elementEventNamespace, - id, - observer, - module - ; - module = { - - initialize: function() { - module.cache = {}; - module.verbose('Initializing dimmer', $context); - - module.create.id(); - module.create.dimmer(); - - if ( settings.allowMultiple ) { - module.create.innerDimmer(); - } - if (!settings.centered){ - $module.addClass('top aligned'); - } - module.refreshModals(); - - module.bind.events(); - if(settings.observeChanges) { - module.observeChanges(); - } - module.instantiate(); - }, - - instantiate: function() { - module.verbose('Storing instance of modal'); - instance = module; - $module - .data(moduleNamespace, instance) - ; - }, - - create: { - dimmer: function() { - var - defaultSettings = { - debug : settings.debug, - dimmerName : 'modals' - }, - dimmerSettings = $.extend(true, defaultSettings, settings.dimmerSettings) - ; - if($.fn.dimmer === undefined) { - module.error(error.dimmer); - return; - } - module.debug('Creating dimmer'); - $dimmable = $context.dimmer(dimmerSettings); - if(settings.detachable) { - module.verbose('Modal is detachable, moving content into dimmer'); - $dimmable.dimmer('add content', $module); - } - else { - module.set.undetached(); - } - $dimmer = $dimmable.dimmer('get dimmer'); - }, - id: function() { - id = (Math.random().toString(16) + '000000000').substr(2, 8); - elementEventNamespace = '.' + id; - module.verbose('Creating unique id for element', id); - }, - innerDimmer: function() { - if ( $module.find(selector.dimmer).length == 0 ) { - $module.prepend('<div class="ui inverted dimmer"></div>'); - } - } - }, - - destroy: function() { - if (observer) { - observer.disconnect(); - } - module.verbose('Destroying previous modal'); - $module - .removeData(moduleNamespace) - .off(eventNamespace) - ; - $window.off(elementEventNamespace); - $dimmer.off(elementEventNamespace); - $close.off(eventNamespace); - $context.dimmer('destroy'); - }, - - observeChanges: function() { - if('MutationObserver' in window) { - observer = new MutationObserver(function(mutations) { - module.debug('DOM tree modified, refreshing'); - module.refresh(); - }); - observer.observe(element, { - childList : true, - subtree : true - }); - module.debug('Setting up mutation observer', observer); - } - }, - - refresh: function() { - module.remove.scrolling(); - module.cacheSizes(); - if(!module.can.useFlex()) { - module.set.modalOffset(); - } - module.set.screenHeight(); - module.set.type(); - }, - - refreshModals: function() { - $otherModals = $module.siblings(selector.modal); - $allModals = $otherModals.add($module); - }, - - attachEvents: function(selector, event) { - var - $toggle = $(selector) - ; - event = $.isFunction(module[event]) - ? module[event] - : module.toggle - ; - if($toggle.length > 0) { - module.debug('Attaching modal events to element', selector, event); - $toggle - .off(eventNamespace) - .on('click' + eventNamespace, event) - ; - } - else { - module.error(error.notFound, selector); - } - }, - - bind: { - events: function() { - module.verbose('Attaching events'); - $module - .on('click' + eventNamespace, selector.close, module.event.close) - .on('click' + eventNamespace, selector.approve, module.event.approve) - .on('click' + eventNamespace, selector.deny, module.event.deny) - ; - $window - .on('resize' + elementEventNamespace, module.event.resize) - ; - }, - scrollLock: function() { - // touch events default to passive, due to changes in chrome to optimize mobile perf - $dimmable.get(0).addEventListener('touchmove', module.event.preventScroll, { passive: false }); - } - }, - - unbind: { - scrollLock: function() { - $dimmable.get(0).removeEventListener('touchmove', module.event.preventScroll, { passive: false }); - } - }, - - get: { - id: function() { - return (Math.random().toString(16) + '000000000').substr(2, 8); - } - }, - - event: { - approve: function() { - if(ignoreRepeatedEvents || settings.onApprove.call(element, $(this)) === false) { - module.verbose('Approve callback returned false cancelling hide'); - return; - } - ignoreRepeatedEvents = true; - module.hide(function() { - ignoreRepeatedEvents = false; - }); - }, - preventScroll: function(event) { - if(event.target.className.indexOf('dimmer') !== -1) { - event.preventDefault(); - } - }, - deny: function() { - if(ignoreRepeatedEvents || settings.onDeny.call(element, $(this)) === false) { - module.verbose('Deny callback returned false cancelling hide'); - return; - } - ignoreRepeatedEvents = true; - module.hide(function() { - ignoreRepeatedEvents = false; - }); - }, - close: function() { - module.hide(); - }, - mousedown: function(event) { - var - $target = $(event.target), - isRtl = module.is.rtl(); - ; - initialMouseDownInModal = ($target.closest(selector.modal).length > 0); - if(initialMouseDownInModal) { - module.verbose('Mouse down event registered inside the modal'); - } - initialMouseDownInScrollbar = module.is.scrolling() && ((!isRtl && $(window).outerWidth() - settings.scrollbarWidth <= event.clientX) || (isRtl && settings.scrollbarWidth >= event.clientX)); - if(initialMouseDownInScrollbar) { - module.verbose('Mouse down event registered inside the scrollbar'); - } - }, - mouseup: function(event) { - if(!settings.closable) { - module.verbose('Dimmer clicked but closable setting is disabled'); - return; - } - if(initialMouseDownInModal) { - module.debug('Dimmer clicked but mouse down was initially registered inside the modal'); - return; - } - if(initialMouseDownInScrollbar){ - module.debug('Dimmer clicked but mouse down was initially registered inside the scrollbar'); - return; - } - var - $target = $(event.target), - isInModal = ($target.closest(selector.modal).length > 0), - isInDOM = $.contains(document.documentElement, event.target) - ; - if(!isInModal && isInDOM && module.is.active() && $module.hasClass(className.front) ) { - module.debug('Dimmer clicked, hiding all modals'); - if(settings.allowMultiple) { - if(!module.hideAll()) { - return; - } - } - else if(!module.hide()){ - return; - } - module.remove.clickaway(); - } - }, - debounce: function(method, delay) { - clearTimeout(module.timer); - module.timer = setTimeout(method, delay); - }, - keyboard: function(event) { - var - keyCode = event.which, - escapeKey = 27 - ; - if(keyCode == escapeKey) { - if(settings.closable) { - module.debug('Escape key pressed hiding modal'); - if ( $module.hasClass(className.front) ) { - module.hide(); - } - } - else { - module.debug('Escape key pressed, but closable is set to false'); - } - event.preventDefault(); - } - }, - resize: function() { - if( $dimmable.dimmer('is active') && ( module.is.animating() || module.is.active() ) ) { - requestAnimationFrame(module.refresh); - } - } - }, - - toggle: function() { - if( module.is.active() || module.is.animating() ) { - module.hide(); - } - else { - module.show(); - } - }, - - show: function(callback) { - callback = $.isFunction(callback) - ? callback - : function(){} - ; - module.refreshModals(); - module.set.dimmerSettings(); - module.set.dimmerStyles(); - - module.showModal(callback); - }, - - hide: function(callback) { - callback = $.isFunction(callback) - ? callback - : function(){} - ; - module.refreshModals(); - return module.hideModal(callback); - }, - - showModal: function(callback) { - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if( module.is.animating() || !module.is.active() ) { - module.showDimmer(); - module.cacheSizes(); - module.set.bodyMargin(); - if(module.can.useFlex()) { - module.remove.legacy(); - } - else { - module.set.legacy(); - module.set.modalOffset(); - module.debug('Using non-flex legacy modal positioning.'); - } - module.set.screenHeight(); - module.set.type(); - module.set.clickaway(); - - if( !settings.allowMultiple && module.others.active() ) { - module.hideOthers(module.showModal); - } - else { - ignoreRepeatedEvents = false; - if( settings.allowMultiple ) { - if ( module.others.active() ) { - $otherModals.filter('.' + className.active).find(selector.dimmer).addClass('active'); - } - - if ( settings.detachable ) { - $module.detach().appendTo($dimmer); - } - } - settings.onShow.call(element); - if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { - module.debug('Showing modal with css animations'); - $module - .transition({ - debug : settings.debug, - animation : settings.transition + ' in', - queue : settings.queue, - duration : settings.duration, - useFailSafe : true, - onComplete : function() { - settings.onVisible.apply(element); - if(settings.keyboardShortcuts) { - module.add.keyboardShortcuts(); - } - module.save.focus(); - module.set.active(); - if(settings.autofocus) { - module.set.autofocus(); - } - callback(); - } - }) - ; - } - else { - module.error(error.noTransition); - } - } - } - else { - module.debug('Modal is already visible'); - } - }, - - hideModal: function(callback, keepDimmed, hideOthersToo) { - var - $previousModal = $otherModals.filter('.' + className.active).last() - ; - callback = $.isFunction(callback) - ? callback - : function(){} - ; - module.debug('Hiding modal'); - if(settings.onHide.call(element, $(this)) === false) { - module.verbose('Hide callback returned false cancelling hide'); - ignoreRepeatedEvents = false; - return false; - } - - if( module.is.animating() || module.is.active() ) { - if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { - module.remove.active(); - $module - .transition({ - debug : settings.debug, - animation : settings.transition + ' out', - queue : settings.queue, - duration : settings.duration, - useFailSafe : true, - onStart : function() { - if(!module.others.active() && !module.others.animating() && !keepDimmed) { - module.hideDimmer(); - } - if( settings.keyboardShortcuts && !module.others.active() ) { - module.remove.keyboardShortcuts(); - } - }, - onComplete : function() { - module.unbind.scrollLock(); - if ( settings.allowMultiple ) { - $previousModal.addClass(className.front); - $module.removeClass(className.front); - - if ( hideOthersToo ) { - $allModals.find(selector.dimmer).removeClass('active'); - } - else { - $previousModal.find(selector.dimmer).removeClass('active'); - } - } - settings.onHidden.call(element); - module.remove.dimmerStyles(); - module.restore.focus(); - callback(); - } - }) - ; - } - else { - module.error(error.noTransition); - } - } - }, - - showDimmer: function() { - if($dimmable.dimmer('is animating') || !$dimmable.dimmer('is active') ) { - module.save.bodyMargin(); - module.debug('Showing dimmer'); - $dimmable.dimmer('show'); - } - else { - module.debug('Dimmer already visible'); - } - }, - - hideDimmer: function() { - if( $dimmable.dimmer('is animating') || ($dimmable.dimmer('is active')) ) { - module.unbind.scrollLock(); - $dimmable.dimmer('hide', function() { - module.restore.bodyMargin(); - module.remove.clickaway(); - module.remove.screenHeight(); - }); - } - else { - module.debug('Dimmer is not visible cannot hide'); - return; - } - }, - - hideAll: function(callback) { - var - $visibleModals = $allModals.filter('.' + className.active + ', .' + className.animating) - ; - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if( $visibleModals.length > 0 ) { - module.debug('Hiding all visible modals'); - var hideOk = true; -//check in reverse order trying to hide most top displayed modal first - $($visibleModals.get().reverse()).each(function(index,element){ - if(hideOk){ - hideOk = $(element).modal('hide modal', callback, false, true); - } - }); - if(hideOk) { - module.hideDimmer(); - } - return hideOk; - } - }, - - hideOthers: function(callback) { - var - $visibleModals = $otherModals.filter('.' + className.active + ', .' + className.animating) - ; - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if( $visibleModals.length > 0 ) { - module.debug('Hiding other modals', $otherModals); - $visibleModals - .modal('hide modal', callback, true) - ; - } - }, - - others: { - active: function() { - return ($otherModals.filter('.' + className.active).length > 0); - }, - animating: function() { - return ($otherModals.filter('.' + className.animating).length > 0); - } - }, - - - add: { - keyboardShortcuts: function() { - module.verbose('Adding keyboard shortcuts'); - $document - .on('keyup' + eventNamespace, module.event.keyboard) - ; - } - }, - - save: { - focus: function() { - var - $activeElement = $(document.activeElement), - inCurrentModal = $activeElement.closest($module).length > 0 - ; - if(!inCurrentModal) { - $focusedElement = $(document.activeElement).blur(); - } - }, - bodyMargin: function() { - initialBodyMargin = $body.css('margin-'+(module.can.leftBodyScrollbar() ? 'left':'right')); - var bodyMarginRightPixel = parseInt(initialBodyMargin.replace(/[^\d.]/g, '')), - bodyScrollbarWidth = window.innerWidth - document.documentElement.clientWidth; - tempBodyMargin = bodyMarginRightPixel + bodyScrollbarWidth; - } - }, - - restore: { - focus: function() { - if($focusedElement && $focusedElement.length > 0 && settings.restoreFocus) { - $focusedElement.focus(); - } - }, - bodyMargin: function() { - var position = module.can.leftBodyScrollbar() ? 'left':'right'; - $body.css('margin-'+position, initialBodyMargin); - $body.find(selector.bodyFixed.replace('right',position)).css('padding-'+position, initialBodyMargin); - } - }, - - remove: { - active: function() { - $module.removeClass(className.active); - }, - legacy: function() { - $module.removeClass(className.legacy); - }, - clickaway: function() { - if (!settings.detachable) { - $module - .off('mousedown' + elementEventNamespace) - ; - } - $dimmer - .off('mousedown' + elementEventNamespace) - ; - $dimmer - .off('mouseup' + elementEventNamespace) - ; - }, - dimmerStyles: function() { - $dimmer.removeClass(className.inverted); - $dimmable.removeClass(className.blurring); - }, - bodyStyle: function() { - if($body.attr('style') === '') { - module.verbose('Removing style attribute'); - $body.removeAttr('style'); - } - }, - screenHeight: function() { - module.debug('Removing page height'); - $body - .css('height', '') - ; - }, - keyboardShortcuts: function() { - module.verbose('Removing keyboard shortcuts'); - $document - .off('keyup' + eventNamespace) - ; - }, - scrolling: function() { - $dimmable.removeClass(className.scrolling); - $module.removeClass(className.scrolling); - } - }, - - cacheSizes: function() { - $module.addClass(className.loading); - var - scrollHeight = $module.prop('scrollHeight'), - modalWidth = $module.outerWidth(), - modalHeight = $module.outerHeight() - ; - if(module.cache.pageHeight === undefined || modalHeight !== 0) { - $.extend(module.cache, { - pageHeight : $(document).outerHeight(), - width : modalWidth, - height : modalHeight + settings.offset, - scrollHeight : scrollHeight + settings.offset, - contextHeight : (settings.context == 'body') - ? $(window).height() - : $dimmable.height(), - }); - module.cache.topOffset = -(module.cache.height / 2); - } - $module.removeClass(className.loading); - module.debug('Caching modal and container sizes', module.cache); - }, - - can: { - leftBodyScrollbar: function(){ - if(module.cache.leftBodyScrollbar === undefined) { - module.cache.leftBodyScrollbar = module.is.rtl() && ((module.is.iframe && !module.is.firefox()) || module.is.safari() || module.is.edge() || module.is.ie()); - } - return module.cache.leftBodyScrollbar; - }, - useFlex: function() { - if (settings.useFlex === 'auto') { - return settings.detachable && !module.is.ie(); - } - if(settings.useFlex && module.is.ie()) { - module.debug('useFlex true is not supported in IE'); - } else if(settings.useFlex && !settings.detachable) { - module.debug('useFlex true in combination with detachable false is not supported'); - } - return settings.useFlex; - }, - fit: function() { - var - contextHeight = module.cache.contextHeight, - verticalCenter = module.cache.contextHeight / 2, - topOffset = module.cache.topOffset, - scrollHeight = module.cache.scrollHeight, - height = module.cache.height, - paddingHeight = settings.padding, - startPosition = (verticalCenter + topOffset) - ; - return (scrollHeight > height) - ? (startPosition + scrollHeight + paddingHeight < contextHeight) - : (height + (paddingHeight * 2) < contextHeight) - ; - } - }, - - is: { - active: function() { - return $module.hasClass(className.active); - }, - ie: function() { - if(module.cache.isIE === undefined) { - var - isIE11 = (!(window.ActiveXObject) && 'ActiveXObject' in window), - isIE = ('ActiveXObject' in window) - ; - module.cache.isIE = (isIE11 || isIE); - } - return module.cache.isIE; - }, - animating: function() { - return $module.transition('is supported') - ? $module.transition('is animating') - : $module.is(':visible') - ; - }, - scrolling: function() { - return $dimmable.hasClass(className.scrolling); - }, - modernBrowser: function() { - // appName for IE11 reports 'Netscape' can no longer use - return !(window.ActiveXObject || 'ActiveXObject' in window); - }, - rtl: function() { - if(module.cache.isRTL === undefined) { - module.cache.isRTL = $body.attr('dir') === 'rtl' || $body.css('direction') === 'rtl'; - } - return module.cache.isRTL; - }, - safari: function() { - if(module.cache.isSafari === undefined) { - module.cache.isSafari = /constructor/i.test(window.HTMLElement) || !!window.ApplePaySession; - } - return module.cache.isSafari; - }, - edge: function(){ - if(module.cache.isEdge === undefined) { - module.cache.isEdge = !!window.setImmediate && !module.is.ie(); - } - return module.cache.isEdge; - }, - firefox: function(){ - if(module.cache.isFirefox === undefined) { - module.cache.isFirefox = !!window.InstallTrigger; - } - return module.cache.isFirefox; - }, - iframe: function() { - return !(self === top); - } - }, - - set: { - autofocus: function() { - var - $inputs = $module.find('[tabindex], :input').filter(':visible').filter(function() { - return $(this).closest('.disabled').length === 0; - }), - $autofocus = $inputs.filter('[autofocus]'), - $input = ($autofocus.length > 0) - ? $autofocus.first() - : $inputs.first() - ; - if($input.length > 0) { - $input.focus(); - } - }, - bodyMargin: function() { - var position = module.can.leftBodyScrollbar() ? 'left':'right'; - if(settings.detachable || module.can.fit()) { - $body.css('margin-'+position, tempBodyMargin + 'px'); - } - $body.find(selector.bodyFixed.replace('right',position)).css('padding-'+position, tempBodyMargin + 'px'); - }, - clickaway: function() { - if (!settings.detachable) { - $module - .on('mousedown' + elementEventNamespace, module.event.mousedown) - ; - } - $dimmer - .on('mousedown' + elementEventNamespace, module.event.mousedown) - ; - $dimmer - .on('mouseup' + elementEventNamespace, module.event.mouseup) - ; - }, - dimmerSettings: function() { - if($.fn.dimmer === undefined) { - module.error(error.dimmer); - return; - } - var - defaultSettings = { - debug : settings.debug, - dimmerName : 'modals', - closable : 'auto', - useFlex : module.can.useFlex(), - duration : { - show : settings.duration, - hide : settings.duration - } - }, - dimmerSettings = $.extend(true, defaultSettings, settings.dimmerSettings) - ; - if(settings.inverted) { - dimmerSettings.variation = (dimmerSettings.variation !== undefined) - ? dimmerSettings.variation + ' inverted' - : 'inverted' - ; - } - $context.dimmer('setting', dimmerSettings); - }, - dimmerStyles: function() { - if(settings.inverted) { - $dimmer.addClass(className.inverted); - } - else { - $dimmer.removeClass(className.inverted); - } - if(settings.blurring) { - $dimmable.addClass(className.blurring); - } - else { - $dimmable.removeClass(className.blurring); - } - }, - modalOffset: function() { - if (!settings.detachable) { - var canFit = module.can.fit(); - $module - .css({ - top: (!$module.hasClass('aligned') && canFit) - ? $(document).scrollTop() + (module.cache.contextHeight - module.cache.height) / 2 - : !canFit || $module.hasClass('top') - ? $(document).scrollTop() + settings.padding - : $(document).scrollTop() + (module.cache.contextHeight - module.cache.height - settings.padding), - marginLeft: -(module.cache.width / 2) - }) - ; - } else { - $module - .css({ - marginTop: (!$module.hasClass('aligned') && module.can.fit()) - ? -(module.cache.height / 2) - : settings.padding / 2, - marginLeft: -(module.cache.width / 2) - }) - ; - } - module.verbose('Setting modal offset for legacy mode'); - }, - screenHeight: function() { - if( module.can.fit() ) { - $body.css('height', ''); - } - else if(!$module.hasClass('bottom')) { - module.debug('Modal is taller than page content, resizing page height'); - $body - .css('height', module.cache.height + (settings.padding * 2) ) - ; - } - }, - active: function() { - $module.addClass(className.active + ' ' + className.front); - $otherModals.filter('.' + className.active).removeClass(className.front); - }, - scrolling: function() { - $dimmable.addClass(className.scrolling); - $module.addClass(className.scrolling); - module.unbind.scrollLock(); - }, - legacy: function() { - $module.addClass(className.legacy); - }, - type: function() { - if(module.can.fit()) { - module.verbose('Modal fits on screen'); - if(!module.others.active() && !module.others.animating()) { - module.remove.scrolling(); - module.bind.scrollLock(); - } - } - else if (!$module.hasClass('bottom')){ - module.verbose('Modal cannot fit on screen setting to scrolling'); - module.set.scrolling(); - } else { - module.verbose('Bottom aligned modal not fitting on screen is unsupported for scrolling'); - } - }, - undetached: function() { - $dimmable.addClass(className.undetached); - } - }, - - setting: function(name, value) { - module.debug('Changing setting', name, value); - if( $.isPlainObject(name) ) { - $.extend(true, settings, name); - } - else if(value !== undefined) { - if($.isPlainObject(settings[name])) { - $.extend(true, settings[name], value); - } - else { - settings[name] = value; - } - } - else { - return settings[name]; - } - }, - internal: function(name, value) { - if( $.isPlainObject(name) ) { - $.extend(true, module, name); - } - else if(value !== undefined) { - module[name] = value; - } - else { - return module[name]; - } - }, - debug: function() { - if(!settings.silent && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.debug.apply(console, arguments); - } - } - }, - verbose: function() { - if(!settings.silent && settings.verbose && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.verbose.apply(console, arguments); - } - } - }, - error: function() { - if(!settings.silent) { - module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); - module.error.apply(console, arguments); - } - }, - performance: { - log: function(message) { - var - currentTime, - executionTime, - previousTime - ; - if(settings.performance) { - currentTime = new Date().getTime(); - previousTime = time || currentTime; - executionTime = currentTime - previousTime; - time = currentTime; - performance.push({ - 'Name' : message[0], - 'Arguments' : [].slice.call(message, 1) || '', - 'Element' : element, - 'Execution Time' : executionTime - }); - } - clearTimeout(module.performance.timer); - module.performance.timer = setTimeout(module.performance.display, 500); - }, - display: function() { - var - title = settings.name + ':', - totalTime = 0 - ; - time = false; - clearTimeout(module.performance.timer); - $.each(performance, function(index, data) { - totalTime += data['Execution Time']; - }); - title += ' ' + totalTime + 'ms'; - if(moduleSelector) { - title += ' \'' + moduleSelector + '\''; - } - if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { - console.groupCollapsed(title); - if(console.table) { - console.table(performance); - } - else { - $.each(performance, function(index, data) { - console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); - }); - } - console.groupEnd(); - } - performance = []; - } - }, - invoke: function(query, passedArguments, context) { - var - object = instance, - maxDepth, - found, - response - ; - passedArguments = passedArguments || queryArguments; - context = element || context; - if(typeof query == 'string' && object !== undefined) { - query = query.split(/[\. ]/); - maxDepth = query.length - 1; - $.each(query, function(depth, value) { - var camelCaseValue = (depth != maxDepth) - ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) - : query - ; - if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { - object = object[camelCaseValue]; - } - else if( object[camelCaseValue] !== undefined ) { - found = object[camelCaseValue]; - return false; - } - else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { - object = object[value]; - } - else if( object[value] !== undefined ) { - found = object[value]; - return false; - } - else { - return false; - } - }); - } - if ( $.isFunction( found ) ) { - response = found.apply(context, passedArguments); - } - else if(found !== undefined) { - response = found; - } - if(Array.isArray(returnedValue)) { - returnedValue.push(response); - } - else if(returnedValue !== undefined) { - returnedValue = [returnedValue, response]; - } - else if(response !== undefined) { - returnedValue = response; - } - return found; - } - }; - - if(methodInvoked) { - if(instance === undefined) { - module.initialize(); - } - module.invoke(query); - } - else { - if(instance !== undefined) { - instance.invoke('destroy'); - } - module.initialize(); - } - }) - ; - - return (returnedValue !== undefined) - ? returnedValue - : this - ; -}; - -$.fn.modal.settings = { - - name : 'Modal', - namespace : 'modal', - - useFlex : 'auto', - offset : 0, - - silent : false, - debug : false, - verbose : false, - performance : true, - - observeChanges : false, - - allowMultiple : false, - detachable : true, - closable : true, - autofocus : true, - restoreFocus : true, - - inverted : false, - blurring : false, - - centered : true, - - dimmerSettings : { - closable : false, - useCSS : true - }, - - // whether to use keyboard shortcuts - keyboardShortcuts: true, - - context : 'body', - - queue : false, - duration : 500, - transition : 'scale', - - // padding with edge of page - padding : 50, - scrollbarWidth: 10, - - // called before show animation - onShow : function(){}, - - // called after show animation - onVisible : function(){}, - - // called before hide animation - onHide : function(){ return true; }, - - // called after hide animation - onHidden : function(){}, - - // called after approve selector match - onApprove : function(){ return true; }, - - // called after deny selector match - onDeny : function(){ return true; }, - - selector : { - close : '> .close', - approve : '.actions .positive, .actions .approve, .actions .ok', - deny : '.actions .negative, .actions .deny, .actions .cancel', - modal : '.ui.modal', - dimmer : '> .ui.dimmer', - bodyFixed: '> .ui.fixed.menu, > .ui.right.toast-container, > .ui.right.sidebar' - }, - error : { - dimmer : 'UI Dimmer, a required component is not included in this page', - method : 'The method you called is not defined.', - notFound : 'The element you specified could not be found' - }, - className : { - active : 'active', - animating : 'animating', - blurring : 'blurring', - inverted : 'inverted', - legacy : 'legacy', - loading : 'loading', - scrolling : 'scrolling', - undetached : 'undetached', - front : 'front' - } -}; - - -})( jQuery, window, document ); - -/*! - * # Fomantic-UI - Search - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -;(function ($, window, document, undefined) { - -'use strict'; - -$.isFunction = $.isFunction || function(obj) { - return typeof obj === "function" && typeof obj.nodeType !== "number"; -}; - -window = (typeof window != 'undefined' && window.Math == Math) - ? window - : (typeof self != 'undefined' && self.Math == Math) - ? self - : Function('return this')() -; - -$.fn.search = function(parameters) { - var - $allModules = $(this), - moduleSelector = $allModules.selector || '', - - time = new Date().getTime(), - performance = [], - - query = arguments[0], - methodInvoked = (typeof query == 'string'), - queryArguments = [].slice.call(arguments, 1), - returnedValue - ; - $(this) - .each(function() { - var - settings = ( $.isPlainObject(parameters) ) - ? $.extend(true, {}, $.fn.search.settings, parameters) - : $.extend({}, $.fn.search.settings), - - className = settings.className, - metadata = settings.metadata, - regExp = settings.regExp, - fields = settings.fields, - selector = settings.selector, - error = settings.error, - namespace = settings.namespace, - - eventNamespace = '.' + namespace, - moduleNamespace = namespace + '-module', - - $module = $(this), - $prompt = $module.find(selector.prompt), - $searchButton = $module.find(selector.searchButton), - $results = $module.find(selector.results), - $result = $module.find(selector.result), - $category = $module.find(selector.category), - - element = this, - instance = $module.data(moduleNamespace), - - disabledBubbled = false, - resultsDismissed = false, - - module - ; - - module = { - - initialize: function() { - module.verbose('Initializing module'); - module.get.settings(); - module.determine.searchFields(); - module.bind.events(); - module.set.type(); - module.create.results(); - module.instantiate(); - }, - instantiate: function() { - module.verbose('Storing instance of module', module); - instance = module; - $module - .data(moduleNamespace, module) - ; - }, - destroy: function() { - module.verbose('Destroying instance'); - $module - .off(eventNamespace) - .removeData(moduleNamespace) - ; - }, - - refresh: function() { - module.debug('Refreshing selector cache'); - $prompt = $module.find(selector.prompt); - $searchButton = $module.find(selector.searchButton); - $category = $module.find(selector.category); - $results = $module.find(selector.results); - $result = $module.find(selector.result); - }, - - refreshResults: function() { - $results = $module.find(selector.results); - $result = $module.find(selector.result); - }, - - bind: { - events: function() { - module.verbose('Binding events to search'); - if(settings.automatic) { - $module - .on(module.get.inputEvent() + eventNamespace, selector.prompt, module.event.input) - ; - $prompt - .attr('autocomplete', 'off') - ; - } - $module - // prompt - .on('focus' + eventNamespace, selector.prompt, module.event.focus) - .on('blur' + eventNamespace, selector.prompt, module.event.blur) - .on('keydown' + eventNamespace, selector.prompt, module.handleKeyboard) - // search button - .on('click' + eventNamespace, selector.searchButton, module.query) - // results - .on('mousedown' + eventNamespace, selector.results, module.event.result.mousedown) - .on('mouseup' + eventNamespace, selector.results, module.event.result.mouseup) - .on('click' + eventNamespace, selector.result, module.event.result.click) - ; - } - }, - - determine: { - searchFields: function() { - // this makes sure $.extend does not add specified search fields to default fields - // this is the only setting which should not extend defaults - if(parameters && parameters.searchFields !== undefined) { - settings.searchFields = parameters.searchFields; - } - } - }, - - event: { - input: function() { - if(settings.searchDelay) { - clearTimeout(module.timer); - module.timer = setTimeout(function() { - if(module.is.focused()) { - module.query(); - } - }, settings.searchDelay); - } - else { - module.query(); - } - }, - focus: function() { - module.set.focus(); - if(settings.searchOnFocus && module.has.minimumCharacters() ) { - module.query(function() { - if(module.can.show() ) { - module.showResults(); - } - }); - } - }, - blur: function(event) { - var - pageLostFocus = (document.activeElement === this), - callback = function() { - module.cancel.query(); - module.remove.focus(); - module.timer = setTimeout(module.hideResults, settings.hideDelay); - } - ; - if(pageLostFocus) { - return; - } - resultsDismissed = false; - if(module.resultsClicked) { - module.debug('Determining if user action caused search to close'); - $module - .one('click.close' + eventNamespace, selector.results, function(event) { - if(module.is.inMessage(event) || disabledBubbled) { - $prompt.focus(); - return; - } - disabledBubbled = false; - if( !module.is.animating() && !module.is.hidden()) { - callback(); - } - }) - ; - } - else { - module.debug('Input blurred without user action, closing results'); - callback(); - } - }, - result: { - mousedown: function() { - module.resultsClicked = true; - }, - mouseup: function() { - module.resultsClicked = false; - }, - click: function(event) { - module.debug('Search result selected'); - var - $result = $(this), - $title = $result.find(selector.title).eq(0), - $link = $result.is('a[href]') - ? $result - : $result.find('a[href]').eq(0), - href = $link.attr('href') || false, - target = $link.attr('target') || false, - // title is used for result lookup - value = ($title.length > 0) - ? $title.text() - : false, - results = module.get.results(), - result = $result.data(metadata.result) || module.get.result(value, results) - ; - if(value) { - module.set.value(value); - } - if( $.isFunction(settings.onSelect) ) { - if(settings.onSelect.call(element, result, results) === false) { - module.debug('Custom onSelect callback cancelled default select action'); - disabledBubbled = true; - return; - } - } - module.hideResults(); - if(href) { - event.preventDefault(); - module.verbose('Opening search link found in result', $link); - if(target == '_blank' || event.ctrlKey) { - window.open(href); - } - else { - window.location.href = (href); - } - } - } - } - }, - ensureVisible: function ensureVisible($el) { - var elTop, elBottom, resultsScrollTop, resultsHeight; - - elTop = $el.position().top; - elBottom = elTop + $el.outerHeight(true); - - resultsScrollTop = $results.scrollTop(); - resultsHeight = $results.height() - parseInt($results.css('paddingTop'), 0) + - parseInt($results.css('paddingBottom'), 0); - - if (elTop < 0) { - $results.scrollTop(resultsScrollTop + elTop); - } - - else if (resultsHeight < elBottom) { - $results.scrollTop(resultsScrollTop + (elBottom - resultsHeight)); - } - }, - handleKeyboard: function(event) { - var - // force selector refresh - $result = $module.find(selector.result), - $category = $module.find(selector.category), - $activeResult = $result.filter('.' + className.active), - currentIndex = $result.index( $activeResult ), - resultSize = $result.length, - hasActiveResult = $activeResult.length > 0, - - keyCode = event.which, - keys = { - backspace : 8, - enter : 13, - escape : 27, - upArrow : 38, - downArrow : 40 - }, - newIndex - ; - // search shortcuts - if(keyCode == keys.escape) { - module.verbose('Escape key pressed, blurring search field'); - module.hideResults(); - resultsDismissed = true; - } - if( module.is.visible() ) { - if(keyCode == keys.enter) { - module.verbose('Enter key pressed, selecting active result'); - if( $result.filter('.' + className.active).length > 0 ) { - module.event.result.click.call($result.filter('.' + className.active), event); - event.preventDefault(); - return false; - } - } - else if(keyCode == keys.upArrow && hasActiveResult) { - module.verbose('Up key pressed, changing active result'); - newIndex = (currentIndex - 1 < 0) - ? currentIndex - : currentIndex - 1 - ; - $category - .removeClass(className.active) - ; - $result - .removeClass(className.active) - .eq(newIndex) - .addClass(className.active) - .closest($category) - .addClass(className.active) - ; - module.ensureVisible($result.eq(newIndex)); - event.preventDefault(); - } - else if(keyCode == keys.downArrow) { - module.verbose('Down key pressed, changing active result'); - newIndex = (currentIndex + 1 >= resultSize) - ? currentIndex - : currentIndex + 1 - ; - $category - .removeClass(className.active) - ; - $result - .removeClass(className.active) - .eq(newIndex) - .addClass(className.active) - .closest($category) - .addClass(className.active) - ; - module.ensureVisible($result.eq(newIndex)); - event.preventDefault(); - } - } - else { - // query shortcuts - if(keyCode == keys.enter) { - module.verbose('Enter key pressed, executing query'); - module.query(); - module.set.buttonPressed(); - $prompt.one('keyup', module.remove.buttonFocus); - } - } - }, - - setup: { - api: function(searchTerm, callback) { - var - apiSettings = { - debug : settings.debug, - on : false, - cache : settings.cache, - action : 'search', - urlData : { - query : searchTerm - }, - onSuccess : function(response) { - module.parse.response.call(element, response, searchTerm); - callback(); - }, - onFailure : function() { - module.displayMessage(error.serverError); - callback(); - }, - onAbort : function(response) { - }, - onError : module.error - } - ; - $.extend(true, apiSettings, settings.apiSettings); - module.verbose('Setting up API request', apiSettings); - $module.api(apiSettings); - } - }, - - can: { - useAPI: function() { - return $.fn.api !== undefined; - }, - show: function() { - return module.is.focused() && !module.is.visible() && !module.is.empty(); - }, - transition: function() { - return settings.transition && $.fn.transition !== undefined && $module.transition('is supported'); - } - }, - - is: { - animating: function() { - return $results.hasClass(className.animating); - }, - hidden: function() { - return $results.hasClass(className.hidden); - }, - inMessage: function(event) { - if(!event.target) { - return; - } - var - $target = $(event.target), - isInDOM = $.contains(document.documentElement, event.target) - ; - return (isInDOM && $target.closest(selector.message).length > 0); - }, - empty: function() { - return ($results.html() === ''); - }, - visible: function() { - return ($results.filter(':visible').length > 0); - }, - focused: function() { - return ($prompt.filter(':focus').length > 0); - } - }, - - get: { - settings: function() { - if($.isPlainObject(parameters) && parameters.searchFullText) { - settings.fullTextSearch = parameters.searchFullText; - module.error(settings.error.oldSearchSyntax, element); - } - if (settings.ignoreDiacritics && !String.prototype.normalize) { - settings.ignoreDiacritics = false; - module.error(error.noNormalize, element); - } - }, - inputEvent: function() { - var - prompt = $prompt[0], - inputEvent = (prompt !== undefined && prompt.oninput !== undefined) - ? 'input' - : (prompt !== undefined && prompt.onpropertychange !== undefined) - ? 'propertychange' - : 'keyup' - ; - return inputEvent; - }, - value: function() { - return $prompt.val(); - }, - results: function() { - var - results = $module.data(metadata.results) - ; - return results; - }, - result: function(value, results) { - var - result = false - ; - value = (value !== undefined) - ? value - : module.get.value() - ; - results = (results !== undefined) - ? results - : module.get.results() - ; - if(settings.type === 'category') { - module.debug('Finding result that matches', value); - $.each(results, function(index, category) { - if(Array.isArray(category.results)) { - result = module.search.object(value, category.results)[0]; - // don't continue searching if a result is found - if(result) { - return false; - } - } - }); - } - else { - module.debug('Finding result in results object', value); - result = module.search.object(value, results)[0]; - } - return result || false; - }, - }, - - select: { - firstResult: function() { - module.verbose('Selecting first result'); - $result.first().addClass(className.active); - } - }, - - set: { - focus: function() { - $module.addClass(className.focus); - }, - loading: function() { - $module.addClass(className.loading); - }, - value: function(value) { - module.verbose('Setting search input value', value); - $prompt - .val(value) - ; - }, - type: function(type) { - type = type || settings.type; - if(settings.type == 'category') { - $module.addClass(settings.type); - } - }, - buttonPressed: function() { - $searchButton.addClass(className.pressed); - } - }, - - remove: { - loading: function() { - $module.removeClass(className.loading); - }, - focus: function() { - $module.removeClass(className.focus); - }, - buttonPressed: function() { - $searchButton.removeClass(className.pressed); - }, - diacritics: function(text) { - return settings.ignoreDiacritics ? text.normalize('NFD').replace(/[\u0300-\u036f]/g, '') : text; - } - }, - - query: function(callback) { - callback = $.isFunction(callback) - ? callback - : function(){} - ; - var - searchTerm = module.get.value(), - cache = module.read.cache(searchTerm) - ; - callback = callback || function() {}; - if( module.has.minimumCharacters() ) { - if(cache) { - module.debug('Reading result from cache', searchTerm); - module.save.results(cache.results); - module.addResults(cache.html); - module.inject.id(cache.results); - callback(); - } - else { - module.debug('Querying for', searchTerm); - if($.isPlainObject(settings.source) || Array.isArray(settings.source)) { - module.search.local(searchTerm); - callback(); - } - else if( module.can.useAPI() ) { - module.search.remote(searchTerm, callback); - } - else { - module.error(error.source); - callback(); - } - } - settings.onSearchQuery.call(element, searchTerm); - } - else { - module.hideResults(); - } - }, - - search: { - local: function(searchTerm) { - var - results = module.search.object(searchTerm, settings.source), - searchHTML - ; - module.set.loading(); - module.save.results(results); - module.debug('Returned full local search results', results); - if(settings.maxResults > 0) { - module.debug('Using specified max results', results); - results = results.slice(0, settings.maxResults); - } - if(settings.type == 'category') { - results = module.create.categoryResults(results); - } - searchHTML = module.generateResults({ - results: results - }); - module.remove.loading(); - module.addResults(searchHTML); - module.inject.id(results); - module.write.cache(searchTerm, { - html : searchHTML, - results : results - }); - }, - remote: function(searchTerm, callback) { - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if($module.api('is loading')) { - $module.api('abort'); - } - module.setup.api(searchTerm, callback); - $module - .api('query') - ; - }, - object: function(searchTerm, source, searchFields) { - searchTerm = module.remove.diacritics(String(searchTerm)); - var - results = [], - exactResults = [], - fuzzyResults = [], - searchExp = searchTerm.replace(regExp.escape, '\\$&'), - matchRegExp = new RegExp(regExp.beginsWith + searchExp, 'i'), - - // avoid duplicates when pushing results - addResult = function(array, result) { - var - notResult = ($.inArray(result, results) == -1), - notFuzzyResult = ($.inArray(result, fuzzyResults) == -1), - notExactResults = ($.inArray(result, exactResults) == -1) - ; - if(notResult && notFuzzyResult && notExactResults) { - array.push(result); - } - } - ; - source = source || settings.source; - searchFields = (searchFields !== undefined) - ? searchFields - : settings.searchFields - ; - - // search fields should be array to loop correctly - if(!Array.isArray(searchFields)) { - searchFields = [searchFields]; - } - - // exit conditions if no source - if(source === undefined || source === false) { - module.error(error.source); - return []; - } - // iterate through search fields looking for matches - $.each(searchFields, function(index, field) { - $.each(source, function(label, content) { - var - fieldExists = (typeof content[field] == 'string') || (typeof content[field] == 'number') - ; - if(fieldExists) { - var text; - if (typeof content[field] === 'string'){ - text = module.remove.diacritics(content[field]); - } else { - text = content[field].toString(); - } - if( text.search(matchRegExp) !== -1) { - // content starts with value (first in results) - addResult(results, content); - } - else if(settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text) ) { - // content fuzzy matches (last in results) - addResult(exactResults, content); - } - else if(settings.fullTextSearch == true && module.fuzzySearch(searchTerm, text) ) { - // content fuzzy matches (last in results) - addResult(fuzzyResults, content); - } - } - }); - }); - $.merge(exactResults, fuzzyResults); - $.merge(results, exactResults); - return results; - } - }, - exactSearch: function (query, term) { - query = query.toLowerCase(); - term = term.toLowerCase(); - return term.indexOf(query) > -1; - }, - fuzzySearch: function(query, term) { - var - termLength = term.length, - queryLength = query.length - ; - if(typeof query !== 'string') { - return false; - } - query = query.toLowerCase(); - term = term.toLowerCase(); - if(queryLength > termLength) { - return false; - } - if(queryLength === termLength) { - return (query === term); - } - search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) { - var - queryCharacter = query.charCodeAt(characterIndex) - ; - while(nextCharacterIndex < termLength) { - if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) { - continue search; - } - } - return false; - } - return true; - }, - - parse: { - response: function(response, searchTerm) { - if(Array.isArray(response)){ - var o={}; - o[fields.results]=response; - response = o; - } - var - searchHTML = module.generateResults(response) - ; - module.verbose('Parsing server response', response); - if(response !== undefined) { - if(searchTerm !== undefined && response[fields.results] !== undefined) { - module.addResults(searchHTML); - module.inject.id(response[fields.results]); - module.write.cache(searchTerm, { - html : searchHTML, - results : response[fields.results] - }); - module.save.results(response[fields.results]); - } - } - } - }, - - cancel: { - query: function() { - if( module.can.useAPI() ) { - $module.api('abort'); - } - } - }, - - has: { - minimumCharacters: function() { - var - searchTerm = module.get.value(), - numCharacters = searchTerm.length - ; - return (numCharacters >= settings.minCharacters); - }, - results: function() { - if($results.length === 0) { - return false; - } - var - html = $results.html() - ; - return html != ''; - } - }, - - clear: { - cache: function(value) { - var - cache = $module.data(metadata.cache) - ; - if(!value) { - module.debug('Clearing cache', value); - $module.removeData(metadata.cache); - } - else if(value && cache && cache[value]) { - module.debug('Removing value from cache', value); - delete cache[value]; - $module.data(metadata.cache, cache); - } - } - }, - - read: { - cache: function(name) { - var - cache = $module.data(metadata.cache) - ; - if(settings.cache) { - module.verbose('Checking cache for generated html for query', name); - return (typeof cache == 'object') && (cache[name] !== undefined) - ? cache[name] - : false - ; - } - return false; - } - }, - - create: { - categoryResults: function(results) { - var - categoryResults = {} - ; - $.each(results, function(index, result) { - if(!result.category) { - return; - } - if(categoryResults[result.category] === undefined) { - module.verbose('Creating new category of results', result.category); - categoryResults[result.category] = { - name : result.category, - results : [result] - }; - } - else { - categoryResults[result.category].results.push(result); - } - }); - return categoryResults; - }, - id: function(resultIndex, categoryIndex) { - var - resultID = (resultIndex + 1), // not zero indexed - letterID, - id - ; - if(categoryIndex !== undefined) { - // start char code for "A" - letterID = String.fromCharCode(97 + categoryIndex); - id = letterID + resultID; - module.verbose('Creating category result id', id); - } - else { - id = resultID; - module.verbose('Creating result id', id); - } - return id; - }, - results: function() { - if($results.length === 0) { - $results = $('<div />') - .addClass(className.results) - .appendTo($module) - ; - } - } - }, - - inject: { - result: function(result, resultIndex, categoryIndex) { - module.verbose('Injecting result into results'); - var - $selectedResult = (categoryIndex !== undefined) - ? $results - .children().eq(categoryIndex) - .children(selector.results) - .first() - .children(selector.result) - .eq(resultIndex) - : $results - .children(selector.result).eq(resultIndex) - ; - module.verbose('Injecting results metadata', $selectedResult); - $selectedResult - .data(metadata.result, result) - ; - }, - id: function(results) { - module.debug('Injecting unique ids into results'); - var - // since results may be object, we must use counters - categoryIndex = 0, - resultIndex = 0 - ; - if(settings.type === 'category') { - // iterate through each category result - $.each(results, function(index, category) { - if(category.results.length > 0){ - resultIndex = 0; - $.each(category.results, function(index, result) { - if(result.id === undefined) { - result.id = module.create.id(resultIndex, categoryIndex); - } - module.inject.result(result, resultIndex, categoryIndex); - resultIndex++; - }); - categoryIndex++; - } - }); - } - else { - // top level - $.each(results, function(index, result) { - if(result.id === undefined) { - result.id = module.create.id(resultIndex); - } - module.inject.result(result, resultIndex); - resultIndex++; - }); - } - return results; - } - }, - - save: { - results: function(results) { - module.verbose('Saving current search results to metadata', results); - $module.data(metadata.results, results); - } - }, - - write: { - cache: function(name, value) { - var - cache = ($module.data(metadata.cache) !== undefined) - ? $module.data(metadata.cache) - : {} - ; - if(settings.cache) { - module.verbose('Writing generated html to cache', name, value); - cache[name] = value; - $module - .data(metadata.cache, cache) - ; - } - } - }, - - addResults: function(html) { - if( $.isFunction(settings.onResultsAdd) ) { - if( settings.onResultsAdd.call($results, html) === false ) { - module.debug('onResultsAdd callback cancelled default action'); - return false; - } - } - if(html) { - $results - .html(html) - ; - module.refreshResults(); - if(settings.selectFirstResult) { - module.select.firstResult(); - } - module.showResults(); - } - else { - module.hideResults(function() { - $results.empty(); - }); - } - }, - - showResults: function(callback) { - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if(resultsDismissed) { - return; - } - if(!module.is.visible() && module.has.results()) { - if( module.can.transition() ) { - module.debug('Showing results with css animations'); - $results - .transition({ - animation : settings.transition + ' in', - debug : settings.debug, - verbose : settings.verbose, - duration : settings.duration, - onShow : function() { - var $firstResult = $module.find(selector.result).eq(0); - if($firstResult.length > 0) { - module.ensureVisible($firstResult); - } - }, - onComplete : function() { - callback(); - }, - queue : true - }) - ; - } - else { - module.debug('Showing results with javascript'); - $results - .stop() - .fadeIn(settings.duration, settings.easing) - ; - } - settings.onResultsOpen.call($results); - } - }, - hideResults: function(callback) { - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if( module.is.visible() ) { - if( module.can.transition() ) { - module.debug('Hiding results with css animations'); - $results - .transition({ - animation : settings.transition + ' out', - debug : settings.debug, - verbose : settings.verbose, - duration : settings.duration, - onComplete : function() { - callback(); - }, - queue : true - }) - ; - } - else { - module.debug('Hiding results with javascript'); - $results - .stop() - .fadeOut(settings.duration, settings.easing) - ; - } - settings.onResultsClose.call($results); - } - }, - - generateResults: function(response) { - module.debug('Generating html from response', response); - var - template = settings.templates[settings.type], - isProperObject = ($.isPlainObject(response[fields.results]) && !$.isEmptyObject(response[fields.results])), - isProperArray = (Array.isArray(response[fields.results]) && response[fields.results].length > 0), - html = '' - ; - if(isProperObject || isProperArray ) { - if(settings.maxResults > 0) { - if(isProperObject) { - if(settings.type == 'standard') { - module.error(error.maxResults); - } - } - else { - response[fields.results] = response[fields.results].slice(0, settings.maxResults); - } - } - if($.isFunction(template)) { - html = template(response, fields, settings.preserveHTML); - } - else { - module.error(error.noTemplate, false); - } - } - else if(settings.showNoResults) { - html = module.displayMessage(error.noResults, 'empty', error.noResultsHeader); - } - settings.onResults.call(element, response); - return html; - }, - - displayMessage: function(text, type, header) { - type = type || 'standard'; - module.debug('Displaying message', text, type, header); - module.addResults( settings.templates.message(text, type, header) ); - return settings.templates.message(text, type, header); - }, - - setting: function(name, value) { - if( $.isPlainObject(name) ) { - $.extend(true, settings, name); - } - else if(value !== undefined) { - settings[name] = value; - } - else { - return settings[name]; - } - }, - internal: function(name, value) { - if( $.isPlainObject(name) ) { - $.extend(true, module, name); - } - else if(value !== undefined) { - module[name] = value; - } - else { - return module[name]; - } - }, - debug: function() { - if(!settings.silent && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.debug.apply(console, arguments); - } - } - }, - verbose: function() { - if(!settings.silent && settings.verbose && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.verbose.apply(console, arguments); - } - } - }, - error: function() { - if(!settings.silent) { - module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); - module.error.apply(console, arguments); - } - }, - performance: { - log: function(message) { - var - currentTime, - executionTime, - previousTime - ; - if(settings.performance) { - currentTime = new Date().getTime(); - previousTime = time || currentTime; - executionTime = currentTime - previousTime; - time = currentTime; - performance.push({ - 'Name' : message[0], - 'Arguments' : [].slice.call(message, 1) || '', - 'Element' : element, - 'Execution Time' : executionTime - }); - } - clearTimeout(module.performance.timer); - module.performance.timer = setTimeout(module.performance.display, 500); - }, - display: function() { - var - title = settings.name + ':', - totalTime = 0 - ; - time = false; - clearTimeout(module.performance.timer); - $.each(performance, function(index, data) { - totalTime += data['Execution Time']; - }); - title += ' ' + totalTime + 'ms'; - if(moduleSelector) { - title += ' \'' + moduleSelector + '\''; - } - if($allModules.length > 1) { - title += ' ' + '(' + $allModules.length + ')'; - } - if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { - console.groupCollapsed(title); - if(console.table) { - console.table(performance); - } - else { - $.each(performance, function(index, data) { - console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); - }); - } - console.groupEnd(); - } - performance = []; - } - }, - invoke: function(query, passedArguments, context) { - var - object = instance, - maxDepth, - found, - response - ; - passedArguments = passedArguments || queryArguments; - context = element || context; - if(typeof query == 'string' && object !== undefined) { - query = query.split(/[\. ]/); - maxDepth = query.length - 1; - $.each(query, function(depth, value) { - var camelCaseValue = (depth != maxDepth) - ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) - : query - ; - if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { - object = object[camelCaseValue]; - } - else if( object[camelCaseValue] !== undefined ) { - found = object[camelCaseValue]; - return false; - } - else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { - object = object[value]; - } - else if( object[value] !== undefined ) { - found = object[value]; - return false; - } - else { - return false; - } - }); - } - if( $.isFunction( found ) ) { - response = found.apply(context, passedArguments); - } - else if(found !== undefined) { - response = found; - } - if(Array.isArray(returnedValue)) { - returnedValue.push(response); - } - else if(returnedValue !== undefined) { - returnedValue = [returnedValue, response]; - } - else if(response !== undefined) { - returnedValue = response; - } - return found; - } - }; - if(methodInvoked) { - if(instance === undefined) { - module.initialize(); - } - module.invoke(query); - } - else { - if(instance !== undefined) { - instance.invoke('destroy'); - } - module.initialize(); - } - - }) - ; - - return (returnedValue !== undefined) - ? returnedValue - : this - ; -}; - -$.fn.search.settings = { - - name : 'Search', - namespace : 'search', - - silent : false, - debug : false, - verbose : false, - performance : true, - - // template to use (specified in settings.templates) - type : 'standard', - - // minimum characters required to search - minCharacters : 1, - - // whether to select first result after searching automatically - selectFirstResult : false, - - // API config - apiSettings : false, - - // object to search - source : false, - - // Whether search should query current term on focus - searchOnFocus : true, - - // fields to search - searchFields : [ - 'id', - 'title', - 'description' - ], - - // field to display in standard results template - displayField : '', - - // search anywhere in value (set to 'exact' to require exact matches - fullTextSearch : 'exact', - - // match results also if they contain diacritics of the same base character (for example searching for "a" will also match "á" or "â" or "à", etc...) - ignoreDiacritics : false, - - // whether to add events to prompt automatically - automatic : true, - - // delay before hiding menu after blur - hideDelay : 0, - - // delay before searching - searchDelay : 200, - - // maximum results returned from search - maxResults : 7, - - // whether to store lookups in local cache - cache : true, - - // whether no results errors should be shown - showNoResults : true, - - // preserve possible html of resultset values - preserveHTML : true, - - // transition settings - transition : 'scale', - duration : 200, - easing : 'easeOutExpo', - - // callbacks - onSelect : false, - onResultsAdd : false, - - onSearchQuery : function(query){}, - onResults : function(response){}, - - onResultsOpen : function(){}, - onResultsClose : function(){}, - - className: { - animating : 'animating', - active : 'active', - empty : 'empty', - focus : 'focus', - hidden : 'hidden', - loading : 'loading', - results : 'results', - pressed : 'down' - }, - - error : { - source : 'Cannot search. No source used, and Semantic API module was not included', - noResultsHeader : 'No Results', - noResults : 'Your search returned no results', - logging : 'Error in debug logging, exiting.', - noEndpoint : 'No search endpoint was specified', - noTemplate : 'A valid template name was not specified.', - oldSearchSyntax : 'searchFullText setting has been renamed fullTextSearch for consistency, please adjust your settings.', - serverError : 'There was an issue querying the server.', - maxResults : 'Results must be an array to use maxResults setting', - method : 'The method you called is not defined.', - noNormalize : '"ignoreDiacritics" setting will be ignored. Browser does not support String().normalize(). You may consider including <https://cdn.jsdelivr.net/npm/unorm@1.4.1/lib/unorm.min.js> as a polyfill.' - }, - - metadata: { - cache : 'cache', - results : 'results', - result : 'result' - }, - - regExp: { - escape : /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, - beginsWith : '(?:\s|^)' - }, - - // maps api response attributes to internal representation - fields: { - categories : 'results', // array of categories (category view) - categoryName : 'name', // name of category (category view) - categoryResults : 'results', // array of results (category view) - description : 'description', // result description - image : 'image', // result image - price : 'price', // result price - results : 'results', // array of results (standard) - title : 'title', // result title - url : 'url', // result url - action : 'action', // "view more" object name - actionText : 'text', // "view more" text - actionURL : 'url' // "view more" url - }, - - selector : { - prompt : '.prompt', - searchButton : '.search.button', - results : '.results', - message : '.results > .message', - category : '.category', - result : '.result', - title : '.title, .name' - }, - - templates: { - escape: function(string, preserveHTML) { - if (preserveHTML){ - return string; - } - var - badChars = /[<>"'`]/g, - shouldEscape = /[&<>"'`]/, - escape = { - "<": "<", - ">": ">", - '"': """, - "'": "'", - "`": "`" - }, - escapedChar = function(chr) { - return escape[chr]; - } - ; - if(shouldEscape.test(string)) { - string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); - return string.replace(badChars, escapedChar); - } - return string; - }, - message: function(message, type, header) { - var - html = '' - ; - if(message !== undefined && type !== undefined) { - html += '' - + '<div class="message ' + type + '">' - ; - if(header) { - html += '' - + '<div class="header">' + header + '</div>' - ; - } - html += ' <div class="description">' + message + '</div>'; - html += '</div>'; - } - return html; - }, - category: function(response, fields, preserveHTML) { - var - html = '', - escape = $.fn.search.settings.templates.escape - ; - if(response[fields.categoryResults] !== undefined) { - - // each category - $.each(response[fields.categoryResults], function(index, category) { - if(category[fields.results] !== undefined && category.results.length > 0) { - - html += '<div class="category">'; - - if(category[fields.categoryName] !== undefined) { - html += '<div class="name">' + escape(category[fields.categoryName], preserveHTML) + '</div>'; - } - - // each item inside category - html += '<div class="results">'; - $.each(category.results, function(index, result) { - if(result[fields.url]) { - html += '<a class="result" href="' + result[fields.url].replace(/"/g,"") + '">'; - } - else { - html += '<a class="result">'; - } - if(result[fields.image] !== undefined) { - html += '' - + '<div class="image">' - + ' <img src="' + result[fields.image].replace(/"/g,"") + '">' - + '</div>' - ; - } - html += '<div class="content">'; - if(result[fields.price] !== undefined) { - html += '<div class="price">' + escape(result[fields.price], preserveHTML) + '</div>'; - } - if(result[fields.title] !== undefined) { - html += '<div class="title">' + escape(result[fields.title], preserveHTML) + '</div>'; - } - if(result[fields.description] !== undefined) { - html += '<div class="description">' + escape(result[fields.description], preserveHTML) + '</div>'; - } - html += '' - + '</div>' - ; - html += '</a>'; - }); - html += '</div>'; - html += '' - + '</div>' - ; - } - }); - if(response[fields.action]) { - if(fields.actionURL === false) { - html += '' - + '<div class="action">' - + escape(response[fields.action][fields.actionText], preserveHTML) - + '</div>'; - } else { - html += '' - + '<a href="' + response[fields.action][fields.actionURL].replace(/"/g,"") + '" class="action">' - + escape(response[fields.action][fields.actionText], preserveHTML) - + '</a>'; - } - } - return html; - } - return false; - }, - standard: function(response, fields, preserveHTML) { - var - html = '', - escape = $.fn.search.settings.templates.escape - ; - if(response[fields.results] !== undefined) { - - // each result - $.each(response[fields.results], function(index, result) { - if(result[fields.url]) { - html += '<a class="result" href="' + result[fields.url].replace(/"/g,"") + '">'; - } - else { - html += '<a class="result">'; - } - if(result[fields.image] !== undefined) { - html += '' - + '<div class="image">' - + ' <img src="' + result[fields.image].replace(/"/g,"") + '">' - + '</div>' - ; - } - html += '<div class="content">'; - if(result[fields.price] !== undefined) { - html += '<div class="price">' + escape(result[fields.price], preserveHTML) + '</div>'; - } - if(result[fields.title] !== undefined) { - html += '<div class="title">' + escape(result[fields.title], preserveHTML) + '</div>'; - } - if(result[fields.description] !== undefined) { - html += '<div class="description">' + escape(result[fields.description], preserveHTML) + '</div>'; - } - html += '' - + '</div>' - ; - html += '</a>'; - }); - if(response[fields.action]) { - if(fields.actionURL === false) { - html += '' - + '<div class="action">' - + escape(response[fields.action][fields.actionText], preserveHTML) - + '</div>'; - } else { - html += '' - + '<a href="' + response[fields.action][fields.actionURL].replace(/"/g,"") + '" class="action">' - + escape(response[fields.action][fields.actionText], preserveHTML) - + '</a>'; - } - } - return html; - } - return false; - } - } -}; - -})( jQuery, window, document ); - -/*! - * # Fomantic-UI - Tab - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -;(function ($, window, document, undefined) { - -'use strict'; - -$.isWindow = $.isWindow || function(obj) { - return obj != null && obj === obj.window; -}; -$.isFunction = $.isFunction || function(obj) { - return typeof obj === "function" && typeof obj.nodeType !== "number"; -}; - -window = (typeof window != 'undefined' && window.Math == Math) - ? window - : (typeof self != 'undefined' && self.Math == Math) - ? self - : Function('return this')() -; - -$.fn.tab = function(parameters) { - - var - // use window context if none specified - $allModules = $.isFunction(this) - ? $(window) - : $(this), - - moduleSelector = $allModules.selector || '', - time = new Date().getTime(), - performance = [], - - query = arguments[0], - methodInvoked = (typeof query == 'string'), - queryArguments = [].slice.call(arguments, 1), - - initializedHistory = false, - returnedValue - ; - - $allModules - .each(function() { - var - - settings = ( $.isPlainObject(parameters) ) - ? $.extend(true, {}, $.fn.tab.settings, parameters) - : $.extend({}, $.fn.tab.settings), - - className = settings.className, - metadata = settings.metadata, - selector = settings.selector, - error = settings.error, - regExp = settings.regExp, - - eventNamespace = '.' + settings.namespace, - moduleNamespace = 'module-' + settings.namespace, - - $module = $(this), - $context, - $tabs, - - cache = {}, - firstLoad = true, - recursionDepth = 0, - element = this, - instance = $module.data(moduleNamespace), - - activeTabPath, - parameterArray, - module, - - historyEvent - - ; - - module = { - - initialize: function() { - module.debug('Initializing tab menu item', $module); - module.fix.callbacks(); - module.determineTabs(); - - module.debug('Determining tabs', settings.context, $tabs); - // set up automatic routing - if(settings.auto) { - module.set.auto(); - } - module.bind.events(); - - if(settings.history && !initializedHistory) { - module.initializeHistory(); - initializedHistory = true; - } - - if(settings.autoTabActivation && instance === undefined && module.determine.activeTab() == null) { - module.debug('No active tab detected, setting first tab active', module.get.initialPath()); - module.changeTab(settings.autoTabActivation === true ? module.get.initialPath() : settings.autoTabActivation); - }; - - module.instantiate(); - }, - - instantiate: function () { - module.verbose('Storing instance of module', module); - instance = module; - $module - .data(moduleNamespace, module) - ; - }, - - destroy: function() { - module.debug('Destroying tabs', $module); - $module - .removeData(moduleNamespace) - .off(eventNamespace) - ; - }, - - bind: { - events: function() { - // if using $.tab don't add events - if( !$.isWindow( element ) ) { - module.debug('Attaching tab activation events to element', $module); - $module - .on('click' + eventNamespace, module.event.click) - ; - } - } - }, - - determineTabs: function() { - var - $reference - ; - - // determine tab context - if(settings.context === 'parent') { - if($module.closest(selector.ui).length > 0) { - $reference = $module.closest(selector.ui); - module.verbose('Using closest UI element as parent', $reference); - } - else { - $reference = $module; - } - $context = $reference.parent(); - module.verbose('Determined parent element for creating context', $context); - } - else if(settings.context) { - $context = $(settings.context); - module.verbose('Using selector for tab context', settings.context, $context); - } - else { - $context = $('body'); - } - // find tabs - if(settings.childrenOnly) { - $tabs = $context.children(selector.tabs); - module.debug('Searching tab context children for tabs', $context, $tabs); - } - else { - $tabs = $context.find(selector.tabs); - module.debug('Searching tab context for tabs', $context, $tabs); - } - }, - - fix: { - callbacks: function() { - if( $.isPlainObject(parameters) && (parameters.onTabLoad || parameters.onTabInit) ) { - if(parameters.onTabLoad) { - parameters.onLoad = parameters.onTabLoad; - delete parameters.onTabLoad; - module.error(error.legacyLoad, parameters.onLoad); - } - if(parameters.onTabInit) { - parameters.onFirstLoad = parameters.onTabInit; - delete parameters.onTabInit; - module.error(error.legacyInit, parameters.onFirstLoad); - } - settings = $.extend(true, {}, $.fn.tab.settings, parameters); - } - } - }, - - initializeHistory: function() { - module.debug('Initializing page state'); - if( $.address === undefined ) { - module.error(error.state); - return false; - } - else { - if(settings.historyType == 'state') { - module.debug('Using HTML5 to manage state'); - if(settings.path !== false) { - $.address - .history(true) - .state(settings.path) - ; - } - else { - module.error(error.path); - return false; - } - } - $.address - .bind('change', module.event.history.change) - ; - } - }, - - event: { - click: function(event) { - var - tabPath = $(this).data(metadata.tab) - ; - if(tabPath !== undefined) { - if(settings.history) { - module.verbose('Updating page state', event); - $.address.value(tabPath); - } - else { - module.verbose('Changing tab', event); - module.changeTab(tabPath); - } - event.preventDefault(); - } - else { - module.debug('No tab specified'); - } - }, - history: { - change: function(event) { - var - tabPath = event.pathNames.join('/') || module.get.initialPath(), - pageTitle = settings.templates.determineTitle(tabPath) || false - ; - module.performance.display(); - module.debug('History change event', tabPath, event); - historyEvent = event; - if(tabPath !== undefined) { - module.changeTab(tabPath); - } - if(pageTitle) { - $.address.title(pageTitle); - } - } - } - }, - - refresh: function() { - if(activeTabPath) { - module.debug('Refreshing tab', activeTabPath); - module.changeTab(activeTabPath); - } - }, - - cache: { - - read: function(cacheKey) { - return (cacheKey !== undefined) - ? cache[cacheKey] - : false - ; - }, - add: function(cacheKey, content) { - cacheKey = cacheKey || activeTabPath; - module.debug('Adding cached content for', cacheKey); - cache[cacheKey] = content; - }, - remove: function(cacheKey) { - cacheKey = cacheKey || activeTabPath; - module.debug('Removing cached content for', cacheKey); - delete cache[cacheKey]; - } - }, - - escape: { - string: function(text) { - text = String(text); - return text.replace(regExp.escape, '\\$&'); - } - }, - - set: { - auto: function() { - var - url = (typeof settings.path == 'string') - ? settings.path.replace(/\/$/, '') + '/{$tab}' - : '/{$tab}' - ; - module.verbose('Setting up automatic tab retrieval from server', url); - if($.isPlainObject(settings.apiSettings)) { - settings.apiSettings.url = url; - } - else { - settings.apiSettings = { - url: url - }; - } - }, - loading: function(tabPath) { - var - $tab = module.get.tabElement(tabPath), - isLoading = $tab.hasClass(className.loading) - ; - if(!isLoading) { - module.verbose('Setting loading state for', $tab); - $tab - .addClass(className.loading) - .siblings($tabs) - .removeClass(className.active + ' ' + className.loading) - ; - if($tab.length > 0) { - settings.onRequest.call($tab[0], tabPath); - } - } - }, - state: function(state) { - $.address.value(state); - } - }, - - changeTab: function(tabPath) { - var - pushStateAvailable = (window.history && window.history.pushState), - shouldIgnoreLoad = (pushStateAvailable && settings.ignoreFirstLoad && firstLoad), - remoteContent = (settings.auto || $.isPlainObject(settings.apiSettings) ), - // only add default path if not remote content - pathArray = (remoteContent && !shouldIgnoreLoad) - ? module.utilities.pathToArray(tabPath) - : module.get.defaultPathArray(tabPath) - ; - tabPath = module.utilities.arrayToPath(pathArray); - $.each(pathArray, function(index, tab) { - var - currentPathArray = pathArray.slice(0, index + 1), - currentPath = module.utilities.arrayToPath(currentPathArray), - - isTab = module.is.tab(currentPath), - isLastIndex = (index + 1 == pathArray.length), - - $tab = module.get.tabElement(currentPath), - $anchor, - nextPathArray, - nextPath, - isLastTab - ; - module.verbose('Looking for tab', tab); - if(isTab) { - module.verbose('Tab was found', tab); - // scope up - activeTabPath = currentPath; - parameterArray = module.utilities.filterArray(pathArray, currentPathArray); - - if(isLastIndex) { - isLastTab = true; - } - else { - nextPathArray = pathArray.slice(0, index + 2); - nextPath = module.utilities.arrayToPath(nextPathArray); - isLastTab = ( !module.is.tab(nextPath) ); - if(isLastTab) { - module.verbose('Tab parameters found', nextPathArray); - } - } - if(isLastTab && remoteContent) { - if(!shouldIgnoreLoad) { - module.activate.navigation(currentPath); - module.fetch.content(currentPath, tabPath); - } - else { - module.debug('Ignoring remote content on first tab load', currentPath); - firstLoad = false; - module.cache.add(tabPath, $tab.html()); - module.activate.all(currentPath); - settings.onFirstLoad.call($tab[0], currentPath, parameterArray, historyEvent); - settings.onLoad.call($tab[0], currentPath, parameterArray, historyEvent); - } - return false; - } - else { - module.debug('Opened local tab', currentPath); - module.activate.all(currentPath); - if( !module.cache.read(currentPath) ) { - module.cache.add(currentPath, true); - module.debug('First time tab loaded calling tab init'); - settings.onFirstLoad.call($tab[0], currentPath, parameterArray, historyEvent); - } - settings.onLoad.call($tab[0], currentPath, parameterArray, historyEvent); - } - - } - else if(tabPath.search('/') == -1 && tabPath !== '') { - // look for in page anchor - tabPath = module.escape.string(tabPath); - $anchor = $('#' + tabPath + ', a[name="' + tabPath + '"]'); - currentPath = $anchor.closest('[data-tab]').data(metadata.tab); - $tab = module.get.tabElement(currentPath); - // if anchor exists use parent tab - if($anchor && $anchor.length > 0 && currentPath) { - module.debug('Anchor link used, opening parent tab', $tab, $anchor); - if( !$tab.hasClass(className.active) ) { - setTimeout(function() { - module.scrollTo($anchor); - }, 0); - } - module.activate.all(currentPath); - if( !module.cache.read(currentPath) ) { - module.cache.add(currentPath, true); - module.debug('First time tab loaded calling tab init'); - settings.onFirstLoad.call($tab[0], currentPath, parameterArray, historyEvent); - } - settings.onLoad.call($tab[0], currentPath, parameterArray, historyEvent); - return false; - } - } - else { - module.error(error.missingTab, $module, $context, currentPath); - return false; - } - }); - }, - - scrollTo: function($element) { - var - scrollOffset = ($element && $element.length > 0) - ? $element.offset().top - : false - ; - if(scrollOffset !== false) { - module.debug('Forcing scroll to an in-page link in a hidden tab', scrollOffset, $element); - $(document).scrollTop(scrollOffset); - } - }, - - update: { - content: function(tabPath, html, evaluateScripts) { - var - $tab = module.get.tabElement(tabPath), - tab = $tab[0] - ; - evaluateScripts = (evaluateScripts !== undefined) - ? evaluateScripts - : settings.evaluateScripts - ; - if(typeof settings.cacheType == 'string' && settings.cacheType.toLowerCase() == 'dom' && typeof html !== 'string') { - $tab - .empty() - .append($(html).clone(true)) - ; - } - else { - if(evaluateScripts) { - module.debug('Updating HTML and evaluating inline scripts', tabPath, html); - $tab.html(html); - } - else { - module.debug('Updating HTML', tabPath, html); - tab.innerHTML = html; - } - } - } - }, - - fetch: { - - content: function(tabPath, fullTabPath) { - var - $tab = module.get.tabElement(tabPath), - apiSettings = { - dataType : 'html', - encodeParameters : false, - on : 'now', - cache : settings.alwaysRefresh, - headers : { - 'X-Remote': true - }, - onSuccess : function(response) { - if(settings.cacheType == 'response') { - module.cache.add(fullTabPath, response); - } - module.update.content(tabPath, response); - if(tabPath == activeTabPath) { - module.debug('Content loaded', tabPath); - module.activate.tab(tabPath); - } - else { - module.debug('Content loaded in background', tabPath); - } - settings.onFirstLoad.call($tab[0], tabPath, parameterArray, historyEvent); - settings.onLoad.call($tab[0], tabPath, parameterArray, historyEvent); - - if(settings.loadOnce) { - module.cache.add(fullTabPath, true); - } - else if(typeof settings.cacheType == 'string' && settings.cacheType.toLowerCase() == 'dom' && $tab.children().length > 0) { - setTimeout(function() { - var - $clone = $tab.children().clone(true) - ; - $clone = $clone.not('script'); - module.cache.add(fullTabPath, $clone); - }, 0); - } - else { - module.cache.add(fullTabPath, $tab.html()); - } - }, - urlData: { - tab: fullTabPath - } - }, - request = $tab.api('get request') || false, - existingRequest = ( request && request.state() === 'pending' ), - requestSettings, - cachedContent - ; - - fullTabPath = fullTabPath || tabPath; - cachedContent = module.cache.read(fullTabPath); - - - if(settings.cache && cachedContent) { - module.activate.tab(tabPath); - module.debug('Adding cached content', fullTabPath); - if(!settings.loadOnce) { - if(settings.evaluateScripts == 'once') { - module.update.content(tabPath, cachedContent, false); - } - else { - module.update.content(tabPath, cachedContent); - } - } - settings.onLoad.call($tab[0], tabPath, parameterArray, historyEvent); - } - else if(existingRequest) { - module.set.loading(tabPath); - module.debug('Content is already loading', fullTabPath); - } - else if($.api !== undefined) { - requestSettings = $.extend(true, {}, settings.apiSettings, apiSettings); - module.debug('Retrieving remote content', fullTabPath, requestSettings); - module.set.loading(tabPath); - $tab.api(requestSettings); - } - else { - module.error(error.api); - } - } - }, - - activate: { - all: function(tabPath) { - module.activate.tab(tabPath); - module.activate.navigation(tabPath); - }, - tab: function(tabPath) { - var - $tab = module.get.tabElement(tabPath), - $deactiveTabs = (settings.deactivate == 'siblings') - ? $tab.siblings($tabs) - : $tabs.not($tab), - isActive = $tab.hasClass(className.active) - ; - module.verbose('Showing tab content for', $tab); - if(!isActive) { - $tab - .addClass(className.active) - ; - $deactiveTabs - .removeClass(className.active + ' ' + className.loading) - ; - if($tab.length > 0) { - settings.onVisible.call($tab[0], tabPath); - } - } - }, - navigation: function(tabPath) { - var - $navigation = module.get.navElement(tabPath), - $deactiveNavigation = (settings.deactivate == 'siblings') - ? $navigation.siblings($allModules) - : $allModules.not($navigation), - isActive = $navigation.hasClass(className.active) - ; - module.verbose('Activating tab navigation for', $navigation, tabPath); - if(!isActive) { - $navigation - .addClass(className.active) - ; - $deactiveNavigation - .removeClass(className.active + ' ' + className.loading) - ; - } - } - }, - - deactivate: { - all: function() { - module.deactivate.navigation(); - module.deactivate.tabs(); - }, - navigation: function() { - $allModules - .removeClass(className.active) - ; - }, - tabs: function() { - $tabs - .removeClass(className.active + ' ' + className.loading) - ; - } - }, - - is: { - tab: function(tabName) { - return (tabName !== undefined) - ? ( module.get.tabElement(tabName).length > 0 ) - : false - ; - } - }, - - get: { - initialPath: function() { - return $allModules.eq(0).data(metadata.tab) || $tabs.eq(0).data(metadata.tab); - }, - path: function() { - return $.address.value(); - }, - // adds default tabs to tab path - defaultPathArray: function(tabPath) { - return module.utilities.pathToArray( module.get.defaultPath(tabPath) ); - }, - defaultPath: function(tabPath) { - var - $defaultNav = $allModules.filter('[data-' + metadata.tab + '^="' + module.escape.string(tabPath) + '/"]').eq(0), - defaultTab = $defaultNav.data(metadata.tab) || false - ; - if( defaultTab ) { - module.debug('Found default tab', defaultTab); - if(recursionDepth < settings.maxDepth) { - recursionDepth++; - return module.get.defaultPath(defaultTab); - } - module.error(error.recursion); - } - else { - module.debug('No default tabs found for', tabPath, $tabs); - } - recursionDepth = 0; - return tabPath; - }, - navElement: function(tabPath) { - tabPath = tabPath || activeTabPath; - return $allModules.filter('[data-' + metadata.tab + '="' + module.escape.string(tabPath) + '"]'); - }, - tabElement: function(tabPath) { - var - $fullPathTab, - $simplePathTab, - tabPathArray, - lastTab - ; - tabPath = tabPath || activeTabPath; - tabPathArray = module.utilities.pathToArray(tabPath); - lastTab = module.utilities.last(tabPathArray); - $fullPathTab = $tabs.filter('[data-' + metadata.tab + '="' + module.escape.string(tabPath) + '"]'); - $simplePathTab = $tabs.filter('[data-' + metadata.tab + '="' + module.escape.string(lastTab) + '"]'); - return ($fullPathTab.length > 0) - ? $fullPathTab - : $simplePathTab - ; - }, - tab: function() { - return activeTabPath; - } - }, - - determine: { - activeTab: function() { - var activeTab = null; - - $tabs.each(function(_index, tab) { - var $tab = $(tab); - - if( $tab.hasClass(className.active) ) { - var - tabPath = $(this).data(metadata.tab), - $anchor = $allModules.filter('[data-' + metadata.tab + '="' + module.escape.string(tabPath) + '"]') - ; - - if( $anchor.hasClass(className.active) ) { - activeTab = tabPath; - } - } - }); - - return activeTab; - } - }, - - utilities: { - filterArray: function(keepArray, removeArray) { - return $.grep(keepArray, function(keepValue) { - return ( $.inArray(keepValue, removeArray) == -1); - }); - }, - last: function(array) { - return Array.isArray(array) - ? array[ array.length - 1] - : false - ; - }, - pathToArray: function(pathName) { - if(pathName === undefined) { - pathName = activeTabPath; - } - return typeof pathName == 'string' - ? pathName.split('/') - : [pathName] - ; - }, - arrayToPath: function(pathArray) { - return Array.isArray(pathArray) - ? pathArray.join('/') - : false - ; - } - }, - - setting: function(name, value) { - module.debug('Changing setting', name, value); - if( $.isPlainObject(name) ) { - $.extend(true, settings, name); - } - else if(value !== undefined) { - if($.isPlainObject(settings[name])) { - $.extend(true, settings[name], value); - } - else { - settings[name] = value; - } - } - else { - return settings[name]; - } - }, - internal: function(name, value) { - if( $.isPlainObject(name) ) { - $.extend(true, module, name); - } - else if(value !== undefined) { - module[name] = value; - } - else { - return module[name]; - } - }, - debug: function() { - if(!settings.silent && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.debug.apply(console, arguments); - } - } - }, - verbose: function() { - if(!settings.silent && settings.verbose && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.verbose.apply(console, arguments); - } - } - }, - error: function() { - if(!settings.silent) { - module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); - module.error.apply(console, arguments); - } - }, - performance: { - log: function(message) { - var - currentTime, - executionTime, - previousTime - ; - if(settings.performance) { - currentTime = new Date().getTime(); - previousTime = time || currentTime; - executionTime = currentTime - previousTime; - time = currentTime; - performance.push({ - 'Name' : message[0], - 'Arguments' : [].slice.call(message, 1) || '', - 'Element' : element, - 'Execution Time' : executionTime - }); - } - clearTimeout(module.performance.timer); - module.performance.timer = setTimeout(module.performance.display, 500); - }, - display: function() { - var - title = settings.name + ':', - totalTime = 0 - ; - time = false; - clearTimeout(module.performance.timer); - $.each(performance, function(index, data) { - totalTime += data['Execution Time']; - }); - title += ' ' + totalTime + 'ms'; - if(moduleSelector) { - title += ' \'' + moduleSelector + '\''; - } - if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { - console.groupCollapsed(title); - if(console.table) { - console.table(performance); - } - else { - $.each(performance, function(index, data) { - console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); - }); - } - console.groupEnd(); - } - performance = []; - } - }, - invoke: function(query, passedArguments, context) { - var - object = instance, - maxDepth, - found, - response - ; - passedArguments = passedArguments || queryArguments; - context = element || context; - if(typeof query == 'string' && object !== undefined) { - query = query.split(/[\. ]/); - maxDepth = query.length - 1; - $.each(query, function(depth, value) { - var camelCaseValue = (depth != maxDepth) - ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) - : query - ; - if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { - object = object[camelCaseValue]; - } - else if( object[camelCaseValue] !== undefined ) { - found = object[camelCaseValue]; - return false; - } - else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { - object = object[value]; - } - else if( object[value] !== undefined ) { - found = object[value]; - return false; - } - else { - module.error(error.method, query); - return false; - } - }); - } - if ( $.isFunction( found ) ) { - response = found.apply(context, passedArguments); - } - else if(found !== undefined) { - response = found; - } - if(Array.isArray(returnedValue)) { - returnedValue.push(response); - } - else if(returnedValue !== undefined) { - returnedValue = [returnedValue, response]; - } - else if(response !== undefined) { - returnedValue = response; - } - return found; - } - }; - if(methodInvoked) { - if(instance === undefined) { - module.initialize(); - } - module.invoke(query); - } - else { - if(instance !== undefined) { - instance.invoke('destroy'); - } - module.initialize(); - } - }) - ; - return (returnedValue !== undefined) - ? returnedValue - : this - ; - -}; - -// shortcut for tabbed content with no defined navigation -$.tab = function() { - $(window).tab.apply(this, arguments); -}; - -$.fn.tab.settings = { - - name : 'Tab', - namespace : 'tab', - - silent : false, - debug : false, - verbose : false, - performance : true, - - auto : false, // uses pjax style endpoints fetching content from same url with remote-content headers - history : false, // use browser history - historyType : 'hash', // #/ or html5 state - path : false, // base path of url - - context : false, // specify a context that tabs must appear inside - childrenOnly : false, // use only tabs that are children of context - maxDepth : 25, // max depth a tab can be nested - - deactivate : 'siblings', // whether tabs should deactivate sibling menu elements or all elements initialized together - - alwaysRefresh : false, // load tab content new every tab click - cache : true, // cache the content requests to pull locally - loadOnce : false, // Whether tab data should only be loaded once when using remote content - cacheType : 'response', // Whether to cache exact response, or to html cache contents after scripts execute - ignoreFirstLoad : false, // don't load remote content on first load - - apiSettings : false, // settings for api call - evaluateScripts : 'once', // whether inline scripts should be parsed (true/false/once). Once will not re-evaluate on cached content - autoTabActivation: true, // whether a non existing active tab will auto activate the first available tab - - onFirstLoad : function(tabPath, parameterArray, historyEvent) {}, // called first time loaded - onLoad : function(tabPath, parameterArray, historyEvent) {}, // called on every load - onVisible : function(tabPath, parameterArray, historyEvent) {}, // called every time tab visible - onRequest : function(tabPath, parameterArray, historyEvent) {}, // called ever time a tab beings loading remote content - - templates : { - determineTitle: function(tabArray) {} // returns page title for path - }, - - error: { - api : 'You attempted to load content without API module', - method : 'The method you called is not defined', - missingTab : 'Activated tab cannot be found. Tabs are case-sensitive.', - noContent : 'The tab you specified is missing a content url.', - path : 'History enabled, but no path was specified', - recursion : 'Max recursive depth reached', - legacyInit : 'onTabInit has been renamed to onFirstLoad in 2.0, please adjust your code.', - legacyLoad : 'onTabLoad has been renamed to onLoad in 2.0. Please adjust your code', - state : 'History requires Asual\'s Address library <https://github.com/asual/jquery-address>' - }, - - regExp : { - escape : /[-[\]{}()*+?.,\\^$|#\s:=@]/g - }, - - metadata : { - tab : 'tab', - loaded : 'loaded', - promise: 'promise' - }, - - className : { - loading : 'loading', - active : 'active' - }, - - selector : { - tabs : '.ui.tab', - ui : '.ui' - } - -}; - -})( jQuery, window, document ); diff --git a/web_src/fomantic/build/themes/default/assets/fonts/icons.woff2 b/web_src/fomantic/build/themes/default/assets/fonts/icons.woff2 Binary files differdeleted file mode 100644 index 978a681a10..0000000000 --- a/web_src/fomantic/build/themes/default/assets/fonts/icons.woff2 +++ /dev/null diff --git a/web_src/fomantic/build/themes/default/assets/fonts/outline-icons.woff2 b/web_src/fomantic/build/themes/default/assets/fonts/outline-icons.woff2 Binary files differdeleted file mode 100644 index 7e0118e526..0000000000 --- a/web_src/fomantic/build/themes/default/assets/fonts/outline-icons.woff2 +++ /dev/null diff --git a/web_src/fomantic/package-lock.json b/web_src/fomantic/package-lock.json deleted file mode 100644 index 0c02f59806..0000000000 --- a/web_src/fomantic/package-lock.json +++ /dev/null @@ -1,8777 +0,0 @@ -{ - "name": "fomantic", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "fomantic-ui": "2.8.7" - } - }, - "node_modules/@choojs/findup": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@choojs/findup/-/findup-0.2.1.tgz", - "integrity": "sha512-YstAqNb0MCN8PjdLCDfRsBcGVRN41f3vgLvaI0IrIcBp4AqILRSS0DeWNGkicC+f/zRIPJLc+9RURVSepwvfBw==", - "license": "MIT", - "dependencies": { - "commander": "^2.15.1" - }, - "bin": { - "findup": "bin/findup.js" - } - }, - "node_modules/@choojs/findup/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT" - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@octokit/auth-token": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", - "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^6.0.3" - } - }, - "node_modules/@octokit/core": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.2.tgz", - "integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@octokit/auth-token": "^5.0.0", - "@octokit/graphql": "^8.0.0", - "@octokit/request": "^9.0.0", - "@octokit/request-error": "^6.0.1", - "@octokit/types": "^13.0.0", - "before-after-hook": "^3.0.2", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/core/node_modules/@octokit/auth-token": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz", - "integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/core/node_modules/@octokit/endpoint": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz", - "integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==", - "license": "MIT", - "peer": true, - "dependencies": { - "@octokit/types": "^13.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/core/node_modules/@octokit/openapi-types": { - "version": "22.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", - "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", - "license": "MIT", - "peer": true - }, - "node_modules/@octokit/core/node_modules/@octokit/request": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz", - "integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@octokit/endpoint": "^10.0.0", - "@octokit/request-error": "^6.0.1", - "@octokit/types": "^13.1.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/core/node_modules/@octokit/request-error": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz", - "integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@octokit/types": "^13.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/core/node_modules/@octokit/types": { - "version": "13.6.2", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", - "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@octokit/openapi-types": "^22.2.0" - } - }, - "node_modules/@octokit/core/node_modules/before-after-hook": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", - "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", - "license": "Apache-2.0", - "peer": true - }, - "node_modules/@octokit/core/node_modules/universal-user-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", - "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", - "license": "ISC", - "peer": true - }, - "node_modules/@octokit/endpoint": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", - "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^6.0.3", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/endpoint/node_modules/universal-user-agent": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", - "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", - "license": "ISC" - }, - "node_modules/@octokit/graphql": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.1.tgz", - "integrity": "sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@octokit/request": "^9.0.0", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/graphql/node_modules/@octokit/endpoint": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz", - "integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==", - "license": "MIT", - "peer": true, - "dependencies": { - "@octokit/types": "^13.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": { - "version": "22.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", - "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", - "license": "MIT", - "peer": true - }, - "node_modules/@octokit/graphql/node_modules/@octokit/request": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz", - "integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@octokit/endpoint": "^10.0.0", - "@octokit/request-error": "^6.0.1", - "@octokit/types": "^13.1.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/graphql/node_modules/@octokit/request-error": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz", - "integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@octokit/types": "^13.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/graphql/node_modules/@octokit/types": { - "version": "13.6.2", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", - "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@octokit/openapi-types": "^22.2.0" - } - }, - "node_modules/@octokit/graphql/node_modules/universal-user-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", - "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", - "license": "ISC", - "peer": true - }, - "node_modules/@octokit/openapi-types": { - "version": "12.11.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", - "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==", - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-1.1.2.tgz", - "integrity": "sha512-jbsSoi5Q1pj63sC16XIUboklNw+8tL9VOnJsWycWYR78TKss5PVpIPb1TUUcMQ+bBh7cY579cVAWmf5qG+dw+Q==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^2.0.1" - } - }, - "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", - "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", - "license": "MIT", - "dependencies": { - "@types/node": ">= 8" - } - }, - "node_modules/@octokit/plugin-request-log": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", - "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", - "license": "MIT", - "peerDependencies": { - "@octokit/core": ">=3" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-2.4.0.tgz", - "integrity": "sha512-EZi/AWhtkdfAYi01obpX0DF7U6b1VRr30QNQ5xSFPITMdLSfhcBqjamE3F+sKcxPbD7eZuMHu3Qkk2V+JGxBDQ==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^2.0.1", - "deprecation": "^2.3.1" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", - "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", - "license": "MIT", - "dependencies": { - "@types/node": ">= 8" - } - }, - "node_modules/@octokit/request": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", - "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^6.0.1", - "@octokit/request-error": "^2.1.0", - "@octokit/types": "^6.16.1", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.7", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/request-error": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.1.tgz", - "integrity": "sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^2.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - }, - "node_modules/@octokit/request-error/node_modules/@octokit/types": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", - "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", - "license": "MIT", - "dependencies": { - "@types/node": ">= 8" - } - }, - "node_modules/@octokit/request/node_modules/@octokit/request-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", - "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^6.0.3", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - }, - "node_modules/@octokit/request/node_modules/universal-user-agent": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", - "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", - "license": "ISC" - }, - "node_modules/@octokit/rest": { - "version": "16.43.2", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.43.2.tgz", - "integrity": "sha512-ngDBevLbBTFfrHZeiS7SAMAZ6ssuVmXuya+F/7RaVvlysgGa1JKJkKWY+jV6TCJYcW0OALfJ7nTIGXcBXzycfQ==", - "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^2.4.0", - "@octokit/plugin-paginate-rest": "^1.1.1", - "@octokit/plugin-request-log": "^1.0.0", - "@octokit/plugin-rest-endpoint-methods": "2.4.0", - "@octokit/request": "^5.2.0", - "@octokit/request-error": "^1.0.2", - "atob-lite": "^2.0.0", - "before-after-hook": "^2.0.0", - "btoa-lite": "^1.0.0", - "deprecation": "^2.0.0", - "lodash.get": "^4.4.2", - "lodash.set": "^4.3.2", - "lodash.uniq": "^4.5.0", - "octokit-pagination-methods": "^1.1.0", - "once": "^1.4.0", - "universal-user-agent": "^4.0.0" - } - }, - "node_modules/@octokit/types": { - "version": "6.41.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", - "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^12.11.0" - } - }, - "node_modules/@one-ini/wasm": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", - "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", - "license": "MIT" - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@types/expect": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", - "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "node_modules/@types/vinyl": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.12.tgz", - "integrity": "sha512-Sr2fYMBUVGYq8kj3UthXFAu5UN6ZW+rYr4NACjZQJvHvj+c8lYv0CahmZ2P/r7iUkN44gGUBwqxZkrKXYPb7cw==", - "license": "MIT", - "dependencies": { - "@types/expect": "^1.20.4", - "@types/node": "*" - } - }, - "node_modules/abbrev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", - "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/accord": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/accord/-/accord-0.29.0.tgz", - "integrity": "sha512-3OOR92FTc2p5/EcOzPcXp+Cbo+3C15nV9RXHlOUBCBpHhcB+0frbSNR9ehED/o7sTcyGVtqGJpguToEdlXhD0w==", - "license": "MIT", - "dependencies": { - "convert-source-map": "^1.5.0", - "glob": "^7.0.5", - "indx": "^0.2.3", - "lodash.clone": "^4.3.2", - "lodash.defaults": "^4.0.1", - "lodash.flatten": "^4.2.0", - "lodash.merge": "^4.4.0", - "lodash.partialright": "^4.1.4", - "lodash.pick": "^4.2.1", - "lodash.uniq": "^4.3.0", - "resolve": "^1.5.0", - "semver": "^5.3.0", - "uglify-js": "^2.8.22", - "when": "^3.7.8" - } - }, - "node_modules/accord/node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", - "license": "MIT" - }, - "node_modules/align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha512-GrTZLRpmp6wIC2ztrWW9MjjTgSKccffgFagbNDOX95/dcjEcYZibYTeaOntySQLcdw1ztBoFkviiUvTMbb9MYg==", - "license": "MIT", - "dependencies": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/align-text/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "license": "MIT", - "dependencies": { - "ansi-wrap": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-cyan": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", - "integrity": "sha512-eCjan3AVo/SxZ0/MyIYRtkpxIu/H3xZN7URr1vXVrISxeyz8fUFz0FJziamK4sS8I+t35y4rHg1b2PklyBe/7A==", - "license": "MIT", - "dependencies": { - "ansi-wrap": "0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha512-HrgGIZUl8h2EHuZaU9hTR/cU5nhKxpVE1V6kdGsQ8e4zirElJ5fvtfc8N7Q1oq1aatO275i8pUFUCpNWCAnVWw==", - "license": "MIT", - "dependencies": { - "ansi-wrap": "0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-red": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", - "integrity": "sha512-ewaIr5y+9CUTGFwZfpECUbFlGcC0GCw1oqR9RI6h1gQCd9Aj2GxSckCnPsVJnmfMZbwFYE+leZGASgkWl06Jow==", - "license": "MIT", - "dependencies": { - "ansi-wrap": "0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/any-shell-escape": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/any-shell-escape/-/any-shell-escape-0.1.1.tgz", - "integrity": "sha512-36j4l5HVkboyRhIWgtMh1I9i8LTdFqVwDEHy1cp+QioJyKgAUG40X0W8s7jakWRta/Sjvm8mUG1fU6Tj8mWagQ==", - "license": "MIT" - }, - "node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "license": "ISC", - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "license": "MIT", - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha512-WLbYiXzD3y/ATLZFufV/rZvWdZOs+Z/+5v1rBZ463Jn398pa6kcde27cvozYnBoxXblGZTFfoPpsaEw0orU5BA==", - "license": "MIT", - "dependencies": { - "buffer-equal": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "license": "MIT" - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-filter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", - "integrity": "sha512-A2BETWCqhsecSvCkWAeVBFLH6sXEUGASuzkpjL3GR1SlL/PWL6M3J8EAAld2Uubmh39tvkJTqC9LeLHCUKmFXA==", - "license": "MIT", - "dependencies": { - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", - "integrity": "sha512-tVqVTHt+Q5Xb09qRkbu+DidW1yYzz5izWS2Xm2yFm7qJnmUfz4HPzNxbHkdRJbz2lrqI7S+z17xNYdFcBBO8Hw==", - "license": "MIT", - "dependencies": { - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-initial": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", - "integrity": "sha512-BC4Yl89vneCYfpLrs5JU2aAu9/a+xWbeKhvISg9PT7eWFB9UlRvI+rKEtk6mgxWr3dSkk9gQ8hCrdqt06NXPdw==", - "license": "MIT", - "dependencies": { - "array-slice": "^1.0.0", - "is-number": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-initial/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-last": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", - "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", - "license": "MIT", - "dependencies": { - "is-number": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-last/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-sort": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", - "license": "MIT", - "dependencies": { - "default-compare": "^1.0.0", - "get-value": "^2.0.6", - "kind-of": "^5.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", - "license": "MIT", - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", - "license": "MIT" - }, - "node_modules/async-done": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", - "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.2", - "process-nextick-args": "^2.0.0", - "stream-exhaust": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/async-each": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.6.tgz", - "integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT" - }, - "node_modules/async-settle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", - "integrity": "sha512-VPXfB4Vk49z1LHHodrEQ6Xf7W4gg1w0dAPROHngx7qgDjqmIQ+fXmwgGXTW/ITLai0YLSvWepJOP9EVpMnEAcw==", - "license": "MIT", - "dependencies": { - "async-done": "^1.2.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "license": "(MIT OR Apache-2.0)", - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/atob-lite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz", - "integrity": "sha512-LEeSAWeh2Gfa2FtlQE1shxQ8zi5F9GHarrGKz08TMdODD5T4eH6BMsvtnhbWZ+XQn+Gb6om/917ucvRu7l7ukw==", - "license": "MIT" - }, - "node_modules/autoprefixer": { - "version": "9.8.8", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.8.tgz", - "integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.12.0", - "caniuse-lite": "^1.0.30001109", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "picocolors": "^0.2.1", - "postcss": "^7.0.32", - "postcss-value-parser": "^4.1.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "funding": { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - } - }, - "node_modules/bach": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", - "integrity": "sha512-bZOOfCb3gXBXbTFXq3OZtGR88LwGeJvzu6szttaIzymOTS4ZttBNOWSv7aLZja2EMycKtRYV0Oa8SNKH/zkxvg==", - "license": "MIT", - "dependencies": { - "arr-filter": "^1.1.1", - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "array-each": "^1.0.0", - "array-initial": "^1.0.0", - "array-last": "^1.1.1", - "async-done": "^1.2.2", - "async-settle": "^1.0.0", - "now-and-later": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "license": "MIT", - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/beeper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", - "integrity": "sha512-3vqtKL1N45I5dV0RdssXZG7X6pCqQrWPNOlBPZPrd+QkE2HEhR57Z04m0KtpbsZH73j+a3F8UD1TQnn+ExTvIA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/before-after-hook": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", - "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", - "license": "Apache-2.0" - }, - "node_modules/better-console": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/better-console/-/better-console-1.0.1.tgz", - "integrity": "sha512-M/azU25cj3ZHbMSoXEroDfzcolfUvM03PZw5EEBk9T3tqdIYfMXrIkEKb9q8OZMC8Hic8Q9l8jk6TZq9cyRrcw==", - "license": "BSD", - "dependencies": { - "chalk": "^1.1.3", - "cli-table": "~0.3.1" - } - }, - "node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/binaryextensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz", - "integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==", - "license": "MIT", - "engines": { - "node": ">=0.8" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "license": "MIT", - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.1" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/btoa-lite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", - "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==", - "license": "MIT" - }, - "node_modules/buffer-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", - "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", - "license": "MIT", - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "license": "MIT", - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001684", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz", - "integrity": "sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha512-Baz3aNe2gd2LP2qk5U+sDk/m4oSuwSDcBfayTCTBoWpfIGO5XFxPmjILQII4NGiZjD6DoDI6kf7gKaxkf7s3VQ==", - "license": "MIT", - "dependencies": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "license": "MIT" - }, - "node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "license": "MIT", - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "optionalDependencies": { - "fsevents": "^1.2.7" - } - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "license": "MIT", - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/clean-css": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", - "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", - "license": "MIT", - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cli-table": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", - "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", - "dependencies": { - "colors": "1.0.3" - }, - "engines": { - "node": ">= 0.2.0" - } - }, - "node_modules/cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", - "license": "ISC" - }, - "node_modules/cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==", - "license": "ISC", - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", - "license": "MIT", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", - "license": "MIT", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==", - "license": "MIT" - }, - "node_modules/cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - } - }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collection-map": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", - "integrity": "sha512-5D2XXSpkOnleOI21TG7p3T0bGAsZ/XknZpKBmGYyluO8pw4zA3K8ZlrBIbC4FXg3m6z/RNFiUFfT2sQK01+UHA==", - "license": "MIT", - "dependencies": { - "arr-map": "^2.0.2", - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", - "license": "MIT", - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "license": "ISC", - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", - "license": "MIT", - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/component-emitter": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "engines": [ - "node >= 0.8" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/concat-with-sourcemaps": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", - "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", - "license": "ISC", - "dependencies": { - "source-map": "^0.6.1" - } - }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "license": "MIT", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "license": "MIT" - }, - "node_modules/copy-anything": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", - "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", - "license": "MIT", - "dependencies": { - "is-what": "^3.14.1" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/copy-props": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", - "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", - "license": "MIT", - "dependencies": { - "each-props": "^1.3.2", - "is-plain-object": "^5.0.0" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", - "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", - "license": "MIT", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/css": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", - "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "source-map": "^0.6.1", - "source-map-resolve": "^0.5.2", - "urix": "^0.1.0" - } - }, - "node_modules/d": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", - "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", - "license": "ISC", - "dependencies": { - "es5-ext": "^0.10.64", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha512-GODcnWq3YGoTnygPfi02ygEiRxqUxpJwuRHjdhJYuxpcZmDq4rjBiXYmbCCzStxo176ixfLT6i4NPwQooRySnw==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/deep-assign": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-1.0.0.tgz", - "integrity": "sha512-iAL1PDjxqhANx86VhUjK0HSb4bozMfJUK64rxdrlWPCgMv7rBvP6AFySY69e+k8JAtPHNWoTsQT5OJvE+Jgpjg==", - "license": "MIT", - "dependencies": { - "is-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", - "license": "MIT", - "dependencies": { - "kind-of": "^5.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-resolution": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", - "integrity": "sha512-2xaP6GiwVwOEbXCGoJ4ufgC76m8cj805jrghScewJC2ZDsb9U0b4BIrba+xt/Uytyd0HvQ6+WymSRTfnYj59GQ==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", - "integrity": "sha512-7yjqSoVSlJzA4t/VUwazuEagGeANEKB3f/aNI//06pfKgwoCb7f6Q1gETN1sZzYaj6chTQ0AhIwDiPdfOjko4A==", - "license": "MIT", - "dependencies": { - "globby": "^6.1.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "p-map": "^1.1.1", - "pify": "^3.0.0", - "rimraf": "^2.2.8" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/deprecation": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", - "license": "ISC" - }, - "node_modules/detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/diff": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.8.tgz", - "integrity": "sha512-1zEb73vemXFpUmfh3fsta4YHz3lwebxXvaWmPbFv9apujQBWDnkrPDLXLQs1gZo4RCWMDsT89r0Pf/z8/02TGA==", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/duplexer2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha512-+AWBwjGadtksxjOQSFDhPNQbed7icNXApT4+2BNpsXzcCBiInq2H9XW0O8sfHFaPmnQRs7cg/P0fAr2IWQSW0g==", - "license": "BSD", - "dependencies": { - "readable-stream": "~1.1.9" - } - }, - "node_modules/duplexer2/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "license": "MIT" - }, - "node_modules/duplexer2/node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/duplexer2/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "license": "MIT" - }, - "node_modules/duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/each-props": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.1", - "object.defaults": "^1.1.0" - } - }, - "node_modules/each-props/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, - "node_modules/editorconfig": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", - "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", - "license": "MIT", - "dependencies": { - "@one-ini/wasm": "0.1.1", - "commander": "^10.0.0", - "minimatch": "9.0.1", - "semver": "^7.5.3" - }, - "bin": { - "editorconfig": "bin/editorconfig" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/editorconfig/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/editorconfig/node_modules/minimatch": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", - "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/editorconfig/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.65", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.65.tgz", - "integrity": "sha512-PWVzBjghx7/wop6n22vS2MLU8tKGd4Q91aCEGhG/TYmW6PP5OcSXcdnxTe1NNt0T66N8D6jxh4kC8UsdzOGaIw==", - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "license": "MIT", - "optional": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", - "hasInstallScript": true, - "license": "ISC", - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", - "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", - "license": "ISC", - "dependencies": { - "d": "^1.0.2", - "ext": "^1.7.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "license": "ISC", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", - "license": "ISC", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "license": "MIT", - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", - "license": "MIT", - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "license": "ISC", - "dependencies": { - "type": "^2.7.2" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "license": "MIT", - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "license": "MIT", - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fancy-log": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", - "license": "MIT", - "dependencies": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fast-levenshtein": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", - "integrity": "sha512-Ia0sQNrMPXXkqVFt6w6M1n1oKo3NfKs+mvaV811Jwir7vAk9a6PVV9VPYf6X3BU97QiLEmuW3uXH9u87zDFfdw==", - "license": "MIT" - }, - "node_modules/figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT", - "optional": true - }, - "node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "license": "MIT", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", - "license": "MIT", - "dependencies": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", - "license": "MIT", - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fined/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/first-chunk-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", - "integrity": "sha512-X8Z+b/0L4lToKYq+lwnKqi9X/Zek0NibLpsJgVsSxpoYq7JtiCtRb5HqKVEjEw/qAb/4AKKRLOwwKHlWNpm2Eg==", - "license": "MIT", - "dependencies": { - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "node_modules/fomantic-ui": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/fomantic-ui/-/fomantic-ui-2.8.7.tgz", - "integrity": "sha512-u22d28Z+U8mduTIM50MYzBGRz7CXYjGs2fUY6KO8N3enE8OAatDOXV4Mb/Xvj/ck5aNE6er6XJNK1fFWXt/u/w==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "@octokit/rest": "^16.16.0", - "better-console": "1.0.1", - "del": "^3.0.0", - "extend": "^3.0.2", - "gulp": "^4.0.0", - "gulp-autoprefixer": "^6.0.0", - "gulp-chmod": "^2.0.0", - "gulp-clean-css": "^3.10.0", - "gulp-clone": "^2.0.1", - "gulp-concat": "^2.6.1", - "gulp-concat-css": "^3.1.0", - "gulp-concat-filenames": "^1.2.0", - "gulp-copy": "^4.0.0", - "gulp-debug": "^4.0.0", - "gulp-dedupe": "0.0.2", - "gulp-flatten": "^0.4.0", - "gulp-git": "^2.9.0", - "gulp-header": "^2.0.5", - "gulp-if": "^2.0.2", - "gulp-json-editor": "^2.4.3", - "gulp-less": "^4.0.1", - "gulp-notify": "^3.0.0", - "gulp-plumber": "^1.1.0", - "gulp-print": "^5.0.0", - "gulp-rename": "^1.4.0", - "gulp-replace": "^1.0.0", - "gulp-rtlcss": "^1.3.0", - "gulp-tap": "^1.0.1", - "gulp-uglify": "^3.0.1", - "inquirer": "^6.2.1", - "jquery": "^3.4.0", - "less": "^3.7.0", - "map-stream": "^0.1.0", - "merge-stream": "^2.0.0", - "mkdirp": "^0.5.1", - "normalize-path": "^3.0.0", - "replace-ext": "^1.0.0", - "require-dot-file": "^0.4.0", - "wrench-sui": "^0.0.3", - "yamljs": "^0.3.0" - }, - "engines": { - "node": ">=10.15.3", - "npm": ">=6.4.1" - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", - "license": "MIT", - "dependencies": { - "for-in": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/foreground-child/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fork-stream": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/fork-stream/-/fork-stream-0.0.4.tgz", - "integrity": "sha512-Pqq5NnT78ehvUnAk/We/Jr22vSvanRlFTpAmQ88xBY/M1TlHe+P0ILuEyXS595ysdGfaj22634LBkGMA2GTcpA==", - "license": "BSD" - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", - "license": "MIT", - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fs-mkdirp-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", - "integrity": "sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fs-mkdirp-stream/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "Upgrade to fsevents v2 to mitigate potential security issues", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "license": "ISC" - }, - "node_modules/get-imports": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-imports/-/get-imports-1.0.0.tgz", - "integrity": "sha512-9FjKG2Os+o/EuOIh3B/LNMbU2FWPGHVy/gs9TJpytK95IPl7lLqiu+VAU7JX6VZimqdmpLemgsGMdQWdKvqYGQ==", - "license": "MIT", - "dependencies": { - "array-uniq": "^1.0.1", - "import-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", - "license": "ISC" - }, - "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/get-stream/node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", - "license": "ISC", - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw==", - "license": "MIT", - "dependencies": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/glob-watcher": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", - "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", - "license": "MIT", - "dependencies": { - "anymatch": "^2.0.0", - "async-done": "^1.2.0", - "chokidar": "^2.0.0", - "is-negated-glob": "^1.0.0", - "just-debounce": "^1.0.0", - "normalize-path": "^3.0.0", - "object.defaults": "^1.1.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "license": "MIT", - "dependencies": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", - "license": "MIT", - "dependencies": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/globby/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glogg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", - "license": "MIT", - "dependencies": { - "sparkles": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==", - "license": "MIT" - }, - "node_modules/gulp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", - "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", - "license": "MIT", - "dependencies": { - "glob-watcher": "^5.0.3", - "gulp-cli": "^2.2.0", - "undertaker": "^1.2.1", - "vinyl-fs": "^3.0.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-autoprefixer": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/gulp-autoprefixer/-/gulp-autoprefixer-6.1.0.tgz", - "integrity": "sha512-Ti/BUFe+ekhbDJfspZIMiOsOvw51KhI9EncsDfK7NaxjqRm+v4xS9v99kPxEoiDavpWqQWvG8Y6xT1mMlB3aXA==", - "license": "MIT", - "dependencies": { - "autoprefixer": "^9.5.1", - "fancy-log": "^1.3.2", - "plugin-error": "^1.0.1", - "postcss": "^7.0.2", - "through2": "^3.0.1", - "vinyl-sourcemaps-apply": "^0.2.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/gulp-chmod": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/gulp-chmod/-/gulp-chmod-2.0.0.tgz", - "integrity": "sha512-ttOK11mugzcy6D5CQD8rXqS7M4Ecoo64bDNhRXT9Yok9ztAcOeIK8hsv7LlV1eFS4iSQKZETvEZC5Kt/sH74sw==", - "license": "MIT", - "dependencies": { - "deep-assign": "^1.0.0", - "stat-mode": "^0.2.0", - "through2": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/gulp-chmod/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-clean-css": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/gulp-clean-css/-/gulp-clean-css-3.10.0.tgz", - "integrity": "sha512-7Isf9Y690o/Q5MVjEylH1H7L8WeZ89woW7DnhD5unTintOdZb67KdOayRgp9trUFo+f9UyJtuatV42e/+kghPg==", - "license": "MIT", - "dependencies": { - "clean-css": "4.2.1", - "plugin-error": "1.0.1", - "through2": "2.0.3", - "vinyl-sourcemaps-apply": "0.2.1" - } - }, - "node_modules/gulp-clean-css/node_modules/through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha512-tmNYYHFqXmaKSSlOU4ZbQ82cxmFQa5LRWKFtWCNkGIiZ3/VHmOffCeWfBRZZRyXAhNP9itVMR+cuvomBOPlm8g==", - "license": "MIT", - "dependencies": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-cli": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", - "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", - "license": "MIT", - "dependencies": { - "ansi-colors": "^1.0.1", - "archy": "^1.0.0", - "array-sort": "^1.0.0", - "color-support": "^1.1.3", - "concat-stream": "^1.6.0", - "copy-props": "^2.0.1", - "fancy-log": "^1.3.2", - "gulplog": "^1.0.0", - "interpret": "^1.4.0", - "isobject": "^3.0.1", - "liftoff": "^3.1.0", - "matchdep": "^2.0.0", - "mute-stdout": "^1.0.0", - "pretty-hrtime": "^1.0.0", - "replace-homedir": "^1.0.0", - "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.2.0", - "yargs": "^7.1.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-clone": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/gulp-clone/-/gulp-clone-2.0.1.tgz", - "integrity": "sha512-SLg/KsHBbinR/pCX3PF5l1YlR28hLp0X+bcpf77PtMJ6zvAQ5kRjtCPV5Wt1wHXsXWZN0eTUZ15R8ZYpi/CdCA==", - "dependencies": { - "plugin-error": "^0.1.2", - "through2": "^2.0.3" - } - }, - "node_modules/gulp-clone/node_modules/arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", - "license": "MIT", - "dependencies": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clone/node_modules/arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clone/node_modules/array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clone/node_modules/extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha512-L7AGmkO6jhDkEBBGWlLtftA80Xq8DipnrRPr0pyi7GQLXkaq9JYA4xF4z6qnadIC6euiTDKco0cGSU9muw+WTw==", - "license": "MIT", - "dependencies": { - "kind-of": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clone/node_modules/kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha512-aUH6ElPnMGon2/YkxRIigV32MOpTVcoXQ1Oo8aYn40s+sJ3j+0gFZsT8HKDcxNy7Fi9zuquWtGaGAahOdv5p/g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clone/node_modules/plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha512-WzZHcm4+GO34sjFMxQMqZbsz3xiNEgonCskQ9v+IroMmYgk/tas8dG+Hr2D6IbRPybZ12oWpzE/w3cGJ6FJzOw==", - "license": "MIT", - "dependencies": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clone/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-concat": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", - "integrity": "sha512-a2scActrQrDBpBbR3WUZGyGS1JEPLg5PZJdIa7/Bi3GuKAmPYDK6SFhy/NZq5R8KsKKFvtfR0fakbUCcKGCCjg==", - "license": "MIT", - "dependencies": { - "concat-with-sourcemaps": "^1.0.0", - "through2": "^2.0.0", - "vinyl": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-concat-css": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/gulp-concat-css/-/gulp-concat-css-3.1.0.tgz", - "integrity": "sha512-iLTBPS+cutlgLyK3bp9DMts+WuS8n2mQpjzQ7p/ZVQc8FO5fvpN+ntg9U6jsuNvPeuii82aKm8XeOzF0nUK+TA==", - "dependencies": { - "lodash.defaults": "^3.0.0", - "parse-import": "^2.0.0", - "plugin-error": "^0.1.2", - "rework": "~1.0.0", - "rework-import": "^2.0.0", - "rework-plugin-url": "^1.0.1", - "through2": "~1.1.1", - "vinyl": "^2.1.0" - } - }, - "node_modules/gulp-concat-css/node_modules/arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", - "license": "MIT", - "dependencies": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-concat-css/node_modules/arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-concat-css/node_modules/array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-concat-css/node_modules/extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha512-L7AGmkO6jhDkEBBGWlLtftA80Xq8DipnrRPr0pyi7GQLXkaq9JYA4xF4z6qnadIC6euiTDKco0cGSU9muw+WTw==", - "license": "MIT", - "dependencies": { - "kind-of": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-concat-css/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "license": "MIT" - }, - "node_modules/gulp-concat-css/node_modules/kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha512-aUH6ElPnMGon2/YkxRIigV32MOpTVcoXQ1Oo8aYn40s+sJ3j+0gFZsT8HKDcxNy7Fi9zuquWtGaGAahOdv5p/g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-concat-css/node_modules/plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha512-WzZHcm4+GO34sjFMxQMqZbsz3xiNEgonCskQ9v+IroMmYgk/tas8dG+Hr2D6IbRPybZ12oWpzE/w3cGJ6FJzOw==", - "license": "MIT", - "dependencies": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-concat-css/node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/gulp-concat-css/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "license": "MIT" - }, - "node_modules/gulp-concat-css/node_modules/through2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", - "integrity": "sha512-zEbpaeSMHxczpTzO1KkMHjBC1enTA68ojeaZGG4toqdASpb9t4xUZaYFBq2/9OHo5nTGFVSYd4c910OR+6wxbQ==", - "license": "MIT", - "dependencies": { - "readable-stream": ">=1.1.13-1 <1.2.0-0", - "xtend": ">=4.0.0 <4.1.0-0" - } - }, - "node_modules/gulp-concat-filenames": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gulp-concat-filenames/-/gulp-concat-filenames-1.2.0.tgz", - "integrity": "sha512-2wHcntxftYa2kiv5QOaniSNQuRf1axHGqkyXhRoCBXAVvwzrUp++qW9GNSAdvb3h+7m8yC8Fu25guuaDU+1WaA==", - "dependencies": { - "gulp-util": "3.x.x", - "through": "2.x.x" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-concat/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-copy": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/gulp-copy/-/gulp-copy-4.0.1.tgz", - "integrity": "sha512-UbdAwmEiVNNv55KAiUYWOP6Za7h8JPHNNyekNx8Gyc5XRlpUzTrlEclps939nOeiDPsd6jUtT2LmfavJirbZQg==", - "license": "MIT", - "dependencies": { - "gulp": "^4.0.0", - "plugin-error": "^0.1.2", - "through2": "^2.0.3" - } - }, - "node_modules/gulp-copy/node_modules/arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", - "license": "MIT", - "dependencies": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-copy/node_modules/arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-copy/node_modules/array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-copy/node_modules/extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha512-L7AGmkO6jhDkEBBGWlLtftA80Xq8DipnrRPr0pyi7GQLXkaq9JYA4xF4z6qnadIC6euiTDKco0cGSU9muw+WTw==", - "license": "MIT", - "dependencies": { - "kind-of": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-copy/node_modules/kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha512-aUH6ElPnMGon2/YkxRIigV32MOpTVcoXQ1Oo8aYn40s+sJ3j+0gFZsT8HKDcxNy7Fi9zuquWtGaGAahOdv5p/g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-copy/node_modules/plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha512-WzZHcm4+GO34sjFMxQMqZbsz3xiNEgonCskQ9v+IroMmYgk/tas8dG+Hr2D6IbRPybZ12oWpzE/w3cGJ6FJzOw==", - "license": "MIT", - "dependencies": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-copy/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-debug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/gulp-debug/-/gulp-debug-4.0.0.tgz", - "integrity": "sha512-cn/GhMD2nVZCVxAl5vWao4/dcoZ8wUJ8w3oqTvQaGDmC1vT7swNOEbhQTWJp+/otKePT64aENcqAQXDcdj5H1g==", - "license": "MIT", - "dependencies": { - "chalk": "^2.3.0", - "fancy-log": "^1.3.2", - "plur": "^3.0.0", - "stringify-object": "^3.0.0", - "through2": "^2.0.0", - "tildify": "^1.1.2" - }, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "gulp": ">=4" - } - }, - "node_modules/gulp-debug/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/gulp-debug/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/gulp-debug/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/gulp-debug/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-dedupe": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/gulp-dedupe/-/gulp-dedupe-0.0.2.tgz", - "integrity": "sha512-Y+FZmAVHUYDgJiGneLXY2sCErvcY89sskjGQILhh5YvNGZq5M+pKsY54K0MyquZGxj2g10ZDVM5vQnEP7yUrVA==", - "license": "MIT", - "dependencies": { - "colors": "~1.0.2", - "diff": "~1.0.8", - "gulp-util": "~3.0.1", - "lodash.defaults": "~2.4.1", - "through": "~2.3.6" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/gulp-dedupe/node_modules/lodash.defaults": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-2.4.1.tgz", - "integrity": "sha512-5wTIPWwGGr07JFysAZB8+7JB2NjJKXDIwogSaRX5zED85zyUAQwtOqUk8AsJkkigUcL3akbHYXd5+BPtTGQPZw==", - "license": "MIT", - "dependencies": { - "lodash._objecttypes": "~2.4.1", - "lodash.keys": "~2.4.1" - } - }, - "node_modules/gulp-dedupe/node_modules/lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha512-ZpJhwvUXHSNL5wYd1RM6CUa2ZuqorG9ngoJ9Ix5Cce+uX7I5O/E06FCJdhSZ33b5dVyeQDnIlWH7B2s5uByZ7g==", - "license": "MIT", - "dependencies": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" - } - }, - "node_modules/gulp-flatten": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/gulp-flatten/-/gulp-flatten-0.4.0.tgz", - "integrity": "sha512-eg4spVTAiv1xXmugyaCxWne1oPtNG0UHEtABx5W8ScLiqAYceyYm6GYA36x0Qh8KOIXmAZV97L2aYGnKREG3Sg==", - "license": "MIT", - "dependencies": { - "plugin-error": "^0.1.2", - "through2": "^2.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/gulp-flatten/node_modules/arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", - "license": "MIT", - "dependencies": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-flatten/node_modules/arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-flatten/node_modules/array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-flatten/node_modules/extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha512-L7AGmkO6jhDkEBBGWlLtftA80Xq8DipnrRPr0pyi7GQLXkaq9JYA4xF4z6qnadIC6euiTDKco0cGSU9muw+WTw==", - "license": "MIT", - "dependencies": { - "kind-of": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-flatten/node_modules/kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha512-aUH6ElPnMGon2/YkxRIigV32MOpTVcoXQ1Oo8aYn40s+sJ3j+0gFZsT8HKDcxNy7Fi9zuquWtGaGAahOdv5p/g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-flatten/node_modules/plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha512-WzZHcm4+GO34sjFMxQMqZbsz3xiNEgonCskQ9v+IroMmYgk/tas8dG+Hr2D6IbRPybZ12oWpzE/w3cGJ6FJzOw==", - "license": "MIT", - "dependencies": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-flatten/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-git": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/gulp-git/-/gulp-git-2.11.0.tgz", - "integrity": "sha512-7YOcwin7sr68weYhBNOtZia3LZOGZWXgGcxxcxCi2hjljTgysOhH9mLTH2hdG5YLcuAFNg7mMbb2xIRfYsaQZw==", - "license": "MIT", - "dependencies": { - "any-shell-escape": "^0.1.1", - "fancy-log": "^1.3.2", - "lodash": "^4.17.21", - "plugin-error": "^1.0.1", - "require-dir": "^1.0.0", - "strip-bom-stream": "^3.0.0", - "vinyl": "^2.0.1" - }, - "engines": { - "node": ">= 0.9.0" - } - }, - "node_modules/gulp-header": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-2.0.9.tgz", - "integrity": "sha512-LMGiBx+qH8giwrOuuZXSGvswcIUh0OiioNkUpLhNyvaC6/Ga8X6cfAeme2L5PqsbXMhL8o8b/OmVqIQdxprhcQ==", - "license": "MIT", - "dependencies": { - "concat-with-sourcemaps": "^1.1.0", - "lodash.template": "^4.5.0", - "map-stream": "0.0.7", - "through2": "^2.0.0" - } - }, - "node_modules/gulp-header/node_modules/map-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", - "integrity": "sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==", - "license": "MIT" - }, - "node_modules/gulp-header/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-if": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/gulp-if/-/gulp-if-2.0.2.tgz", - "integrity": "sha512-tV0UfXkZodpFq6CYxEqH8tqLQgN6yR9qOhpEEN3O6N5Hfqk3fFLcbAavSex5EqnmoQjyaZ/zvgwclvlTI1KGfw==", - "license": "MIT", - "dependencies": { - "gulp-match": "^1.0.3", - "ternary-stream": "^2.0.1", - "through2": "^2.0.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/gulp-if/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-json-editor": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/gulp-json-editor/-/gulp-json-editor-2.6.0.tgz", - "integrity": "sha512-Ni0ZUpNrhesHiTlHQth/Nv1rXCn0LUicEvzA5XuGy186C4PVeNoRjfuAIQrbmt3scKv8dgGbCs0hd77ScTw7hA==", - "license": "MIT", - "dependencies": { - "deepmerge": "^4.3.1", - "detect-indent": "^6.1.0", - "js-beautify": "^1.14.11", - "plugin-error": "^2.0.1", - "through2": "^4.0.2" - } - }, - "node_modules/gulp-json-editor/node_modules/plugin-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-2.0.1.tgz", - "integrity": "sha512-zMakqvIDyY40xHOvzXka0kUvf40nYIuwRE8dWhti2WtjQZ31xAgBZBhxsK7vK3QbRXS1Xms/LO7B5cuAsfB2Gg==", - "license": "MIT", - "dependencies": { - "ansi-colors": "^1.0.1" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/gulp-json-editor/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/gulp-json-editor/node_modules/through2": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", - "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", - "license": "MIT", - "dependencies": { - "readable-stream": "3" - } - }, - "node_modules/gulp-less": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/gulp-less/-/gulp-less-4.0.1.tgz", - "integrity": "sha512-hmM2k0FfQp7Ptm3ZaqO2CkMX3hqpiIOn4OHtuSsCeFym63F7oWlEua5v6u1cIjVUKYsVIs9zPg9vbqTEb/udpA==", - "license": "MIT", - "dependencies": { - "accord": "^0.29.0", - "less": "2.6.x || ^3.7.1", - "object-assign": "^4.0.1", - "plugin-error": "^0.1.2", - "replace-ext": "^1.0.0", - "through2": "^2.0.0", - "vinyl-sourcemaps-apply": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-less/node_modules/arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", - "license": "MIT", - "dependencies": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-less/node_modules/arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-less/node_modules/array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-less/node_modules/extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha512-L7AGmkO6jhDkEBBGWlLtftA80Xq8DipnrRPr0pyi7GQLXkaq9JYA4xF4z6qnadIC6euiTDKco0cGSU9muw+WTw==", - "license": "MIT", - "dependencies": { - "kind-of": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-less/node_modules/kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha512-aUH6ElPnMGon2/YkxRIigV32MOpTVcoXQ1Oo8aYn40s+sJ3j+0gFZsT8HKDcxNy7Fi9zuquWtGaGAahOdv5p/g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-less/node_modules/plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha512-WzZHcm4+GO34sjFMxQMqZbsz3xiNEgonCskQ9v+IroMmYgk/tas8dG+Hr2D6IbRPybZ12oWpzE/w3cGJ6FJzOw==", - "license": "MIT", - "dependencies": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-less/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-match": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/gulp-match/-/gulp-match-1.1.0.tgz", - "integrity": "sha512-DlyVxa1Gj24DitY2OjEsS+X6tDpretuxD6wTfhXE/Rw2hweqc1f6D/XtsJmoiCwLWfXgR87W9ozEityPCVzGtQ==", - "license": "MIT", - "dependencies": { - "minimatch": "^3.0.3" - } - }, - "node_modules/gulp-notify": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/gulp-notify/-/gulp-notify-3.2.0.tgz", - "integrity": "sha512-qEocs1UVoDKKUjfsxJNMNwkRla0PbsyJwsqNNXpzYWsLQ29LhxRMY3wnTGZcc4hMHtalnvah/Dwlwb4NijH/0A==", - "license": "MIT", - "dependencies": { - "ansi-colors": "^1.0.1", - "fancy-log": "^1.3.2", - "lodash.template": "^4.4.0", - "node-notifier": "^5.2.1", - "node.extend": "^2.0.0", - "plugin-error": "^0.1.2", - "through2": "^2.0.3" - }, - "engines": { - "node": ">=0.8.0", - "npm": ">=1.2.10" - } - }, - "node_modules/gulp-notify/node_modules/arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", - "license": "MIT", - "dependencies": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-notify/node_modules/arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-notify/node_modules/array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-notify/node_modules/extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha512-L7AGmkO6jhDkEBBGWlLtftA80Xq8DipnrRPr0pyi7GQLXkaq9JYA4xF4z6qnadIC6euiTDKco0cGSU9muw+WTw==", - "license": "MIT", - "dependencies": { - "kind-of": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-notify/node_modules/kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha512-aUH6ElPnMGon2/YkxRIigV32MOpTVcoXQ1Oo8aYn40s+sJ3j+0gFZsT8HKDcxNy7Fi9zuquWtGaGAahOdv5p/g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-notify/node_modules/plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha512-WzZHcm4+GO34sjFMxQMqZbsz3xiNEgonCskQ9v+IroMmYgk/tas8dG+Hr2D6IbRPybZ12oWpzE/w3cGJ6FJzOw==", - "license": "MIT", - "dependencies": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-notify/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-plumber": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/gulp-plumber/-/gulp-plumber-1.2.1.tgz", - "integrity": "sha512-mctAi9msEAG7XzW5ytDVZ9PxWMzzi1pS2rBH7lA095DhMa6KEXjm+St0GOCc567pJKJ/oCvosVAZEpAey0q2eQ==", - "license": "MIT", - "dependencies": { - "chalk": "^1.1.3", - "fancy-log": "^1.3.2", - "plugin-error": "^0.1.2", - "through2": "^2.0.3" - }, - "engines": { - "node": ">=0.10", - "npm": ">=1.2.10" - } - }, - "node_modules/gulp-plumber/node_modules/arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", - "license": "MIT", - "dependencies": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-plumber/node_modules/arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-plumber/node_modules/array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-plumber/node_modules/extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha512-L7AGmkO6jhDkEBBGWlLtftA80Xq8DipnrRPr0pyi7GQLXkaq9JYA4xF4z6qnadIC6euiTDKco0cGSU9muw+WTw==", - "license": "MIT", - "dependencies": { - "kind-of": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-plumber/node_modules/kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha512-aUH6ElPnMGon2/YkxRIigV32MOpTVcoXQ1Oo8aYn40s+sJ3j+0gFZsT8HKDcxNy7Fi9zuquWtGaGAahOdv5p/g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-plumber/node_modules/plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha512-WzZHcm4+GO34sjFMxQMqZbsz3xiNEgonCskQ9v+IroMmYgk/tas8dG+Hr2D6IbRPybZ12oWpzE/w3cGJ6FJzOw==", - "license": "MIT", - "dependencies": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-plumber/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-print": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/gulp-print/-/gulp-print-5.0.2.tgz", - "integrity": "sha512-iIpHMzC/b3gFvVXOfP9Jk94SWGIsDLVNUrxULRleQev+08ug07mh84b1AOlW6QDQdmInQiqDFqJN1UvhU2nXdg==", - "license": "MIT", - "dependencies": { - "ansi-colors": "^3.2.4", - "fancy-log": "^1.3.3", - "map-stream": "0.0.7", - "vinyl": "^2.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/gulp-print/node_modules/ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/gulp-print/node_modules/map-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", - "integrity": "sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==", - "license": "MIT" - }, - "node_modules/gulp-rename": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.4.0.tgz", - "integrity": "sha512-swzbIGb/arEoFK89tPY58vg3Ok1bw+d35PfUNwWqdo7KM4jkmuGA78JiDNqR+JeZFaeeHnRg9N7aihX3YPmsyg==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/gulp-replace": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.1.4.tgz", - "integrity": "sha512-SVSF7ikuWKhpAW4l4wapAqPPSToJoiNKsbDoUnRrSgwZHH7lH8pbPeQj1aOVYQrbZKhfSVBxVW+Py7vtulRktw==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/vinyl": "^2.0.4", - "istextorbinary": "^3.0.0", - "replacestream": "^4.0.3", - "yargs-parser": ">=5.0.0-security.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gulp-rtlcss": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/gulp-rtlcss/-/gulp-rtlcss-1.4.2.tgz", - "integrity": "sha512-wd807z/xq4XKtSwgrEetbx/aPoI5gV0yWV2rNqEBRwe2cJvNKLDsYR9A968c3gZtaKRMGAue5g3pHn40R+GWSA==", - "license": "MIT", - "dependencies": { - "plugin-error": "^1.0.1", - "rtlcss": "^2.4.0", - "through2": "^2.0.5", - "vinyl-sourcemaps-apply": "^0.2.1" - } - }, - "node_modules/gulp-rtlcss/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-tap": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gulp-tap/-/gulp-tap-1.0.1.tgz", - "integrity": "sha512-VpCARRSyr+WP16JGnoIg98/AcmyQjOwCpQgYoE35CWTdEMSbpgtAIK2fndqv2yY7aXstW27v3ZNBs0Ltb0Zkbg==", - "license": "MIT", - "dependencies": { - "through2": "^2.0.3" - } - }, - "node_modules/gulp-tap/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-uglify": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gulp-uglify/-/gulp-uglify-3.0.2.tgz", - "integrity": "sha512-gk1dhB74AkV2kzqPMQBLA3jPoIAPd/nlNzP2XMDSG8XZrqnlCiDGAqC+rZOumzFvB5zOphlFh6yr3lgcAb/OOg==", - "license": "MIT", - "dependencies": { - "array-each": "^1.0.1", - "extend-shallow": "^3.0.2", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "isobject": "^3.0.1", - "make-error-cause": "^1.1.1", - "safe-buffer": "^5.1.2", - "through2": "^2.0.0", - "uglify-js": "^3.0.5", - "vinyl-sourcemaps-apply": "^0.2.0" - } - }, - "node_modules/gulp-uglify/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-uglify/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-uglify/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-uglify/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-uglify/node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "license": "BSD-2-Clause", - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/gulp-util": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha512-q5oWPc12lwSFS9h/4VIjG+1NuNDlJ48ywV2JKItY4Ycc/n1fXJeYPVQsfu5ZrhQi7FGSDBalwUCLar/GyHXKGw==", - "deprecated": "gulp-util is deprecated - replace it, following the guidelines at https://medium.com/gulpjs/gulp-util-ca3b1f9f9ac5", - "license": "MIT", - "dependencies": { - "array-differ": "^1.0.0", - "array-uniq": "^1.0.2", - "beeper": "^1.0.0", - "chalk": "^1.0.0", - "dateformat": "^2.0.0", - "fancy-log": "^1.1.0", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.0.0", - "minimist": "^1.1.0", - "multipipe": "^0.1.2", - "object-assign": "^3.0.0", - "replace-ext": "0.0.1", - "through2": "^2.0.0", - "vinyl": "^0.5.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/gulp-util/node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/gulp-util/node_modules/clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha512-dhUqc57gSMCo6TX85FLfe51eC/s+Im2MLkAgJwfaRRexR2tA4dd3eLEW4L6efzHc2iNorrRRXITifnDLlRrhaA==", - "license": "MIT" - }, - "node_modules/gulp-util/node_modules/lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha512-0B4Y53I0OgHUJkt+7RmlDFWKjVAI/YUpWNiL9GQz5ORDr4ttgfQGo+phBWKFLJbBdtOwgMuUkdOHOnPg45jKmQ==", - "license": "MIT", - "dependencies": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" - } - }, - "node_modules/gulp-util/node_modules/lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha512-TcrlEr31tDYnWkHFWDCV3dHYroKEXpJZ2YJYvJdhN+y4AkWMDZ5I4I8XDtUKqSAyG81N7w+I1mFEJtcED+tGqQ==", - "license": "MIT", - "dependencies": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" - } - }, - "node_modules/gulp-util/node_modules/object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-util/node_modules/replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha512-AFBWBy9EVRTa/LhEcG8QDP3FvpwZqmvN2QFDuJswFeaVhWnZMp8q3E6Zd90SR04PlIwfGdyVjNyLPyen/ek5CQ==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gulp-util/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-util/node_modules/vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha512-P5zdf3WB9uzr7IFoVQ2wZTmUwHL8cMZWJGzLBNCHNZ3NB6HTMsYABtt7z8tAGIINLXyAob9B9a1yzVGMFOYKEA==", - "license": "MIT", - "dependencies": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" - }, - "engines": { - "node": ">= 0.9" - } - }, - "node_modules/gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha512-hm6N8nrm3Y08jXie48jsC55eCZz9mnb4OirAStEk2deqeyhXU3C1otDVh+ccttMuc1sBi6RX6ZJ720hs9RCvgw==", - "license": "MIT", - "dependencies": { - "glogg": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/has-gulplog": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha512-+F4GzLjwHNNDEAJW2DC1xXfEoPkRDmUdJ7CBYw4MpqtDwOnqdImJl7GWlpqx+Wko6//J8uKTnIe4wZSv7yCqmw==", - "license": "MIT", - "dependencies": { - "sparkles": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", - "license": "MIT", - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", - "license": "MIT", - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "license": "MIT", - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "license": "ISC" - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", - "license": "MIT", - "optional": true, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/import-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/import-regex/-/import-regex-1.1.0.tgz", - "integrity": "sha512-EblpleIyIdATUKj8ovFojUHyToxgjeKXQgTHZBGZ4cEkbtV21BlO1PSrzZQ6Fei2fgk7uhDeEx656yvPhlRGeA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/indx": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/indx/-/indx-0.2.3.tgz", - "integrity": "sha512-SEM+Px+Ghr3fZ+i9BNvUIZJ4UhojFuf+sT7x3cl2/ElL7NXne1A/m29VYzWTTypdOgDnWfoKNewIuPA6y+NMyQ==", - "license": "MIT" - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/inquirer": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", - "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^3.2.0", - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.12", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", - "strip-ansi": "^5.1.0", - "through": "^2.3.6" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/inquirer/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/inquirer/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/inquirer/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ip-regex": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-1.0.3.tgz", - "integrity": "sha512-HjpCHTuxbR/6jWJroc/VN+npo5j0T4Vv2TAI5qdEHQx7hsL767MeccGFSsLtF694EiZKTSEqgoeU6DtGFCcuqQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/irregular-plurals": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-2.0.0.tgz", - "integrity": "sha512-Y75zBYLkh0lJ9qxeHlMjQ7bSbyiSqNW/UOPWDmzC7cXskL1hekSITh1Oc6JV0XCWWZ9DE8VYSB71xocLk3gmGw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", - "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "license": "MIT", - "dependencies": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz", - "integrity": "sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "license": "MIT" - }, - "node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", - "license": "MIT", - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-descriptor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz", - "integrity": "sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negated-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "license": "MIT", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha512-cnS56eR9SPAscL77ik76ATVqoPARTqPIVkMDVxRaWH06zT+6+CzIroYRJ0VVvm0Z1zfAvxvz9i/D3Ppjaqt5Nw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "license": "MIT", - "dependencies": { - "is-path-inside": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha512-qhsCR/Esx4U4hg/9I19OVUAJkGWtjRYHMRgUMZE2TDdj+Ag+kttZanLupfddNyglzz50cUlmWzUaI37GDfNx/g==", - "license": "MIT", - "dependencies": { - "path-is-inside": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "license": "MIT", - "dependencies": { - "is-unc-path": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "license": "MIT", - "dependencies": { - "unc-path-regex": "^0.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", - "license": "MIT" - }, - "node_modules/is-valid-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", - "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-what": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", - "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", - "license": "MIT" - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istextorbinary": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-3.3.0.tgz", - "integrity": "sha512-Tvq1W6NAcZeJ8op+Hq7tdZ434rqnMx4CCZ7H0ff83uEloDvVbqAwaMTZcafKGJT0VHkYzuXUiCY4hlXQg6WfoQ==", - "license": "MIT", - "dependencies": { - "binaryextensions": "^2.2.0", - "textextensions": "^3.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jquery": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", - "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "license": "MIT" - }, - "node_modules/js-beautify": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", - "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==", - "license": "MIT", - "dependencies": { - "config-chain": "^1.1.13", - "editorconfig": "^1.0.4", - "glob": "^10.3.3", - "js-cookie": "^3.0.5", - "nopt": "^7.2.0" - }, - "bin": { - "css-beautify": "js/bin/css-beautify.js", - "html-beautify": "js/bin/html-beautify.js", - "js-beautify": "js/bin/js-beautify.js" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/js-beautify/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/js-beautify/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/js-beautify/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/js-cookie": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", - "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "license": "MIT" - }, - "node_modules/just-debounce": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", - "integrity": "sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==", - "license": "MIT" - }, - "node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/last-run": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", - "integrity": "sha512-U/VxvpX4N/rFvPzr3qG5EtLKEnNI0emvIQB3/ecEwv+8GHaUKbIB8vxv1Oai5FAF0d0r7LXHhLLe5K/yChm5GQ==", - "license": "MIT", - "dependencies": { - "default-resolution": "^2.0.0", - "es6-weak-map": "^2.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", - "license": "MIT", - "dependencies": { - "readable-stream": "^2.0.5" - }, - "engines": { - "node": ">= 0.6.3" - } - }, - "node_modules/lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==", - "license": "MIT", - "dependencies": { - "invert-kv": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lead": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", - "integrity": "sha512-IpSVCk9AYvLHo5ctcIXxOBpMWUe+4TKN3VPWAKUbJikkmsGp0VrSM8IttVc32D6J4WUsiPE6aEFRNmIoF/gdow==", - "license": "MIT", - "dependencies": { - "flush-write-stream": "^1.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/less": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/less/-/less-3.13.1.tgz", - "integrity": "sha512-SwA1aQXGUvp+P5XdZslUOhhLnClSLIjWvJhmd+Vgib5BFIr9lMNlQwmwUNOjXThF/A0x+MCYYPeWEfeWiLRnTw==", - "license": "Apache-2.0", - "dependencies": { - "copy-anything": "^2.0.1", - "tslib": "^1.10.0" - }, - "bin": { - "lessc": "bin/lessc" - }, - "engines": { - "node": ">=6" - }, - "optionalDependencies": { - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "make-dir": "^2.1.0", - "mime": "^1.4.1", - "native-request": "^1.0.5", - "source-map": "~0.6.0" - } - }, - "node_modules/liftoff": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", - "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", - "license": "MIT", - "dependencies": { - "extend": "^3.0.0", - "findup-sync": "^3.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/liftoff/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/load-json-file/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, - "node_modules/lodash._baseassign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha512-t3N26QR2IdSN+gqSy9Ds9pBu/J1EAFEshKlUHpJG3rvyJOYgcELIxcIeKKfZk7sjOz11cFfzJRsyFry/JyabJQ==", - "license": "MIT", - "dependencies": { - "lodash._basecopy": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "node_modules/lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha512-rFR6Vpm4HeCK1WPGvjZSJ+7yik8d8PVUdCJx5rT2pogG4Ve/2ZS7kfmO5l5T2o5V2mqlNIfSF5MZlr1+xOoYQQ==", - "license": "MIT" - }, - "node_modules/lodash._basetostring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", - "integrity": "sha512-mTzAr1aNAv/i7W43vOR/uD/aJ4ngbtsRaCubp2BfZhlGU/eORUjg/7F6X0orNMdv33JOrdgGybtvMN/po3EWrA==", - "license": "MIT" - }, - "node_modules/lodash._basevalues": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha512-H94wl5P13uEqlCg7OcNNhMQ8KvWSIyqXzOPusRgHC9DK3o54P6P3xtbXlVbRABG4q5gSmp7EDdJ0MSuW9HX6Mg==", - "license": "MIT" - }, - "node_modules/lodash._bindcallback": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", - "integrity": "sha512-2wlI0JRAGX8WEf4Gm1p/mv/SZ+jLijpj0jyaE/AXeuQphzCgD8ZQW4oSpoN8JAopujOFGU3KMuq7qfHBWlGpjQ==", - "license": "MIT" - }, - "node_modules/lodash._createassigner": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", - "integrity": "sha512-LziVL7IDnJjQeeV95Wvhw6G28Z8Q6da87LWKOPWmzBLv4u6FAT/x5v00pyGW0u38UoogNF2JnD3bGgZZDaNEBw==", - "license": "MIT", - "dependencies": { - "lodash._bindcallback": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash.restparam": "^3.0.0" - } - }, - "node_modules/lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha512-RrL9VxMEPyDMHOd9uFbvMe8X55X16/cGM5IgOKgRElQZutpX89iS6vwl64duTV1/16w5JY7tuFNXqoekmh1EmA==", - "license": "MIT" - }, - "node_modules/lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha512-De+ZbrMu6eThFti/CSzhRvTKMgQToLxbij58LMfM8JnYDNSOjkjTCIaa8ixglOeGh2nyPlakbt5bJWJ7gvpYlQ==", - "license": "MIT" - }, - "node_modules/lodash._isnative": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", - "integrity": "sha512-BOlKGKNHhCHswGOWtmVb5zBygyxN7EmTuzVOSQI6QSoGhG+kvv71gICFS1TBpnqvT1n53txK8CDK3u5D2/GZxQ==", - "license": "MIT" - }, - "node_modules/lodash._objecttypes": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", - "integrity": "sha512-XpqGh1e7hhkOzftBfWE7zt+Yn9mVHFkDhicVttvKLsoCMLVVL+xTQjfjB4X4vtznauxv0QZ5ZAeqjvat0dh62Q==", - "license": "MIT" - }, - "node_modules/lodash._reescape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", - "integrity": "sha512-Sjlavm5y+FUVIF3vF3B75GyXrzsfYV8Dlv3L4mEpuB9leg8N6yf/7rU06iLPx9fY0Mv3khVp9p7Dx0mGV6V5OQ==", - "license": "MIT" - }, - "node_modules/lodash._reevaluate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", - "integrity": "sha512-OrPwdDc65iJiBeUe5n/LIjd7Viy99bKwDdk7Z5ljfZg0uFRFlfQaCy9tZ4YMAag9WAZmlVpe1iZrkIMMSMHD3w==", - "license": "MIT" - }, - "node_modules/lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==", - "license": "MIT" - }, - "node_modules/lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha512-O0pWuFSK6x4EXhM1dhZ8gchNtG7JMqBtrHdoUFUWXD7dJnNSUze1GuyQr5sOs0aCvgGeI3o/OJW8f4ca7FDxmQ==", - "license": "MIT" - }, - "node_modules/lodash._shimkeys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", - "integrity": "sha512-lBrglYxLD/6KAJ8IEa5Lg+YHgNAL7FyKqXg4XOUI+Du/vtniLs1ZqS+yHNKPkK54waAgkdUnDOYaWf+rv4B+AA==", - "license": "MIT", - "dependencies": { - "lodash._objecttypes": "~2.4.1" - } - }, - "node_modules/lodash.assign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", - "integrity": "sha512-/VVxzgGBmbphasTg51FrztxQJ/VgAUpol6zmJuSVSGcNg4g7FA4z7rQV8Ovr9V3vFBNWZhvKWHfpAytjTVUfFA==", - "license": "MIT", - "dependencies": { - "lodash._baseassign": "^3.0.0", - "lodash._createassigner": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "node_modules/lodash.clone": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", - "integrity": "sha512-GhrVeweiTD6uTmmn5hV/lzgCQhccwReIVRLHp7LT4SopOjqEZ5BbX8b5WWEtAKasjmy8hR7ZPwsYlxRCku5odg==", - "license": "MIT" - }, - "node_modules/lodash.defaults": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-3.1.2.tgz", - "integrity": "sha512-X7135IXFQt5JDFnYxOVAzVz+kFvwDn3N8DJYf+nrz/mMWEuSu7+OL6rWqsk3+VR1T4TejFCSu5isBJOLSID2bg==", - "license": "MIT", - "dependencies": { - "lodash.assign": "^3.0.0", - "lodash.restparam": "^3.0.0" - } - }, - "node_modules/lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha512-n1PZMXgaaDWZDSvuNZ/8XOcYO2hOKDqZel5adtR30VKQAtoWs/5AOeFA0vPV8moiPzlqe7F4cP2tzpFewQyelQ==", - "license": "MIT", - "dependencies": { - "lodash._root": "^3.0.0" - } - }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", - "license": "MIT" - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "license": "MIT" - }, - "node_modules/lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", - "license": "MIT" - }, - "node_modules/lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha512-JwObCrNJuT0Nnbuecmqr5DgtuBppuCvGD9lxjFpAzwnVtdGoDQ1zig+5W8k5/6Gcn0gZ3936HDAlGd28i7sOGQ==", - "license": "MIT" - }, - "node_modules/lodash.isobject": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", - "integrity": "sha512-sTebg2a1PoicYEZXD5PBdQcTlIJ6hUslrlWr7iV0O7n+i4596s2NQ9I5CaZ5FbXSfya/9WQsrYLANUJv9paYVA==", - "license": "MIT", - "dependencies": { - "lodash._objecttypes": "~2.4.1" - } - }, - "node_modules/lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha512-CuBsapFjcubOGMn3VD+24HOAPxM79tH+V6ivJL3CHYjtrawauDJHUk//Yew9Hvc6e9rbCrURGk8z6PC+8WJBfQ==", - "license": "MIT", - "dependencies": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "license": "MIT" - }, - "node_modules/lodash.partialright": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.partialright/-/lodash.partialright-4.2.1.tgz", - "integrity": "sha512-yebmPMQZH7i4El6SdJTW9rn8irWl8VTcsmiWqm/I4sY8/ZjbSo0Z512HL6soeAu3mh5rhx5uIIo6kYJOQXbCxw==", - "license": "MIT" - }, - "node_modules/lodash.pick": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==", - "license": "MIT" - }, - "node_modules/lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha512-L4/arjjuq4noiUJpt3yS6KIKDtJwNe2fIYgMqyYYKoeIfV1iEqvPwhCx23o+R9dzouGihDAPN1dTIRWa7zk8tw==", - "license": "MIT" - }, - "node_modules/lodash.set": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==", - "license": "MIT" - }, - "node_modules/lodash.template": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", - "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", - "license": "MIT", - "dependencies": { - "lodash._reinterpolate": "^3.0.0", - "lodash.templatesettings": "^4.0.0" - } - }, - "node_modules/lodash.templatesettings": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", - "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", - "license": "MIT", - "dependencies": { - "lodash._reinterpolate": "^3.0.0" - } - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "license": "MIT" - }, - "node_modules/longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/macos-release": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.1.tgz", - "integrity": "sha512-DXqXhEM7gW59OjZO8NIjBCz9AQ1BEMrfiOAl4AYByHCtVHRF4KoGNO8mqQeM8lRCtQe/UnJ4imO/d2HdkKsd+A==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "license": "MIT", - "optional": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "license": "ISC" - }, - "node_modules/make-error-cause": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/make-error-cause/-/make-error-cause-1.2.2.tgz", - "integrity": "sha512-4TO2Y3HkBnis4c0dxhAgD/jprySYLACf7nwN6V0HAHDx59g12WlRpUmFy1bRHamjGUEEBrEvCq6SUpsEE2lhUg==", - "license": "Apache-2.0", - "dependencies": { - "make-error": "^1.2.0" - } - }, - "node_modules/make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/make-iterator/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==" - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "license": "MIT", - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", - "integrity": "sha512-LFgVbaHIHMqCRuCZyfCtUOq9/Lnzhi7Z0KFUE2fhD54+JN2jLh3hC02RLkqauJ3U4soU6H1J3tfj/Byk7GoEjA==", - "license": "MIT", - "dependencies": { - "findup-sync": "^2.0.0", - "micromatch": "^3.0.4", - "resolve": "^1.4.0", - "stack-trace": "0.0.10" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/matchdep/node_modules/findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha512-vs+3unmJT45eczmcAZ6zMJtxN3l/QXeccaXQx5cu/MeJMhewVfoWZqibRkOxPnmoR59+Zy5hjabfQc6JLSah4g==", - "license": "MIT", - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/matchdep/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "license": "MIT", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "optional": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "license": "MIT", - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/multipipe": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", - "integrity": "sha512-7ZxrUybYv9NonoXgwoOqtStIu18D1c3eFZj27hqgf5kBrBF8Q+tE8V0MW8dKM5QLkQPh1JhhbKgHLY9kifov4Q==", - "license": "MIT", - "dependencies": { - "duplexer2": "0.0.2" - } - }, - "node_modules/mute-stdout": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", - "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==", - "license": "ISC" - }, - "node_modules/nan": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", - "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==", - "license": "MIT", - "optional": true - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "license": "MIT", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/native-request": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/native-request/-/native-request-1.1.2.tgz", - "integrity": "sha512-/etjwrK0J4Ebbcnt35VMWnfiUX/B04uwGJxyJInagxDqf2z5drSt/lsOvEMWGYunz1kaLZAFrV4NDAbOoDKvAQ==", - "license": "MIT", - "optional": true - }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "license": "ISC" - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "license": "MIT" - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-notifier": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.5.tgz", - "integrity": "sha512-tVbHs7DyTLtzOiN78izLA85zRqB9NvEXkAf014Vx3jtSvn/xBl6bR8ZYifj+dFcFrKI21huSQgJZ6ZtL3B4HfQ==", - "license": "MIT", - "dependencies": { - "growly": "^1.3.0", - "is-wsl": "^1.1.0", - "semver": "^5.5.0", - "shellwords": "^0.1.1", - "which": "^1.3.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", - "license": "MIT" - }, - "node_modules/node.extend": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.3.tgz", - "integrity": "sha512-xwADg/okH48PvBmRZyoX8i8GJaKuJ1CqlqotlZOhUio8egD1P5trJupHKBzcPjSF9ifK2gPcEICRBnkfPqQXZw==", - "license": "(MIT OR GPL-2.0)", - "dependencies": { - "hasown": "^2.0.0", - "is": "^3.3.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/nopt": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", - "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", - "license": "ISC", - "dependencies": { - "abbrev": "^2.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/now-and-later": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", - "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", - "license": "MIT", - "dependencies": { - "once": "^1.3.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "license": "MIT", - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==", - "license": "MIT" - }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", - "license": "MIT", - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", - "license": "MIT", - "dependencies": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==", - "license": "MIT", - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.reduce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", - "integrity": "sha512-naLhxxpUESbNkRqc35oQ2scZSJueHGQNUfMW/0U37IgN6tE2dgDWg3whf+NEliy3F/QysrO48XKUz/nGPe+AQw==", - "license": "MIT", - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/octokit-pagination-methods": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz", - "integrity": "sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==", - "license": "MIT" - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ordered-read-streams": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", - "integrity": "sha512-Z87aSjx3r5c0ZB7bcJqIgIRX5bxR7A4aSzvIbaxd0oTkWBCOoKfuGHiKj60CHVUgg1Phm5yMZzBdt8XqRs73Mw==", - "license": "MIT", - "dependencies": { - "readable-stream": "^2.0.1" - } - }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g==", - "license": "MIT", - "dependencies": { - "lcid": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/os-name": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz", - "integrity": "sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==", - "license": "MIT", - "dependencies": { - "macos-release": "^2.2.0", - "windows-release": "^3.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/p-map": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", - "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0" - }, - "node_modules/parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", - "license": "MIT", - "dependencies": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/parse-import": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-import/-/parse-import-2.0.0.tgz", - "integrity": "sha512-c59vdx1LiQT+majNKMyfFLrNMAVS9U1bychTv3CEuxbKspgnVTrzLRtgtfCWyAmTuFAxQVSJFasVv8svJLksIg==", - "license": "MIT", - "dependencies": { - "get-imports": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", - "license": "MIT", - "dependencies": { - "error-ex": "^1.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", - "license": "MIT" - }, - "node_modules/path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", - "license": "MIT", - "dependencies": { - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", - "license": "(WTFPL OR MIT)" - }, - "node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "license": "MIT" - }, - "node_modules/path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", - "license": "MIT", - "dependencies": { - "path-root-regex": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-type/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "license": "ISC" - }, - "node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "license": "MIT", - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/plugin-error": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", - "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", - "license": "MIT", - "dependencies": { - "ansi-colors": "^1.0.1", - "arr-diff": "^4.0.0", - "arr-union": "^3.1.0", - "extend-shallow": "^3.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/plugin-error/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/plugin-error/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/plugin-error/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/plur": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/plur/-/plur-3.1.1.tgz", - "integrity": "sha512-t1Ax8KUvV3FFII8ltczPn2tJdjqbd1sIzu6t4JL7nQ3EyeL/lTrj5PWKb06ic5/6XYDr65rQ4uzQEGN70/6X5w==", - "license": "MIT", - "dependencies": { - "irregular-plurals": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "license": "MIT", - "dependencies": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "license": "MIT" - }, - "node_modules/pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", - "license": "ISC" - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "license": "MIT", - "optional": true - }, - "node_modules/pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "license": "MIT", - "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "node_modules/read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", - "license": "MIT", - "dependencies": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", - "license": "MIT", - "dependencies": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "license": "MIT", - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remove-bom-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", - "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5", - "is-utf8": "^0.2.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remove-bom-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", - "integrity": "sha512-wigO8/O08XHb8YPzpDDT+QmRANfW6vLqxfaXm1YXhnFf3AkSLyjfG3GEFg4McZkmgL7KvCj5u2KczkvSP6NfHA==", - "license": "MIT", - "dependencies": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/remove-bom-stream/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", - "license": "ISC" - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/replace-ext": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/replace-homedir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", - "integrity": "sha512-CHPV/GAglbIB1tnQgaiysb8H2yCy8WQ7lcEwQ/eT+kLj0QHV8LnJW0zpqpE7RSkrMSRoa+EBoag86clf7WAgSg==", - "license": "MIT", - "dependencies": { - "homedir-polyfill": "^1.0.1", - "is-absolute": "^1.0.0", - "remove-trailing-separator": "^1.1.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/replacestream": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/replacestream/-/replacestream-4.0.3.tgz", - "integrity": "sha512-AC0FiLS352pBBiZhd4VXB1Ab/lh0lEgpP+GGvZqbQh8a5cmXVoTe5EX/YeTFArnp4SRGTHh1qCHu9lGs1qG8sA==", - "license": "BSD-3-Clause", - "dependencies": { - "escape-string-regexp": "^1.0.3", - "object-assign": "^4.0.1", - "readable-stream": "^2.0.2" - } - }, - "node_modules/require-dir": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/require-dir/-/require-dir-1.2.0.tgz", - "integrity": "sha512-LY85DTSu+heYgDqq/mK+7zFHWkttVNRXC9NKcKGyuGLdlsfbjEPrIEYdCVrx6hqnJb+xSu3Lzaoo8VnmOhhjNA==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-dot-file": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/require-dot-file/-/require-dot-file-0.4.0.tgz", - "integrity": "sha512-pMe/T7+uFi2NMYsxuQtTh9n/UKD13HAHeDOk7KuP2pr7aKi5aMhvkbGD4IeoJKjy+3vdIUy8ggXYWzlZTL5FWA==", - "license": "MIT" - }, - "node_modules/require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", - "license": "ISC" - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", - "integrity": "sha512-NYDgziiroVeDC29xq7bp/CacZERYsA9bXYd1ZmcJlF3BcrZv5pTb4NG7SjdyKDnXZ84aC4vo2u6sNKIA1LCu/A==", - "license": "MIT", - "dependencies": { - "value-or-function": "^3.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "license": "MIT" - }, - "node_modules/restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", - "license": "MIT", - "dependencies": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "license": "MIT", - "engines": { - "node": ">=0.12" - } - }, - "node_modules/rework": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rework/-/rework-1.0.1.tgz", - "integrity": "sha512-eEjL8FdkdsxApd0yWVZgBGzfCQiT8yqSc2H1p4jpZpQdtz7ohETiDMoje5PlM8I9WgkqkreVxFUKYOiJdVWDXw==", - "dependencies": { - "convert-source-map": "^0.3.3", - "css": "^2.0.0" - } - }, - "node_modules/rework-import": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/rework-import/-/rework-import-2.1.0.tgz", - "integrity": "sha512-ufvoQX6cDhrqYc8ZXvJ+6FqimwyI4qn8cH1ypAiS9Mn41iVPN/9RGwRvscBtUEkHA09w8voTIakRJKslgWcTEQ==", - "license": "MIT", - "dependencies": { - "css": "^2.0.0", - "globby": "^2.0.0", - "parse-import": "^2.0.0", - "url-regex": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rework-import/node_modules/glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "dependencies": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/rework-import/node_modules/globby": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-2.1.0.tgz", - "integrity": "sha512-CqRID2dMaN4Zi9PANiQHhmKaGu7ZASehBLnaDogjR9L3L1EqAGFhflafT0IrSN/zm9xFk+KMTXZCN8pUYOiO/Q==", - "license": "MIT", - "dependencies": { - "array-union": "^1.0.1", - "async": "^1.2.1", - "glob": "^5.0.3", - "object-assign": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rework-import/node_modules/object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rework-plugin-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/rework-plugin-function/-/rework-plugin-function-1.0.2.tgz", - "integrity": "sha512-kyIphbC2Kuc3iFz1CSAQ5zmt4o/IHquhO+uG0kK0FQTjs4Z5eAxrqmrv3rZMR1KXa77SesaW9KwKyfbYoLMEqw==", - "license": "MIT", - "dependencies": { - "rework-visit": "^1.0.0" - } - }, - "node_modules/rework-plugin-url": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/rework-plugin-url/-/rework-plugin-url-1.1.0.tgz", - "integrity": "sha512-qlAhbJKfEK59jAPQppIn8bNXffW1INlaJZaXdX/ZLs/CzZSnn38Y0wESQ3tjOwRsDbPEUHN2XJ3ZgueDaaCC0A==", - "license": "MIT", - "dependencies": { - "rework-plugin-function": "^1.0.0" - } - }, - "node_modules/rework-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rework-visit/-/rework-visit-1.0.0.tgz", - "integrity": "sha512-W6V2fix7nCLUYX1v6eGPrBOZlc03/faqzP4sUxMAJMBMOPYhfV/RyLegTufn5gJKaOITyi+gvf0LXDZ9NzkHnQ==", - "license": "MIT" - }, - "node_modules/rework/node_modules/convert-source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz", - "integrity": "sha512-+4nRk0k3oEpwUB7/CalD7xE2z4VmtEnnq0GO2IPTkrooTrAhEsWvuLF5iWP1dXrwluki/azwXV1ve7gtYuPldg==", - "license": "MIT" - }, - "node_modules/right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha512-yqINtL/G7vs2v+dFIZmFUDbnVyFUJFKd6gK22Kgo6R4jfJGFtisKyncWDDULgjfqf4ASQuIQyjJ7XZ+3aWpsAg==", - "license": "MIT", - "dependencies": { - "align-text": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/rtlcss": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-2.6.2.tgz", - "integrity": "sha512-06LFAr+GAPo+BvaynsXRfoYTJvSaWRyOhURCQ7aeI1MKph9meM222F+Zkt3bDamyHHJuGi3VPtiRkpyswmQbGA==", - "license": "MIT", - "dependencies": { - "@choojs/findup": "^0.2.1", - "chalk": "^2.4.2", - "mkdirp": "^0.5.1", - "postcss": "^6.0.23", - "strip-json-comments": "^2.0.0" - }, - "bin": { - "rtlcss": "bin/rtlcss.js" - } - }, - "node_modules/rtlcss/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/rtlcss/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/rtlcss/node_modules/postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "license": "MIT", - "dependencies": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/rtlcss/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "license": "MIT", - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/semver-greatest-satisfied-range": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", - "integrity": "sha512-Ny/iyOzSSa8M5ML46IAx3iXc6tfOsYU2R4AXi2UpHk60Zrgyq6eqPj/xiOfS0rRl/iiQ/rdJkVjw/5cdUyCntQ==", - "license": "MIT", - "dependencies": { - "sver-compat": "^1.5.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "license": "ISC" - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "license": "MIT", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "license": "MIT" - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "license": "MIT", - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "license": "MIT", - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "license": "MIT", - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/snapdragon/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", - "license": "MIT", - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "deprecated": "See https://github.com/lydell/source-map-url#deprecated", - "license": "MIT" - }, - "node_modules/sparkles": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.20", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", - "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", - "license": "CC0-1.0" - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "license": "MIT", - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "license": "BSD-3-Clause" - }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/stat-mode": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.2.2.tgz", - "integrity": "sha512-o+7DC0OM5Jt3+gratXXqfXf62V/CBoqQbT7Kp7jCxTYW2PLOB2/ZSGIfm9T5/QZe1Vw1MCbu6DoB6JnhVtxcJw==", - "license": "MIT" - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", - "license": "MIT", - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "license": "MIT", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/stream-exhaust": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", - "license": "MIT" - }, - "node_modules/stream-shift": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", - "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", - "license": "MIT" - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "license": "MIT", - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "license": "BSD-2-Clause", - "dependencies": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", - "license": "MIT", - "dependencies": { - "is-utf8": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-bom-buf": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz", - "integrity": "sha512-1sUIL1jck0T1mhOLP2c696BIznzT525Lkub+n4jjMHjhjhoAQA6Ye659DxdlZBr0aLDMQoTxKIpnlqxgtwjsuQ==", - "license": "MIT", - "dependencies": { - "is-utf8": "^0.2.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-bom-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-3.0.0.tgz", - "integrity": "sha512-2di6sulSHfspbuEJHwwF6vzwijA4uaKsKYtviRQsJsOdxxb6yexiDcZFQ5oY10J50YxmCdHn/1sQmxDKbrGOVw==", - "license": "MIT", - "dependencies": { - "first-chunk-stream": "^2.0.0", - "strip-bom-buf": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sver-compat": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", - "integrity": "sha512-aFTHfmjwizMNlNE6dsGmoAM4lHjL0CyiobWaFiXWSlD7cIxshW422Nb8KbXCmR6z+0ZEPY+daXJrDyh/vuwTyg==", - "license": "MIT", - "dependencies": { - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/ternary-stream": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-2.1.1.tgz", - "integrity": "sha512-j6ei9hxSoyGlqTmoMjOm+QNvUKDOIY6bNl4Uh1lhBvl6yjPW2iLqxDUYyfDPZknQ4KdRziFl+ec99iT4l7g0cw==", - "license": "MIT", - "dependencies": { - "duplexify": "^3.5.0", - "fork-stream": "^0.0.4", - "merge-stream": "^1.0.0", - "through2": "^2.0.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/ternary-stream/node_modules/merge-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", - "integrity": "sha512-e6RM36aegd4f+r8BZCcYXlO2P3H6xbUM6ktL2Xmf45GAOit9bI4z6/3VU7JwllVO1L7u0UDSg/EhzQ5lmMLolA==", - "license": "MIT", - "dependencies": { - "readable-stream": "^2.0.1" - } - }, - "node_modules/ternary-stream/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/textextensions": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-3.3.0.tgz", - "integrity": "sha512-mk82dS8eRABNbeVJrEiN5/UMSCliINAuz8mkUwH4SwslkNP//gbEzlWNS5au0z5Dpx40SQxzqZevZkn+WYJ9Dw==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "license": "MIT" - }, - "node_modules/through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" - } - }, - "node_modules/through2-filter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", - "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", - "license": "MIT", - "dependencies": { - "through2": "~2.0.0", - "xtend": "~4.0.0" - } - }, - "node_modules/through2-filter/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/tildify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz", - "integrity": "sha512-Y9q1GaV/BO65Z9Yf4NOGMuwt3SGdptkZBnaaKfTQakrDyCLiuO1Kc5wxW4xLdsjzunRtqtOdhekiUFmZbklwYQ==", - "license": "MIT", - "dependencies": { - "os-homedir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha512-gLCeArryy2yNTRzTGKbZbloctj64jkZ57hj5zdraXue6aFgd6PmvVtEyiUU+hvU0v7q08oVv8r8ev0tRo6bvgw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/to-absolute-glob": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", - "integrity": "sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA==", - "license": "MIT", - "dependencies": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", - "license": "MIT", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "license": "MIT", - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "license": "MIT", - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-through": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", - "integrity": "sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q==", - "license": "MIT", - "dependencies": { - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/to-through/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD" - }, - "node_modules/type": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", - "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", - "license": "ISC" - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "license": "MIT" - }, - "node_modules/uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha512-qLq/4y2pjcU3vhlhseXGGJ7VbFO4pBANu0kwl8VCa9KEI0V8VfZIx2Fy3w01iSTA/pGwKZSmu/+I4etLNDdt5w==", - "license": "BSD-2-Clause", - "dependencies": { - "source-map": "~0.5.1", - "yargs": "~3.10.0" - }, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - }, - "optionalDependencies": { - "uglify-to-browserify": "~1.0.0" - } - }, - "node_modules/uglify-js/node_modules/camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/uglify-js/node_modules/cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha512-GIOYRizG+TGoc7Wgc1LiOTLare95R3mzKgoln+Q/lE4ceiYH19gUpl0l0Ffq4lJDEf3FxujMe6IBfOCs7pfqNA==", - "license": "ISC", - "dependencies": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" - } - }, - "node_modules/uglify-js/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/uglify-js/node_modules/yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha512-QFzUah88GAGy9lyDKGBqZdkYApt63rCXYBGYnEP4xDJPXNqXXnBDACnbrXnViV6jRSqAePwrATi2i8mfYm4L1A==", - "license": "MIT", - "dependencies": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "window-size": "0.1.0" - } - }, - "node_modules/uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha512-vb2s1lYx2xBtUgy+ta+b2J/GLVUR+wmpINwHePmPRhOsIVCG2wDzKJ0n14GslH1BifsqVzSOwQhRaCAsZ/nI4Q==", - "license": "MIT", - "optional": true - }, - "node_modules/unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/undertaker": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", - "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", - "license": "MIT", - "dependencies": { - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "bach": "^1.0.0", - "collection-map": "^1.0.0", - "es6-weak-map": "^2.0.1", - "fast-levenshtein": "^1.0.0", - "last-run": "^1.1.0", - "object.defaults": "^1.0.0", - "object.reduce": "^1.0.0", - "undertaker-registry": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/undertaker-registry": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", - "integrity": "sha512-UR1khWeAjugW3548EfQmL9Z7pGMlBgXteQpr1IZeZBtnkCJQJIJ1Scj0mb9wQaPvUZ9Q17XqW6TIaPchJkyfqw==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "license": "MIT" - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "license": "MIT", - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unique-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", - "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", - "license": "MIT", - "dependencies": { - "json-stable-stringify-without-jsonify": "^1.0.1", - "through2-filter": "^3.0.0" - } - }, - "node_modules/universal-user-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-4.0.1.tgz", - "integrity": "sha512-LnST3ebHwVL2aNe4mejI9IQh2HfZ1RLo8Io2HugSif8ekzD1TlWpHpColOB/eh8JHMLkGH3Akqf040I+4ylNxg==", - "license": "ISC", - "dependencies": { - "os-name": "^3.1.0" - } - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", - "license": "MIT", - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", - "license": "MIT", - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "license": "MIT", - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "license": "MIT", - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/update-browserslist-db/node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "license": "MIT" - }, - "node_modules/url-regex": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/url-regex/-/url-regex-3.2.0.tgz", - "integrity": "sha512-dQ9cJzMou5OKr6ZzfvwJkCq3rC72PNXhqz0v3EIhF4a3Np+ujr100AhUx2cKx5ei3iymoJpJrPB3sVSEMdqAeg==", - "license": "MIT", - "dependencies": { - "ip-regex": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", - "license": "MIT", - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/value-or-function": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", - "integrity": "sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", - "license": "MIT", - "dependencies": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vinyl-fs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", - "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", - "license": "MIT", - "dependencies": { - "fs-mkdirp-stream": "^1.0.0", - "glob-stream": "^6.1.0", - "graceful-fs": "^4.0.0", - "is-valid-glob": "^1.0.0", - "lazystream": "^1.0.0", - "lead": "^1.0.0", - "object.assign": "^4.0.4", - "pumpify": "^1.3.5", - "readable-stream": "^2.3.3", - "remove-bom-buffer": "^3.0.0", - "remove-bom-stream": "^1.2.0", - "resolve-options": "^1.1.0", - "through2": "^2.0.0", - "to-through": "^2.0.0", - "value-or-function": "^3.0.0", - "vinyl": "^2.0.0", - "vinyl-sourcemap": "^1.1.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vinyl-fs/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/vinyl-sourcemap": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", - "integrity": "sha512-NiibMgt6VJGJmyw7vtzhctDcfKch4e4n9TBeoWlirb7FMg9/1Ov9k+A5ZRAtywBpRPiyECvQRQllYM8dECegVA==", - "license": "MIT", - "dependencies": { - "append-buffer": "^1.0.2", - "convert-source-map": "^1.5.0", - "graceful-fs": "^4.1.6", - "normalize-path": "^2.1.1", - "now-and-later": "^2.0.0", - "remove-bom-buffer": "^3.0.0", - "vinyl": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vinyl-sourcemap/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "license": "MIT", - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vinyl-sourcemaps-apply": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", - "integrity": "sha512-+oDh3KYZBoZC8hfocrbrxbLUeaYtQK7J5WU5Br9VqWqmCll3tFJqKp97GC9GmMsVIL0qnx2DgEDVxdo5EZ5sSw==", - "license": "ISC", - "dependencies": { - "source-map": "^0.5.1" - } - }, - "node_modules/vinyl-sourcemaps-apply/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/when": { - "version": "3.7.8", - "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz", - "integrity": "sha512-5cZ7mecD3eYcMiCH4wtRPA5iFJZ50BJYDfckI5RRpQiktMiYTcn0ccLTZOvcbBume+1304fQztxeNzNS9Gvrnw==", - "license": "MIT" - }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==", - "license": "ISC" - }, - "node_modules/window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha512-1pTPQDKTdd61ozlKGNCjhNRd+KPmgLSGa3mZTHoOliaGcESD8G1PXhh7c1fgiPjVbNVfgy2Faw4BI8/m0cC8Mg==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/windows-release": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.3.3.tgz", - "integrity": "sha512-OSOGH1QYiW5yVor9TtmXKQvt2vjQqbYS+DqmsZw+r7xDwLXEeT3JGW0ZppFmHx4diyXmxt238KFR3N9jzevBRg==", - "license": "MIT", - "dependencies": { - "execa": "^1.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==", - "license": "MIT/X11", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", - "license": "MIT", - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", - "license": "MIT", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", - "license": "MIT", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/wrench-sui": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wrench-sui/-/wrench-sui-0.0.3.tgz", - "integrity": "sha512-Y6qzMpcMG9akKnIdUsKzEF/Ht0KQJBP8ETkZj3FcGe93NC71e940WZUP1y+j+hc8Ecx9TyX0GvAWC4yymA88yA==", - "engines": { - "node": ">=0.1.97" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", - "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", - "license": "ISC" - }, - "node_modules/yamljs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", - "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "glob": "^7.0.5" - }, - "bin": { - "json2yaml": "bin/json2yaml", - "yaml2json": "bin/yaml2json" - } - }, - "node_modules/yargs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", - "integrity": "sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==", - "license": "MIT", - "dependencies": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.1" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", - "license": "MIT", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", - "license": "MIT", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", - "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", - "license": "ISC", - "dependencies": { - "camelcase": "^3.0.0", - "object.assign": "^4.1.0" - } - } - } -} diff --git a/web_src/fomantic/package.json b/web_src/fomantic/package.json deleted file mode 100644 index c031c070c5..0000000000 --- a/web_src/fomantic/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "dependencies": { - "fomantic-ui": "2.8.7" - } -} diff --git a/web_src/fomantic/theme.config.less b/web_src/fomantic/theme.config.less index b92399409d..632ddce07f 100644 --- a/web_src/fomantic/theme.config.less +++ b/web_src/fomantic/theme.config.less @@ -1,21 +1,4 @@ -/* - -████████╗██╗ ██╗███████╗███╗ ███╗███████╗███████╗ -╚══██╔══╝██║ ██║██╔════╝████╗ ████║██╔════╝██╔════╝ - ██║ ███████║█████╗ ██╔████╔██║█████╗ ███████╗ - ██║ ██╔══██║██╔══╝ ██║╚██╔╝██║██╔══╝ ╚════██║ - ██║ ██║ ██║███████╗██║ ╚═╝ ██║███████╗███████║ - ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚══════╝ - -*/ - -/******************************* - Theme Selection -*******************************/ - -/* To override a theme for an individual element - specify theme name below -*/ +/* To override a theme for an individual element, specify theme name below */ /* Global */ @site : 'default'; diff --git a/web_src/js/bootstrap.ts b/web_src/js/bootstrap.ts index 9e41673b86..96a2759a23 100644 --- a/web_src/js/bootstrap.ts +++ b/web_src/js/bootstrap.ts @@ -2,6 +2,7 @@ // to make sure the error handler always works, we should never import `window.config`, because // some user's custom template breaks it. import type {Intent} from './types.ts'; +import {html} from './utils/html.ts'; // This sets up the URL prefix used in webpack's chunk loading. // This file must be imported before any lazy-loading is being attempted. @@ -23,7 +24,7 @@ export function showGlobalErrorMessage(msg: string, msgType: Intent = 'error') { let msgDiv = msgContainer.querySelector<HTMLDivElement>(`.js-global-error[data-global-error-msg-compact="${msgCompact}"]`); if (!msgDiv) { const el = document.createElement('div'); - el.innerHTML = `<div class="ui container js-global-error tw-my-[--page-spacing]"><div class="ui ${msgType} message tw-text-center tw-whitespace-pre-line"></div></div>`; + el.innerHTML = html`<div class="ui container js-global-error tw-my-[--page-spacing]"><div class="ui ${msgType} message tw-text-center tw-whitespace-pre-line"></div></div>`; msgDiv = el.childNodes[0] as HTMLDivElement; } // merge duplicated messages into "the message (count)" format diff --git a/web_src/js/components/ActionRunStatus.vue b/web_src/js/components/ActionRunStatus.vue index 96c6c441be..bc3b99ab89 100644 --- a/web_src/js/components/ActionRunStatus.vue +++ b/web_src/js/components/ActionRunStatus.vue @@ -19,12 +19,12 @@ withDefaults(defineProps<{ <template> <span :data-tooltip-content="localeStatus ?? status" v-if="status"> - <SvgIcon name="octicon-check-circle-fill" class="text green" :size="size" :class-name="className" v-if="status === 'success'"/> - <SvgIcon name="octicon-skip" class="text grey" :size="size" :class-name="className" v-else-if="status === 'skipped'"/> - <SvgIcon name="octicon-stop" class="text yellow" :size="size" :class-name="className" v-else-if="status === 'cancelled'"/> - <SvgIcon name="octicon-clock" class="text yellow" :size="size" :class-name="className" v-else-if="status === 'waiting'"/> - <SvgIcon name="octicon-blocked" class="text yellow" :size="size" :class-name="className" v-else-if="status === 'blocked'"/> - <SvgIcon name="octicon-meter" class="text yellow" :size="size" :class-name="'job-status-rotate ' + className" v-else-if="status === 'running'"/> + <SvgIcon name="octicon-check-circle-fill" class="text green" :size="size" :class="className" v-if="status === 'success'"/> + <SvgIcon name="octicon-skip" class="text grey" :size="size" :class="className" v-else-if="status === 'skipped'"/> + <SvgIcon name="octicon-stop" class="text yellow" :size="size" :class="className" v-else-if="status === 'cancelled'"/> + <SvgIcon name="octicon-clock" class="text yellow" :size="size" :class="className" v-else-if="status === 'waiting'"/> + <SvgIcon name="octicon-blocked" class="text yellow" :size="size" :class="className" v-else-if="status === 'blocked'"/> + <SvgIcon name="octicon-meter" class="text yellow" :size="size" :class="'circular-spin ' + className" v-else-if="status === 'running'"/> <SvgIcon name="octicon-x-circle-fill" class="text red" :size="size" v-else/><!-- failure, unknown --> </span> </template> diff --git a/web_src/js/components/ActivityHeatmap.vue b/web_src/js/components/ActivityHeatmap.vue index eaa9b0ffb1..296cb61cff 100644 --- a/web_src/js/components/ActivityHeatmap.vue +++ b/web_src/js/components/ActivityHeatmap.vue @@ -1,7 +1,7 @@ <script lang="ts" setup> // TODO: Switch to upstream after https://github.com/razorness/vue3-calendar-heatmap/pull/34 is merged import {CalendarHeatmap} from '@silverwind/vue3-calendar-heatmap'; -import {onMounted, ref} from 'vue'; +import {onMounted, shallowRef} from 'vue'; import type {Value as HeatmapValue, Locale as HeatmapLocale} from '@silverwind/vue3-calendar-heatmap'; defineProps<{ @@ -24,7 +24,7 @@ const colorRange = [ 'var(--color-primary-dark-4)', ]; -const endDate = ref(new Date()); +const endDate = shallowRef(new Date()); onMounted(() => { // work around issue with first legend color being rendered twice and legend cut off diff --git a/web_src/js/components/ContextPopup.vue b/web_src/js/components/ContextPopup.vue index 0aae202d42..5ec4499e48 100644 --- a/web_src/js/components/ContextPopup.vue +++ b/web_src/js/components/ContextPopup.vue @@ -2,16 +2,16 @@ import {SvgIcon} from '../svg.ts'; import {GET} from '../modules/fetch.ts'; import {getIssueColor, getIssueIcon} from '../features/issue.ts'; -import {computed, onMounted, ref} from 'vue'; +import {computed, onMounted, shallowRef, useTemplateRef} from 'vue'; import type {IssuePathInfo} from '../types.ts'; const {appSubUrl, i18n} = window.config; -const loading = ref(false); -const issue = ref(null); -const renderedLabels = ref(''); +const loading = shallowRef(false); +const issue = shallowRef(null); +const renderedLabels = shallowRef(''); const i18nErrorOccurred = i18n.error_occurred; -const i18nErrorMessage = ref(null); +const i18nErrorMessage = shallowRef(null); const createdAt = computed(() => new Date(issue.value.created_at).toLocaleDateString(undefined, {year: 'numeric', month: 'short', day: 'numeric'})); const body = computed(() => { @@ -22,7 +22,7 @@ const body = computed(() => { return body; }); -const root = ref<HTMLElement | null>(null); +const root = useTemplateRef('root'); onMounted(() => { root.value.addEventListener('ce-load-context-popup', (e: CustomEventInit<IssuePathInfo>) => { diff --git a/web_src/js/components/DashboardRepoList.vue b/web_src/js/components/DashboardRepoList.vue index 41793d60ed..e938814ec6 100644 --- a/web_src/js/components/DashboardRepoList.vue +++ b/web_src/js/components/DashboardRepoList.vue @@ -1,12 +1,12 @@ <script lang="ts"> -import {createApp, nextTick} from 'vue'; +import {nextTick, defineComponent} from 'vue'; import {SvgIcon} from '../svg.ts'; import {GET} from '../modules/fetch.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; const {appSubUrl, assetUrlPrefix, pageData} = window.config; -type CommitStatus = 'pending' | 'success' | 'error' | 'failure' | 'warning'; +type CommitStatus = 'pending' | 'success' | 'error' | 'failure' | 'warning' | 'skipped'; type CommitStatusMap = { [status in CommitStatus]: { @@ -22,9 +22,10 @@ const commitStatus: CommitStatusMap = { error: {name: 'gitea-exclamation', color: 'red'}, failure: {name: 'octicon-x', color: 'red'}, warning: {name: 'gitea-exclamation', color: 'yellow'}, + skipped: {name: 'octicon-skip', color: 'grey'}, }; -const sfc = { +export default defineComponent({ components: {SvgIcon}, data() { const params = new URLSearchParams(window.location.search); @@ -38,7 +39,7 @@ const sfc = { return { tab, repos: [], - reposTotalCount: 0, + reposTotalCount: null, reposFilter, archivedFilter, privateFilter, @@ -112,9 +113,6 @@ const sfc = { const el = document.querySelector('#dashboard-repo-list'); this.changeReposFilter(this.reposFilter); fomanticQuery(el.querySelector('.ui.dropdown')).dropdown(); - nextTick(() => { - this.$refs.search.focus(); - }); this.textArchivedFilterTitles = { 'archived': this.textShowOnlyArchived, @@ -130,12 +128,12 @@ const sfc = { }, methods: { - changeTab(t) { - this.tab = t; + changeTab(tab: string) { + this.tab = tab; this.updateHistory(); }, - changeReposFilter(filter) { + changeReposFilter(filter: string) { this.reposFilter = filter; this.repos = []; this.page = 1; @@ -218,7 +216,9 @@ const sfc = { this.searchRepos(); }, - changePage(page) { + async changePage(page: number) { + if (this.isLoading) return; + this.page = page; if (this.page > this.finalPage) { this.page = this.finalPage; @@ -228,7 +228,7 @@ const sfc = { } this.repos = []; this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0; - this.searchRepos(); + await this.searchRepos(); }, async searchRepos() { @@ -240,12 +240,20 @@ const sfc = { let response, json; try { + const firstLoad = this.reposTotalCount === null; if (!this.reposTotalCount) { const totalCountSearchURL = `${this.subUrl}/repo/search?count_only=1&uid=${this.uid}&team_id=${this.teamId}&q=&page=1&mode=`; response = await GET(totalCountSearchURL); - this.reposTotalCount = response.headers.get('X-Total-Count') ?? '?'; + this.reposTotalCount = parseInt(response.headers.get('X-Total-Count') ?? '0'); + } + if (firstLoad && this.reposTotalCount) { + nextTick(() => { + // MDN: If there's no focused element, this is the Document.body or Document.documentElement. + if ((document.activeElement === document.body || document.activeElement === document.documentElement)) { + this.$refs.search.focus({preventScroll: true}); + } + }); } - response = await GET(searchedURL); json = await response.json(); } catch { @@ -256,7 +264,7 @@ const sfc = { } if (searchedURL === this.searchURL) { - this.repos = json.data.map((webSearchRepo) => { + this.repos = json.data.map((webSearchRepo: any) => { return { ...webSearchRepo.repository, latest_commit_status_state: webSearchRepo.latest_commit_status?.State, // if latest_commit_status is null, it means there is no commit status @@ -264,7 +272,7 @@ const sfc = { locale_latest_commit_status_state: webSearchRepo.locale_latest_commit_status, }; }); - const count = response.headers.get('X-Total-Count'); + const count = Number(response.headers.get('X-Total-Count')); if (searchedQuery === '' && searchedMode === '' && this.archivedFilter === 'both') { this.reposTotalCount = count; } @@ -275,7 +283,7 @@ const sfc = { } }, - repoIcon(repo) { + repoIcon(repo: any) { if (repo.fork) { return 'octicon-repo-forked'; } else if (repo.mirror) { @@ -298,7 +306,7 @@ const sfc = { return commitStatus[status].color; }, - reposFilterKeyControl(e) { + async reposFilterKeyControl(e: KeyboardEvent) { switch (e.key) { case 'Enter': document.querySelector<HTMLAnchorElement>('.repo-owner-name-list li.active a')?.click(); @@ -307,7 +315,7 @@ const sfc = { if (this.activeIndex > 0) { this.activeIndex--; } else if (this.page > 1) { - this.changePage(this.page - 1); + await this.changePage(this.page - 1); this.activeIndex = this.searchLimit - 1; } break; @@ -316,17 +324,17 @@ const sfc = { this.activeIndex++; } else if (this.page < this.finalPage) { this.activeIndex = 0; - this.changePage(this.page + 1); + await this.changePage(this.page + 1); } break; case 'ArrowRight': if (this.page < this.finalPage) { - this.changePage(this.page + 1); + await this.changePage(this.page + 1); } break; case 'ArrowLeft': if (this.page > 1) { - this.changePage(this.page - 1); + await this.changePage(this.page - 1); } break; } @@ -335,16 +343,7 @@ const sfc = { } }, }, -}; - -export function initDashboardRepoList() { - const el = document.querySelector('#dashboard-repo-list'); - if (el) { - createApp(sfc).mount(el); - } -} - -export default sfc; // activate the IDE's Vue plugin +}); </script> <template> <div> @@ -356,13 +355,21 @@ export default sfc; // activate the IDE's Vue plugin <h4 class="ui top attached header tw-flex tw-items-center"> <div class="tw-flex-1 tw-flex tw-items-center"> {{ textMyRepos }} - <span class="ui grey label tw-ml-2">{{ reposTotalCount }}</span> + <span v-if="reposTotalCount" class="ui grey label tw-ml-2">{{ reposTotalCount }}</span> </div> <a class="tw-flex tw-items-center muted" :href="subUrl + '/repo/create' + (isOrganization ? '?org=' + organizationId : '')" :data-tooltip-content="textNewRepo"> <svg-icon name="octicon-plus"/> </a> </h4> - <div class="ui attached segment repos-search"> + <div v-if="!reposTotalCount" class="ui attached segment"> + <div v-if="!isLoading" class="empty-repo-or-org"> + <svg-icon name="octicon-git-branch" :size="24"/> + <p>{{ textNoRepo }}</p> + </div> + <!-- using the loading indicator here will cause more (unnecessary) page flickers, so at the moment, not use the loading indicator --> + <!-- <div v-else class="is-loading loading-icon-2px tw-min-h-16"/> --> + </div> + <div v-else class="ui attached segment repos-search"> <div class="ui small fluid action left icon input"> <input type="search" spellcheck="false" maxlength="255" @input="changeReposFilter(reposFilter)" v-model="searchQuery" ref="search" @keydown="reposFilterKeyControl" :placeholder="textSearchRepos"> <i class="icon loading-icon-3px" :class="{'is-loading': isLoading}"><svg-icon name="octicon-search" :size="16"/></i> @@ -375,7 +382,7 @@ export default sfc; // activate the IDE's Vue plugin otherwise if the "input" handles click event for intermediate status, it breaks the internal state--> <input type="checkbox" class="tw-pointer-events-none" v-bind.prop="checkboxArchivedFilterProps"> <label> - <svg-icon name="octicon-archive" :size="16" class-name="tw-mr-1"/> + <svg-icon name="octicon-archive" :size="16" class="tw-mr-1"/> {{ textShowArchived }} </label> </div> @@ -384,7 +391,7 @@ export default sfc; // activate the IDE's Vue plugin <div class="ui checkbox" ref="checkboxPrivateFilter" :title="checkboxPrivateFilterTitle"> <input type="checkbox" class="tw-pointer-events-none" v-bind.prop="checkboxPrivateFilterProps"> <label> - <svg-icon name="octicon-lock" :size="16" class-name="tw-mr-1"/> + <svg-icon name="octicon-lock" :size="16" class="tw-mr-1"/> {{ textShowPrivate }} </label> </div> @@ -419,17 +426,17 @@ export default sfc; // activate the IDE's Vue plugin </div> <div v-if="repos.length" class="ui attached table segment tw-rounded-b"> <ul class="repo-owner-name-list"> - <li class="tw-flex tw-items-center tw-py-2" v-for="repo, index in repos" :class="{'active': index === activeIndex}" :key="repo.id"> + <li class="tw-flex tw-items-center tw-py-2" v-for="(repo, index) in repos" :class="{'active': index === activeIndex}" :key="repo.id"> <a class="repo-list-link muted" :href="repo.link"> - <svg-icon :name="repoIcon(repo)" :size="16" class-name="repo-list-icon"/> + <svg-icon :name="repoIcon(repo)" :size="16" class="repo-list-icon"/> <div class="text truncate">{{ repo.full_name }}</div> <div v-if="repo.archived"> <svg-icon name="octicon-archive" :size="16"/> </div> </a> - <a class="tw-flex tw-items-center" v-if="repo.latest_commit_status_state" :href="repo.latest_commit_status_state_link" :data-tooltip-content="repo.locale_latest_commit_status_state"> + <a class="tw-flex tw-items-center" v-if="repo.latest_commit_status_state" :href="repo.latest_commit_status_state_link || null" :data-tooltip-content="repo.locale_latest_commit_status_state"> <!-- the commit status icon logic is taken from templates/repo/commit_status.tmpl --> - <svg-icon :name="statusIcon(repo.latest_commit_status_state)" :class-name="'tw-ml-2 commit-status icon text ' + statusColor(repo.latest_commit_status_state)" :size="16"/> + <svg-icon :name="statusIcon(repo.latest_commit_status_state)" :class="'tw-ml-2 commit-status icon text ' + statusColor(repo.latest_commit_status_state)" :size="16"/> </a> </li> </ul> @@ -440,26 +447,26 @@ export default sfc; // activate the IDE's Vue plugin class="item navigation tw-py-1" :class="{'disabled': page === 1}" @click="changePage(1)" :title="textFirstPage" > - <svg-icon name="gitea-double-chevron-left" :size="16" class-name="tw-mr-1"/> + <svg-icon name="gitea-double-chevron-left" :size="16" class="tw-mr-1"/> </a> <a class="item navigation tw-py-1" :class="{'disabled': page === 1}" @click="changePage(page - 1)" :title="textPreviousPage" > - <svg-icon name="octicon-chevron-left" :size="16" clsas-name="tw-mr-1"/> + <svg-icon name="octicon-chevron-left" :size="16" class="tw-mr-1"/> </a> <a class="active item tw-py-1">{{ page }}</a> <a class="item navigation" :class="{'disabled': page === finalPage}" @click="changePage(page + 1)" :title="textNextPage" > - <svg-icon name="octicon-chevron-right" :size="16" class-name="tw-ml-1"/> + <svg-icon name="octicon-chevron-right" :size="16" class="tw-ml-1"/> </a> <a class="item navigation tw-py-1" :class="{'disabled': page === finalPage}" @click="changePage(finalPage)" :title="textLastPage" > - <svg-icon name="gitea-double-chevron-right" :size="16" class-name="tw-ml-1"/> + <svg-icon name="gitea-double-chevron-right" :size="16" class="tw-ml-1"/> </a> </div> </div> @@ -475,11 +482,17 @@ export default sfc; // activate the IDE's Vue plugin <svg-icon name="octicon-plus"/> </a> </h4> - <div v-if="organizations.length" class="ui attached table segment tw-rounded-b"> + <div v-if="!organizations.length" class="ui attached segment"> + <div class="empty-repo-or-org"> + <svg-icon name="octicon-organization" :size="24"/> + <p>{{ textNoOrg }}</p> + </div> + </div> + <div v-else class="ui attached table segment tw-rounded-b"> <ul class="repo-owner-name-list"> <li class="tw-flex tw-items-center tw-py-2" v-for="org in organizations" :key="org.name"> <a class="repo-list-link muted" :href="subUrl + '/' + encodeURIComponent(org.name)"> - <svg-icon name="octicon-organization" :size="16" class-name="repo-list-icon"/> + <svg-icon name="octicon-organization" :size="16" class="repo-list-icon"/> <div class="text truncate">{{ org.full_name ? `${org.full_name} (${org.name})` : org.name }}</div> <div><!-- div to prevent underline of label on hover --> <span class="ui tiny basic label" v-if="org.org_visibility !== 'public'"> @@ -489,7 +502,7 @@ export default sfc; // activate the IDE's Vue plugin </a> <div class="text light grey tw-flex tw-items-center tw-ml-2"> {{ org.num_repos }} - <svg-icon name="octicon-repo" :size="16" class-name="tw-ml-1 tw-mt-0.5"/> + <svg-icon name="octicon-repo" :size="16" class="tw-ml-1 tw-mt-0.5"/> </div> </li> </ul> @@ -554,4 +567,14 @@ ul li:not(:last-child) { .repo-owner-name-list li.active { background: var(--color-hover); } + +.empty-repo-or-org { + margin-top: 1em; + text-align: center; + color: var(--color-placeholder-text); +} + +.empty-repo-or-org p { + margin: 1em auto; +} </style> diff --git a/web_src/js/components/DiffCommitSelector.vue b/web_src/js/components/DiffCommitSelector.vue index 3a394955ca..a375343979 100644 --- a/web_src/js/components/DiffCommitSelector.vue +++ b/web_src/js/components/DiffCommitSelector.vue @@ -1,9 +1,26 @@ <script lang="ts"> +import {defineComponent} from 'vue'; import {SvgIcon} from '../svg.ts'; import {GET} from '../modules/fetch.ts'; import {generateAriaId} from '../modules/fomantic/base.ts'; -export default { +type Commit = { + id: string, + hovered: boolean, + selected: boolean, + summary: string, + committer_or_author_name: string, + time: string, + short_sha: string, +} + +type CommitListResult = { + commits: Array<Commit>, + last_review_commit_sha: string, + locale: Record<string, string>, +} + +export default defineComponent({ components: {SvgIcon}, data: () => { const el = document.querySelector('#diff-commit-select'); @@ -15,9 +32,9 @@ export default { locale: { filter_changes_by_commit: el.getAttribute('data-filter_changes_by_commit'), } as Record<string, string>, - commits: [], + commits: [] as Array<Commit>, hoverActivated: false, - lastReviewCommitSha: null, + lastReviewCommitSha: '', uniqueIdMenu: generateAriaId(), uniqueIdShowAll: generateAriaId(), }; @@ -55,11 +72,11 @@ export default { switch (event.key) { case 'ArrowDown': // select next element event.preventDefault(); - this.focusElem(item.nextElementSibling, item); + this.focusElem(item.nextElementSibling as HTMLElement, item); break; case 'ArrowUp': // select previous element event.preventDefault(); - this.focusElem(item.previousElementSibling, item); + this.focusElem(item.previousElementSibling as HTMLElement, item); break; case 'Escape': // close menu event.preventDefault(); @@ -70,7 +87,7 @@ export default { if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { const item = document.activeElement; // try to highlight the selected commits const commitIdx = item?.matches('.item') ? item.getAttribute('data-commit-idx') : null; - if (commitIdx) this.highlight(this.commits[commitIdx]); + if (commitIdx) this.highlight(this.commits[Number(commitIdx)]); } }, onKeyUp(event: KeyboardEvent) { @@ -86,7 +103,7 @@ export default { } } }, - highlight(commit) { + highlight(commit: Commit) { if (!this.hoverActivated) return; const indexSelected = this.commits.findIndex((x) => x.selected); const indexCurrentElem = this.commits.findIndex((x) => x.id === commit.id); @@ -118,16 +135,17 @@ export default { // set correct tabindex to allow easier navigation this.$nextTick(() => { if (this.menuVisible) { - this.focusElem(this.$refs.showAllChanges, this.$refs.expandBtn); + this.focusElem(this.$refs.showAllChanges as HTMLElement, this.$refs.expandBtn as HTMLElement); } else { - this.focusElem(this.$refs.expandBtn, this.$refs.showAllChanges); + this.focusElem(this.$refs.expandBtn as HTMLElement, this.$refs.showAllChanges as HTMLElement); } }); }, + /** Load the commits to show in this dropdown */ async fetchCommits() { const resp = await GET(`${this.issueLink}/commits/list`); - const results = await resp.json(); + const results = await resp.json() as CommitListResult; this.commits.push(...results.commits.map((x) => { x.hovered = false; return x; @@ -165,7 +183,7 @@ export default { * the diff from beginning of PR up to the second clicked commit is * opened */ - commitClickedShift(commit) { + commitClickedShift(commit: Commit) { this.hoverActivated = !this.hoverActivated; commit.selected = true; // Second click -> determine our range and open links accordingly @@ -188,13 +206,13 @@ export default { } }, }, -}; +}); </script> <template> <div class="ui scrolling dropdown custom diff-commit-selector"> <button ref="expandBtn" - class="ui basic button" + class="ui tiny basic button" @click.stop="toggleMenu()" :data-tooltip-content="locale.filter_changes_by_commit" aria-haspopup="true" diff --git a/web_src/js/components/DiffFileList.vue b/web_src/js/components/DiffFileList.vue deleted file mode 100644 index 2888c53d2e..0000000000 --- a/web_src/js/components/DiffFileList.vue +++ /dev/null @@ -1,60 +0,0 @@ -<script lang="ts" setup> -import {onMounted, onUnmounted} from 'vue'; -import {loadMoreFiles} from '../features/repo-diff.ts'; -import {diffTreeStore} from '../modules/stores.ts'; - -const store = diffTreeStore(); - -onMounted(() => { - document.querySelector('#show-file-list-btn').addEventListener('click', toggleFileList); -}); - -onUnmounted(() => { - document.querySelector('#show-file-list-btn').removeEventListener('click', toggleFileList); -}); - -function toggleFileList() { - store.fileListIsVisible = !store.fileListIsVisible; -} - -function diffTypeToString(pType) { - const diffTypes = { - 1: 'add', - 2: 'modify', - 3: 'del', - 4: 'rename', - 5: 'copy', - }; - return diffTypes[pType]; -} - -function diffStatsWidth(adds, dels) { - return `${adds / (adds + dels) * 100}%`; -} - -function loadMoreData() { - loadMoreFiles(store.linkLoadMore); -} -</script> - -<template> - <ol class="diff-stats tw-m-0" ref="root" v-if="store.fileListIsVisible"> - <li v-for="file in store.files" :key="file.NameHash"> - <div class="tw-font-semibold tw-flex tw-items-center pull-right"> - <span v-if="file.IsBin" class="tw-ml-0.5 tw-mr-2">{{ store.binaryFileMessage }}</span> - {{ file.IsBin ? '' : file.Addition + file.Deletion }} - <span v-if="!file.IsBin" class="diff-stats-bar tw-mx-2" :data-tooltip-content="store.statisticsMessage.replace('%d', (file.Addition + file.Deletion)).replace('%d', file.Addition).replace('%d', file.Deletion)"> - <div class="diff-stats-add-bar" :style="{ 'width': diffStatsWidth(file.Addition, file.Deletion) }"/> - </span> - </div> - <!-- todo finish all file status, now modify, add, delete and rename --> - <span :class="['status', diffTypeToString(file.Type)]" :data-tooltip-content="diffTypeToString(file.Type)"> </span> - <a class="file tw-font-mono" :href="'#diff-' + file.NameHash">{{ file.Name }}</a> - </li> - <li v-if="store.isIncomplete" class="tw-pt-1"> - <span class="file tw-flex tw-items-center tw-justify-between">{{ store.tooManyFilesMessage }} - <a :class="['ui', 'basic', 'tiny', 'button', store.isLoadingNewData ? 'disabled' : '']" @click.stop="loadMoreData">{{ store.showMoreMessage }}</a> - </span> - </li> - </ol> -</template> diff --git a/web_src/js/components/DiffFileTree.vue b/web_src/js/components/DiffFileTree.vue index 9eabc65ae9..981d10c1c1 100644 --- a/web_src/js/components/DiffFileTree.vue +++ b/web_src/js/components/DiffFileTree.vue @@ -1,83 +1,14 @@ <script lang="ts" setup> import DiffFileTreeItem from './DiffFileTreeItem.vue'; -import {loadMoreFiles} from '../features/repo-diff.ts'; import {toggleElem} from '../utils/dom.ts'; -import {diffTreeStore} from '../modules/stores.ts'; +import {diffTreeStore} from '../modules/diff-file.ts'; import {setFileFolding} from '../features/file-fold.ts'; -import {computed, onMounted, onUnmounted} from 'vue'; +import {onMounted, onUnmounted} from 'vue'; const LOCAL_STORAGE_KEY = 'diff_file_tree_visible'; const store = diffTreeStore(); -const fileTree = computed(() => { - const result = []; - for (const file of store.files) { - // Split file into directories - const splits = file.Name.split('/'); - let index = 0; - let parent = null; - let isFile = false; - for (const split of splits) { - index += 1; - // reached the end - if (index === splits.length) { - isFile = true; - } - let newParent = { - name: split, - children: [], - isFile, - } as { - name: string, - children: any[], - isFile: boolean, - file?: any, - }; - - if (isFile === true) { - newParent.file = file; - } - - if (parent) { - // check if the folder already exists - const existingFolder = parent.children.find( - (x) => x.name === split, - ); - if (existingFolder) { - newParent = existingFolder; - } else { - parent.children.push(newParent); - } - } else { - const existingFolder = result.find((x) => x.name === split); - if (existingFolder) { - newParent = existingFolder; - } else { - result.push(newParent); - } - } - parent = newParent; - } - } - const mergeChildIfOnlyOneDir = (entries) => { - for (const entry of entries) { - if (entry.children) { - mergeChildIfOnlyOneDir(entry.children); - } - if (entry.children.length === 1 && entry.children[0].isFile === false) { - // Merge it to the parent - entry.name = `${entry.name}/${entry.children[0].name}`; - entry.children = entry.children[0].children; - } - } - }; - // Merge folders with just a folder as children in order to - // reduce the depth of our tree. - mergeChildIfOnlyOneDir(result); - return result; -}); - onMounted(() => { // Default to true if unset store.fileTreeIsVisible = localStorage.getItem(LOCAL_STORAGE_KEY) !== 'false'; @@ -110,13 +41,13 @@ function toggleVisibility() { updateVisibility(!store.fileTreeIsVisible); } -function updateVisibility(visible) { +function updateVisibility(visible: boolean) { store.fileTreeIsVisible = visible; - localStorage.setItem(LOCAL_STORAGE_KEY, store.fileTreeIsVisible); + localStorage.setItem(LOCAL_STORAGE_KEY, store.fileTreeIsVisible.toString()); updateState(store.fileTreeIsVisible); } -function updateState(visible) { +function updateState(visible: boolean) { const btn = document.querySelector('.diff-toggle-file-tree-button'); const [toShow, toHide] = btn.querySelectorAll('.icon'); const tree = document.querySelector('#diff-file-tree'); @@ -126,19 +57,12 @@ function updateState(visible) { toggleElem(toShow, !visible); toggleElem(toHide, visible); } - -function loadMoreData() { - loadMoreFiles(store.linkLoadMore); -} </script> <template> + <!-- only render the tree if we're visible. in many cases this is something that doesn't change very often --> <div v-if="store.fileTreeIsVisible" class="diff-file-tree-items"> - <!-- only render the tree if we're visible. in many cases this is something that doesn't change very often --> - <DiffFileTreeItem v-for="item in fileTree" :key="item.name" :item="item"/> - <div v-if="store.isIncomplete" class="tw-pt-1"> - <a :class="['ui', 'basic', 'tiny', 'button', store.isLoadingNewData ? 'disabled' : '']" @click.stop="loadMoreData">{{ store.showMoreMessage }}</a> - </div> + <DiffFileTreeItem v-for="item in store.diffFileTree.TreeRoot.Children" :key="item.FullName" :item="item"/> </div> </template> diff --git a/web_src/js/components/DiffFileTreeItem.vue b/web_src/js/components/DiffFileTreeItem.vue index 12cafd8f1b..f15f093ff8 100644 --- a/web_src/js/components/DiffFileTreeItem.vue +++ b/web_src/js/components/DiffFileTreeItem.vue @@ -1,66 +1,62 @@ <script lang="ts" setup> -import {SvgIcon} from '../svg.ts'; -import {diffTreeStore} from '../modules/stores.ts'; -import {ref} from 'vue'; +import {SvgIcon, type SvgName} from '../svg.ts'; +import {shallowRef} from 'vue'; +import {type DiffStatus, type DiffTreeEntry, diffTreeStore} from '../modules/diff-file.ts'; -type File = { - Name: string; - NameHash: string; - Type: number; - IsViewed: boolean; -} - -type Item = { - name: string; - isFile: boolean; - file?: File; - children?: Item[]; -}; - -defineProps<{ - item: Item, +const props = defineProps<{ + item: DiffTreeEntry, }>(); const store = diffTreeStore(); -const collapsed = ref(false); +const collapsed = shallowRef(props.item.IsViewed); -function getIconForDiffType(pType) { - const diffTypes = { - 1: {name: 'octicon-diff-added', classes: ['text', 'green']}, - 2: {name: 'octicon-diff-modified', classes: ['text', 'yellow']}, - 3: {name: 'octicon-diff-removed', classes: ['text', 'red']}, - 4: {name: 'octicon-diff-renamed', classes: ['text', 'teal']}, - 5: {name: 'octicon-diff-renamed', classes: ['text', 'green']}, // there is no octicon for copied, so renamed should be ok +function getIconForDiffStatus(pType: DiffStatus) { + const diffTypes: Record<DiffStatus, { name: SvgName, classes: Array<string> }> = { + '': {name: 'octicon-blocked', classes: ['text', 'red']}, // unknown case + 'added': {name: 'octicon-diff-added', classes: ['text', 'green']}, + 'modified': {name: 'octicon-diff-modified', classes: ['text', 'yellow']}, + 'deleted': {name: 'octicon-diff-removed', classes: ['text', 'red']}, + 'renamed': {name: 'octicon-diff-renamed', classes: ['text', 'teal']}, + 'copied': {name: 'octicon-diff-renamed', classes: ['text', 'green']}, + 'typechange': {name: 'octicon-diff-modified', classes: ['text', 'green']}, // there is no octicon for copied, so renamed should be ok }; - return diffTypes[pType]; + return diffTypes[pType] ?? diffTypes['']; } </script> <template> - <!--title instead of tooltip above as the tooltip needs too much work with the current methods, i.e. not being loaded or staying open for "too long"--> + <template v-if="item.EntryMode === 'tree'"> + <div class="item-directory" :class="{ 'viewed': item.IsViewed }" :title="item.DisplayName" @click.stop="collapsed = !collapsed"> + <!-- directory --> + <SvgIcon :name="collapsed ? 'octicon-chevron-right' : 'octicon-chevron-down'"/> + <!-- eslint-disable-next-line vue/no-v-html --> + <span class="tw-contents" v-html="collapsed ? store.folderIcon : store.folderOpenIcon"/> + <span class="gt-ellipsis">{{ item.DisplayName }}</span> + </div> + + <div v-show="!collapsed" class="sub-items"> + <DiffFileTreeItem v-for="childItem in item.Children" :key="childItem.DisplayName" :item="childItem"/> + </div> + </template> <a - v-if="item.isFile" class="item-file" - :class="{'selected': store.selectedItem === '#diff-' + item.file.NameHash, 'viewed': item.file.IsViewed}" - :title="item.name" :href="'#diff-' + item.file.NameHash" + v-else + class="item-file" :class="{ 'selected': store.selectedItem === '#diff-' + item.NameHash, 'viewed': item.IsViewed }" + :title="item.DisplayName" :href="'#diff-' + item.NameHash" > <!-- file --> - <SvgIcon name="octicon-file"/> - <span class="gt-ellipsis tw-flex-1">{{ item.name }}</span> - <SvgIcon :name="getIconForDiffType(item.file.Type).name" :class="getIconForDiffType(item.file.Type).classes"/> + <!-- eslint-disable-next-line vue/no-v-html --> + <span class="tw-contents" v-html="item.FileIcon"/> + <span class="gt-ellipsis tw-flex-1">{{ item.DisplayName }}</span> + <SvgIcon + :name="getIconForDiffStatus(item.DiffStatus).name" + :class="getIconForDiffStatus(item.DiffStatus).classes" + /> </a> - <div v-else class="item-directory" :title="item.name" @click.stop="collapsed = !collapsed"> - <!-- directory --> - <SvgIcon :name="collapsed ? 'octicon-chevron-right' : 'octicon-chevron-down'"/> - <SvgIcon class="text primary" :name="collapsed ? 'octicon-file-directory-fill' : 'octicon-file-directory-open-fill'"/> - <span class="gt-ellipsis">{{ item.name }}</span> - </div> - - <div v-if="item.children?.length" v-show="!collapsed" class="sub-items"> - <DiffFileTreeItem v-for="childItem in item.children" :key="childItem.name" :item="childItem"/> - </div> </template> + <style scoped> -a, a:hover { +a, +a:hover { text-decoration: none; color: var(--color-text); } @@ -83,7 +79,8 @@ a, a:hover { border-radius: 4px; } -.item-file.viewed { +.item-file.viewed, +.item-directory.viewed { color: var(--color-text-light-3); } diff --git a/web_src/js/components/PullRequestMergeForm.vue b/web_src/js/components/PullRequestMergeForm.vue index e8bcee70db..b2c28414c0 100644 --- a/web_src/js/components/PullRequestMergeForm.vue +++ b/web_src/js/components/PullRequestMergeForm.vue @@ -1,19 +1,19 @@ <script lang="ts" setup> -import {computed, onMounted, onUnmounted, ref, watch} from 'vue'; +import {computed, onMounted, onUnmounted, shallowRef, watch} from 'vue'; import {SvgIcon} from '../svg.ts'; import {toggleElem} from '../utils/dom.ts'; const {csrfToken, pageData} = window.config; -const mergeForm = ref(pageData.pullRequestMergeForm); +const mergeForm = pageData.pullRequestMergeForm; -const mergeTitleFieldValue = ref(''); -const mergeMessageFieldValue = ref(''); -const deleteBranchAfterMerge = ref(false); -const autoMergeWhenSucceed = ref(false); +const mergeTitleFieldValue = shallowRef(''); +const mergeMessageFieldValue = shallowRef(''); +const deleteBranchAfterMerge = shallowRef(false); +const autoMergeWhenSucceed = shallowRef(false); -const mergeStyle = ref(''); -const mergeStyleDetail = ref({ +const mergeStyle = shallowRef(''); +const mergeStyleDetail = shallowRef({ hideMergeMessageTexts: false, textDoMerge: '', mergeTitleFieldText: '', @@ -21,33 +21,33 @@ const mergeStyleDetail = ref({ hideAutoMerge: false, }); -const mergeStyleAllowedCount = ref(0); +const mergeStyleAllowedCount = shallowRef(0); -const showMergeStyleMenu = ref(false); -const showActionForm = ref(false); +const showMergeStyleMenu = shallowRef(false); +const showActionForm = shallowRef(false); const mergeButtonStyleClass = computed(() => { - if (mergeForm.value.allOverridableChecksOk) return 'primary'; + if (mergeForm.allOverridableChecksOk) return 'primary'; return autoMergeWhenSucceed.value ? 'primary' : 'red'; }); const forceMerge = computed(() => { - return mergeForm.value.canMergeNow && !mergeForm.value.allOverridableChecksOk; + return mergeForm.canMergeNow && !mergeForm.allOverridableChecksOk; }); watch(mergeStyle, (val) => { - mergeStyleDetail.value = mergeForm.value.mergeStyles.find((e) => e.name === val); + mergeStyleDetail.value = mergeForm.mergeStyles.find((e: any) => e.name === val); for (const elem of document.querySelectorAll('[data-pull-merge-style]')) { toggleElem(elem, elem.getAttribute('data-pull-merge-style') === val); } }); onMounted(() => { - mergeStyleAllowedCount.value = mergeForm.value.mergeStyles.reduce((v, msd) => v + (msd.allowed ? 1 : 0), 0); + mergeStyleAllowedCount.value = mergeForm.mergeStyles.reduce((v: any, msd: any) => v + (msd.allowed ? 1 : 0), 0); - let mergeStyle = mergeForm.value.mergeStyles.find((e) => e.allowed && e.name === mergeForm.value.defaultMergeStyle)?.name; - if (!mergeStyle) mergeStyle = mergeForm.value.mergeStyles.find((e) => e.allowed)?.name; - switchMergeStyle(mergeStyle, !mergeForm.value.canMergeNow); + let mergeStyle = mergeForm.mergeStyles.find((e: any) => e.allowed && e.name === mergeForm.defaultMergeStyle)?.name; + if (!mergeStyle) mergeStyle = mergeForm.mergeStyles.find((e: any) => e.allowed)?.name; + switchMergeStyle(mergeStyle, !mergeForm.canMergeNow); document.addEventListener('mouseup', hideMergeStyleMenu); }); @@ -63,18 +63,18 @@ function hideMergeStyleMenu() { function toggleActionForm(show: boolean) { showActionForm.value = show; if (!show) return; - deleteBranchAfterMerge.value = mergeForm.value.defaultDeleteBranchAfterMerge; + deleteBranchAfterMerge.value = mergeForm.defaultDeleteBranchAfterMerge; mergeTitleFieldValue.value = mergeStyleDetail.value.mergeTitleFieldText; mergeMessageFieldValue.value = mergeStyleDetail.value.mergeMessageFieldText; } -function switchMergeStyle(name, autoMerge = false) { +function switchMergeStyle(name: string, autoMerge = false) { mergeStyle.value = name; autoMergeWhenSucceed.value = autoMerge; } function clearMergeMessage() { - mergeMessageFieldValue.value = mergeForm.value.defaultMergeMessage; + mergeMessageFieldValue.value = mergeForm.defaultMergeMessage; } </script> @@ -129,7 +129,7 @@ function clearMergeMessage() { {{ mergeForm.textCancel }} </button> - <div class="ui checkbox tw-ml-1" v-if="mergeForm.isPullBranchDeletable && !autoMergeWhenSucceed"> + <div class="ui checkbox tw-ml-1" v-if="mergeForm.isPullBranchDeletable"> <input name="delete_branch_after_merge" type="checkbox" v-model="deleteBranchAfterMerge" id="delete-branch-after-merge"> <label for="delete-branch-after-merge">{{ mergeForm.textDeleteBranch }}</label> </div> @@ -147,7 +147,7 @@ function clearMergeMessage() { </template> </span> </button> - <div class="ui dropdown icon button" @click.stop="showMergeStyleMenu = !showMergeStyleMenu" v-if="mergeStyleAllowedCount>1"> + <div class="ui dropdown icon button" @click.stop="showMergeStyleMenu = !showMergeStyleMenu"> <svg-icon name="octicon-triangle-down" :size="14"/> <div class="menu" :class="{'show':showMergeStyleMenu}"> <template v-for="msd in mergeForm.mergeStyles"> diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index b083fb0b77..2eb2211269 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -1,11 +1,13 @@ <script lang="ts"> import {SvgIcon} from '../svg.ts'; import ActionRunStatus from './ActionRunStatus.vue'; -import {createApp} from 'vue'; +import {defineComponent, type PropType} from 'vue'; import {createElementFromAttrs, toggleElem} from '../utils/dom.ts'; import {formatDatetime} from '../utils/time.ts'; import {renderAnsi} from '../render/ansi.ts'; import {POST, DELETE} from '../modules/fetch.ts'; +import type {IntervalId} from '../types.ts'; +import {toggleFullScreen} from '../utils.ts'; // see "models/actions/status.go", if it needs to be used somewhere else, move it to a shared file like "types/actions.ts" type RunStatus = 'unknown' | 'waiting' | 'running' | 'success' | 'failure' | 'cancelled' | 'skipped' | 'blocked'; @@ -24,6 +26,20 @@ type LogLineCommand = { prefix: string, } +type Job = { + id: number; + name: string; + status: RunStatus; + canRerun: boolean; + duration: string; +} + +type Step = { + summary: string, + duration: string, + status: RunStatus, +} + function parseLineCommand(line: LogLine): LogLineCommand | null { for (const prefix of LogLinePrefixesGroup) { if (line.message.startsWith(prefix)) { @@ -38,48 +54,77 @@ function parseLineCommand(line: LogLine): LogLineCommand | null { return null; } -function isLogElementInViewport(el: HTMLElement): boolean { +function isLogElementInViewport(el: Element): boolean { const rect = el.getBoundingClientRect(); return rect.top >= 0 && rect.bottom <= window.innerHeight; // only check height but not width } -const sfc = { +type LocaleStorageOptions = { + autoScroll: boolean; + expandRunning: boolean; +}; + +function getLocaleStorageOptions(): LocaleStorageOptions { + try { + const optsJson = localStorage.getItem('actions-view-options'); + if (optsJson) return JSON.parse(optsJson); + } catch {} + // if no options in localStorage, or failed to parse, return default options + return {autoScroll: true, expandRunning: false}; +} + +export default defineComponent({ name: 'RepoActionView', components: { SvgIcon, ActionRunStatus, }, props: { - runIndex: String, - jobIndex: String, - actionsURL: String, - locale: Object, + runIndex: { + type: String, + default: '', + }, + jobIndex: { + type: String, + default: '', + }, + actionsURL: { + type: String, + default: '', + }, + locale: { + type: Object as PropType<Record<string, any>>, + default: null, + }, }, data() { + const {autoScroll, expandRunning} = getLocaleStorageOptions(); return { // internal state - loadingAbortController: null, - intervalID: null, - currentJobStepsStates: [], - artifacts: [], - onHoverRerunIndex: -1, + loadingAbortController: null as AbortController | null, + intervalID: null as IntervalId | null, + currentJobStepsStates: [] as Array<Record<string, any>>, + artifacts: [] as Array<Record<string, any>>, menuVisible: false, isFullScreen: false, timeVisible: { 'log-time-stamp': false, 'log-time-seconds': false, }, + optionAlwaysAutoScroll: autoScroll ?? false, + optionAlwaysExpandRunning: expandRunning ?? false, // provided by backend run: { link: '', title: '', titleHTML: '', - status: '', + status: '' as RunStatus, // do not show the status before initialized, otherwise it would show an incorrect "error" icon canCancel: false, canApprove: false, canRerun: false, + canDeleteArtifact: false, done: false, workflowID: '', workflowLink: '', @@ -92,7 +137,7 @@ const sfc = { // canRerun: false, // duration: '', // }, - ], + ] as Array<Job>, commit: { localeCommit: '', localePushedBy: '', @@ -105,6 +150,7 @@ const sfc = { branch: { name: '', link: '', + isDeleted: false, }, }, }, @@ -117,11 +163,20 @@ const sfc = { // duration: '', // status: '', // } - ], + ] as Array<Step>, }, }; }, + watch: { + optionAlwaysAutoScroll() { + this.saveLocaleStorageOptions(); + }, + optionAlwaysExpandRunning() { + this.saveLocaleStorageOptions(); + }, + }, + async mounted() { // load job data and then auto-reload periodically // need to await first loadJob so this.currentJobStepsStates is initialized and can be used in hashChangeListener @@ -147,19 +202,25 @@ const sfc = { }, methods: { + saveLocaleStorageOptions() { + const opts: LocaleStorageOptions = {autoScroll: this.optionAlwaysAutoScroll, expandRunning: this.optionAlwaysExpandRunning}; + localStorage.setItem('actions-view-options', JSON.stringify(opts)); + }, + // get the job step logs container ('.job-step-logs') getJobStepLogsContainer(stepIndex: number): HTMLElement { - return this.$refs.logs[stepIndex]; + return (this.$refs.logs as any)[stepIndex]; }, // get the active logs container element, either the `job-step-logs` or the `job-log-list` in the `job-log-group` getActiveLogsContainer(stepIndex: number): HTMLElement { const el = this.getJobStepLogsContainer(stepIndex); + // @ts-expect-error - _stepLogsActiveContainer is a custom property return el._stepLogsActiveContainer ?? el; }, // begin a log group beginLogGroup(stepIndex: number, startTime: number, line: LogLine, cmd: LogLineCommand) { - const el = this.$refs.logs[stepIndex]; + const el = (this.$refs.logs as any)[stepIndex]; const elJobLogGroupSummary = createElementFromAttrs('summary', {class: 'job-log-group-summary'}, this.createLogLine(stepIndex, startTime, { index: line.index, @@ -177,7 +238,7 @@ const sfc = { }, // end a log group endLogGroup(stepIndex: number, startTime: number, line: LogLine, cmd: LogLineCommand) { - const el = this.$refs.logs[stepIndex]; + const el = (this.$refs.logs as any)[stepIndex]; el._stepLogsActiveContainer = null; el.append(this.createLogLine(stepIndex, startTime, { index: line.index, @@ -228,9 +289,11 @@ const sfc = { }, shouldAutoScroll(stepIndex: number): boolean { + if (!this.optionAlwaysAutoScroll) return false; const el = this.getJobStepLogsContainer(stepIndex); - if (!el.lastChild) return false; - return isLogElementInViewport(el.lastChild); + // if the logs container is empty, then auto-scroll if the step is expanded + if (!el.lastChild) return this.currentJobStepsStates[stepIndex].expanded; + return isLogElementInViewport(el.lastChild as Element); }, appendLogs(stepIndex: number, startTime: number, logLines: LogLine[]) { @@ -280,6 +343,7 @@ const sfc = { const abortController = new AbortController(); this.loadingAbortController = abortController; try { + const isFirstLoad = !this.run.status; const job = await this.fetchJobData(abortController); if (this.loadingAbortController !== abortController) return; @@ -289,9 +353,10 @@ const sfc = { // sync the currentJobStepsStates to store the job step states for (let i = 0; i < this.currentJob.steps.length; i++) { + const expanded = isFirstLoad && this.optionAlwaysExpandRunning && this.currentJob.steps[i].status === 'running'; if (!this.currentJobStepsStates[i]) { // initial states for job steps - this.currentJobStepsStates[i] = {cursor: null, expanded: false}; + this.currentJobStepsStates[i] = {cursor: null, expanded}; } } @@ -343,96 +408,39 @@ const sfc = { if (this.menuVisible) this.menuVisible = false; }, - toggleTimeDisplay(type: string) { + toggleTimeDisplay(type: 'seconds' | 'stamp') { this.timeVisible[`log-time-${type}`] = !this.timeVisible[`log-time-${type}`]; - for (const el of this.$refs.steps.querySelectorAll(`.log-time-${type}`)) { + for (const el of (this.$refs.steps as HTMLElement).querySelectorAll(`.log-time-${type}`)) { toggleElem(el, this.timeVisible[`log-time-${type}`]); } }, toggleFullScreen() { this.isFullScreen = !this.isFullScreen; - const fullScreenEl = document.querySelector('.action-view-right'); - const outerEl = document.querySelector('.full.height'); - const actionBodyEl = document.querySelector('.action-view-body'); - const headerEl = document.querySelector('#navbar'); - const contentEl = document.querySelector('.page-content'); - const footerEl = document.querySelector('.page-footer'); - toggleElem(headerEl, !this.isFullScreen); - toggleElem(contentEl, !this.isFullScreen); - toggleElem(footerEl, !this.isFullScreen); - // move .action-view-right to new parent - if (this.isFullScreen) { - outerEl.append(fullScreenEl); - } else { - actionBodyEl.append(fullScreenEl); - } + toggleFullScreen('.action-view-right', this.isFullScreen, '.action-view-body'); }, async hashChangeListener() { const selectedLogStep = window.location.hash; if (!selectedLogStep) return; const [_, step, _line] = selectedLogStep.split('-'); - if (!this.currentJobStepsStates[step]) return; - if (!this.currentJobStepsStates[step].expanded && this.currentJobStepsStates[step].cursor === null) { - this.currentJobStepsStates[step].expanded = true; + const stepNum = Number(step); + if (!this.currentJobStepsStates[stepNum]) return; + if (!this.currentJobStepsStates[stepNum].expanded && this.currentJobStepsStates[stepNum].cursor === null) { + this.currentJobStepsStates[stepNum].expanded = true; // need to await for load job if the step log is loaded for the first time // so logline can be selected by querySelector await this.loadJob(); } - const logLine = this.$refs.steps.querySelector(selectedLogStep); + const logLine = (this.$refs.steps as HTMLElement).querySelector(selectedLogStep); if (!logLine) return; - logLine.querySelector('.line-num').click(); + logLine.querySelector<HTMLAnchorElement>('.line-num').click(); }, }, -}; - -export default sfc; - -export function initRepositoryActionView() { - const el = document.querySelector('#repo-action-view'); - if (!el) return; - - // TODO: the parent element's full height doesn't work well now, - // but we can not pollute the global style at the moment, only fix the height problem for pages with this component - const parentFullHeight = document.querySelector<HTMLElement>('body > div.full.height'); - if (parentFullHeight) parentFullHeight.style.paddingBottom = '0'; - - const view = createApp(sfc, { - runIndex: el.getAttribute('data-run-index'), - jobIndex: el.getAttribute('data-job-index'), - actionsURL: el.getAttribute('data-actions-url'), - locale: { - approve: el.getAttribute('data-locale-approve'), - cancel: el.getAttribute('data-locale-cancel'), - rerun: el.getAttribute('data-locale-rerun'), - rerun_all: el.getAttribute('data-locale-rerun-all'), - scheduled: el.getAttribute('data-locale-runs-scheduled'), - commit: el.getAttribute('data-locale-runs-commit'), - pushedBy: el.getAttribute('data-locale-runs-pushed-by'), - artifactsTitle: el.getAttribute('data-locale-artifacts-title'), - areYouSure: el.getAttribute('data-locale-are-you-sure'), - confirmDeleteArtifact: el.getAttribute('data-locale-confirm-delete-artifact'), - showTimeStamps: el.getAttribute('data-locale-show-timestamps'), - showLogSeconds: el.getAttribute('data-locale-show-log-seconds'), - showFullScreen: el.getAttribute('data-locale-show-full-screen'), - downloadLogs: el.getAttribute('data-locale-download-logs'), - status: { - unknown: el.getAttribute('data-locale-status-unknown'), - waiting: el.getAttribute('data-locale-status-waiting'), - running: el.getAttribute('data-locale-status-running'), - success: el.getAttribute('data-locale-status-success'), - failure: el.getAttribute('data-locale-status-failure'), - cancelled: el.getAttribute('data-locale-status-cancelled'), - skipped: el.getAttribute('data-locale-status-skipped'), - blocked: el.getAttribute('data-locale-status-blocked'), - }, - }, - }); - view.mount(el); -} +}); </script> <template> - <div class="ui container action-view-container"> + <!-- make the view container full width to make users easier to read logs --> + <div class="ui fluid container"> <div class="action-view-header"> <div class="action-info-summary"> <div class="action-info-summary-title"> @@ -471,13 +479,13 @@ export function initRepositoryActionView() { <div class="action-view-left"> <div class="job-group-section"> <div class="job-brief-list"> - <a class="job-brief-item" :href="run.link+'/jobs/'+index" :class="parseInt(jobIndex) === index ? 'selected' : ''" v-for="(job, index) in run.jobs" :key="job.id" @mouseenter="onHoverRerunIndex = job.id" @mouseleave="onHoverRerunIndex = -1"> + <a class="job-brief-item" :href="run.link+'/jobs/'+index" :class="parseInt(jobIndex) === index ? 'selected' : ''" v-for="(job, index) in run.jobs" :key="job.id"> <div class="job-brief-item-left"> <ActionRunStatus :locale-status="locale.status[job.status]" :status="job.status"/> <span class="job-brief-name tw-mx-2 gt-ellipsis">{{ job.name }}</span> </div> <span class="job-brief-item-right"> - <SvgIcon name="octicon-sync" role="button" :data-tooltip-content="locale.rerun" class="job-brief-rerun tw-mx-2 link-action" :data-url="`${run.link}/jobs/${index}/rerun`" v-if="job.canRerun && onHoverRerunIndex === job.id"/> + <SvgIcon name="octicon-sync" role="button" :data-tooltip-content="locale.rerun" class="job-brief-rerun tw-mx-2 link-action" :data-url="`${run.link}/jobs/${index}/rerun`" v-if="job.canRerun"/> <span class="step-summary-duration">{{ job.duration }}</span> </span> </a> @@ -488,14 +496,24 @@ export function initRepositoryActionView() { {{ locale.artifactsTitle }} </div> <ul class="job-artifacts-list"> - <li class="job-artifacts-item" v-for="artifact in artifacts" :key="artifact.name"> - <a class="job-artifacts-link" target="_blank" :href="run.link+'/artifacts/'+artifact.name"> - <SvgIcon name="octicon-file" class="ui text black job-artifacts-icon"/>{{ artifact.name }} - </a> - <a v-if="run.canDeleteArtifact" @click="deleteArtifact(artifact.name)" class="job-artifacts-delete"> - <SvgIcon name="octicon-trash" class="ui text black job-artifacts-icon"/> - </a> - </li> + <template v-for="artifact in artifacts" :key="artifact.name"> + <li class="job-artifacts-item"> + <template v-if="artifact.status !== 'expired'"> + <a class="flex-text-inline" target="_blank" :href="run.link+'/artifacts/'+artifact.name"> + <SvgIcon name="octicon-file" class="text black"/> + <span class="gt-ellipsis">{{ artifact.name }}</span> + </a> + <a v-if="run.canDeleteArtifact" @click="deleteArtifact(artifact.name)"> + <SvgIcon name="octicon-trash" class="text black"/> + </a> + </template> + <span v-else class="flex-text-inline text light grey"> + <SvgIcon name="octicon-file"/> + <span class="gt-ellipsis">{{ artifact.name }}</span> + <span class="ui label text light grey tw-flex-shrink-0">{{ locale.artifactExpired }}</span> + </span> + </li> + </template> </ul> </div> </div> @@ -528,6 +546,17 @@ export function initRepositoryActionView() { <i class="icon"><SvgIcon :name="isFullScreen ? 'octicon-check' : 'gitea-empty-checkbox'"/></i> {{ locale.showFullScreen }} </a> + + <div class="divider"/> + <a class="item" @click="optionAlwaysAutoScroll = !optionAlwaysAutoScroll"> + <i class="icon"><SvgIcon :name="optionAlwaysAutoScroll ? 'octicon-check' : 'gitea-empty-checkbox'"/></i> + {{ locale.logsAlwaysAutoScroll }} + </a> + <a class="item" @click="optionAlwaysExpandRunning = !optionAlwaysExpandRunning"> + <i class="icon"><SvgIcon :name="optionAlwaysExpandRunning ? 'octicon-check' : 'gitea-empty-checkbox'"/></i> + {{ locale.logsAlwaysExpandRunning }} + </a> + <div class="divider"/> <a :class="['item', !currentJob.steps.length ? 'disabled' : '']" :href="run.link+'/jobs/'+jobIndex+'/logs'" target="_blank"> <i class="icon"><SvgIcon name="octicon-download"/></i> @@ -543,7 +572,7 @@ export function initRepositoryActionView() { <!-- If the job is done and the job step log is loaded for the first time, show the loading icon currentJobStepsStates[i].cursor === null means the log is loaded for the first time --> - <SvgIcon v-if="isDone(run.status) && currentJobStepsStates[i].expanded && currentJobStepsStates[i].cursor === null" name="octicon-sync" class="tw-mr-2 job-status-rotate"/> + <SvgIcon v-if="isDone(run.status) && currentJobStepsStates[i].expanded && currentJobStepsStates[i].cursor === null" name="octicon-sync" class="tw-mr-2 circular-spin"/> <SvgIcon v-else :name="currentJobStepsStates[i].expanded ? 'octicon-chevron-down': 'octicon-chevron-right'" :class="['tw-mr-2', !isExpandable(jobStep.status) && 'tw-invisible']"/> <ActionRunStatus :status="jobStep.status" class="tw-mr-2"/> @@ -646,6 +675,7 @@ export function initRepositoryActionView() { padding: 6px; display: flex; justify-content: space-between; + align-items: center; } .job-artifacts-list { @@ -653,10 +683,6 @@ export function initRepositoryActionView() { list-style: none; } -.job-artifacts-icon { - padding-right: 3px; -} - .job-brief-list { display: flex; flex-direction: column; @@ -689,11 +715,6 @@ export function initRepositoryActionView() { .job-brief-item .job-brief-rerun { cursor: pointer; - transition: transform 0.2s; -} - -.job-brief-item .job-brief-rerun:hover { - transform: scale(130%); } .job-brief-item .job-brief-item-left { @@ -870,16 +891,6 @@ export function initRepositoryActionView() { <style> /* eslint-disable-line vue-scoped-css/enforce-style-type */ /* some elements are not managed by vue, so we need to use global style */ -.job-status-rotate { - animation: job-status-rotate-keyframes 1s linear infinite; -} - -@keyframes job-status-rotate-keyframes { - 100% { - transform: rotate(-360deg); - } -} - .job-step-section { margin: 10px; } @@ -929,7 +940,6 @@ export function initRepositoryActionView() { .job-step-logs .job-log-line .log-msg { flex: 1; - word-break: break-all; white-space: break-spaces; margin-left: 10px; overflow-wrap: anywhere; diff --git a/web_src/js/components/RepoActivityTopAuthors.vue b/web_src/js/components/RepoActivityTopAuthors.vue index 1f5e9825ba..bbdfda41d0 100644 --- a/web_src/js/components/RepoActivityTopAuthors.vue +++ b/web_src/js/components/RepoActivityTopAuthors.vue @@ -1,20 +1,23 @@ <script lang="ts" setup> +// @ts-expect-error - module exports no types import {VueBarGraph} from 'vue-bar-graph'; -import {computed, onMounted, ref} from 'vue'; +import {computed, onMounted, shallowRef, useTemplateRef} from 'vue'; -const colors = ref({ +const colors = shallowRef({ barColor: 'green', textColor: 'black', textAltColor: 'white', }); -// possible keys: -// * avatar_link: (...) -// * commits: (...) -// * home_link: (...) -// * login: (...) -// * name: (...) -const activityTopAuthors = window.config.pageData.repoActivityTopAuthors || []; +type ActivityAuthorData = { + avatar_link: string; + commits: number; + home_link: string; + login: string; + name: string; +} + +const activityTopAuthors: Array<ActivityAuthorData> = window.config.pageData.repoActivityTopAuthors || []; const graphPoints = computed(() => { return activityTopAuthors.map((item) => { @@ -26,7 +29,7 @@ const graphPoints = computed(() => { }); const graphAuthors = computed(() => { - return activityTopAuthors.map((item, idx) => { + return activityTopAuthors.map((item, idx: number) => { return { position: idx + 1, ...item, @@ -38,8 +41,8 @@ const graphWidth = computed(() => { return activityTopAuthors.length * 40; }); -const styleElement = ref<HTMLElement | null>(null); -const altStyleElement = ref<HTMLElement | null>(null); +const styleElement = useTemplateRef('styleElement'); +const altStyleElement = useTemplateRef('altStyleElement'); onMounted(() => { const refStyle = window.getComputedStyle(styleElement.value); diff --git a/web_src/js/components/RepoBranchTagSelector.vue b/web_src/js/components/RepoBranchTagSelector.vue index a5ed8b6dad..8e3a29a0e0 100644 --- a/web_src/js/components/RepoBranchTagSelector.vue +++ b/web_src/js/components/RepoBranchTagSelector.vue @@ -1,5 +1,5 @@ <script lang="ts"> -import {nextTick} from 'vue'; +import {defineComponent, nextTick} from 'vue'; import {SvgIcon} from '../svg.ts'; import {showErrorToast} from '../modules/toast.ts'; import {GET} from '../modules/fetch.ts'; @@ -17,51 +17,11 @@ type SelectedTab = 'branches' | 'tags'; type TabLoadingStates = Record<SelectedTab, '' | 'loading' | 'done'> -const sfc = { +export default defineComponent({ components: {SvgIcon}, props: { elRoot: HTMLElement, }, - computed: { - searchFieldPlaceholder() { - return this.selectedTab === 'branches' ? this.textFilterBranch : this.textFilterTag; - }, - filteredItems(): ListItem[] { - const searchTermLower = this.searchTerm.toLowerCase(); - const items = this.allItems.filter((item: ListItem) => { - const typeMatched = (this.selectedTab === 'branches' && item.refType === 'branch') || (this.selectedTab === 'tags' && item.refType === 'tag'); - if (!typeMatched) return false; - if (!this.searchTerm) return true; // match all - return item.refShortName.toLowerCase().includes(searchTermLower); - }); - - // TODO: fix this anti-pattern: side-effects-in-computed-properties - this.activeItemIndex = !items.length && this.showCreateNewRef ? 0 : -1; - return items; - }, - showNoResults() { - if (this.tabLoadingStates[this.selectedTab] !== 'done') return false; - return !this.filteredItems.length && !this.showCreateNewRef; - }, - showCreateNewRef() { - if (!this.allowCreateNewRef || !this.searchTerm) { - return false; - } - return !this.allItems.filter((item: ListItem) => { - return item.refShortName === this.searchTerm; // FIXME: not quite right here, it mixes "branch" and "tag" names - }).length; - }, - createNewRefFormActionUrl() { - return `${this.currentRepoLink}/branches/_new/${this.currentRefType}/${pathEscapeSegments(this.currentRefShortName)}`; - }, - }, - watch: { - menuVisible(visible: boolean) { - if (!visible) return; - this.focusSearchField(); - this.loadTabItems(); - }, - }, data() { const shouldShowTabBranches = this.elRoot.getAttribute('data-show-tab-branches') === 'true'; return { @@ -89,7 +49,7 @@ const sfc = { currentRepoDefaultBranch: this.elRoot.getAttribute('data-current-repo-default-branch'), currentRepoLink: this.elRoot.getAttribute('data-current-repo-link'), currentTreePath: this.elRoot.getAttribute('data-current-tree-path'), - currentRefType: this.elRoot.getAttribute('data-current-ref-type'), + currentRefType: this.elRoot.getAttribute('data-current-ref-type') as GitRefType, currentRefShortName: this.elRoot.getAttribute('data-current-ref-short-name'), refLinkTemplate: this.elRoot.getAttribute('data-ref-link-template'), @@ -102,6 +62,46 @@ const sfc = { enableFeed: this.elRoot.getAttribute('data-enable-feed') === 'true', }; }, + computed: { + searchFieldPlaceholder() { + return this.selectedTab === 'branches' ? this.textFilterBranch : this.textFilterTag; + }, + filteredItems(): ListItem[] { + const searchTermLower = this.searchTerm.toLowerCase(); + const items = this.allItems.filter((item: ListItem) => { + const typeMatched = (this.selectedTab === 'branches' && item.refType === 'branch') || (this.selectedTab === 'tags' && item.refType === 'tag'); + if (!typeMatched) return false; + if (!this.searchTerm) return true; // match all + return item.refShortName.toLowerCase().includes(searchTermLower); + }); + + // TODO: fix this anti-pattern: side-effects-in-computed-properties + this.activeItemIndex = !items.length && this.showCreateNewRef ? 0 : -1; // eslint-disable-line vue/no-side-effects-in-computed-properties + return items; + }, + showNoResults() { + if (this.tabLoadingStates[this.selectedTab] !== 'done') return false; + return !this.filteredItems.length && !this.showCreateNewRef; + }, + showCreateNewRef() { + if (!this.allowCreateNewRef || !this.searchTerm) { + return false; + } + return !this.allItems.filter((item: ListItem) => { + return item.refShortName === this.searchTerm; // FIXME: not quite right here, it mixes "branch" and "tag" names + }).length; + }, + createNewRefFormActionUrl() { + return `${this.currentRepoLink}/branches/_new/${this.currentRefType}/${pathEscapeSegments(this.currentRefShortName)}`; + }, + }, + watch: { + menuVisible(visible: boolean) { + if (!visible) return; + this.focusSearchField(); + this.loadTabItems(); + }, + }, beforeMount() { document.body.addEventListener('click', (e) => { if (this.$el.contains(e.target)) return; @@ -139,11 +139,11 @@ const sfc = { } }, createNewRef() { - this.$refs.createNewRefForm?.submit(); + (this.$refs.createNewRefForm as HTMLFormElement)?.submit(); }, focusSearchField() { nextTick(() => { - this.$refs.searchField.focus(); + (this.$refs.searchField as HTMLInputElement).focus(); }); }, getSelectedIndexInFiltered() { @@ -154,9 +154,10 @@ const sfc = { }, getActiveItem() { const el = this.$refs[`listItem${this.activeItemIndex}`]; // eslint-disable-line no-jquery/variable-pattern + // @ts-expect-error - el is unknown type return (el && el.length) ? el[0] : null; }, - keydown(e) { + keydown(e: KeyboardEvent) { if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { e.preventDefault(); @@ -180,7 +181,7 @@ const sfc = { this.menuVisible = false; } }, - handleTabSwitch(selectedTab) { + handleTabSwitch(selectedTab: SelectedTab) { this.selectedTab = selectedTab; this.focusSearchField(); this.loadTabItems(); @@ -212,22 +213,21 @@ const sfc = { } }, }, -}; - -export default sfc; // activate IDE's Vue plugin +}); </script> <template> - <div class="ui dropdown custom branch-selector-dropdown ellipsis-items-nowrap"> - <div tabindex="0" class="ui button branch-dropdown-button" @click="menuVisible = !menuVisible"> + <div class="ui dropdown custom branch-selector-dropdown ellipsis-text-items"> + <div tabindex="0" class="ui compact button branch-dropdown-button" @click="menuVisible = !menuVisible"> <span class="flex-text-block gt-ellipsis"> <template v-if="dropdownFixedText">{{ dropdownFixedText }}</template> <template v-else> <svg-icon v-if="currentRefType === 'tag'" name="octicon-tag"/> - <svg-icon v-else name="octicon-git-branch"/> - <strong ref="dropdownRefName" class="tw-ml-2 tw-inline-block gt-ellipsis">{{ currentRefShortName }}</strong> + <svg-icon v-else-if="currentRefType === 'branch'" name="octicon-git-branch"/> + <svg-icon v-else name="octicon-git-commit"/> + <strong ref="dropdownRefName" class="tw-inline-block gt-ellipsis">{{ currentRefShortName }}</strong> </template> </span> - <svg-icon name="octicon-triangle-down" :size="14" class-name="dropdown icon"/> + <svg-icon name="octicon-triangle-down" :size="14" class="dropdown icon"/> </div> <div class="menu transition" :class="{visible: menuVisible}" v-show="menuVisible" v-cloak> <div class="ui icon search input"> @@ -236,10 +236,10 @@ export default sfc; // activate IDE's Vue plugin </div> <div v-if="showTabBranches" class="branch-tag-tab"> <a class="branch-tag-item muted" :class="{active: selectedTab === 'branches'}" href="#" @click="handleTabSwitch('branches')"> - <svg-icon name="octicon-git-branch" :size="16" class-name="tw-mr-1"/>{{ textBranches }} + <svg-icon name="octicon-git-branch" :size="16" class="tw-mr-1"/>{{ textBranches }} </a> <a v-if="showTabTags" class="branch-tag-item muted" :class="{active: selectedTab === 'tags'}" href="#" @click="handleTabSwitch('tags')"> - <svg-icon name="octicon-tag" :size="16" class-name="tw-mr-1"/>{{ textTags }} + <svg-icon name="octicon-tag" :size="16" class="tw-mr-1"/>{{ textTags }} </a> </div> <div class="branch-tag-divider"/> diff --git a/web_src/js/components/RepoCodeFrequency.vue b/web_src/js/components/RepoCodeFrequency.vue index 7696996cf6..f331a26fe9 100644 --- a/web_src/js/components/RepoCodeFrequency.vue +++ b/web_src/js/components/RepoCodeFrequency.vue @@ -23,7 +23,7 @@ import { import {chartJsColors} from '../utils/color.ts'; import {sleep} from '../utils.ts'; import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm'; -import {onMounted, ref} from 'vue'; +import {onMounted, shallowRef} from 'vue'; const {pageData} = window.config; @@ -47,10 +47,10 @@ defineProps<{ }; }>(); -const isLoading = ref(false); -const errorText = ref(''); -const repoLink = ref(pageData.repoLink || []); -const data = ref<DayData[]>([]); +const isLoading = shallowRef(false); +const errorText = shallowRef(''); +const repoLink = pageData.repoLink; +const data = shallowRef<DayData[]>([]); onMounted(() => { fetchGraphData(); @@ -61,7 +61,7 @@ async function fetchGraphData() { try { let response: Response; do { - response = await GET(`${repoLink.value}/activity/code-frequency/data`); + response = await GET(`${repoLink}/activity/code-frequency/data`); if (response.status === 202) { await sleep(1000); // wait for 1 second before retrying } @@ -150,7 +150,7 @@ const options: ChartOptions<'line'> = { <div class="tw-flex ui segment main-graph"> <div v-if="isLoading || errorText !== ''" class="gt-tc tw-m-auto"> <div v-if="isLoading"> - <SvgIcon name="octicon-sync" class="tw-mr-2 job-status-rotate"/> + <SvgIcon name="octicon-sync" class="tw-mr-2 circular-spin"/> {{ locale.loadingInfo }} </div> <div v-else class="text red"> diff --git a/web_src/js/components/RepoContributors.vue b/web_src/js/components/RepoContributors.vue index e79fc80d8e..754acb997d 100644 --- a/web_src/js/components/RepoContributors.vue +++ b/web_src/js/components/RepoContributors.vue @@ -1,4 +1,5 @@ <script lang="ts"> +import {defineComponent, type PropType} from 'vue'; import {SvgIcon} from '../svg.ts'; import dayjs from 'dayjs'; import { @@ -56,11 +57,11 @@ Chart.register( customEventListener, ); -export default { +export default defineComponent({ components: {ChartLine, SvgIcon}, props: { locale: { - type: Object, + type: Object as PropType<Record<string, any>>, required: true, }, repoLink: { @@ -79,16 +80,16 @@ export default { sortedContributors: {} as Record<string, any>, type: 'commits', contributorsStats: {} as Record<string, any>, - xAxisStart: null, - xAxisEnd: null, - xAxisMin: null, - xAxisMax: null, + xAxisStart: null as number | null, + xAxisEnd: null as number | null, + xAxisMin: null as number | null, + xAxisMax: null as number | null, }), mounted() { this.fetchGraphData(); fomanticQuery('#repo-contributors').dropdown({ - onChange: (val) => { + onChange: (val: string) => { this.xAxisMin = this.xAxisStart; this.xAxisMax = this.xAxisEnd; this.type = val; @@ -98,7 +99,7 @@ export default { }, methods: { sortContributors() { - const contributors = this.filterContributorWeeksByDateRange(); + const contributors: Record<string, any> = this.filterContributorWeeksByDateRange(); const criteria = `total_${this.type}`; this.sortedContributors = Object.values(contributors) .filter((contributor) => contributor[criteria] !== 0) @@ -157,7 +158,7 @@ export default { }, filterContributorWeeksByDateRange() { - const filteredData = {}; + const filteredData: Record<string, any> = {}; const data = this.contributorsStats; for (const key of Object.keys(data)) { const user = data[key]; @@ -195,7 +196,7 @@ export default { // Normally, chartjs handles this automatically, but it will resize the graph when you // zoom, pan etc. I think resizing the graph makes it harder to compare things visually. const maxValue = Math.max( - ...this.totalStats.weeks.map((o) => o[this.type]), + ...this.totalStats.weeks.map((o: Record<string, any>) => o[this.type]), ); const [coefficient, exp] = maxValue.toExponential().split('e').map(Number); if (coefficient % 1 === 0) return maxValue; @@ -207,7 +208,7 @@ export default { // for contributors' graph. If I let chartjs do this for me, it will choose different // maxY value for each contributors' graph which again makes it harder to compare. const maxValue = Math.max( - ...this.sortedContributors.map((c) => c.max_contribution_type), + ...this.sortedContributors.map((c: Record<string, any>) => c.max_contribution_type), ); const [coefficient, exp] = maxValue.toExponential().split('e').map(Number); if (coefficient % 1 === 0) return maxValue; @@ -231,8 +232,8 @@ export default { }, updateOtherCharts({chart}: {chart: Chart}, reset: boolean = false) { - const minVal = chart.options.scales.x.min; - const maxVal = chart.options.scales.x.max; + const minVal = Number(chart.options.scales.x.min); + const maxVal = Number(chart.options.scales.x.max); if (reset) { this.xAxisMin = this.xAxisStart; this.xAxisMax = this.xAxisEnd; @@ -320,7 +321,7 @@ export default { }; }, }, -}; +}); </script> <template> <div> @@ -352,12 +353,12 @@ export default { </div> <div> <!-- Contribution type --> - <div class="ui dropdown jump" id="repo-contributors"> + <div class="ui floating dropdown jump" id="repo-contributors"> <div class="ui basic compact button"> <span class="not-mobile">{{ locale.filterLabel }}</span> <strong>{{ locale.contributionType[type] }}</strong> <svg-icon name="octicon-triangle-down" :size="14"/> </div> - <div class="menu"> + <div class="left menu"> <div :class="['item', {'selected': type === 'commits'}]" data-value="commits"> {{ locale.contributionType.commits }} </div> @@ -374,7 +375,7 @@ export default { <div class="tw-flex ui segment main-graph"> <div v-if="isLoading || errorText !== ''" class="gt-tc tw-m-auto"> <div v-if="isLoading"> - <SvgIcon name="octicon-sync" class="tw-mr-2 job-status-rotate"/> + <SvgIcon name="octicon-sync" class="tw-mr-2 circular-spin"/> {{ locale.loadingInfo }} </div> <div v-else class="text red"> @@ -396,7 +397,7 @@ export default { <div class="ui top attached header tw-flex tw-flex-1"> <b class="ui right">#{{ index + 1 }}</b> <a :href="contributor.home_link"> - <img class="ui avatar tw-align-middle" height="40" width="40" :src="contributor.avatar_link" alt=""> + <img loading="lazy" class="ui avatar tw-align-middle" height="40" width="40" :src="contributor.avatar_link" alt=""> </a> <div class="tw-ml-2"> <a v-if="contributor.home_link !== ''" :href="contributor.home_link"><h4>{{ contributor.name }}</h4></a> diff --git a/web_src/js/components/RepoRecentCommits.vue b/web_src/js/components/RepoRecentCommits.vue index 10e1fdd70c..27aa27dfc3 100644 --- a/web_src/js/components/RepoRecentCommits.vue +++ b/web_src/js/components/RepoRecentCommits.vue @@ -21,7 +21,7 @@ import { import {chartJsColors} from '../utils/color.ts'; import {sleep} from '../utils.ts'; import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm'; -import {onMounted, ref} from 'vue'; +import {onMounted, ref, shallowRef} from 'vue'; const {pageData} = window.config; @@ -43,9 +43,9 @@ defineProps<{ }; }>(); -const isLoading = ref(false); -const errorText = ref(''); -const repoLink = ref(pageData.repoLink || []); +const isLoading = shallowRef(false); +const errorText = shallowRef(''); +const repoLink = pageData.repoLink; const data = ref<DayData[]>([]); onMounted(() => { @@ -57,7 +57,7 @@ async function fetchGraphData() { try { let response: Response; do { - response = await GET(`${repoLink.value}/activity/recent-commits/data`); + response = await GET(`${repoLink}/activity/recent-commits/data`); if (response.status === 202) { await sleep(1000); // wait for 1 second before retrying } @@ -128,7 +128,7 @@ const options: ChartOptions<'bar'> = { <div class="tw-flex ui segment main-graph"> <div v-if="isLoading || errorText !== ''" class="gt-tc tw-m-auto"> <div v-if="isLoading"> - <SvgIcon name="octicon-sync" class="tw-mr-2 job-status-rotate"/> + <SvgIcon name="octicon-sync" class="tw-mr-2 circular-spin"/> {{ locale.loadingInfo }} </div> <div v-else class="text red"> diff --git a/web_src/js/components/ScopedAccessTokenSelector.vue b/web_src/js/components/ScopedAccessTokenSelector.vue deleted file mode 100644 index 63214d0bf5..0000000000 --- a/web_src/js/components/ScopedAccessTokenSelector.vue +++ /dev/null @@ -1,81 +0,0 @@ -<script lang="ts" setup> -import {computed, onMounted, onUnmounted} from 'vue'; -import {hideElem, showElem} from '../utils/dom.ts'; - -const props = defineProps<{ - isAdmin: boolean; - noAccessLabel: string; - readLabel: string; - writeLabel: string; -}>(); - -const categories = computed(() => { - const categories = [ - 'activitypub', - ]; - if (props.isAdmin) { - categories.push('admin'); - } - categories.push( - 'issue', - 'misc', - 'notification', - 'organization', - 'package', - 'repository', - 'user'); - return categories; -}); - -onMounted(() => { - document.querySelector('#scoped-access-submit').addEventListener('click', onClickSubmit); -}); - -onUnmounted(() => { - document.querySelector('#scoped-access-submit').removeEventListener('click', onClickSubmit); -}); - -function onClickSubmit(e) { - e.preventDefault(); - - const warningEl = document.querySelector('#scoped-access-warning'); - // check that at least one scope has been selected - for (const el of document.querySelectorAll<HTMLInputElement>('.access-token-select')) { - if (el.value) { - // Hide the error if it was visible from previous attempt. - hideElem(warningEl); - // Submit the form. - document.querySelector<HTMLFormElement>('#scoped-access-form').submit(); - // Don't show the warning. - return; - } - } - // no scopes selected, show validation error - showElem(warningEl); -} -</script> - -<template> - <div v-for="category in categories" :key="category" class="field tw-pl-1 tw-pb-1 access-token-category"> - <label class="category-label" :for="'access-token-scope-' + category"> - {{ category }} - </label> - <div class="gitea-select"> - <select - class="ui selection access-token-select" - name="scope" - :id="'access-token-scope-' + category" - > - <option value=""> - {{ noAccessLabel }} - </option> - <option :value="'read:' + category"> - {{ readLabel }} - </option> - <option :value="'write:' + category"> - {{ writeLabel }} - </option> - </select> - </div> - </div> -</template> diff --git a/web_src/js/components/ViewFileTree.vue b/web_src/js/components/ViewFileTree.vue new file mode 100644 index 0000000000..1f90f92586 --- /dev/null +++ b/web_src/js/components/ViewFileTree.vue @@ -0,0 +1,38 @@ +<script lang="ts" setup> +import ViewFileTreeItem from './ViewFileTreeItem.vue'; +import {onMounted, useTemplateRef} from 'vue'; +import {createViewFileTreeStore} from './ViewFileTreeStore.ts'; + +const elRoot = useTemplateRef('elRoot'); + +const props = defineProps({ + repoLink: {type: String, required: true}, + treePath: {type: String, required: true}, + currentRefNameSubURL: {type: String, required: true}, +}); + +const store = createViewFileTreeStore(props); +onMounted(async () => { + store.rootFiles = await store.loadChildren('', props.treePath); + elRoot.value.closest('.is-loading')?.classList?.remove('is-loading'); + window.addEventListener('popstate', (e) => { + store.selectedItem = e.state?.treePath || ''; + if (e.state?.url) store.loadViewContent(e.state.url); + }); +}); +</script> + +<template> + <div class="view-file-tree-items" ref="elRoot"> + <ViewFileTreeItem v-for="item in store.rootFiles" :key="item.name" :item="item" :store="store"/> + </div> +</template> + +<style scoped> +.view-file-tree-items { + display: flex; + flex-direction: column; + gap: 1px; + margin-right: .5rem; +} +</style> diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue new file mode 100644 index 0000000000..5173c7eb46 --- /dev/null +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -0,0 +1,128 @@ +<script lang="ts" setup> +import {SvgIcon} from '../svg.ts'; +import {isPlainClick} from '../utils/dom.ts'; +import {shallowRef} from 'vue'; +import {type createViewFileTreeStore} from './ViewFileTreeStore.ts'; + +type Item = { + entryName: string; + entryMode: 'blob' | 'exec' | 'tree' | 'commit' | 'symlink' | 'unknown'; + entryIcon: string; + entryIconOpen: string; + fullPath: string; + submoduleUrl?: string; + children?: Item[]; +}; + +const props = defineProps<{ + item: Item, + store: ReturnType<typeof createViewFileTreeStore> +}>(); + +const store = props.store; +const isLoading = shallowRef(false); +const children = shallowRef(props.item.children); +const collapsed = shallowRef(!props.item.children); + +const doLoadChildren = async () => { + collapsed.value = !collapsed.value; + if (!collapsed.value) { + isLoading.value = true; + try { + children.value = await store.loadChildren(props.item.fullPath); + } finally { + isLoading.value = false; + } + } +}; + +const onItemClick = (e: MouseEvent) => { + // only handle the click event with page partial reloading if the user didn't press any special key + // let browsers handle special keys like "Ctrl+Click" + if (!isPlainClick(e)) return; + e.preventDefault(); + if (props.item.entryMode === 'tree') doLoadChildren(); + store.navigateTreeView(props.item.fullPath); +}; + +</script> + +<template> + <a + class="tree-item silenced" + :class="{ + 'selected': store.selectedItem === item.fullPath, + 'type-submodule': item.entryMode === 'commit', + 'type-directory': item.entryMode === 'tree', + 'type-symlink': item.entryMode === 'symlink', + 'type-file': item.entryMode === 'blob' || item.entryMode === 'exec', + }" + :title="item.entryName" + :href="store.buildTreePathWebUrl(item.fullPath)" + @click.stop="onItemClick" + > + <div v-if="item.entryMode === 'tree'" class="item-toggle"> + <SvgIcon v-if="isLoading" name="octicon-sync" class="circular-spin"/> + <SvgIcon v-else :name="collapsed ? 'octicon-chevron-right' : 'octicon-chevron-down'" @click.stop.prevent="doLoadChildren"/> + </div> + <div class="item-content"> + <!-- eslint-disable-next-line vue/no-v-html --> + <span class="tw-contents" v-html="(!collapsed && item.entryIconOpen) ? item.entryIconOpen : item.entryIcon"/> + <span class="gt-ellipsis">{{ item.entryName }}</span> + </div> + </a> + + <div v-if="children?.length" v-show="!collapsed" class="sub-items"> + <ViewFileTreeItem v-for="childItem in children" :key="childItem.entryName" :item="childItem" :store="store"/> + </div> +</template> + +<style scoped> +.sub-items { + display: flex; + flex-direction: column; + gap: 1px; + margin-left: 14px; + border-left: 1px solid var(--color-secondary); +} + +.tree-item.selected { + color: var(--color-text); + background: var(--color-active); + border-radius: 4px; +} + +.tree-item.type-directory { + user-select: none; +} + +.tree-item { + display: grid; + grid-template-columns: 16px 1fr; + grid-template-areas: "toggle content"; + gap: 0.25em; + padding: 6px; +} + +.tree-item:hover { + color: var(--color-text); + background: var(--color-hover); + border-radius: 4px; + cursor: pointer; +} + +.item-toggle { + grid-area: toggle; + display: flex; + align-items: center; +} + +.item-content { + grid-area: content; + display: flex; + align-items: center; + gap: 0.5em; + text-overflow: ellipsis; + min-width: 0; +} +</style> diff --git a/web_src/js/components/ViewFileTreeStore.ts b/web_src/js/components/ViewFileTreeStore.ts new file mode 100644 index 0000000000..e2155bd58a --- /dev/null +++ b/web_src/js/components/ViewFileTreeStore.ts @@ -0,0 +1,45 @@ +import {reactive} from 'vue'; +import {GET} from '../modules/fetch.ts'; +import {pathEscapeSegments} from '../utils/url.ts'; +import {createElementFromHTML} from '../utils/dom.ts'; +import {html} from '../utils/html.ts'; + +export function createViewFileTreeStore(props: { repoLink: string, treePath: string, currentRefNameSubURL: string}) { + const store = reactive({ + rootFiles: [], + selectedItem: props.treePath, + + async loadChildren(treePath: string, subPath: string = '') { + const response = await GET(`${props.repoLink}/tree-view/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}?sub_path=${encodeURIComponent(subPath)}`); + const json = await response.json(); + const poolSvgs = []; + for (const [svgId, svgContent] of Object.entries(json.renderedIconPool ?? {})) { + if (!document.querySelector(`.global-svg-icon-pool #${svgId}`)) poolSvgs.push(svgContent); + } + if (poolSvgs.length) { + const svgContainer = createElementFromHTML(html`<div class="global-svg-icon-pool tw-hidden"></div>`); + svgContainer.innerHTML = poolSvgs.join(''); + document.body.append(svgContainer); + } + return json.fileTreeNodes ?? null; + }, + + async loadViewContent(url: string) { + url = url.includes('?') ? url.replace('?', '?only_content=true') : `${url}?only_content=true`; + const response = await GET(url); + document.querySelector('.repo-view-content').innerHTML = await response.text(); + }, + + async navigateTreeView(treePath: string) { + const url = store.buildTreePathWebUrl(treePath); + window.history.pushState({treePath, url}, null, url); + store.selectedItem = treePath; + await store.loadViewContent(url); + }, + + buildTreePathWebUrl(treePath: string) { + return `${props.repoLink}/src/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}`; + }, + }); + return store; +} diff --git a/web_src/js/features/admin/common.ts b/web_src/js/features/admin/common.ts index 6c725a3efe..4ed5d62eee 100644 --- a/web_src/js/features/admin/common.ts +++ b/web_src/js/features/admin/common.ts @@ -1,7 +1,7 @@ -import $ from 'jquery'; import {checkAppUrl} from '../common-page.ts'; -import {hideElem, showElem, toggleElem} from '../../utils/dom.ts'; +import {hideElem, queryElems, showElem, toggleElem} from '../../utils/dom.ts'; import {POST} from '../../modules/fetch.ts'; +import {fomanticQuery} from '../../modules/fomantic/base.ts'; const {appSubUrl} = window.config; @@ -19,32 +19,47 @@ export function initAdminCommon(): void { // check whether appUrl(ROOT_URL) is correct, if not, show an error message checkAppUrl(); - // New user - if ($('.admin.new.user').length > 0 || $('.admin.edit.user').length > 0) { - document.querySelector<HTMLInputElement>('#login_type')?.addEventListener('change', function () { - if (this.value?.startsWith('0')) { - document.querySelector<HTMLInputElement>('#user_name')?.removeAttribute('disabled'); - document.querySelector<HTMLInputElement>('#login_name')?.removeAttribute('required'); - hideElem('.non-local'); - showElem('.local'); - document.querySelector<HTMLInputElement>('#user_name')?.focus(); + initAdminUser(); + initAdminAuthentication(); + initAdminNotice(); +} - if (this.getAttribute('data-password') === 'required') { - document.querySelector('#password')?.setAttribute('required', 'required'); - } - } else { - if (document.querySelector<HTMLDivElement>('.admin.edit.user')) { - document.querySelector<HTMLInputElement>('#user_name')?.setAttribute('disabled', 'disabled'); - } - document.querySelector<HTMLInputElement>('#login_name')?.setAttribute('required', 'required'); - showElem('.non-local'); - hideElem('.local'); - document.querySelector<HTMLInputElement>('#login_name')?.focus(); +function initAdminUser() { + const pageContent = document.querySelector('.page-content.admin.edit.user, .page-content.admin.new.user'); + if (!pageContent) return; - document.querySelector<HTMLInputElement>('#password')?.removeAttribute('required'); + document.querySelector<HTMLInputElement>('#login_type')?.addEventListener('change', function () { + if (this.value?.startsWith('0')) { + document.querySelector<HTMLInputElement>('#user_name')?.removeAttribute('disabled'); + document.querySelector<HTMLInputElement>('#login_name')?.removeAttribute('required'); + hideElem('.non-local'); + showElem('.local'); + document.querySelector<HTMLInputElement>('#user_name')?.focus(); + + if (this.getAttribute('data-password') === 'required') { + document.querySelector('#password')?.setAttribute('required', 'required'); } - }); - } + } else { + if (document.querySelector<HTMLDivElement>('.admin.edit.user')) { + document.querySelector<HTMLInputElement>('#user_name')?.setAttribute('disabled', 'disabled'); + } + document.querySelector<HTMLInputElement>('#login_name')?.setAttribute('required', 'required'); + showElem('.non-local'); + hideElem('.local'); + document.querySelector<HTMLInputElement>('#login_name')?.focus(); + + document.querySelector<HTMLInputElement>('#password')?.removeAttribute('required'); + } + }); +} + +function initAdminAuthentication() { + const pageContent = document.querySelector('.page-content.admin.authentication'); + if (!pageContent) return; + + const isNewPage = pageContent.classList.contains('new'); + const isEditPage = pageContent.classList.contains('edit'); + if (!isNewPage && !isEditPage) return; function onUsePagedSearchChange() { const searchPageSizeElements = document.querySelectorAll<HTMLDivElement>('.search-page-size'); @@ -90,7 +105,7 @@ export function initAdminCommon(): void { onOAuth2UseCustomURLChange(applyDefaultValues); } - function onOAuth2UseCustomURLChange(applyDefaultValues) { + function onOAuth2UseCustomURLChange(applyDefaultValues: boolean) { const provider = document.querySelector<HTMLInputElement>('#oauth2_provider').value; hideElem('.oauth2_use_custom_url_field'); for (const input of document.querySelectorAll<HTMLInputElement>('.oauth2_use_custom_url_field input[required]')) { @@ -119,9 +134,11 @@ export function initAdminCommon(): void { toggleElem(document.querySelector('#ldap-group-options'), checked); } + const elAuthType = document.querySelector<HTMLInputElement>('#auth_type'); + // New authentication - if (document.querySelector<HTMLDivElement>('.admin.new.authentication')) { - document.querySelector<HTMLInputElement>('#auth_type')?.addEventListener('change', function () { + if (isNewPage) { + const onAuthTypeChange = function () { hideElem('.ldap, .dldap, .smtp, .pam, .oauth2, .has-tls, .search-page-size, .sspi'); for (const input of document.querySelectorAll<HTMLInputElement>('.ldap input[required], .binddnrequired input[required], .dldap input[required], .smtp input[required], .pam input[required], .oauth2 input[required], .has-tls input[required], .sspi input[required]')) { @@ -130,7 +147,7 @@ export function initAdminCommon(): void { document.querySelector<HTMLDivElement>('.binddnrequired')?.classList.remove('required'); - const authType = this.value; + const authType = elAuthType.value; switch (authType) { case '2': // LDAP showElem('.ldap'); @@ -179,20 +196,23 @@ export function initAdminCommon(): void { if (authType === '2') { onUsePagedSearchChange(); } - }); - $('#auth_type').trigger('change'); + }; + elAuthType.addEventListener('change', onAuthTypeChange); + onAuthTypeChange(); + document.querySelector<HTMLInputElement>('#security_protocol')?.addEventListener('change', onSecurityProtocolChange); document.querySelector<HTMLInputElement>('#use_paged_search')?.addEventListener('change', onUsePagedSearchChange); document.querySelector<HTMLInputElement>('#oauth2_provider')?.addEventListener('change', () => onOAuth2Change(true)); document.querySelector<HTMLInputElement>('#oauth2_use_custom_url')?.addEventListener('change', () => onOAuth2UseCustomURLChange(true)); - $('.js-ldap-group-toggle').on('change', onEnableLdapGroupsChange); + + document.querySelector('.js-ldap-group-toggle').addEventListener('change', onEnableLdapGroupsChange); } // Edit authentication - if (document.querySelector<HTMLDivElement>('.admin.edit.authentication')) { - const authType = document.querySelector<HTMLInputElement>('#auth_type')?.value; + if (isEditPage) { + const authType = elAuthType.value; if (authType === '2' || authType === '5') { document.querySelector<HTMLInputElement>('#security_protocol')?.addEventListener('change', onSecurityProtocolChange); - $('.js-ldap-group-toggle').on('change', onEnableLdapGroupsChange); + document.querySelector('.js-ldap-group-toggle').addEventListener('change', onEnableLdapGroupsChange); onEnableLdapGroupsChange(); if (authType === '2') { document.querySelector<HTMLInputElement>('#use_paged_search')?.addEventListener('change', onUsePagedSearchChange); @@ -204,58 +224,63 @@ export function initAdminCommon(): void { } } - if (document.querySelector<HTMLDivElement>('.admin.authentication')) { - $('#auth_name').on('input', function () { - // appSubUrl is either empty or is a path that starts with `/` and doesn't have a trailing slash. - document.querySelector('#oauth2-callback-url').textContent = `${window.location.origin}${appSubUrl}/user/oauth2/${encodeURIComponent((this as HTMLInputElement).value)}/callback`; - }).trigger('input'); - } + const elAuthName = document.querySelector<HTMLInputElement>('#auth_name'); + const onAuthNameChange = function () { + // appSubUrl is either empty or is a path that starts with `/` and doesn't have a trailing slash. + document.querySelector('#oauth2-callback-url').textContent = `${window.location.origin}${appSubUrl}/user/oauth2/${encodeURIComponent(elAuthName.value)}/callback`; + }; + elAuthName.addEventListener('input', onAuthNameChange); + onAuthNameChange(); +} - // Notice - if (document.querySelector<HTMLDivElement>('.admin.notice')) { - const detailModal = document.querySelector<HTMLDivElement>('#detail-modal'); +function initAdminNotice() { + const pageContent = document.querySelector('.page-content.admin.notice'); + if (!pageContent) return; - // Attach view detail modals - $('.view-detail').on('click', function () { - const description = this.closest('tr').querySelector('.notice-description').textContent; - detailModal.querySelector('.content pre').textContent = description; - $(detailModal).modal('show'); - return false; - }); + const detailModal = document.querySelector<HTMLDivElement>('#detail-modal'); - // Select actions - const checkboxes = document.querySelectorAll<HTMLInputElement>('.select.table .ui.checkbox input'); + // Attach view detail modals + queryElems(pageContent, '.view-detail', (el) => el.addEventListener('click', (e) => { + e.preventDefault(); + const elNoticeDesc = el.closest('tr').querySelector('.notice-description'); + const elModalDesc = detailModal.querySelector('.content pre'); + elModalDesc.textContent = elNoticeDesc.textContent; + fomanticQuery(detailModal).modal('show'); + })); - $('.select.action').on('click', function () { - switch ($(this).data('action')) { - case 'select-all': - for (const checkbox of checkboxes) { - checkbox.checked = true; - } - break; - case 'deselect-all': - for (const checkbox of checkboxes) { - checkbox.checked = false; - } - break; - case 'inverse': - for (const checkbox of checkboxes) { - checkbox.checked = !checkbox.checked; - } - break; - } - }); - document.querySelector<HTMLButtonElement>('#delete-selection')?.addEventListener('click', async function (e) { - e.preventDefault(); - this.classList.add('is-loading', 'disabled'); - const data = new FormData(); - for (const checkbox of checkboxes) { - if (checkbox.checked) { - data.append('ids[]', checkbox.closest('.ui.checkbox').getAttribute('data-id')); + // Select actions + const checkboxes = document.querySelectorAll<HTMLInputElement>('.select.table .ui.checkbox input'); + + queryElems(pageContent, '.select.action', (el) => el.addEventListener('click', () => { + switch (el.getAttribute('data-action')) { + case 'select-all': + for (const checkbox of checkboxes) { + checkbox.checked = true; } + break; + case 'deselect-all': + for (const checkbox of checkboxes) { + checkbox.checked = false; + } + break; + case 'inverse': + for (const checkbox of checkboxes) { + checkbox.checked = !checkbox.checked; + } + break; + } + })); + + document.querySelector<HTMLButtonElement>('#delete-selection')?.addEventListener('click', async function (e) { + e.preventDefault(); + this.classList.add('is-loading', 'disabled'); + const data = new FormData(); + for (const checkbox of checkboxes) { + if (checkbox.checked) { + data.append('ids[]', checkbox.closest('.ui.checkbox').getAttribute('data-id')); } - await POST(this.getAttribute('data-link'), {data}); - window.location.href = this.getAttribute('data-redirect'); - }); - } + } + await POST(this.getAttribute('data-link'), {data}); + window.location.href = this.getAttribute('data-redirect'); + }); } diff --git a/web_src/js/features/autofocus-end.ts b/web_src/js/features/autofocus-end.ts deleted file mode 100644 index 53e475b543..0000000000 --- a/web_src/js/features/autofocus-end.ts +++ /dev/null @@ -1,6 +0,0 @@ -export function initAutoFocusEnd() { - for (const el of document.querySelectorAll<HTMLInputElement>('.js-autofocus-end')) { - el.focus(); // expects only one such element on one page. If there are many, then the last one gets the focus. - el.setSelectionRange(el.value.length, el.value.length); - } -} diff --git a/web_src/js/features/captcha.ts b/web_src/js/features/captcha.ts index 69b4aa6852..df234d0e5c 100644 --- a/web_src/js/features/captcha.ts +++ b/web_src/js/features/captcha.ts @@ -34,13 +34,18 @@ export async function initCaptcha() { break; } case 'm-captcha': { - const {default: mCaptcha} = await import(/* webpackChunkName: "mcaptcha-vanilla-glue" */'@mcaptcha/vanilla-glue'); - // @ts-expect-error + const mCaptcha = await import(/* webpackChunkName: "mcaptcha-vanilla-glue" */'@mcaptcha/vanilla-glue'); + + // FIXME: the mCaptcha code is not right, it's a miracle that the wrong code could run + // * the "vanilla-glue" has some problems with es6 module. + // * the INPUT_NAME is a "const", it should not be changed. + // * the "mCaptcha.default" is actually the "Widget". + + // @ts-expect-error TS2540: Cannot assign to 'INPUT_NAME' because it is a read-only property. mCaptcha.INPUT_NAME = 'm-captcha-response'; const instanceURL = captchaEl.getAttribute('data-instance-url'); - // @ts-expect-error - mCaptcha.default({ + new mCaptcha.default({ siteKey: { instanceUrl: new URL(instanceURL), key: siteKey, diff --git a/web_src/js/features/citation.ts b/web_src/js/features/citation.ts index fc5bb38f0a..3c9fe0afc8 100644 --- a/web_src/js/features/citation.ts +++ b/web_src/js/features/citation.ts @@ -5,9 +5,13 @@ const {pageData} = window.config; async function initInputCitationValue(citationCopyApa: HTMLButtonElement, citationCopyBibtex: HTMLButtonElement) { const [{Cite, plugins}] = await Promise.all([ + // @ts-expect-error: module exports no types import(/* webpackChunkName: "citation-js-core" */'@citation-js/core'), + // @ts-expect-error: module exports no types import(/* webpackChunkName: "citation-js-formats" */'@citation-js/plugin-software-formats'), + // @ts-expect-error: module exports no types import(/* webpackChunkName: "citation-js-bibtex" */'@citation-js/plugin-bibtex'), + // @ts-expect-error: module exports no types import(/* webpackChunkName: "citation-js-csl" */'@citation-js/plugin-csl'), ]); const {citationFileContent} = pageData; diff --git a/web_src/js/features/common-button.test.ts b/web_src/js/features/common-button.test.ts new file mode 100644 index 0000000000..f41bafbc79 --- /dev/null +++ b/web_src/js/features/common-button.test.ts @@ -0,0 +1,14 @@ +import {assignElementProperty} from './common-button.ts'; + +test('assignElementProperty', () => { + const elForm = document.createElement('form'); + assignElementProperty(elForm, 'action', '/test-link'); + expect(elForm.action).contains('/test-link'); // the DOM always returns absolute URL + assignElementProperty(elForm, 'text-content', 'dummy'); + expect(elForm.textContent).toBe('dummy'); + + const elInput = document.createElement('input'); + expect(elInput.readOnly).toBe(false); + assignElementProperty(elInput, 'read-only', 'true'); + expect(elInput.readOnly).toBe(true); +}); diff --git a/web_src/js/features/common-button.ts b/web_src/js/features/common-button.ts index acce992b90..22a7890857 100644 --- a/web_src/js/features/common-button.ts +++ b/web_src/js/features/common-button.ts @@ -1,5 +1,5 @@ import {POST} from '../modules/fetch.ts'; -import {addDelegatedEventListener, hideElem, queryElems, showElem, toggleElem} from '../utils/dom.ts'; +import {addDelegatedEventListener, hideElem, isElemVisible, showElem, toggleElem} from '../utils/dom.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; import {camelize} from 'vue'; @@ -17,7 +17,8 @@ export function initGlobalDeleteButton(): void { // Some model/form elements will be filled by `data-id` / `data-name` / `data-data-xxx` attributes. // If there is a form defined by `data-form`, then the form will be submitted as-is (without any modification). // If there is no form, then the data will be posted to `data-url`. - // TODO: it's not encouraged to use this method. `show-modal` does far better than this. + // TODO: do not use this method in new code. `show-modal` / `link-action(data-modal-confirm)` does far better than this. + // FIXME: all legacy `delete-button` should be refactored to use `show-modal` or `link-action` for (const btn of document.querySelectorAll<HTMLElement>('.delete-button')) { btn.addEventListener('click', (e) => { e.preventDefault(); @@ -42,13 +43,16 @@ export function initGlobalDeleteButton(): void { fomanticQuery(modal).modal({ closable: false, - onApprove: async () => { + onApprove: () => { // if `data-type="form"` exists, then submit the form by the selector provided by `data-form="..."` if (btn.getAttribute('data-type') === 'form') { const formSelector = btn.getAttribute('data-form'); const form = document.querySelector<HTMLFormElement>(formSelector); if (!form) throw new Error(`no form named ${formSelector} found`); + modal.classList.add('is-loading'); // the form is not in the modal, so also add loading indicator to the modal + form.classList.add('is-loading'); form.submit(); + return false; // prevent modal from closing automatically } // prepare an AJAX form by data attributes @@ -61,34 +65,36 @@ export function initGlobalDeleteButton(): void { postData.append('id', value); } } - - const response = await POST(btn.getAttribute('data-url'), {data: postData}); - if (response.ok) { - const data = await response.json(); - window.location.href = data.redirect; - } + (async () => { + const response = await POST(btn.getAttribute('data-url'), {data: postData}); + if (response.ok) { + const data = await response.json(); + window.location.href = data.redirect; + } + })(); + modal.classList.add('is-loading'); // the request is in progress, so also add loading indicator to the modal + return false; // prevent modal from closing automatically }, }).modal('show'); }); } } -function onShowPanelClick(e) { +function onShowPanelClick(el: HTMLElement, e: MouseEvent) { // a '.show-panel' element can show a panel, by `data-panel="selector"` // if it has "toggle" class, it toggles the panel - const el = e.currentTarget; e.preventDefault(); const sel = el.getAttribute('data-panel'); - if (el.classList.contains('toggle')) { - toggleElem(sel); - } else { - showElem(sel); + const elems = el.classList.contains('toggle') ? toggleElem(sel) : showElem(sel); + for (const elem of elems) { + if (isElemVisible(elem as HTMLElement)) { + elem.querySelector<HTMLElement>('[autofocus]')?.focus(); + } } } -function onHidePanelClick(e) { +function onHidePanelClick(el: HTMLElement, e: MouseEvent) { // a `.hide-panel` element can hide a panel, by `data-panel="selector"` or `data-panel-closest="selector"` - const el = e.currentTarget; e.preventDefault(); let sel = el.getAttribute('data-panel'); if (sel) { @@ -97,21 +103,35 @@ function onHidePanelClick(e) { } sel = el.getAttribute('data-panel-closest'); if (sel) { - hideElem(el.parentNode.closest(sel)); + hideElem((el.parentNode as HTMLElement).closest(sel)); return; } throw new Error('no panel to hide'); // should never happen, otherwise there is a bug in code } -function onShowModalClick(e) { +export function assignElementProperty(el: any, name: string, val: string) { + name = camelize(name); + const old = el[name]; + if (typeof old === 'boolean') { + el[name] = val === 'true'; + } else if (typeof old === 'number') { + el[name] = parseFloat(val); + } else if (typeof old === 'string') { + el[name] = val; + } else { + // in the future, we could introduce a better typing system like `data-modal-form.action:string="..."` + throw new Error(`cannot assign element property ${name} by value ${val}`); + } +} + +function onShowModalClick(el: HTMLElement, e: MouseEvent) { // A ".show-modal" button will show a modal dialog defined by its "data-modal" attribute. // Each "data-modal-{target}" attribute will be filled to target element's value or text-content. // * First, try to query '#target' // * Then, try to query '[name=target]' // * Then, try to query '.target' // * Then, try to query 'target' as HTML tag - // If there is a ".{attr}" part like "data-modal-form.action", then the form's "action" attribute will be set. - const el = e.currentTarget; + // If there is a ".{prop-name}" part like "data-modal-form.action", the "form" element's "action" property will be set, the "prop-name" will be camel-cased to "propName". e.preventDefault(); const modalSelector = el.getAttribute('data-modal'); const elModal = document.querySelector(modalSelector); @@ -124,7 +144,7 @@ function onShowModalClick(e) { } const attrTargetCombo = attrib.name.substring(modalAttrPrefix.length); - const [attrTargetName, attrTargetAttr] = attrTargetCombo.split('.'); + const [attrTargetName, attrTargetProp] = attrTargetCombo.split('.'); // try to find target by: "#target" -> "[name=target]" -> ".target" -> "<target> tag" const attrTarget = elModal.querySelector(`#${attrTargetName}`) || elModal.querySelector(`[name=${attrTargetName}]`) || @@ -135,22 +155,16 @@ function onShowModalClick(e) { continue; } - if (attrTargetAttr) { - attrTarget[camelize(attrTargetAttr)] = attrib.value; + if (attrTargetProp) { + assignElementProperty(attrTarget, attrTargetProp, attrib.value); } else if (attrTarget.matches('input, textarea')) { - attrTarget.value = attrib.value; // FIXME: add more supports like checkbox + (attrTarget as HTMLInputElement | HTMLTextAreaElement).value = attrib.value; // FIXME: add more supports like checkbox } else { attrTarget.textContent = attrib.value; // FIXME: it should be more strict here, only handle div/span/p } } - fomanticQuery(elModal).modal('setting', { - onApprove: () => { - // "form-fetch-action" can handle network errors gracefully, - // so keep the modal dialog to make users can re-submit the form if anything wrong happens. - if (elModal.querySelector('.form-fetch-action')) return false; - }, - }).modal('show'); + fomanticQuery(elModal).modal('show'); } export function initGlobalButtons(): void { @@ -159,7 +173,15 @@ export function initGlobalButtons(): void { // There are a few cancel buttons in non-modal forms, and there are some dynamically created forms (eg: the "Edit Issue Content") addDelegatedEventListener(document, 'click', 'form button.ui.cancel.button', (_ /* el */, e) => e.preventDefault()); - queryElems(document, '.show-panel', (el) => el.addEventListener('click', onShowPanelClick)); - queryElems(document, '.hide-panel', (el) => el.addEventListener('click', onHidePanelClick)); - queryElems(document, '.show-modal', (el) => el.addEventListener('click', onShowModalClick)); + // Ideally these "button" events should be handled by registerGlobalEventFunc + // Refactoring would involve too many changes, so at the moment, just use the global event listener. + addDelegatedEventListener(document, 'click', '.show-panel, .hide-panel, .show-modal', (el, e: MouseEvent) => { + if (el.classList.contains('show-panel')) { + onShowPanelClick(el, e); + } else if (el.classList.contains('hide-panel')) { + onHidePanelClick(el, e); + } else if (el.classList.contains('show-modal')) { + onShowModalClick(el, e); + } + }); } diff --git a/web_src/js/features/common-fetch-action.ts b/web_src/js/features/common-fetch-action.ts index bc72f4089a..3ca361b6e2 100644 --- a/web_src/js/features/common-fetch-action.ts +++ b/web_src/js/features/common-fetch-action.ts @@ -1,11 +1,11 @@ import {request} from '../modules/fetch.ts'; -import {showErrorToast} from '../modules/toast.ts'; -import {addDelegatedEventListener, submitEventSubmitter} from '../utils/dom.ts'; -import {confirmModal} from './comp/ConfirmModal.ts'; +import {hideToastsAll, showErrorToast} from '../modules/toast.ts'; +import {addDelegatedEventListener, createElementFromHTML, submitEventSubmitter} from '../utils/dom.ts'; +import {confirmModal, createConfirmModal} from './comp/ConfirmModal.ts'; import type {RequestOpts} from '../types.ts'; import {ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts'; -const {appSubUrl, i18n} = window.config; +const {appSubUrl} = window.config; // fetchActionDoRedirect does real redirection to bypass the browser's limitations of "location" // more details are in the backend's fetch-redirect handler @@ -23,10 +23,20 @@ function fetchActionDoRedirect(redirect: string) { } async function fetchActionDoRequest(actionElem: HTMLElement, url: string, opt: RequestOpts) { + const showErrorForResponse = (code: number, message: string) => { + showErrorToast(`Error ${code || 'request'}: ${message}`); + }; + + let respStatus = 0; + let respText = ''; try { + hideToastsAll(); const resp = await request(url, opt); - if (resp.status === 200) { - let {redirect} = await resp.json(); + respStatus = resp.status; + respText = await resp.text(); + const respJson = JSON.parse(respText); + if (respStatus === 200) { + let {redirect} = respJson; redirect = redirect || actionElem.getAttribute('data-redirect'); ignoreAreYouSure(actionElem); // ignore the areYouSure check before reloading if (redirect) { @@ -35,29 +45,32 @@ async function fetchActionDoRequest(actionElem: HTMLElement, url: string, opt: R window.location.reload(); } return; - } else if (resp.status >= 400 && resp.status < 500) { - const data = await resp.json(); + } + + if (respStatus >= 400 && respStatus < 500 && respJson?.errorMessage) { // the code was quite messy, sometimes the backend uses "err", sometimes it uses "error", and even "user_error" // but at the moment, as a new approach, we only use "errorMessage" here, backend can use JSONError() to respond. - if (data.errorMessage) { - showErrorToast(data.errorMessage, {useHtmlBody: data.renderFormat === 'html'}); - } else { - showErrorToast(`server error: ${resp.status}`); - } + showErrorToast(respJson.errorMessage, {useHtmlBody: respJson.renderFormat === 'html'}); } else { - showErrorToast(`server error: ${resp.status}`); + showErrorForResponse(respStatus, respText); } } catch (e) { - if (e.name !== 'AbortError') { - console.error('error when doRequest', e); - showErrorToast(`${i18n.network_error} ${e}`); + if (e.name === 'SyntaxError') { + showErrorForResponse(respStatus, (respText || '').substring(0, 100)); + } else if (e.name !== 'AbortError') { + console.error('fetchActionDoRequest error', e); + showErrorForResponse(respStatus, `${e}`); } } actionElem.classList.remove('is-loading', 'loading-icon-2px'); } -async function formFetchAction(formEl: HTMLFormElement, e: SubmitEvent) { +async function onFormFetchActionSubmit(formEl: HTMLFormElement, e: SubmitEvent) { e.preventDefault(); + await submitFormFetchAction(formEl, submitEventSubmitter(e)); +} + +export async function submitFormFetchAction(formEl: HTMLFormElement, formSubmitter?: HTMLElement) { if (formEl.classList.contains('is-loading')) return; formEl.classList.add('is-loading'); @@ -66,16 +79,18 @@ async function formFetchAction(formEl: HTMLFormElement, e: SubmitEvent) { } const formMethod = formEl.getAttribute('method') || 'get'; - const formActionUrl = formEl.getAttribute('action'); + const formActionUrl = formEl.getAttribute('action') || window.location.href; const formData = new FormData(formEl); - const formSubmitter = submitEventSubmitter(e); const [submitterName, submitterValue] = [formSubmitter?.getAttribute('name'), formSubmitter?.getAttribute('value')]; if (submitterName) { formData.append(submitterName, submitterValue || ''); } let reqUrl = formActionUrl; - const reqOpt = {method: formMethod.toUpperCase(), body: null}; + const reqOpt = { + method: formMethod.toUpperCase(), + body: null as FormData | null, + }; if (formMethod.toLowerCase() === 'get') { const params = new URLSearchParams(); for (const [key, value] of formData) { @@ -93,36 +108,52 @@ async function formFetchAction(formEl: HTMLFormElement, e: SubmitEvent) { await fetchActionDoRequest(formEl, reqUrl, reqOpt); } -async function linkAction(el: HTMLElement, e: Event) { +async function onLinkActionClick(el: HTMLElement, e: Event) { // A "link-action" can post AJAX request to its "data-url" // Then the browser is redirected to: the "redirect" in response, or "data-redirect" attribute, or current URL by reloading. - // If the "link-action" has "data-modal-confirm" attribute, a confirm modal dialog will be shown before taking action. + // If the "link-action" has "data-modal-confirm" attribute, a "confirm modal dialog" will be shown before taking action. + // Attribute "data-modal-confirm" can be a modal element by "#the-modal-id", or a string content for the modal dialog. e.preventDefault(); const url = el.getAttribute('data-url'); const doRequest = async () => { - if ('disabled' in el) el.disabled = true; // el could be A or BUTTON, but A doesn't have disabled attribute + if ('disabled' in el) el.disabled = true; // el could be A or BUTTON, but "A" doesn't have the "disabled" attribute await fetchActionDoRequest(el, url, {method: el.getAttribute('data-link-action-method') || 'POST'}); if ('disabled' in el) el.disabled = false; }; - const modalConfirmContent = el.getAttribute('data-modal-confirm') || - el.getAttribute('data-modal-confirm-content') || ''; - if (!modalConfirmContent) { + let elModal: HTMLElement | null = null; + const dataModalConfirm = el.getAttribute('data-modal-confirm') || ''; + if (dataModalConfirm.startsWith('#')) { + // eslint-disable-next-line unicorn/prefer-query-selector + elModal = document.getElementById(dataModalConfirm.substring(1)); + if (elModal) { + elModal = createElementFromHTML(elModal.outerHTML); + elModal.removeAttribute('id'); + } + } + if (!elModal) { + const modalConfirmContent = dataModalConfirm || el.getAttribute('data-modal-confirm-content') || ''; + if (modalConfirmContent) { + const isRisky = el.classList.contains('red') || el.classList.contains('negative'); + elModal = createConfirmModal({ + header: el.getAttribute('data-modal-confirm-header') || '', + content: modalConfirmContent, + confirmButtonColor: isRisky ? 'red' : 'primary', + }); + } + } + + if (!elModal) { await doRequest(); return; } - const isRisky = el.classList.contains('red') || el.classList.contains('negative'); - if (await confirmModal({ - header: el.getAttribute('data-modal-confirm-header') || '', - content: modalConfirmContent, - confirmButtonColor: isRisky ? 'red' : 'primary', - })) { + if (await confirmModal(elModal)) { await doRequest(); } } export function initGlobalFetchAction() { - addDelegatedEventListener(document, 'submit', '.form-fetch-action', formFetchAction); - addDelegatedEventListener(document, 'click', '.link-action', linkAction); + addDelegatedEventListener(document, 'submit', '.form-fetch-action', onFormFetchActionSubmit); + addDelegatedEventListener(document, 'click', '.link-action', onLinkActionClick); } diff --git a/web_src/js/features/common-form.ts b/web_src/js/features/common-form.ts index 8532d397cd..7321d80c44 100644 --- a/web_src/js/features/common-form.ts +++ b/web_src/js/features/common-form.ts @@ -17,13 +17,13 @@ export function initGlobalEnterQuickSubmit() { if (e.key !== 'Enter') return; const hasCtrlOrMeta = ((e.ctrlKey || e.metaKey) && !e.altKey); if (hasCtrlOrMeta && e.target.matches('textarea')) { - if (handleGlobalEnterQuickSubmit(e.target)) { + if (handleGlobalEnterQuickSubmit(e.target as HTMLElement)) { e.preventDefault(); } } else if (e.target.matches('input') && !e.target.closest('form')) { // input in a normal form could handle Enter key by default, so we only handle the input outside a form // eslint-disable-next-line unicorn/no-lonely-if - if (handleGlobalEnterQuickSubmit(e.target)) { + if (handleGlobalEnterQuickSubmit(e.target as HTMLElement)) { e.preventDefault(); } } diff --git a/web_src/js/features/common-issue-list.ts b/web_src/js/features/common-issue-list.ts index e207364794..037529bd10 100644 --- a/web_src/js/features/common-issue-list.ts +++ b/web_src/js/features/common-issue-list.ts @@ -1,4 +1,4 @@ -import {isElemHidden, onInputDebounce, submitEventSubmitter, toggleElem} from '../utils/dom.ts'; +import {isElemVisible, onInputDebounce, submitEventSubmitter, toggleElem} from '../utils/dom.ts'; import {GET} from '../modules/fetch.ts'; const {appSubUrl} = window.config; @@ -28,7 +28,7 @@ export function parseIssueListQuickGotoLink(repoLink: string, searchText: string } export function initCommonIssueListQuickGoto() { - const goto = document.querySelector('#issue-list-quick-goto'); + const goto = document.querySelector<HTMLElement>('#issue-list-quick-goto'); if (!goto) return; const form = goto.closest('form'); @@ -37,7 +37,7 @@ export function initCommonIssueListQuickGoto() { form.addEventListener('submit', (e) => { // if there is no goto button, or the form is submitted by non-quick-goto elements, submit the form directly - let doQuickGoto = !isElemHidden(goto); + let doQuickGoto = isElemVisible(goto); const submitter = submitEventSubmitter(e); if (submitter !== form && submitter !== input && submitter !== goto) doQuickGoto = false; if (!doQuickGoto) return; diff --git a/web_src/js/features/common-organization.ts b/web_src/js/features/common-organization.ts index 47a61ece22..a1f19bedea 100644 --- a/web_src/js/features/common-organization.ts +++ b/web_src/js/features/common-organization.ts @@ -6,7 +6,7 @@ export function initCommonOrganization() { return; } - document.querySelector('.organization.settings.options #org_name')?.addEventListener('input', function () { + document.querySelector<HTMLInputElement>('.organization.settings.options #org_name')?.addEventListener('input', function () { const nameChanged = this.value.toLowerCase() !== this.getAttribute('data-org-name').toLowerCase(); toggleElem('#org-name-change-prompt', nameChanged); }); diff --git a/web_src/js/features/common-page.ts b/web_src/js/features/common-page.ts index 56c5915b6d..5a02ee7a6a 100644 --- a/web_src/js/features/common-page.ts +++ b/web_src/js/features/common-page.ts @@ -2,6 +2,8 @@ import {GET} from '../modules/fetch.ts'; import {showGlobalErrorMessage} from '../bootstrap.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; import {queryElems} from '../utils/dom.ts'; +import {registerGlobalInitFunc, registerGlobalSelectorFunc} from '../modules/observer.ts'; +import {initAvatarUploaderWithCropper} from './comp/Cropper.ts'; const {appUrl} = window.config; @@ -28,51 +30,78 @@ export function initFootLanguageMenu() { } export function initGlobalDropdown() { - // Semantic UI modules. - const $uiDropdowns = fomanticQuery('.ui.dropdown'); - // do not init "custom" dropdowns, "custom" dropdowns are managed by their own code. - $uiDropdowns.filter(':not(.custom)').dropdown({hideDividers: 'empty'}); + registerGlobalSelectorFunc('.ui.dropdown:not(.custom)', (el) => { + const $dropdown = fomanticQuery(el); + if ($dropdown.data('module-dropdown')) return; // do not re-init if other code has already initialized it. - // The "jump" means this dropdown is mainly used for "menu" purpose, - // clicking an item will jump to somewhere else or trigger an action/function. - // When a dropdown is used for non-refresh actions with tippy, - // it must have this "jump" class to hide the tippy when dropdown is closed. - $uiDropdowns.filter('.jump').dropdown('setting', { - action: 'hide', - onShow() { - // hide associated tooltip while dropdown is open - this._tippy?.hide(); - this._tippy?.disable(); - }, - onHide() { - this._tippy?.enable(); - // eslint-disable-next-line unicorn/no-this-assignment - const elDropdown = this; + $dropdown.dropdown('setting', {hideDividers: 'empty'}); - // hide all tippy elements of items after a while. eg: use Enter to click "Copy Link" in the Issue Context Menu - setTimeout(() => { - const $dropdown = fomanticQuery(elDropdown); - if ($dropdown.dropdown('is hidden')) { - queryElems(elDropdown, '.menu > .item', (el) => el._tippy?.hide()); - } - }, 2000); - }, - }); + if (el.classList.contains('jump')) { + // The "jump" means this dropdown is mainly used for "menu" purpose, + // clicking an item will jump to somewhere else or trigger an action/function. + // When a dropdown is used for non-refresh actions with tippy, + // it must have this "jump" class to hide the tippy when dropdown is closed. + $dropdown.dropdown('setting', { + action: 'hide', + onShow() { + // hide associated tooltip while dropdown is open + this._tippy?.hide(); + this._tippy?.disable(); + }, + onHide() { + this._tippy?.enable(); + // eslint-disable-next-line unicorn/no-this-assignment + const elDropdown = this; + + // hide all tippy elements of items after a while. eg: use Enter to click "Copy Link" in the Issue Context Menu + setTimeout(() => { + const $dropdown = fomanticQuery(elDropdown); + if ($dropdown.dropdown('is hidden')) { + queryElems(elDropdown, '.menu > .item', (el) => el._tippy?.hide()); + } + }, 2000); + }, + }); + } - // Special popup-directions, prevent Fomantic from guessing the popup direction. - // With default "direction: auto", if the viewport height is small, Fomantic would show the popup upward, - // if the dropdown is at the beginning of the page, then the top part would be clipped by the window view. - // eg: Issue List "Sort" dropdown - // But we can not set "direction: downward" for all dropdowns, because there is a bug in dropdown menu positioning when calculating the "left" position, - // which would make some dropdown popups slightly shift out of the right viewport edge in some cases. - // eg: the "Create New Repo" menu on the navbar. - $uiDropdowns.filter('.upward').dropdown('setting', 'direction', 'upward'); - $uiDropdowns.filter('.downward').dropdown('setting', 'direction', 'downward'); + // Special popup-directions, prevent Fomantic from guessing the popup direction. + // With default "direction: auto", if the viewport height is small, Fomantic would show the popup upward, + // if the dropdown is at the beginning of the page, then the top part would be clipped by the window view. + // eg: Issue List "Sort" dropdown + // But we can not set "direction: downward" for all dropdowns, because there is a bug in dropdown menu positioning when calculating the "left" position, + // which would make some dropdown popups slightly shift out of the right viewport edge in some cases. + // eg: the "Create New Repo" menu on the navbar. + if (el.classList.contains('upward')) $dropdown.dropdown('setting', 'direction', 'upward'); + if (el.classList.contains('downward')) $dropdown.dropdown('setting', 'direction', 'downward'); + }); } export function initGlobalTabularMenu() { - fomanticQuery('.ui.menu.tabular:not(.custom) .item').tab({autoTabActivation: false}); + fomanticQuery('.ui.menu.tabular:not(.custom) .item').tab(); +} + +export function initGlobalAvatarUploader() { + registerGlobalInitFunc('initAvatarUploader', initAvatarUploaderWithCropper); +} + +// for performance considerations, it only uses performant syntax +function attachInputDirAuto(el: Partial<HTMLInputElement | HTMLTextAreaElement>) { + if (el.type !== 'hidden' && + el.type !== 'checkbox' && + el.type !== 'radio' && + el.type !== 'range' && + el.type !== 'color') { + el.dir = 'auto'; + } +} + +export function initGlobalInput() { + registerGlobalSelectorFunc('input, textarea', attachInputDirAuto); + registerGlobalInitFunc('initInputAutoFocusEnd', (el: HTMLInputElement) => { + el.focus(); // expects only one such element on one page. If there are many, then the last one gets the focus. + el.setSelectionRange(el.value.length, el.value.length); + }); } /** diff --git a/web_src/js/features/comp/ComboMarkdownEditor.ts b/web_src/js/features/comp/ComboMarkdownEditor.ts index bba50a1296..d3773a89c4 100644 --- a/web_src/js/features/comp/ComboMarkdownEditor.ts +++ b/web_src/js/features/comp/ComboMarkdownEditor.ts @@ -29,10 +29,10 @@ let elementIdCounter = 0; /** * validate if the given textarea is non-empty. - * @param {HTMLElement} textarea - The textarea element to be validated. + * @param {HTMLTextAreaElement} textarea - The textarea element to be validated. * @returns {boolean} returns true if validation succeeded. */ -export function validateTextareaNonEmpty(textarea) { +export function validateTextareaNonEmpty(textarea: HTMLTextAreaElement) { // When using EasyMDE, the original edit area HTML element is hidden, breaking HTML5 input validation. // The workaround (https://github.com/sparksuite/simplemde-markdown-editor/issues/324) doesn't work with contenteditable, so we just show an alert. if (!textarea.value) { @@ -49,16 +49,25 @@ export function validateTextareaNonEmpty(textarea) { return true; } +type Heights = { + minHeight?: string, + height?: string, + maxHeight?: string, +}; + type ComboMarkdownEditorOptions = { - editorHeights?: {minHeight?: string, height?: string, maxHeight?: string}, + editorHeights?: Heights, easyMDEOptions?: EasyMDE.Options, }; +type ComboMarkdownEditorTextarea = HTMLTextAreaElement & {_giteaComboMarkdownEditor: any}; +type ComboMarkdownEditorContainer = HTMLElement & {_giteaComboMarkdownEditor?: any}; + export class ComboMarkdownEditor { static EventEditorContentChanged = EventEditorContentChanged; static EventUploadStateChanged = EventUploadStateChanged; - public container : HTMLElement; + public container: HTMLElement; options: ComboMarkdownEditorOptions; @@ -70,7 +79,7 @@ export class ComboMarkdownEditor { easyMDEToolbarActions: any; easyMDEToolbarDefault: any; - textarea: HTMLTextAreaElement & {_giteaComboMarkdownEditor: any}; + textarea: ComboMarkdownEditorTextarea; textareaMarkdownToolbar: HTMLElement; textareaAutosize: any; @@ -81,7 +90,7 @@ export class ComboMarkdownEditor { previewUrl: string; previewContext: string; - constructor(container, options:ComboMarkdownEditorOptions = {}) { + constructor(container: ComboMarkdownEditorContainer, options:ComboMarkdownEditorOptions = {}) { if (container._giteaComboMarkdownEditor) throw new Error('ComboMarkdownEditor already initialized'); container._giteaComboMarkdownEditor = this; this.options = options; @@ -98,7 +107,7 @@ export class ComboMarkdownEditor { await this.switchToUserPreference(); } - applyEditorHeights(el, heights) { + applyEditorHeights(el: HTMLElement, heights: Heights) { if (!heights) return; if (heights.minHeight) el.style.minHeight = heights.minHeight; if (heights.height) el.style.height = heights.height; @@ -283,7 +292,7 @@ export class ComboMarkdownEditor { ]; } - parseEasyMDEToolbar(easyMde: typeof EasyMDE, actions) { + parseEasyMDEToolbar(easyMde: typeof EasyMDE, actions: any) { this.easyMDEToolbarActions = this.easyMDEToolbarActions || easyMDEToolbarActions(easyMde, this); const processed = []; for (const action of actions) { @@ -332,21 +341,21 @@ export class ComboMarkdownEditor { this.easyMDE = new EasyMDE(easyMDEOpt); this.easyMDE.codemirror.on('change', () => triggerEditorContentChanged(this.container)); this.easyMDE.codemirror.setOption('extraKeys', { - 'Cmd-Enter': (cm) => handleGlobalEnterQuickSubmit(cm.getTextArea()), - 'Ctrl-Enter': (cm) => handleGlobalEnterQuickSubmit(cm.getTextArea()), - Enter: (cm) => { + 'Cmd-Enter': (cm: any) => handleGlobalEnterQuickSubmit(cm.getTextArea()), + 'Ctrl-Enter': (cm: any) => handleGlobalEnterQuickSubmit(cm.getTextArea()), + Enter: (cm: any) => { const tributeContainer = document.querySelector<HTMLElement>('.tribute-container'); if (!tributeContainer || tributeContainer.style.display === 'none') { cm.execCommand('newlineAndIndent'); } }, - Up: (cm) => { + Up: (cm: any) => { const tributeContainer = document.querySelector<HTMLElement>('.tribute-container'); if (!tributeContainer || tributeContainer.style.display === 'none') { return cm.execCommand('goLineUp'); } }, - Down: (cm) => { + Down: (cm: any) => { const tributeContainer = document.querySelector<HTMLElement>('.tribute-container'); if (!tributeContainer || tributeContainer.style.display === 'none') { return cm.execCommand('goLineDown'); @@ -354,14 +363,14 @@ export class ComboMarkdownEditor { }, }); this.applyEditorHeights(this.container.querySelector('.CodeMirror-scroll'), this.options.editorHeights); - await attachTribute(this.easyMDE.codemirror.getInputField(), {mentions: true, emoji: true}); + await attachTribute(this.easyMDE.codemirror.getInputField()); if (this.dropzone) { initEasyMDEPaste(this.easyMDE, this.dropzone); } hideElem(this.textareaMarkdownToolbar); } - value(v = undefined) { + value(v: any = undefined) { if (v === undefined) { if (this.easyMDE) { return this.easyMDE.value(); @@ -402,7 +411,7 @@ export class ComboMarkdownEditor { } } -export function getComboMarkdownEditor(el) { +export function getComboMarkdownEditor(el: any) { if (!el) return null; if (el.length) el = el[0]; return el._giteaComboMarkdownEditor; diff --git a/web_src/js/features/comp/ConfirmModal.ts b/web_src/js/features/comp/ConfirmModal.ts index 1ce490ec2e..97a73eace6 100644 --- a/web_src/js/features/comp/ConfirmModal.ts +++ b/web_src/js/features/comp/ConfirmModal.ts @@ -1,24 +1,33 @@ import {svg} from '../../svg.ts'; -import {htmlEscape} from 'escape-goat'; +import {html, htmlRaw} from '../../utils/html.ts'; import {createElementFromHTML} from '../../utils/dom.ts'; import {fomanticQuery} from '../../modules/fomantic/base.ts'; const {i18n} = window.config; -export function confirmModal({header = '', content = '', confirmButtonColor = 'primary'} = {}): Promise<boolean> { - return new Promise((resolve) => { - const headerHtml = header ? `<div class="header">${htmlEscape(header)}</div>` : ''; - const modal = createElementFromHTML(` - <div class="ui g-modal-confirm modal"> - ${headerHtml} - <div class="content">${htmlEscape(content)}</div> - <div class="actions"> - <button class="ui cancel button">${svg('octicon-x')} ${htmlEscape(i18n.modal_cancel)}</button> - <button class="ui ${confirmButtonColor} ok button">${svg('octicon-check')} ${htmlEscape(i18n.modal_confirm)}</button> - </div> +type ConfirmModalOptions = { + header?: string; + content?: string; + confirmButtonColor?: 'primary' | 'red' | 'green' | 'blue'; +} + +export function createConfirmModal({header = '', content = '', confirmButtonColor = 'primary'}:ConfirmModalOptions = {}): HTMLElement { + const headerHtml = header ? html`<div class="header">${header}</div>` : ''; + return createElementFromHTML(html` + <div class="ui g-modal-confirm modal"> + ${htmlRaw(headerHtml)} + <div class="content">${content}</div> + <div class="actions"> + <button class="ui cancel button">${htmlRaw(svg('octicon-x'))} ${i18n.modal_cancel}</button> + <button class="ui ${confirmButtonColor} ok button">${htmlRaw(svg('octicon-check'))} ${i18n.modal_confirm}</button> </div> - `); - document.body.append(modal); + </div> + `.trim()); +} + +export function confirmModal(modal: HTMLElement | ConfirmModalOptions): Promise<boolean> { + if (!(modal instanceof HTMLElement)) modal = createConfirmModal(modal); + return new Promise((resolve) => { const $modal = fomanticQuery(modal); $modal.modal({ onApprove() { diff --git a/web_src/js/features/comp/Cropper.ts b/web_src/js/features/comp/Cropper.ts index e65dcfbe13..aaa1691152 100644 --- a/web_src/js/features/comp/Cropper.ts +++ b/web_src/js/features/comp/Cropper.ts @@ -6,7 +6,7 @@ type CropperOpts = { fileInput: HTMLInputElement, } -export async function initCompCropper({container, fileInput, imageSource}: CropperOpts) { +async function initCompCropper({container, fileInput, imageSource}: CropperOpts) { const {default: Cropper} = await import(/* webpackChunkName: "cropperjs" */'cropperjs'); let currentFileName = ''; let currentFileLastModified = 0; @@ -38,3 +38,10 @@ export async function initCompCropper({container, fileInput, imageSource}: Cropp } }); } + +export async function initAvatarUploaderWithCropper(fileInput: HTMLInputElement) { + const panel = fileInput.nextElementSibling as HTMLElement; + if (!panel?.matches('.cropper-panel')) throw new Error('Missing cropper panel for avatar uploader'); + const imageSource = panel.querySelector<HTMLImageElement>('.cropper-source'); + await initCompCropper({container: panel, fileInput, imageSource}); +} diff --git a/web_src/js/features/comp/EditorMarkdown.test.ts b/web_src/js/features/comp/EditorMarkdown.test.ts index acd496bed6..9f34d77348 100644 --- a/web_src/js/features/comp/EditorMarkdown.test.ts +++ b/web_src/js/features/comp/EditorMarkdown.test.ts @@ -1,16 +1,189 @@ -import {initTextareaMarkdown} from './EditorMarkdown.ts'; +import {initTextareaMarkdown, markdownHandleIndention, textareaSplitLines} from './EditorMarkdown.ts'; + +test('textareaSplitLines', () => { + let ret = textareaSplitLines('a\nbc\nd', 0); + expect(ret).toEqual({lines: ['a', 'bc', 'd'], lengthBeforePosLine: 0, posLineIndex: 0, inlinePos: 0}); + + ret = textareaSplitLines('a\nbc\nd', 1); + expect(ret).toEqual({lines: ['a', 'bc', 'd'], lengthBeforePosLine: 0, posLineIndex: 0, inlinePos: 1}); + + ret = textareaSplitLines('a\nbc\nd', 2); + expect(ret).toEqual({lines: ['a', 'bc', 'd'], lengthBeforePosLine: 2, posLineIndex: 1, inlinePos: 0}); + + ret = textareaSplitLines('a\nbc\nd', 3); + expect(ret).toEqual({lines: ['a', 'bc', 'd'], lengthBeforePosLine: 2, posLineIndex: 1, inlinePos: 1}); + + ret = textareaSplitLines('a\nbc\nd', 4); + expect(ret).toEqual({lines: ['a', 'bc', 'd'], lengthBeforePosLine: 2, posLineIndex: 1, inlinePos: 2}); + + ret = textareaSplitLines('a\nbc\nd', 5); + expect(ret).toEqual({lines: ['a', 'bc', 'd'], lengthBeforePosLine: 5, posLineIndex: 2, inlinePos: 0}); + + ret = textareaSplitLines('a\nbc\nd', 6); + expect(ret).toEqual({lines: ['a', 'bc', 'd'], lengthBeforePosLine: 5, posLineIndex: 2, inlinePos: 1}); +}); + +test('markdownHandleIndention', () => { + const testInput = (input: string, expected?: string) => { + const inputPos = input.indexOf('|'); + input = input.replace('|', ''); + const ret = markdownHandleIndention({value: input, selStart: inputPos, selEnd: inputPos}); + if (expected === null) { + expect(ret).toEqual({handled: false}); + } else { + const expectedPos = expected.indexOf('|'); + expected = expected.replace('|', ''); + expect(ret).toEqual({ + handled: true, + valueSelection: {value: expected, selStart: expectedPos, selEnd: expectedPos}, + }); + } + }; + + testInput(` + a|b +`, ` + a + |b +`); + + testInput(` +1. a +2. | +`, ` +1. a +| +`); + + testInput(` +|1. a +`, null); // let browser handle it + + testInput(` +1. a +1. b|c +`, ` +1. a +2. b +3. |c +`); + + testInput(` +2. a +2. b| + +1. x +1. y +`, ` +1. a +2. b +3. | + +1. x +1. y +`); + + testInput(` +2. a +2. b + +1. x| +1. y +`, ` +2. a +2. b + +1. x +2. | +3. y +`); + + testInput(` +1. a +2. b| +3. c +`, ` +1. a +2. b +3. | +4. c +`); + + testInput(` +1. a + 1. b + 2. b + 3. b + 4. b +1. c| +`, ` +1. a + 1. b + 2. b + 3. b + 4. b +2. c +3. | +`); + + testInput(` +1. a +2. a +3. a +4. a +5. a +6. a +7. a +8. a +9. b|c +`, ` +1. a +2. a +3. a +4. a +5. a +6. a +7. a +8. a +9. b +10. |c +`); + + // this is a special case, it's difficult to re-format the parent level at the moment, so leave it to the future + testInput(` +1. a + 2. b| +3. c +`, ` +1. a + 1. b + 2. | +3. c +`); +}); test('EditorMarkdown', () => { const textarea = document.createElement('textarea'); initTextareaMarkdown(textarea); - const testInput = (value, expected) => { - textarea.value = value; - textarea.setSelectionRange(value.length, value.length); + type ValueWithCursor = string | { + value: string; + pos: number; + } + const testInput = (input: ValueWithCursor, result: ValueWithCursor) => { + const intputValue = typeof input === 'string' ? input : input.value; + const inputPos = typeof input === 'string' ? intputValue.length : input.pos; + textarea.value = intputValue; + textarea.setSelectionRange(inputPos, inputPos); + const e = new KeyboardEvent('keydown', {key: 'Enter', cancelable: true}); textarea.dispatchEvent(e); - if (!e.defaultPrevented) textarea.value += '\n'; - expect(textarea.value).toEqual(expected); + if (!e.defaultPrevented) textarea.value += '\n'; // simulate default behavior + + const expectedValue = typeof result === 'string' ? result : result.value; + const expectedPos = typeof result === 'string' ? expectedValue.length : result.pos; + expect(textarea.value).toEqual(expectedValue); + expect(textarea.selectionStart).toEqual(expectedPos); }; testInput('-', '-\n'); @@ -18,10 +191,13 @@ test('EditorMarkdown', () => { testInput('- ', ''); testInput('1. ', ''); + testInput({value: '1. \n2. ', pos: 3}, {value: '\n2. ', pos: 0}); testInput('- x', '- x\n- '); + testInput('1. foo', '1. foo\n2. '); + testInput({value: '1. a\n2. b\n3. c', pos: 4}, {value: '1. a\n2. \n3. b\n4. c', pos: 8}); testInput('- [ ]', '- [ ]\n- '); testInput('- [ ] foo', '- [ ] foo\n- [ ] '); testInput('* [x] foo', '* [x] foo\n* [ ] '); - testInput('1. [x] foo', '1. [x] foo\n1. [ ] '); + testInput('1. [x] foo', '1. [x] foo\n2. [ ] '); }); diff --git a/web_src/js/features/comp/EditorMarkdown.ts b/web_src/js/features/comp/EditorMarkdown.ts index 2af003ccb0..6e66c15763 100644 --- a/web_src/js/features/comp/EditorMarkdown.ts +++ b/web_src/js/features/comp/EditorMarkdown.ts @@ -1,10 +1,10 @@ export const EventEditorContentChanged = 'ce-editor-content-changed'; -export function triggerEditorContentChanged(target) { +export function triggerEditorContentChanged(target: HTMLElement) { target.dispatchEvent(new CustomEvent(EventEditorContentChanged, {bubbles: true})); } -export function textareaInsertText(textarea, value) { +export function textareaInsertText(textarea: HTMLTextAreaElement, value: string) { const startPos = textarea.selectionStart; const endPos = textarea.selectionEnd; textarea.value = textarea.value.substring(0, startPos) + value + textarea.value.substring(endPos); @@ -14,7 +14,13 @@ export function textareaInsertText(textarea, value) { triggerEditorContentChanged(textarea); } -function handleIndentSelection(textarea, e) { +type TextareaValueSelection = { + value: string; + selStart: number; + selEnd: number; +} + +function handleIndentSelection(textarea: HTMLTextAreaElement, e: KeyboardEvent) { const selStart = textarea.selectionStart; const selEnd = textarea.selectionEnd; if (selEnd === selStart) return; // do not process when no selection @@ -56,57 +62,135 @@ function handleIndentSelection(textarea, e) { triggerEditorContentChanged(textarea); } -function handleNewline(textarea: HTMLTextAreaElement, e: Event) { - const selStart = textarea.selectionStart; - const selEnd = textarea.selectionEnd; - if (selEnd !== selStart) return; // do not process when there is a selection +type MarkdownHandleIndentionResult = { + handled: boolean; + valueSelection?: TextareaValueSelection; +} + +type TextLinesBuffer = { + lines: string[]; + lengthBeforePosLine: number; + posLineIndex: number; + inlinePos: number +} + +export function textareaSplitLines(value: string, pos: number): TextLinesBuffer { + const lines = value.split('\n'); + let lengthBeforePosLine = 0, inlinePos = 0, posLineIndex = 0; + for (; posLineIndex < lines.length; posLineIndex++) { + const lineLength = lines[posLineIndex].length + 1; + if (lengthBeforePosLine + lineLength > pos) { + inlinePos = pos - lengthBeforePosLine; + break; + } + lengthBeforePosLine += lineLength; + } + return {lines, lengthBeforePosLine, posLineIndex, inlinePos}; +} + +function markdownReformatListNumbers(linesBuf: TextLinesBuffer, indention: string) { + const reDeeperIndention = new RegExp(`^${indention}\\s+`); + const reSameLevel = new RegExp(`^${indention}([0-9]+)\\.`); + let firstLineIdx: number; + for (firstLineIdx = linesBuf.posLineIndex - 1; firstLineIdx >= 0; firstLineIdx--) { + const line = linesBuf.lines[firstLineIdx]; + if (!reDeeperIndention.test(line) && !reSameLevel.test(line)) break; + } + firstLineIdx++; + let num = 1; + for (let i = firstLineIdx; i < linesBuf.lines.length; i++) { + const oldLine = linesBuf.lines[i]; + const sameLevel = reSameLevel.test(oldLine); + if (!sameLevel && !reDeeperIndention.test(oldLine)) break; + if (sameLevel) { + const newLine = `${indention}${num}.${oldLine.replace(reSameLevel, '')}`; + linesBuf.lines[i] = newLine; + num++; + if (linesBuf.posLineIndex === i) { + // need to correct the cursor inline position if the line length changes + linesBuf.inlinePos += newLine.length - oldLine.length; + linesBuf.inlinePos = Math.max(0, linesBuf.inlinePos); + linesBuf.inlinePos = Math.min(newLine.length, linesBuf.inlinePos); + } + } + } + recalculateLengthBeforeLine(linesBuf); +} + +function recalculateLengthBeforeLine(linesBuf: TextLinesBuffer) { + linesBuf.lengthBeforePosLine = 0; + for (let i = 0; i < linesBuf.posLineIndex; i++) { + linesBuf.lengthBeforePosLine += linesBuf.lines[i].length + 1; + } +} - const value = textarea.value; +export function markdownHandleIndention(tvs: TextareaValueSelection): MarkdownHandleIndentionResult { + const unhandled: MarkdownHandleIndentionResult = {handled: false}; + if (tvs.selEnd !== tvs.selStart) return unhandled; // do not process when there is a selection - // find the current line - // * if selStart is 0, lastIndexOf(..., -1) is the same as lastIndexOf(..., 0) - // * if lastIndexOf reruns -1, lineStart is 0 and it is still correct. - const lineStart = value.lastIndexOf('\n', selStart - 1) + 1; - let lineEnd = value.indexOf('\n', selStart); - lineEnd = lineEnd < 0 ? value.length : lineEnd; - let line = value.slice(lineStart, lineEnd); - if (!line) return; // if the line is empty, do nothing, let the browser handle it + const linesBuf = textareaSplitLines(tvs.value, tvs.selStart); + const line = linesBuf.lines[linesBuf.posLineIndex] ?? ''; + if (!line) return unhandled; // if the line is empty, do nothing, let the browser handle it // parse the indention - const indention = /^\s*/.exec(line)[0]; - line = line.slice(indention.length); + let lineContent = line; + const indention = /^\s*/.exec(lineContent)[0]; + lineContent = lineContent.slice(indention.length); + if (linesBuf.inlinePos <= indention.length) return unhandled; // if cursor is at the indention, do nothing, let the browser handle it // parse the prefixes: "1. ", "- ", "* ", there could also be " [ ] " or " [x] " for task lists // there must be a space after the prefix because none of "1.foo" / "-foo" is a list item - const prefixMatch = /^([0-9]+\.|[-*])(\s\[([ x])\])?\s/.exec(line); + const prefixMatch = /^([0-9]+\.|[-*])(\s\[([ x])\])?\s/.exec(lineContent); let prefix = ''; if (prefixMatch) { prefix = prefixMatch[0]; - if (lineStart + prefix.length > selStart) prefix = ''; // do not add new line if cursor is at prefix + if (prefix.length > linesBuf.inlinePos) prefix = ''; // do not add new line if cursor is at prefix } - line = line.slice(prefix.length); - if (!indention && !prefix) return; // if no indention and no prefix, do nothing, let the browser handle it + lineContent = lineContent.slice(prefix.length); + if (!indention && !prefix) return unhandled; // if no indention and no prefix, do nothing, let the browser handle it - e.preventDefault(); - if (!line) { + if (!lineContent) { // clear current line if we only have i.e. '1. ' and the user presses enter again to finish creating a list - textarea.value = value.slice(0, lineStart) + value.slice(lineEnd); + linesBuf.lines[linesBuf.posLineIndex] = ''; + linesBuf.inlinePos = 0; } else { - // start a new line with the same indention and prefix + // start a new line with the same indention let newPrefix = prefix; - // a simple approach, otherwise it needs to parse the lines after the current line if (/^\d+\./.test(prefix)) newPrefix = `1. ${newPrefix.slice(newPrefix.indexOf('.') + 2)}`; newPrefix = newPrefix.replace('[x]', '[ ]'); - const newLine = `\n${indention}${newPrefix}`; - textarea.value = value.slice(0, selStart) + newLine + value.slice(selEnd); - textarea.setSelectionRange(selStart + newLine.length, selStart + newLine.length); + + const inlinePos = linesBuf.inlinePos; + linesBuf.lines[linesBuf.posLineIndex] = line.substring(0, inlinePos); + const newLineLeft = `${indention}${newPrefix}`; + const newLine = `${newLineLeft}${line.substring(inlinePos)}`; + linesBuf.lines.splice(linesBuf.posLineIndex + 1, 0, newLine); + linesBuf.posLineIndex++; + linesBuf.inlinePos = newLineLeft.length; + recalculateLengthBeforeLine(linesBuf); } + + markdownReformatListNumbers(linesBuf, indention); + const newPos = linesBuf.lengthBeforePosLine + linesBuf.inlinePos; + return {handled: true, valueSelection: {value: linesBuf.lines.join('\n'), selStart: newPos, selEnd: newPos}}; +} + +function handleNewline(textarea: HTMLTextAreaElement, e: Event) { + const ret = markdownHandleIndention({value: textarea.value, selStart: textarea.selectionStart, selEnd: textarea.selectionEnd}); + if (!ret.handled) return; + e.preventDefault(); + textarea.value = ret.valueSelection.value; + textarea.setSelectionRange(ret.valueSelection.selStart, ret.valueSelection.selEnd); triggerEditorContentChanged(textarea); } -export function initTextareaMarkdown(textarea) { +function isTextExpanderShown(textarea: HTMLElement): boolean { + return Boolean(textarea.closest('text-expander')?.querySelector('.suggestions')); +} + +export function initTextareaMarkdown(textarea: HTMLTextAreaElement) { textarea.addEventListener('keydown', (e) => { + if (isTextExpanderShown(textarea)) return; if (e.key === 'Tab' && !e.ctrlKey && !e.metaKey && !e.altKey) { // use Tab/Shift-Tab to indent/unindent the selected lines handleIndentSelection(textarea, e); diff --git a/web_src/js/features/comp/EditorUpload.test.ts b/web_src/js/features/comp/EditorUpload.test.ts index 55f3f74389..e6e5f4de13 100644 --- a/web_src/js/features/comp/EditorUpload.test.ts +++ b/web_src/js/features/comp/EditorUpload.test.ts @@ -1,4 +1,4 @@ -import {removeAttachmentLinksFromMarkdown} from './EditorUpload.ts'; +import {pasteAsMarkdownLink, removeAttachmentLinksFromMarkdown} from './EditorUpload.ts'; test('removeAttachmentLinksFromMarkdown', () => { expect(removeAttachmentLinksFromMarkdown('a foo b', 'foo')).toBe('a foo b'); @@ -12,3 +12,13 @@ test('removeAttachmentLinksFromMarkdown', () => { expect(removeAttachmentLinksFromMarkdown('a <img src="/attachments/foo"> b', 'foo')).toBe('a b'); expect(removeAttachmentLinksFromMarkdown('a <img src="/attachments/foo" width="100"/> b', 'foo')).toBe('a b'); }); + +test('preparePasteAsMarkdownLink', () => { + expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 0}, 'bar')).toBeNull(); + expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 0}, 'https://gitea.com')).toBeNull(); + expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 3}, 'bar')).toBeNull(); + expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 3}, 'https://gitea.com')).toBe('[foo](https://gitea.com)'); + expect(pasteAsMarkdownLink({value: '..(url)', selectionStart: 3, selectionEnd: 6}, 'https://gitea.com')).toBe('[url](https://gitea.com)'); + expect(pasteAsMarkdownLink({value: '[](url)', selectionStart: 3, selectionEnd: 6}, 'https://gitea.com')).toBeNull(); + expect(pasteAsMarkdownLink({value: 'https://example.com', selectionStart: 0, selectionEnd: 19}, 'https://gitea.com')).toBeNull(); +}); diff --git a/web_src/js/features/comp/EditorUpload.ts b/web_src/js/features/comp/EditorUpload.ts index 89982747ea..bf78f58daf 100644 --- a/web_src/js/features/comp/EditorUpload.ts +++ b/web_src/js/features/comp/EditorUpload.ts @@ -8,43 +8,46 @@ import { generateMarkdownLinkForAttachment, } from '../dropzone.ts'; import type CodeMirror from 'codemirror'; +import type EasyMDE from 'easymde'; +import type {DropzoneFile} from 'dropzone'; let uploadIdCounter = 0; export const EventUploadStateChanged = 'ce-upload-state-changed'; -export function triggerUploadStateChanged(target) { +export function triggerUploadStateChanged(target: HTMLElement) { target.dispatchEvent(new CustomEvent(EventUploadStateChanged, {bubbles: true})); } -function uploadFile(dropzoneEl, file) { +function uploadFile(dropzoneEl: HTMLElement, file: File) { return new Promise((resolve) => { const curUploadId = uploadIdCounter++; - file._giteaUploadId = curUploadId; + (file as any)._giteaUploadId = curUploadId; const dropzoneInst = dropzoneEl.dropzone; - const onUploadDone = ({file}) => { + const onUploadDone = ({file}: {file: any}) => { if (file._giteaUploadId === curUploadId) { dropzoneInst.off(DropzoneCustomEventUploadDone, onUploadDone); resolve(file); } }; dropzoneInst.on(DropzoneCustomEventUploadDone, onUploadDone); - dropzoneInst.handleFiles([file]); + // FIXME: this is not entirely correct because `file` does not satisfy DropzoneFile (we have abused the Dropzone for long time) + dropzoneInst.addFile(file as DropzoneFile); }); } class TextareaEditor { - editor : HTMLTextAreaElement; + editor: HTMLTextAreaElement; - constructor(editor) { + constructor(editor: HTMLTextAreaElement) { this.editor = editor; } - insertPlaceholder(value) { + insertPlaceholder(value: string) { textareaInsertText(this.editor, value); } - replacePlaceholder(oldVal, newVal) { + replacePlaceholder(oldVal: string, newVal: string) { const editor = this.editor; const startPos = editor.selectionStart; const endPos = editor.selectionEnd; @@ -65,11 +68,11 @@ class TextareaEditor { class CodeMirrorEditor { editor: CodeMirror.EditorFromTextArea; - constructor(editor) { + constructor(editor: CodeMirror.EditorFromTextArea) { this.editor = editor; } - insertPlaceholder(value) { + insertPlaceholder(value: string) { const editor = this.editor; const startPoint = editor.getCursor('start'); const endPoint = editor.getCursor('end'); @@ -80,7 +83,7 @@ class CodeMirrorEditor { triggerEditorContentChanged(editor.getTextArea()); } - replacePlaceholder(oldVal, newVal) { + replacePlaceholder(oldVal: string, newVal: string) { const editor = this.editor; const endPoint = editor.getCursor('end'); if (editor.getSelection() === oldVal) { @@ -96,7 +99,7 @@ class CodeMirrorEditor { } } -async function handleUploadFiles(editor, dropzoneEl, files, e) { +async function handleUploadFiles(editor: CodeMirrorEditor | TextareaEditor, dropzoneEl: HTMLElement, files: Array<File> | FileList, e: Event) { e.preventDefault(); for (const file of files) { const name = file.name.slice(0, file.name.lastIndexOf('.')); @@ -109,29 +112,38 @@ async function handleUploadFiles(editor, dropzoneEl, files, e) { } } -export function removeAttachmentLinksFromMarkdown(text, fileUuid) { +export function removeAttachmentLinksFromMarkdown(text: string, fileUuid: string) { text = text.replace(new RegExp(`!?\\[([^\\]]+)\\]\\(/?attachments/${fileUuid}\\)`, 'g'), ''); - text = text.replace(new RegExp(`<img[^>]+src="/?attachments/${fileUuid}"[^>]*>`, 'g'), ''); + text = text.replace(new RegExp(`[<]img[^>]+src="/?attachments/${fileUuid}"[^>]*>`, 'g'), ''); return text; } -function handleClipboardText(textarea, e, {text, isShiftDown}) { +export function pasteAsMarkdownLink(textarea: {value: string, selectionStart: number, selectionEnd: number}, pastedText: string): string | null { + const {value, selectionStart, selectionEnd} = textarea; + const selectedText = value.substring(selectionStart, selectionEnd); + const trimmedText = pastedText.trim(); + const beforeSelection = value.substring(0, selectionStart); + const afterSelection = value.substring(selectionEnd); + const isInMarkdownLink = beforeSelection.endsWith('](') && afterSelection.startsWith(')'); + const asMarkdownLink = selectedText && isUrl(trimmedText) && !isUrl(selectedText) && !isInMarkdownLink; + return asMarkdownLink ? `[${selectedText}](${trimmedText})` : null; +} + +function handleClipboardText(textarea: HTMLTextAreaElement, e: ClipboardEvent, pastedText: string, isShiftDown: boolean) { // pasting with "shift" means "paste as original content" in most applications if (isShiftDown) return; // let the browser handle it // when pasting links over selected text, turn it into [text](link) - const {value, selectionStart, selectionEnd} = textarea; - const selectedText = value.substring(selectionStart, selectionEnd); - const trimmedText = text.trim(); - if (selectedText && isUrl(trimmedText) && !isUrl(selectedText)) { + const pastedAsMarkdown = pasteAsMarkdownLink(textarea, pastedText); + if (pastedAsMarkdown) { e.preventDefault(); - replaceTextareaSelection(textarea, `[${selectedText}](${trimmedText})`); + replaceTextareaSelection(textarea, pastedAsMarkdown); } // else, let the browser handle it } // extract text and images from "paste" event -function getPastedContent(e) { +function getPastedContent(e: ClipboardEvent) { const images = []; for (const item of e.clipboardData?.items ?? []) { if (item.type?.startsWith('image/')) { @@ -142,8 +154,8 @@ function getPastedContent(e) { return {text, images}; } -export function initEasyMDEPaste(easyMDE, dropzoneEl) { - const editor = new CodeMirrorEditor(easyMDE.codemirror); +export function initEasyMDEPaste(easyMDE: EasyMDE, dropzoneEl: HTMLElement) { + const editor = new CodeMirrorEditor(easyMDE.codemirror as any); easyMDE.codemirror.on('paste', (_, e) => { const {images} = getPastedContent(e); if (!images.length) return; @@ -160,28 +172,28 @@ export function initEasyMDEPaste(easyMDE, dropzoneEl) { }); } -export function initTextareaEvents(textarea, dropzoneEl) { +export function initTextareaEvents(textarea: HTMLTextAreaElement, dropzoneEl: HTMLElement) { let isShiftDown = false; - textarea.addEventListener('keydown', (e) => { + textarea.addEventListener('keydown', (e: KeyboardEvent) => { if (e.shiftKey) isShiftDown = true; }); - textarea.addEventListener('keyup', (e) => { + textarea.addEventListener('keyup', (e: KeyboardEvent) => { if (!e.shiftKey) isShiftDown = false; }); - textarea.addEventListener('paste', (e) => { + textarea.addEventListener('paste', (e: ClipboardEvent) => { const {images, text} = getPastedContent(e); if (images.length && dropzoneEl) { handleUploadFiles(new TextareaEditor(textarea), dropzoneEl, images, e); } else if (text) { - handleClipboardText(textarea, e, {text, isShiftDown}); + handleClipboardText(textarea, e, text, isShiftDown); } }); - textarea.addEventListener('drop', (e) => { + textarea.addEventListener('drop', (e: DragEvent) => { if (!e.dataTransfer.files.length) return; if (!dropzoneEl) return; handleUploadFiles(new TextareaEditor(textarea), dropzoneEl, e.dataTransfer.files, e); }); - dropzoneEl?.dropzone.on(DropzoneCustomEventRemovedFile, ({fileUuid}) => { + dropzoneEl?.dropzone.on(DropzoneCustomEventRemovedFile, ({fileUuid}: {fileUuid: string}) => { const newText = removeAttachmentLinksFromMarkdown(textarea.value, fileUuid); if (textarea.value !== newText) textarea.value = newText; }); diff --git a/web_src/js/features/comp/LabelEdit.ts b/web_src/js/features/comp/LabelEdit.ts index 7bceb636bb..423440129c 100644 --- a/web_src/js/features/comp/LabelEdit.ts +++ b/web_src/js/features/comp/LabelEdit.ts @@ -1,5 +1,6 @@ import {toggleElem} from '../../utils/dom.ts'; import {fomanticQuery} from '../../modules/fomantic/base.ts'; +import {submitFormFetchAction} from '../common-fetch-action.ts'; function nameHasScope(name: string): boolean { return /.*[^/]\/[^/].*/.test(name); @@ -18,6 +19,8 @@ export function initCompLabelEdit(pageSelector: string) { const elExclusiveField = elModal.querySelector('.label-exclusive-input-field'); const elExclusiveInput = elModal.querySelector<HTMLInputElement>('.label-exclusive-input'); const elExclusiveWarning = elModal.querySelector('.label-exclusive-warning'); + const elExclusiveOrderField = elModal.querySelector<HTMLInputElement>('.label-exclusive-order-input-field'); + const elExclusiveOrderInput = elModal.querySelector<HTMLInputElement>('.label-exclusive-order-input'); const elIsArchivedField = elModal.querySelector('.label-is-archived-input-field'); const elIsArchivedInput = elModal.querySelector<HTMLInputElement>('.label-is-archived-input'); const elDescInput = elModal.querySelector<HTMLInputElement>('.label-desc-input'); @@ -29,6 +32,13 @@ export function initCompLabelEdit(pageSelector: string) { const showExclusiveWarning = hasScope && elExclusiveInput.checked && elModal.hasAttribute('data-need-warn-exclusive'); toggleElem(elExclusiveWarning, showExclusiveWarning); if (!hasScope) elExclusiveInput.checked = false; + toggleElem(elExclusiveOrderField, elExclusiveInput.checked); + + if (parseInt(elExclusiveOrderInput.value) <= 0) { + elExclusiveOrderInput.style.color = 'var(--color-placeholder-text) !important'; + } else { + elExclusiveOrderInput.style.color = null; + } }; const showLabelEditModal = (btn:HTMLElement) => { @@ -36,6 +46,7 @@ export function initCompLabelEdit(pageSelector: string) { const form = elModal.querySelector<HTMLFormElement>('form'); elLabelId.value = btn.getAttribute('data-label-id') || ''; elNameInput.value = btn.getAttribute('data-label-name') || ''; + elExclusiveOrderInput.value = btn.getAttribute('data-label-exclusive-order') || '0'; elIsArchivedInput.checked = btn.getAttribute('data-label-is-archived') === 'true'; elExclusiveInput.checked = btn.getAttribute('data-label-exclusive') === 'true'; elDescInput.value = btn.getAttribute('data-label-description') || ''; @@ -60,7 +71,8 @@ export function initCompLabelEdit(pageSelector: string) { form.reportValidity(); return false; } - form.submit(); + submitFormFetchAction(form); + return false; }, }).modal('show'); }; diff --git a/web_src/js/features/comp/QuickSubmit.ts b/web_src/js/features/comp/QuickSubmit.ts index 385acb319f..0a41f69132 100644 --- a/web_src/js/features/comp/QuickSubmit.ts +++ b/web_src/js/features/comp/QuickSubmit.ts @@ -1,6 +1,6 @@ import {querySingleVisibleElem} from '../../utils/dom.ts'; -export function handleGlobalEnterQuickSubmit(target) { +export function handleGlobalEnterQuickSubmit(target: HTMLElement) { let form = target.closest('form'); if (form) { if (!form.checkValidity()) { diff --git a/web_src/js/features/comp/ReactionSelector.ts b/web_src/js/features/comp/ReactionSelector.ts index e93e3b8377..bb54593f11 100644 --- a/web_src/js/features/comp/ReactionSelector.ts +++ b/web_src/js/features/comp/ReactionSelector.ts @@ -1,37 +1,31 @@ import {POST} from '../../modules/fetch.ts'; -import {fomanticQuery} from '../../modules/fomantic/base.ts'; import type {DOMEvent} from '../../utils/dom.ts'; +import {registerGlobalEventFunc} from '../../modules/observer.ts'; -export function initCompReactionSelector(parent: ParentNode = document) { - for (const container of parent.querySelectorAll<HTMLElement>('.issue-content, .diff-file-body')) { - container.addEventListener('click', async (e: DOMEvent<MouseEvent>) => { - // there are 2 places for the "reaction" buttons, one is the top-right reaction menu, one is the bottom of the comment - const target = e.target.closest('.comment-reaction-button'); - if (!target) return; - e.preventDefault(); +export function initCompReactionSelector() { + registerGlobalEventFunc('click', 'onCommentReactionButtonClick', async (target: HTMLElement, e: DOMEvent<MouseEvent>) => { + // there are 2 places for the "reaction" buttons, one is the top-right reaction menu, one is the bottom of the comment + e.preventDefault(); - if (target.classList.contains('disabled')) return; + if (target.classList.contains('disabled')) return; - const actionUrl = target.closest('[data-action-url]').getAttribute('data-action-url'); - const reactionContent = target.getAttribute('data-reaction-content'); + const actionUrl = target.closest('[data-action-url]').getAttribute('data-action-url'); + const reactionContent = target.getAttribute('data-reaction-content'); - const commentContainer = target.closest('.comment-container'); + const commentContainer = target.closest('.comment-container'); - const bottomReactions = commentContainer.querySelector('.bottom-reactions'); // may not exist if there is no reaction - const bottomReactionBtn = bottomReactions?.querySelector(`a[data-reaction-content="${CSS.escape(reactionContent)}"]`); - const hasReacted = bottomReactionBtn?.getAttribute('data-has-reacted') === 'true'; + const bottomReactions = commentContainer.querySelector('.bottom-reactions'); // may not exist if there is no reaction + const bottomReactionBtn = bottomReactions?.querySelector(`a[data-reaction-content="${CSS.escape(reactionContent)}"]`); + const hasReacted = bottomReactionBtn?.getAttribute('data-has-reacted') === 'true'; - const res = await POST(`${actionUrl}/${hasReacted ? 'unreact' : 'react'}`, { - data: new URLSearchParams({content: reactionContent}), - }); - - const data = await res.json(); - bottomReactions?.remove(); - if (data.html) { - commentContainer.insertAdjacentHTML('beforeend', data.html); - const bottomReactionsDropdowns = commentContainer.querySelectorAll('.bottom-reactions .dropdown.select-reaction'); - fomanticQuery(bottomReactionsDropdowns).dropdown(); // re-init the dropdown - } + const res = await POST(`${actionUrl}/${hasReacted ? 'unreact' : 'react'}`, { + data: new URLSearchParams({content: reactionContent}), }); - } + + const data = await res.json(); + bottomReactions?.remove(); + if (data.html) { + commentContainer.insertAdjacentHTML('beforeend', data.html); + } + }); } diff --git a/web_src/js/features/comp/SearchUserBox.ts b/web_src/js/features/comp/SearchUserBox.ts index 2e3b3f83be..4b13a2141f 100644 --- a/web_src/js/features/comp/SearchUserBox.ts +++ b/web_src/js/features/comp/SearchUserBox.ts @@ -1,4 +1,4 @@ -import {htmlEscape} from 'escape-goat'; +import {htmlEscape} from '../../utils/html.ts'; import {fomanticQuery} from '../../modules/fomantic/base.ts'; const {appSubUrl} = window.config; @@ -14,7 +14,7 @@ export function initCompSearchUserBox() { minCharacters: 2, apiSettings: { url: `${appSubUrl}/user/search_candidates?q={query}`, - onResponse(response) { + onResponse(response: any) { const resultItems = []; const searchQuery = searchUserBox.querySelector('input').value; const searchQueryUppercase = searchQuery.toUpperCase(); diff --git a/web_src/js/features/comp/TextExpander.ts b/web_src/js/features/comp/TextExpander.ts index e0c4abed75..2d79fe5029 100644 --- a/web_src/js/features/comp/TextExpander.ts +++ b/web_src/js/features/comp/TextExpander.ts @@ -1,18 +1,25 @@ import {matchEmoji, matchMention, matchIssue} from '../../utils/match.ts'; import {emojiString} from '../emoji.ts'; import {svg} from '../../svg.ts'; -import {parseIssueHref, parseIssueNewHref} from '../../utils.ts'; +import {parseIssueHref, parseRepoOwnerPathInfo} from '../../utils.ts'; import {createElementFromAttrs, createElementFromHTML} from '../../utils/dom.ts'; import {getIssueColor, getIssueIcon} from '../issue.ts'; import {debounce} from 'perfect-debounce'; +import type TextExpanderElement from '@github/text-expander-element'; +import type {TextExpanderChangeEvent, TextExpanderResult} from '@github/text-expander-element'; -const debouncedSuggestIssues = debounce((key: string, text: string) => new Promise<{matched:boolean; fragment?: HTMLElement}>(async (resolve) => { - let issuePathInfo = parseIssueHref(window.location.href); - if (!issuePathInfo.ownerName) issuePathInfo = parseIssueNewHref(window.location.href); - if (!issuePathInfo.ownerName) return resolve({matched: false}); +async function fetchIssueSuggestions(key: string, text: string): Promise<TextExpanderResult> { + const issuePathInfo = parseIssueHref(window.location.href); + if (!issuePathInfo.ownerName) { + const repoOwnerPathInfo = parseRepoOwnerPathInfo(window.location.pathname); + issuePathInfo.ownerName = repoOwnerPathInfo.ownerName; + issuePathInfo.repoName = repoOwnerPathInfo.repoName; + // then no issuePathInfo.indexString here, it is only used to exclude the current issue when "matchIssue" + } + if (!issuePathInfo.ownerName) return {matched: false}; const matches = await matchIssue(issuePathInfo.ownerName, issuePathInfo.repoName, issuePathInfo.indexString, text); - if (!matches.length) return resolve({matched: false}); + if (!matches.length) return {matched: false}; const ul = createElementFromAttrs('ul', {class: 'suggestions'}); for (const issue of matches) { @@ -24,11 +31,40 @@ const debouncedSuggestIssues = debounce((key: string, text: string) => new Promi ); ul.append(li); } - resolve({matched: true, fragment: ul}); -}), 100); + return {matched: true, fragment: ul}; +} + +export function initTextExpander(expander: TextExpanderElement) { + if (!expander) return; + + const textarea = expander.querySelector<HTMLTextAreaElement>('textarea'); -export function initTextExpander(expander) { - expander?.addEventListener('text-expander-change', ({detail: {key, provide, text}}) => { + // help to fix the text-expander "multiword+promise" bug: do not show the popup when there is no "#" before current line + const shouldShowIssueSuggestions = () => { + const posVal = textarea.value.substring(0, textarea.selectionStart); + const lineStart = posVal.lastIndexOf('\n'); + const keyStart = posVal.lastIndexOf('#'); + return keyStart > lineStart; + }; + + const debouncedIssueSuggestions = debounce(async (key: string, text: string): Promise<TextExpanderResult> => { + // https://github.com/github/text-expander-element/issues/71 + // Upstream bug: when using "multiword+promise", TextExpander will get wrong "key" position. + // To reproduce, comment out the "shouldShowIssueSuggestions" check, use the "await sleep" below, + // then use content "close #20\nclose #20\nclose #20" (3 lines), keep changing the last line `#20` part from the end (including removing the `#`) + // There will be a JS error: Uncaught (in promise) IndexSizeError: Failed to execute 'setStart' on 'Range': The offset 28 is larger than the node's length (27). + + // check the input before the request, to avoid emitting empty query to backend (still related to the upstream bug) + if (!shouldShowIssueSuggestions()) return {matched: false}; + // await sleep(Math.random() * 1000); // help to reproduce the text-expander bug + const ret = await fetchIssueSuggestions(key, text); + // check the input again to avoid text-expander using incorrect position (upstream bug) + if (!shouldShowIssueSuggestions()) return {matched: false}; + return ret; + }, 300); // to match onInputDebounce delay + + expander.addEventListener('text-expander-change', (e: TextExpanderChangeEvent) => { + const {key, text, provide} = e.detail; if (key === ':') { const matches = matchEmoji(text); if (!matches.length) return provide({matched: false}); @@ -61,6 +97,7 @@ export function initTextExpander(expander) { li.append(img); const nameSpan = document.createElement('span'); + nameSpan.classList.add('name'); nameSpan.textContent = name; li.append(nameSpan); @@ -76,10 +113,11 @@ export function initTextExpander(expander) { provide({matched: true, fragment: ul}); } else if (key === '#') { - provide(debouncedSuggestIssues(key, text)); + provide(debouncedIssueSuggestions(key, text)); } }); - expander?.addEventListener('text-expander-value', ({detail}) => { + + expander.addEventListener('text-expander-value', ({detail}: Record<string, any>) => { if (detail?.item) { // add a space after @mentions and #issue as it's likely the user wants one const suffix = ['@', '#'].includes(detail.key) ? ' ' : ''; diff --git a/web_src/js/features/comp/WebHookEditor.ts b/web_src/js/features/comp/WebHookEditor.ts index 203396af80..794b3c99ca 100644 --- a/web_src/js/features/comp/WebHookEditor.ts +++ b/web_src/js/features/comp/WebHookEditor.ts @@ -6,7 +6,7 @@ export function initCompWebHookEditor() { return; } - for (const input of document.querySelectorAll('.events.checkbox input')) { + for (const input of document.querySelectorAll<HTMLInputElement>('.events.checkbox input')) { input.addEventListener('change', function () { if (this.checked) { showElem('.events.fields'); @@ -14,7 +14,7 @@ export function initCompWebHookEditor() { }); } - for (const input of document.querySelectorAll('.non-events.checkbox input')) { + for (const input of document.querySelectorAll<HTMLInputElement>('.non-events.checkbox input')) { input.addEventListener('change', function () { if (this.checked) { hideElem('.events.fields'); @@ -34,7 +34,7 @@ export function initCompWebHookEditor() { } // Test delivery - document.querySelector('#test-delivery')?.addEventListener('click', async function () { + document.querySelector<HTMLButtonElement>('#test-delivery')?.addEventListener('click', async function () { this.classList.add('is-loading', 'disabled'); await POST(this.getAttribute('data-link')); setTimeout(() => { diff --git a/web_src/js/features/contextpopup.ts b/web_src/js/features/contextpopup.ts index 33eead8431..7477331dbe 100644 --- a/web_src/js/features/contextpopup.ts +++ b/web_src/js/features/contextpopup.ts @@ -4,11 +4,11 @@ import {parseIssueHref} from '../utils.ts'; import {createTippy} from '../modules/tippy.ts'; export function initContextPopups() { - const refIssues = document.querySelectorAll('.ref-issue'); + const refIssues = document.querySelectorAll<HTMLElement>('.ref-issue'); attachRefIssueContextPopup(refIssues); } -export function attachRefIssueContextPopup(refIssues) { +export function attachRefIssueContextPopup(refIssues: NodeListOf<HTMLElement>) { for (const refIssue of refIssues) { if (refIssue.classList.contains('ref-external-issue')) continue; diff --git a/web_src/js/features/copycontent.ts b/web_src/js/features/copycontent.ts index af867463b2..0fec2a6235 100644 --- a/web_src/js/features/copycontent.ts +++ b/web_src/js/features/copycontent.ts @@ -2,26 +2,24 @@ import {clippie} from 'clippie'; import {showTemporaryTooltip} from '../modules/tippy.ts'; import {convertImage} from '../utils.ts'; import {GET} from '../modules/fetch.ts'; +import {registerGlobalEventFunc} from '../modules/observer.ts'; const {i18n} = window.config; export function initCopyContent() { - const btn = document.querySelector('#copy-content'); - if (!btn || btn.classList.contains('disabled')) return; + registerGlobalEventFunc('click', 'onCopyContentButtonClick', async (btn: HTMLElement) => { + if (btn.classList.contains('disabled') || btn.classList.contains('is-loading')) return; + const rawFileLink = btn.getAttribute('data-raw-file-link'); - btn.addEventListener('click', async () => { - if (btn.classList.contains('is-loading')) return; - let content; - let isRasterImage = false; - const link = btn.getAttribute('data-link'); + let content, isRasterImage = false; - // when data-link is present, we perform a fetch. this is either because - // the text to copy is not in the DOM or it is an image which should be + // when "data-raw-link" is present, we perform a fetch. this is either because + // the text to copy is not in the DOM, or it is an image that should be // fetched to copy in full resolution - if (link) { + if (rawFileLink) { btn.classList.add('is-loading', 'loading-icon-2px'); try { - const res = await GET(link, {credentials: 'include', redirect: 'follow'}); + const res = await GET(rawFileLink, {credentials: 'include', redirect: 'follow'}); const contentType = res.headers.get('content-type'); if (contentType.startsWith('image/') && !contentType.startsWith('image/svg')) { @@ -40,13 +38,13 @@ export function initCopyContent() { content = Array.from(lineEls, (el) => el.textContent).join(''); } - // try copy original first, if that fails and it's an image, convert it to png + // try copy original first, if that fails, and it's an image, convert it to png const success = await clippie(content); if (success) { showTemporaryTooltip(btn, i18n.copy_success); } else { if (isRasterImage) { - const success = await clippie(await convertImage(content, 'image/png')); + const success = await clippie(await convertImage(content as Blob, 'image/png')); showTemporaryTooltip(btn, success ? i18n.copy_success : i18n.copy_error); } else { showTemporaryTooltip(btn, i18n.copy_error); diff --git a/web_src/js/features/dashboard.ts b/web_src/js/features/dashboard.ts new file mode 100644 index 0000000000..71a0df3a64 --- /dev/null +++ b/web_src/js/features/dashboard.ts @@ -0,0 +1,9 @@ +import {createApp} from 'vue'; +import DashboardRepoList from '../components/DashboardRepoList.vue'; + +export function initDashboardRepoList() { + const el = document.querySelector('#dashboard-repo-list'); + if (el) { + createApp(DashboardRepoList).mount(el); + } +} diff --git a/web_src/js/features/dropzone.ts b/web_src/js/features/dropzone.ts index 666c645230..20f7ceb6c3 100644 --- a/web_src/js/features/dropzone.ts +++ b/web_src/js/features/dropzone.ts @@ -1,21 +1,23 @@ import {svg} from '../svg.ts'; -import {htmlEscape} from 'escape-goat'; +import {html} from '../utils/html.ts'; import {clippie} from 'clippie'; import {showTemporaryTooltip} from '../modules/tippy.ts'; import {GET, POST} from '../modules/fetch.ts'; import {showErrorToast} from '../modules/toast.ts'; import {createElementFromHTML, createElementFromAttrs} from '../utils/dom.ts'; import {isImageFile, isVideoFile} from '../utils.ts'; -import type {DropzoneFile} from 'dropzone/index.js'; +import type {DropzoneFile, DropzoneOptions} from 'dropzone/index.js'; const {csrfToken, i18n} = window.config; +type CustomDropzoneFile = DropzoneFile & {uuid: string}; + // dropzone has its owner event dispatcher (emitter) export const DropzoneCustomEventReloadFiles = 'dropzone-custom-reload-files'; export const DropzoneCustomEventRemovedFile = 'dropzone-custom-removed-file'; export const DropzoneCustomEventUploadDone = 'dropzone-custom-upload-done'; -async function createDropzone(el, opts) { +async function createDropzone(el: HTMLElement, opts: DropzoneOptions) { const [{default: Dropzone}] = await Promise.all([ import(/* webpackChunkName: "dropzone" */'dropzone'), import(/* webpackChunkName: "dropzone" */'dropzone/dist/dropzone.css'), @@ -23,7 +25,7 @@ async function createDropzone(el, opts) { return new Dropzone(el, opts); } -export function generateMarkdownLinkForAttachment(file, {width, dppx}: {width?: number, dppx?: number} = {}) { +export function generateMarkdownLinkForAttachment(file: Partial<CustomDropzoneFile>, {width, dppx}: {width?: number, dppx?: number} = {}) { let fileMarkdown = `[${file.name}](/attachments/${file.uuid})`; if (isImageFile(file)) { fileMarkdown = `!${fileMarkdown}`; @@ -31,19 +33,19 @@ export function generateMarkdownLinkForAttachment(file, {width, dppx}: {width?: // Scale down images from HiDPI monitors. This uses the <img> tag because it's the only // method to change image size in Markdown that is supported by all implementations. // Make the image link relative to the repo path, then the final URL is "/sub-path/owner/repo/attachments/{uuid}" - fileMarkdown = `<img width="${Math.round(width / dppx)}" alt="${htmlEscape(file.name)}" src="attachments/${htmlEscape(file.uuid)}">`; + fileMarkdown = html`<img width="${Math.round(width / dppx)}" alt="${file.name}" src="attachments/${file.uuid}">`; } else { // Markdown always renders the image with a relative path, so the final URL is "/sub-path/owner/repo/attachments/{uuid}" // TODO: it should also use relative path for consistency, because absolute is ambiguous for "/sub-path/attachments" or "/attachments" fileMarkdown = ``; } } else if (isVideoFile(file)) { - fileMarkdown = `<video src="attachments/${htmlEscape(file.uuid)}" title="${htmlEscape(file.name)}" controls></video>`; + fileMarkdown = html`<video src="attachments/${file.uuid}" title="${file.name}" controls></video>`; } return fileMarkdown; } -function addCopyLink(file) { +function addCopyLink(file: Partial<CustomDropzoneFile>) { // Create a "Copy Link" element, to conveniently copy the image or file link as Markdown to the clipboard // The "<a>" element has a hardcoded cursor: pointer because the default is overridden by .dropzone const copyLinkEl = createElementFromHTML(` @@ -58,6 +60,8 @@ function addCopyLink(file) { file.previewTemplate.append(copyLinkEl); } +type FileUuidDict = Record<string, {submitted: boolean}>; + /** * @param {HTMLElement} dropzoneEl */ @@ -67,7 +71,7 @@ export async function initDropzone(dropzoneEl: HTMLElement) { const attachmentBaseLinkUrl = dropzoneEl.getAttribute('data-link-url'); let disableRemovedfileEvent = false; // when resetting the dropzone (removeAllFiles), disable the "removedfile" event - let fileUuidDict = {}; // to record: if a comment has been saved, then the uploaded files won't be deleted from server when clicking the Remove in the dropzone + let fileUuidDict: FileUuidDict = {}; // to record: if a comment has been saved, then the uploaded files won't be deleted from server when clicking the Remove in the dropzone const opts: Record<string, any> = { url: dropzoneEl.getAttribute('data-upload-url'), headers: {'X-Csrf-Token': csrfToken}, @@ -89,7 +93,7 @@ export async function initDropzone(dropzoneEl: HTMLElement) { // "http://localhost:3000/owner/repo/issues/[object%20Event]" // the reason is that the preview "callback(dataURL)" is assign to "img.onerror" then "thumbnail" uses the error object as the dataURL and generates '<img src="[object Event]">' const dzInst = await createDropzone(dropzoneEl, opts); - dzInst.on('success', (file: DropzoneFile & {uuid: string}, resp: any) => { + dzInst.on('success', (file: CustomDropzoneFile, resp: any) => { file.uuid = resp.uuid; fileUuidDict[file.uuid] = {submitted: false}; const input = createElementFromAttrs('input', {name: 'files', type: 'hidden', id: `dropzone-file-${resp.uuid}`, value: resp.uuid}); @@ -98,7 +102,7 @@ export async function initDropzone(dropzoneEl: HTMLElement) { dzInst.emit(DropzoneCustomEventUploadDone, {file}); }); - dzInst.on('removedfile', async (file: DropzoneFile & {uuid: string}) => { + dzInst.on('removedfile', async (file: CustomDropzoneFile) => { if (disableRemovedfileEvent) return; dzInst.emit(DropzoneCustomEventRemovedFile, {fileUuid: file.uuid}); diff --git a/web_src/js/features/emoji.ts b/web_src/js/features/emoji.ts index 933aa951c5..69afe491e2 100644 --- a/web_src/js/features/emoji.ts +++ b/web_src/js/features/emoji.ts @@ -1,4 +1,5 @@ import emojis from '../../../assets/emoji.json' with {type: 'json'}; +import {html} from '../utils/html.ts'; const {assetUrlPrefix, customEmojis} = window.config; @@ -15,24 +16,23 @@ export const emojiKeys = Object.keys(tempMap).sort((a, b) => { return a.localeCompare(b); }); -const emojiMap = {}; +const emojiMap: Record<string, string> = {}; for (const key of emojiKeys) { emojiMap[key] = tempMap[key]; } // retrieve HTML for given emoji name -export function emojiHTML(name) { +export function emojiHTML(name: string) { let inner; if (Object.hasOwn(customEmojis, name)) { - inner = `<img alt=":${name}:" src="${assetUrlPrefix}/img/emoji/${name}.png">`; + inner = html`<img alt=":${name}:" src="${assetUrlPrefix}/img/emoji/${name}.png">`; } else { inner = emojiString(name); } - - return `<span class="emoji" title=":${name}:">${inner}</span>`; + return html`<span class="emoji" title=":${name}:">${inner}</span>`; } // retrieve string for given emoji name -export function emojiString(name) { +export function emojiString(name: string) { return emojiMap[name] || `:${name}:`; } diff --git a/web_src/js/features/file-fold.ts b/web_src/js/features/file-fold.ts index 6fe068341a..74b36c0096 100644 --- a/web_src/js/features/file-fold.ts +++ b/web_src/js/features/file-fold.ts @@ -5,15 +5,15 @@ import {svg} from '../svg.ts'; // The fold arrow is the icon displayed on the upper left of the file box, especially intended for components having the 'fold-file' class. // The file content box is the box that should be hidden or shown, especially intended for components having the 'file-content' class. // -export function setFileFolding(fileContentBox, foldArrow, newFold) { +export function setFileFolding(fileContentBox: Element, foldArrow: HTMLElement, newFold: boolean) { foldArrow.innerHTML = svg(`octicon-chevron-${newFold ? 'right' : 'down'}`, 18); - fileContentBox.setAttribute('data-folded', newFold); + fileContentBox.setAttribute('data-folded', String(newFold)); if (newFold && fileContentBox.getBoundingClientRect().top < 0) { fileContentBox.scrollIntoView(); } } // Like `setFileFolding`, except that it automatically inverts the current file folding state. -export function invertFileFolding(fileContentBox, foldArrow) { +export function invertFileFolding(fileContentBox:HTMLElement, foldArrow: HTMLElement) { setFileFolding(fileContentBox, foldArrow, fileContentBox.getAttribute('data-folded') !== 'true'); } diff --git a/web_src/js/features/file-view.ts b/web_src/js/features/file-view.ts new file mode 100644 index 0000000000..d803f53c0d --- /dev/null +++ b/web_src/js/features/file-view.ts @@ -0,0 +1,76 @@ +import type {FileRenderPlugin} from '../render/plugin.ts'; +import {newRenderPlugin3DViewer} from '../render/plugins/3d-viewer.ts'; +import {newRenderPluginPdfViewer} from '../render/plugins/pdf-viewer.ts'; +import {registerGlobalInitFunc} from '../modules/observer.ts'; +import {createElementFromHTML, showElem, toggleClass} from '../utils/dom.ts'; +import {html} from '../utils/html.ts'; +import {basename} from '../utils.ts'; + +const plugins: FileRenderPlugin[] = []; + +function initPluginsOnce(): void { + if (plugins.length) return; + plugins.push(newRenderPlugin3DViewer(), newRenderPluginPdfViewer()); +} + +function findFileRenderPlugin(filename: string, mimeType: string): FileRenderPlugin | null { + return plugins.find((plugin) => plugin.canHandle(filename, mimeType)) || null; +} + +function showRenderRawFileButton(elFileView: HTMLElement, renderContainer: HTMLElement | null): void { + const toggleButtons = elFileView.querySelector('.file-view-toggle-buttons'); + showElem(toggleButtons); + const displayingRendered = Boolean(renderContainer); + toggleClass(toggleButtons.querySelectorAll('.file-view-toggle-source'), 'active', !displayingRendered); // it may not exist + toggleClass(toggleButtons.querySelector('.file-view-toggle-rendered'), 'active', displayingRendered); + // TODO: if there is only one button, hide it? +} + +async function renderRawFileToContainer(container: HTMLElement, rawFileLink: string, mimeType: string) { + const elViewRawPrompt = container.querySelector('.file-view-raw-prompt'); + if (!rawFileLink || !elViewRawPrompt) throw new Error('unexpected file view container'); + + let rendered = false, errorMsg = ''; + try { + const plugin = findFileRenderPlugin(basename(rawFileLink), mimeType); + if (plugin) { + container.classList.add('is-loading'); + container.setAttribute('data-render-name', plugin.name); // not used yet + await plugin.render(container, rawFileLink); + rendered = true; + } + } catch (e) { + errorMsg = `${e}`; + } finally { + container.classList.remove('is-loading'); + } + + if (rendered) { + elViewRawPrompt.remove(); + return; + } + + // remove all children from the container, and only show the raw file link + container.replaceChildren(elViewRawPrompt); + + if (errorMsg) { + const elErrorMessage = createElementFromHTML(html`<div class="ui error message">${errorMsg}</div>`); + elViewRawPrompt.insertAdjacentElement('afterbegin', elErrorMessage); + } +} + +export function initRepoFileView(): void { + registerGlobalInitFunc('initRepoFileView', async (elFileView: HTMLElement) => { + initPluginsOnce(); + const rawFileLink = elFileView.getAttribute('data-raw-file-link'); + const mimeType = elFileView.getAttribute('data-mime-type') || ''; // not used yet + // TODO: we should also provide the prefetched file head bytes to let the plugin decide whether to render or not + const plugin = findFileRenderPlugin(basename(rawFileLink), mimeType); + if (!plugin) return; + + const renderContainer = elFileView.querySelector<HTMLElement>('.file-view-render-container'); + showRenderRawFileButton(elFileView, renderContainer); + // maybe in the future multiple plugins can render the same file, so we should not assume only one plugin will render it + if (renderContainer) await renderRawFileToContainer(renderContainer, rawFileLink, mimeType); + }); +} diff --git a/web_src/js/features/heatmap.ts b/web_src/js/features/heatmap.ts index 53eebc93e5..7cec82108b 100644 --- a/web_src/js/features/heatmap.ts +++ b/web_src/js/features/heatmap.ts @@ -7,7 +7,7 @@ export function initHeatmap() { if (!el) return; try { - const heatmap = {}; + const heatmap: Record<string, number> = {}; for (const {contributions, timestamp} of JSON.parse(el.getAttribute('data-heatmap-data'))) { // Convert to user timezone and sum contributions by date const dateStr = new Date(timestamp * 1000).toDateString(); diff --git a/web_src/js/features/imagediff.ts b/web_src/js/features/imagediff.ts index cd61888f83..20682f74d9 100644 --- a/web_src/js/features/imagediff.ts +++ b/web_src/js/features/imagediff.ts @@ -3,7 +3,7 @@ import {hideElem, loadElem, queryElemChildren, queryElems} from '../utils/dom.ts import {parseDom} from '../utils.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; -function getDefaultSvgBoundsIfUndefined(text, src) { +function getDefaultSvgBoundsIfUndefined(text: string, src: string) { const defaultSize = 300; const maxSize = 99999; @@ -38,7 +38,7 @@ function getDefaultSvgBoundsIfUndefined(text, src) { return null; } -function createContext(imageAfter, imageBefore) { +function createContext(imageAfter: HTMLImageElement, imageBefore: HTMLImageElement) { const sizeAfter = { width: imageAfter?.width || 0, height: imageAfter?.height || 0, @@ -75,7 +75,7 @@ class ImageDiff { this.containerEl = containerEl; containerEl.setAttribute('data-image-diff-loaded', 'true'); - fomanticQuery(containerEl).find('.ui.menu.tabular .item').tab({autoTabActivation: false}); + fomanticQuery(containerEl).find('.ui.menu.tabular .item').tab(); // the container may be hidden by "viewed" checkbox, so use the parent's width for reference this.diffContainerWidth = Math.max(containerEl.closest('.diff-file-box').clientWidth - 300, 100); @@ -123,7 +123,7 @@ class ImageDiff { queryElemChildren(containerEl, '.image-diff-tabs', (el) => el.classList.remove('is-loading')); } - initSideBySide(sizes) { + initSideBySide(sizes: Record<string, any>) { let factor = 1; if (sizes.maxSize.width > (this.diffContainerWidth - 24) / 2) { factor = (this.diffContainerWidth - 24) / 2 / sizes.maxSize.width; @@ -176,7 +176,7 @@ class ImageDiff { } } - initSwipe(sizes) { + initSwipe(sizes: Record<string, any>) { let factor = 1; if (sizes.maxSize.width > this.diffContainerWidth - 12) { factor = (this.diffContainerWidth - 12) / sizes.maxSize.width; @@ -215,14 +215,14 @@ class ImageDiff { this.containerEl.querySelector('.swipe-bar').addEventListener('mousedown', (e) => { e.preventDefault(); - this.initSwipeEventListeners(e.currentTarget); + this.initSwipeEventListeners(e.currentTarget as HTMLElement); }); } - initSwipeEventListeners(swipeBar) { - const swipeFrame = swipeBar.parentNode; + initSwipeEventListeners(swipeBar: HTMLElement) { + const swipeFrame = swipeBar.parentNode as HTMLElement; const width = swipeFrame.clientWidth; - const onSwipeMouseMove = (e) => { + const onSwipeMouseMove = (e: MouseEvent) => { e.preventDefault(); const rect = swipeFrame.getBoundingClientRect(); const value = Math.max(0, Math.min(e.clientX - rect.left, width)); @@ -237,7 +237,7 @@ class ImageDiff { document.addEventListener('mouseup', removeEventListeners); } - initOverlay(sizes) { + initOverlay(sizes: Record<string, any>) { let factor = 1; if (sizes.maxSize.width > this.diffContainerWidth - 12) { factor = (this.diffContainerWidth - 12) / sizes.maxSize.width; diff --git a/web_src/js/features/install.ts b/web_src/js/features/install.ts index 725dcafab0..ca4bcce881 100644 --- a/web_src/js/features/install.ts +++ b/web_src/js/features/install.ts @@ -12,11 +12,12 @@ export function initInstall() { initPreInstall(); } } + function initPreInstall() { const defaultDbUser = 'gitea'; const defaultDbName = 'gitea'; - const defaultDbHosts = { + const defaultDbHosts: Record<string, string> = { mysql: '127.0.0.1:3306', postgres: '127.0.0.1:5432', mssql: '127.0.0.1:1433', @@ -27,7 +28,7 @@ function initPreInstall() { const dbName = document.querySelector<HTMLInputElement>('#db_name'); // Database type change detection. - document.querySelector('#db_type').addEventListener('change', function () { + document.querySelector<HTMLInputElement>('#db_type').addEventListener('change', function () { const dbType = this.value; hideElem('div[data-db-setting-for]'); showElem(`div[data-db-setting-for=${dbType}]`); @@ -59,26 +60,26 @@ function initPreInstall() { } // TODO: better handling of exclusive relations. - document.querySelector('#offline-mode input').addEventListener('change', function () { + document.querySelector<HTMLInputElement>('#offline-mode input').addEventListener('change', function () { if (this.checked) { document.querySelector<HTMLInputElement>('#disable-gravatar input').checked = true; document.querySelector<HTMLInputElement>('#federated-avatar-lookup input').checked = false; } }); - document.querySelector('#disable-gravatar input').addEventListener('change', function () { + document.querySelector<HTMLInputElement>('#disable-gravatar input').addEventListener('change', function () { if (this.checked) { document.querySelector<HTMLInputElement>('#federated-avatar-lookup input').checked = false; } else { document.querySelector<HTMLInputElement>('#offline-mode input').checked = false; } }); - document.querySelector('#federated-avatar-lookup input').addEventListener('change', function () { + document.querySelector<HTMLInputElement>('#federated-avatar-lookup input').addEventListener('change', function () { if (this.checked) { document.querySelector<HTMLInputElement>('#disable-gravatar input').checked = false; document.querySelector<HTMLInputElement>('#offline-mode input').checked = false; } }); - document.querySelector('#enable-openid-signin input').addEventListener('change', function () { + document.querySelector<HTMLInputElement>('#enable-openid-signin input').addEventListener('change', function () { if (this.checked) { if (!document.querySelector<HTMLInputElement>('#disable-registration input').checked) { document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = true; @@ -87,7 +88,7 @@ function initPreInstall() { document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = false; } }); - document.querySelector('#disable-registration input').addEventListener('change', function () { + document.querySelector<HTMLInputElement>('#disable-registration input').addEventListener('change', function () { if (this.checked) { document.querySelector<HTMLInputElement>('#enable-captcha input').checked = false; document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = false; @@ -95,7 +96,7 @@ function initPreInstall() { document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = true; } }); - document.querySelector('#enable-captcha input').addEventListener('change', function () { + document.querySelector<HTMLInputElement>('#enable-captcha input').addEventListener('change', function () { if (this.checked) { document.querySelector<HTMLInputElement>('#disable-registration input').checked = false; } @@ -103,7 +104,7 @@ function initPreInstall() { } function initPostInstall() { - const el = document.querySelector('#goto-user-login'); + const el = document.querySelector('#goto-after-install'); if (!el) return; const targetUrl = el.getAttribute('href'); diff --git a/web_src/js/features/issue.ts b/web_src/js/features/issue.ts index a56015a2a2..911cf713d9 100644 --- a/web_src/js/features/issue.ts +++ b/web_src/js/features/issue.ts @@ -1,17 +1,21 @@ import type {Issue} from '../types.ts'; +// the getIssueIcon/getIssueColor logic should be kept the same as "templates/shared/issueicon.tmpl" + export function getIssueIcon(issue: Issue) { if (issue.pull_request) { if (issue.state === 'open') { - if (issue.pull_request.draft === true) { + if (issue.pull_request.draft) { return 'octicon-git-pull-request-draft'; // WIP PR } return 'octicon-git-pull-request'; // Open PR - } else if (issue.pull_request.merged === true) { + } else if (issue.pull_request.merged) { return 'octicon-git-merge'; // Merged PR } - return 'octicon-git-pull-request'; // Closed PR - } else if (issue.state === 'open') { + return 'octicon-git-pull-request-closed'; // Closed PR + } + + if (issue.state === 'open') { return 'octicon-issue-opened'; // Open Issue } return 'octicon-issue-closed'; // Closed Issue @@ -19,12 +23,17 @@ export function getIssueIcon(issue: Issue) { export function getIssueColor(issue: Issue) { if (issue.pull_request) { - if (issue.pull_request.draft === true) { - return 'grey'; // WIP PR - } else if (issue.pull_request.merged === true) { + if (issue.state === 'open') { + if (issue.pull_request.draft) { + return 'grey'; // WIP PR + } + return 'green'; // Open PR + } else if (issue.pull_request.merged) { return 'purple'; // Merged PR } + return 'red'; // Closed PR } + if (issue.state === 'open') { return 'green'; // Open Issue } diff --git a/web_src/js/features/notification.ts b/web_src/js/features/notification.ts index adfdb157a1..dc0acb0244 100644 --- a/web_src/js/features/notification.ts +++ b/web_src/js/features/notification.ts @@ -1,6 +1,5 @@ -import $ from 'jquery'; import {GET} from '../modules/fetch.ts'; -import {toggleElem, type DOMEvent} from '../utils/dom.ts'; +import {toggleElem, type DOMEvent, createElementFromHTML} from '../utils/dom.ts'; import {logoutFromWorker} from '../modules/worker.ts'; const {appSubUrl, notificationSettings, assetVersionEncoded} = window.config; @@ -158,7 +157,8 @@ async function updateNotificationTable() { } const data = await response.text(); - if ($(data).data('sequence-number') === notificationSequenceNumber) { + const el = createElementFromHTML(data); + if (parseInt(el.getAttribute('data-sequence-number')) === notificationSequenceNumber) { notificationDiv.outerHTML = data; initNotificationsTable(); } diff --git a/web_src/js/features/org-team.ts b/web_src/js/features/org-team.ts index e4e98fd990..d07818b0ac 100644 --- a/web_src/js/features/org-team.ts +++ b/web_src/js/features/org-team.ts @@ -1,35 +1,34 @@ -import $ from 'jquery'; -import {hideElem, showElem} from '../utils/dom.ts'; +import {queryElems, toggleElem} from '../utils/dom.ts'; +import {fomanticQuery} from '../modules/fomantic/base.ts'; const {appSubUrl} = window.config; -export function initOrgTeamSettings() { - // Change team access mode - $('.organization.new.team input[name=permission]').on('change', () => { - const val = $('input[name=permission]:checked', '.organization.new.team').val(); - if (val === 'admin') { - hideElem('.organization.new.team .team-units'); - } else { - showElem('.organization.new.team .team-units'); - } - }); +function initOrgTeamSettings() { + // on the page "page-content organization new team" + const pageContent = document.querySelector('.page-content.organization.new.team'); + if (!pageContent) return; + queryElems(pageContent, 'input[name=permission]', (el) => el.addEventListener('change', () => { + // Change team access mode + const val = pageContent.querySelector<HTMLInputElement>('input[name=permission]:checked')?.value; + toggleElem(pageContent.querySelectorAll('.team-units'), val !== 'admin'); + })); } -export function initOrgTeamSearchRepoBox() { - const $searchRepoBox = $('#search-repo-box'); +function initOrgTeamSearchRepoBox() { + // on the page "page-content organization teams" + const $searchRepoBox = fomanticQuery('#search-repo-box'); $searchRepoBox.search({ minCharacters: 2, apiSettings: { url: `${appSubUrl}/repo/search?q={query}&uid=${$searchRepoBox.data('uid')}`, - onResponse(response) { + onResponse(response: any) { const items = []; - $.each(response.data, (_i, item) => { + for (const item of response.data) { items.push({ title: item.repository.full_name.split('/')[1], description: item.repository.full_name, }); - }); - + } return {results: items}; }, }, @@ -37,3 +36,9 @@ export function initOrgTeamSearchRepoBox() { showNoResults: false, }); } + +export function initOrgTeam() { + if (!document.querySelector('.page-content.organization')) return; + initOrgTeamSettings(); + initOrgTeamSearchRepoBox(); +} diff --git a/web_src/js/features/pull-view-file.ts b/web_src/js/features/pull-view-file.ts index 36fe4bc4df..1124886238 100644 --- a/web_src/js/features/pull-view-file.ts +++ b/web_src/js/features/pull-view-file.ts @@ -1,4 +1,4 @@ -import {diffTreeStore} from '../modules/stores.ts'; +import {diffTreeStore, diffTreeStoreSetViewed} from '../modules/diff-file.ts'; import {setFileFolding} from './file-fold.ts'; import {POST} from '../modules/fetch.ts'; @@ -38,7 +38,7 @@ export function initViewedCheckboxListenerFor() { // The checkbox consists of a div containing the real checkbox with its label and the CSRF token, // hence the actual checkbox first has to be found - const checkbox = form.querySelector('input[type=checkbox]'); + const checkbox = form.querySelector<HTMLInputElement>('input[type=checkbox]'); checkbox.addEventListener('input', function() { // Mark the file as viewed visually - will especially change the background if (this.checked) { @@ -58,14 +58,11 @@ export function initViewedCheckboxListenerFor() { const fileName = checkbox.getAttribute('name'); - // check if the file is in our difftreestore and if we find it -> change the IsViewed status - const fileInPageData = diffTreeStore().files.find((x) => x.Name === fileName); - if (fileInPageData) { - fileInPageData.IsViewed = this.checked; - } + // check if the file is in our diffTreeStore and if we find it -> change the IsViewed status + diffTreeStoreSetViewed(diffTreeStore(), fileName, this.checked); // Unfortunately, actual forms cause too many problems, hence another approach is needed - const files = {}; + const files: Record<string, boolean> = {}; files[fileName] = this.checked; const data: Record<string, any> = {files}; const headCommitSHA = form.getAttribute('data-headcommit'); @@ -82,13 +79,13 @@ export function initViewedCheckboxListenerFor() { export function initExpandAndCollapseFilesButton() { // expand btn document.querySelector(expandFilesBtnSelector)?.addEventListener('click', () => { - for (const box of document.querySelectorAll('.file-content[data-folded="true"]')) { + for (const box of document.querySelectorAll<HTMLElement>('.file-content[data-folded="true"]')) { setFileFolding(box, box.querySelector('.fold-file'), false); } }); // collapse btn, need to exclude the div of “show more” document.querySelector(collapseFilesBtnSelector)?.addEventListener('click', () => { - for (const box of document.querySelectorAll('.file-content:not([data-folded="true"])')) { + for (const box of document.querySelectorAll<HTMLElement>('.file-content:not([data-folded="true"])')) { if (box.getAttribute('id') === 'diff-incomplete') continue; setFileFolding(box, box.querySelector('.fold-file'), true); } diff --git a/web_src/js/features/repo-actions.ts b/web_src/js/features/repo-actions.ts new file mode 100644 index 0000000000..8d93fce53f --- /dev/null +++ b/web_src/js/features/repo-actions.ts @@ -0,0 +1,48 @@ +import {createApp} from 'vue'; +import RepoActionView from '../components/RepoActionView.vue'; + +export function initRepositoryActionView() { + const el = document.querySelector('#repo-action-view'); + if (!el) return; + + // TODO: the parent element's full height doesn't work well now, + // but we can not pollute the global style at the moment, only fix the height problem for pages with this component + const parentFullHeight = document.querySelector<HTMLElement>('body > div.full.height'); + if (parentFullHeight) parentFullHeight.style.paddingBottom = '0'; + + const view = createApp(RepoActionView, { + runIndex: el.getAttribute('data-run-index'), + jobIndex: el.getAttribute('data-job-index'), + actionsURL: el.getAttribute('data-actions-url'), + locale: { + approve: el.getAttribute('data-locale-approve'), + cancel: el.getAttribute('data-locale-cancel'), + rerun: el.getAttribute('data-locale-rerun'), + rerun_all: el.getAttribute('data-locale-rerun-all'), + scheduled: el.getAttribute('data-locale-runs-scheduled'), + commit: el.getAttribute('data-locale-runs-commit'), + pushedBy: el.getAttribute('data-locale-runs-pushed-by'), + artifactsTitle: el.getAttribute('data-locale-artifacts-title'), + areYouSure: el.getAttribute('data-locale-are-you-sure'), + artifactExpired: el.getAttribute('data-locale-artifact-expired'), + confirmDeleteArtifact: el.getAttribute('data-locale-confirm-delete-artifact'), + showTimeStamps: el.getAttribute('data-locale-show-timestamps'), + showLogSeconds: el.getAttribute('data-locale-show-log-seconds'), + showFullScreen: el.getAttribute('data-locale-show-full-screen'), + downloadLogs: el.getAttribute('data-locale-download-logs'), + status: { + unknown: el.getAttribute('data-locale-status-unknown'), + waiting: el.getAttribute('data-locale-status-waiting'), + running: el.getAttribute('data-locale-status-running'), + success: el.getAttribute('data-locale-status-success'), + failure: el.getAttribute('data-locale-status-failure'), + cancelled: el.getAttribute('data-locale-status-cancelled'), + skipped: el.getAttribute('data-locale-status-skipped'), + blocked: el.getAttribute('data-locale-status-blocked'), + }, + logsAlwaysAutoScroll: el.getAttribute('data-locale-logs-always-auto-scroll'), + logsAlwaysExpandRunning: el.getAttribute('data-locale-logs-always-expand-running'), + }, + }); + view.mount(el); +} diff --git a/web_src/js/features/repo-code.test.ts b/web_src/js/features/repo-code.test.ts deleted file mode 100644 index 27554aa847..0000000000 --- a/web_src/js/features/repo-code.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {singleAnchorRegex, rangeAnchorRegex} from './repo-code.ts'; - -test('singleAnchorRegex', () => { - expect(singleAnchorRegex.test('#L0')).toEqual(false); - expect(singleAnchorRegex.test('#L1')).toEqual(true); - expect(singleAnchorRegex.test('#L01')).toEqual(false); - expect(singleAnchorRegex.test('#n0')).toEqual(false); - expect(singleAnchorRegex.test('#n1')).toEqual(true); - expect(singleAnchorRegex.test('#n01')).toEqual(false); -}); - -test('rangeAnchorRegex', () => { - expect(rangeAnchorRegex.test('#L0-L10')).toEqual(false); - expect(rangeAnchorRegex.test('#L1-L10')).toEqual(true); - expect(rangeAnchorRegex.test('#L01-L10')).toEqual(false); - expect(rangeAnchorRegex.test('#L1-L01')).toEqual(false); -}); diff --git a/web_src/js/features/repo-code.ts b/web_src/js/features/repo-code.ts index a8d6e8f97d..bf7fd762b0 100644 --- a/web_src/js/features/repo-code.ts +++ b/web_src/js/features/repo-code.ts @@ -1,12 +1,7 @@ -import $ from 'jquery'; import {svg} from '../svg.ts'; -import {invertFileFolding} from './file-fold.ts'; import {createTippy} from '../modules/tippy.ts'; -import {clippie} from 'clippie'; import {toAbsoluteUrl} from '../utils.ts'; - -export const singleAnchorRegex = /^#(L|n)([1-9][0-9]*)$/; -export const rangeAnchorRegex = /^#(L[1-9][0-9]*)-(L[1-9][0-9]*)$/; +import {addDelegatedEventListener} from '../utils/dom.ts'; function changeHash(hash: string) { if (window.history.pushState) { @@ -16,20 +11,11 @@ function changeHash(hash: string) { } } -function isBlame() { - return Boolean(document.querySelector('div.blame')); -} +// it selects the code lines defined by range: `L1-L3` (3 lines) or `L2` (singe line) +function selectRange(range: string): Element { + for (const el of document.querySelectorAll('.code-view tr.active')) el.classList.remove('active'); + const elLineNums = document.querySelectorAll(`.code-view td.lines-num span[data-line-number]`); -function getLineEls() { - return document.querySelectorAll(`.code-view td.lines-code${isBlame() ? '.blame-code' : ''}`); -} - -function selectRange($linesEls, $selectionEndEl, $selectionStartEls?) { - for (const el of $linesEls) { - el.closest('tr').classList.remove('active'); - } - - // add hashchange to permalink const refInNewIssue = document.querySelector('a.ref-in-new-issue'); const copyPermalink = document.querySelector('a.copy-line-permalink'); const viewGitBlame = document.querySelector('a.view_git_blame'); @@ -56,40 +42,34 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls?) { if (!copyPermalink) return; let link = copyPermalink.getAttribute('data-url'); link = `${link.replace(/#L\d+$|#L\d+-L\d+$/, '')}#${anchor}`; - copyPermalink.setAttribute('data-url', link); + copyPermalink.setAttribute('data-clipboard-text', link); + copyPermalink.setAttribute('data-clipboard-text-type', 'url'); }; - if ($selectionStartEls) { - let a = parseInt($selectionEndEl[0].getAttribute('rel').slice(1)); - let b = parseInt($selectionStartEls[0].getAttribute('rel').slice(1)); - let c; - if (a !== b) { - if (a > b) { - c = a; - a = b; - b = c; - } - const classes = []; - for (let i = a; i <= b; i++) { - classes.push(`[rel=L${i}]`); - } - $linesEls.filter(classes.join(',')).each(function () { - this.closest('tr').classList.add('active'); - }); - changeHash(`#L${a}-L${b}`); - - updateIssueHref(`L${a}-L${b}`); - updateViewGitBlameFragment(`L${a}-L${b}`); - updateCopyPermalinkUrl(`L${a}-L${b}`); - return; - } + const rangeFields = range ? range.split('-') : []; + const start = rangeFields[0] ?? ''; + if (!start) return null; + const stop = rangeFields[1] || start; + + // format is i.e. 'L14-L26' + let startLineNum = parseInt(start.substring(1)); + let stopLineNum = parseInt(stop.substring(1)); + if (startLineNum > stopLineNum) { + const tmp = startLineNum; + startLineNum = stopLineNum; + stopLineNum = tmp; + range = `${stop}-${start}`; } - $selectionEndEl[0].closest('tr').classList.add('active'); - changeHash(`#${$selectionEndEl[0].getAttribute('rel')}`); - updateIssueHref($selectionEndEl[0].getAttribute('rel')); - updateViewGitBlameFragment($selectionEndEl[0].getAttribute('rel')); - updateCopyPermalinkUrl($selectionEndEl[0].getAttribute('rel')); + const first = elLineNums[startLineNum - 1] ?? null; + for (let i = startLineNum - 1; i <= stopLineNum - 1 && i < elLineNums.length; i++) { + elLineNums[i].closest('tr').classList.add('active'); + } + changeHash(`#${range}`); + updateIssueHref(range); + updateViewGitBlameFragment(range); + updateCopyPermalinkUrl(range); + return first; } function showLineButton() { @@ -103,6 +83,8 @@ function showLineButton() { // find active row and add button const tr = document.querySelector('.code-view tr.active'); + if (!tr) return; + const td = tr.querySelector('td.lines-num'); const btn = document.createElement('button'); btn.classList.add('code-line-button', 'ui', 'basic', 'button'); @@ -128,62 +110,39 @@ function showLineButton() { } export function initRepoCodeView() { - if ($('.code-view .lines-num').length > 0) { - $(document).on('click', '.lines-num span', function (e) { - const linesEls = getLineEls(); - const selectedEls = Array.from(linesEls).filter((el) => { - return el.matches(`[rel=${this.getAttribute('id')}]`); - }); - - let from; - if (e.shiftKey) { - from = Array.from(linesEls).filter((el) => { - return el.closest('tr').classList.contains('active'); - }); - } - selectRange($(linesEls), $(selectedEls), from ? $(from) : null); - window.getSelection().removeAllRanges(); - showLineButton(); - }); - - $(window).on('hashchange', () => { - let m = rangeAnchorRegex.exec(window.location.hash); - const $linesEls = $(getLineEls()); - let $first; - if (m) { - $first = $linesEls.filter(`[rel=${m[1]}]`); - if ($first.length) { - selectRange($linesEls, $first, $linesEls.filter(`[rel=${m[2]}]`)); - - // show code view menu marker (don't show in blame page) - if (!isBlame()) { - showLineButton(); - } - - $('html, body').scrollTop($first.offset().top - 200); - return; - } - } - m = singleAnchorRegex.exec(window.location.hash); - if (m) { - $first = $linesEls.filter(`[rel=L${m[2]}]`); - if ($first.length) { - selectRange($linesEls, $first); - - // show code view menu marker (don't show in blame page) - if (!isBlame()) { - showLineButton(); - } - - $('html, body').scrollTop($first.offset().top - 200); - } - } - }).trigger('hashchange'); - } - $(document).on('click', '.fold-file', ({currentTarget}) => { - invertFileFolding(currentTarget.closest('.file-content'), currentTarget); - }); - $(document).on('click', '.copy-line-permalink', async ({currentTarget}) => { - await clippie(toAbsoluteUrl(currentTarget.getAttribute('data-url'))); + // When viewing a file or blame, there is always a ".file-view" element, + // but the ".code-view" class is only present when viewing the "code" of a file; it is not present when viewing a PDF file. + // Since the ".file-view" will be dynamically reloaded when navigating via the left file tree (eg: view a PDF file, then view a source code file, etc.) + // the "code-view" related event listeners should always be added when the current page contains ".file-view" element. + if (!document.querySelector('.repo-view-container .file-view')) return; + + // "file code view" and "blame" pages need this "line number button" feature + let selRangeStart: string; + addDelegatedEventListener(document, 'click', '.code-view .lines-num span', (el: HTMLElement, e: KeyboardEvent) => { + if (!selRangeStart || !e.shiftKey) { + selRangeStart = el.getAttribute('id'); + selectRange(selRangeStart); + } else { + const selRangeStop = el.getAttribute('id'); + selectRange(`${selRangeStart}-${selRangeStop}`); + } + window.getSelection().removeAllRanges(); + showLineButton(); }); + + // apply the selected range from the URL hash + const onHashChange = () => { + if (!window.location.hash) return; + if (!document.querySelector('.code-view .lines-num')) return; + const range = window.location.hash.substring(1); + const first = selectRange(range); + if (first) { + // set scrollRestoration to 'manual' when there is a hash in the URL, so that the scroll position will not be remembered after refreshing + if (window.history.scrollRestoration !== 'manual') window.history.scrollRestoration = 'manual'; + first.scrollIntoView({block: 'start'}); + showLineButton(); + } + }; + onHashChange(); + window.addEventListener('hashchange', onHashChange); } diff --git a/web_src/js/features/repo-commit.ts b/web_src/js/features/repo-commit.ts index 56493443d9..98ec2328ec 100644 --- a/web_src/js/features/repo-commit.ts +++ b/web_src/js/features/repo-commit.ts @@ -1,27 +1,26 @@ import {createTippy} from '../modules/tippy.ts'; import {toggleElem} from '../utils/dom.ts'; +import {registerGlobalEventFunc, registerGlobalInitFunc} from '../modules/observer.ts'; export function initRepoEllipsisButton() { - for (const button of document.querySelectorAll('.js-toggle-commit-body')) { - button.addEventListener('click', function (e) { - e.preventDefault(); - const expanded = this.getAttribute('aria-expanded') === 'true'; - toggleElem(this.parentElement.querySelector('.commit-body')); - this.setAttribute('aria-expanded', String(!expanded)); - }); - } + registerGlobalEventFunc('click', 'onRepoEllipsisButtonClick', async (el: HTMLInputElement, e: Event) => { + e.preventDefault(); + const expanded = el.getAttribute('aria-expanded') === 'true'; + toggleElem(el.parentElement.querySelector('.commit-body')); + el.setAttribute('aria-expanded', String(!expanded)); + }); } export function initCommitStatuses() { - for (const element of document.querySelectorAll('[data-tippy="commit-statuses"]')) { - const top = document.querySelector('.repository.file.list') || document.querySelector('.repository.diff'); - - createTippy(element, { - content: element.nextElementSibling, - placement: top ? 'top-start' : 'bottom-start', + registerGlobalInitFunc('initCommitStatuses', (el: HTMLElement) => { + const nextEl = el.nextElementSibling; + if (!nextEl.matches('.tippy-target')) throw new Error('Expected next element to be a tippy target'); + createTippy(el, { + content: nextEl, + placement: 'bottom-start', interactive: true, role: 'dialog', theme: 'box-with-header', }); - } + }); } diff --git a/web_src/js/features/repo-common.test.ts b/web_src/js/features/repo-common.test.ts new file mode 100644 index 0000000000..33a29ecb2c --- /dev/null +++ b/web_src/js/features/repo-common.test.ts @@ -0,0 +1,22 @@ +import {sanitizeRepoName, substituteRepoOpenWithUrl} from './repo-common.ts'; + +test('substituteRepoOpenWithUrl', () => { + // For example: "x-github-client://openRepo/https://github.com/go-gitea/gitea" + expect(substituteRepoOpenWithUrl('proto://a/{url}', 'https://gitea')).toEqual('proto://a/https://gitea'); + expect(substituteRepoOpenWithUrl('proto://a?link={url}', 'https://gitea')).toEqual('proto://a?link=https%3A%2F%2Fgitea'); +}); + +test('sanitizeRepoName', () => { + expect(sanitizeRepoName(' a b ')).toEqual('a-b'); + expect(sanitizeRepoName('a-b_c.git ')).toEqual('a-b_c'); + expect(sanitizeRepoName('/x.git/')).toEqual('-x.git-'); + expect(sanitizeRepoName('.profile')).toEqual('.profile'); + expect(sanitizeRepoName('.profile.')).toEqual('.profile'); + expect(sanitizeRepoName('.pro..file')).toEqual('.pro.file'); + + expect(sanitizeRepoName('foo.rss.atom.git.wiki')).toEqual('foo'); + + expect(sanitizeRepoName('.')).toEqual(''); + expect(sanitizeRepoName('..')).toEqual(''); + expect(sanitizeRepoName('-')).toEqual(''); +}); diff --git a/web_src/js/features/repo-common.ts b/web_src/js/features/repo-common.ts index 90860720e4..ebb6881c67 100644 --- a/web_src/js/features/repo-common.ts +++ b/web_src/js/features/repo-common.ts @@ -1,4 +1,4 @@ -import {queryElems} from '../utils/dom.ts'; +import {queryElems, type DOMEvent} from '../utils/dom.ts'; import {POST} from '../modules/fetch.ts'; import {showErrorToast} from '../modules/toast.ts'; import {sleep} from '../utils.ts'; @@ -7,10 +7,10 @@ import {createApp} from 'vue'; import {toOriginUrl} from '../utils/url.ts'; import {createTippy} from '../modules/tippy.ts'; -async function onDownloadArchive(e) { +async function onDownloadArchive(e: DOMEvent<MouseEvent>) { e.preventDefault(); // there are many places using the "archive-link", eg: the dropdown on the repo code page, the release list - const el = e.target.closest('a.archive-link[href]'); + const el = e.target.closest<HTMLAnchorElement>('a.archive-link[href]'); const targetLoading = el.closest('.ui.dropdown') ?? el; targetLoading.classList.add('is-loading', 'loading-icon-2px'); try { @@ -42,23 +42,60 @@ export function initRepoActivityTopAuthorsChart() { } } +export function substituteRepoOpenWithUrl(tmpl: string, url: string): string { + const pos = tmpl.indexOf('{url}'); + if (pos === -1) return tmpl; + const posQuestionMark = tmpl.indexOf('?'); + const needEncode = posQuestionMark >= 0 && posQuestionMark < pos; + return tmpl.replace('{url}', needEncode ? encodeURIComponent(url) : url); +} + function initCloneSchemeUrlSelection(parent: Element) { const elCloneUrlInput = parent.querySelector<HTMLInputElement>('.repo-clone-url'); - const tabSsh = parent.querySelector('.repo-clone-ssh'); const tabHttps = parent.querySelector('.repo-clone-https'); + const tabSsh = parent.querySelector('.repo-clone-ssh'); + const tabTea = parent.querySelector('.repo-clone-tea'); const updateClonePanelUi = function() { - const scheme = localStorage.getItem('repo-clone-protocol') || 'https'; - const isSSH = scheme === 'ssh' && Boolean(tabSsh) || scheme !== 'ssh' && !tabHttps; + let scheme = localStorage.getItem('repo-clone-protocol'); + if (!['https', 'ssh', 'tea'].includes(scheme)) { + scheme = 'https'; + } + + // Fallbacks if the scheme preference is not available in the tabs, for example: empty repo page, there are only HTTPS and SSH + if (scheme === 'tea' && !tabTea) { + scheme = 'https'; + } + if (scheme === 'https' && !tabHttps) { + scheme = 'ssh'; + } else if (scheme === 'ssh' && !tabSsh) { + scheme = 'https'; + } + + const isHttps = scheme === 'https'; + const isSsh = scheme === 'ssh'; + const isTea = scheme === 'tea'; + if (tabHttps) { tabHttps.textContent = window.origin.split(':')[0].toUpperCase(); // show "HTTP" or "HTTPS" - tabHttps.classList.toggle('active', !isSSH); + tabHttps.classList.toggle('active', isHttps); } if (tabSsh) { - tabSsh.classList.toggle('active', isSSH); + tabSsh.classList.toggle('active', isSsh); + } + if (tabTea) { + tabTea.classList.toggle('active', isTea); + } + + let tab: Element; + if (isHttps) { + tab = tabHttps; + } else if (isSsh) { + tab = tabSsh; + } else if (isTea) { + tab = tabTea; } - const tab = isSSH ? tabSsh : tabHttps; if (!tab) return; const link = toOriginUrl(tab.getAttribute('data-link')); @@ -70,18 +107,22 @@ function initCloneSchemeUrlSelection(parent: Element) { } } for (const el of parent.querySelectorAll<HTMLAnchorElement>('.js-clone-url-editor')) { - el.href = el.getAttribute('data-href-template').replace('{url}', encodeURIComponent(link)); + el.href = substituteRepoOpenWithUrl(el.getAttribute('data-href-template'), link); } }; updateClonePanelUi(); // tabSsh or tabHttps might not both exist, eg: guest view, or one is disabled by the server + tabHttps?.addEventListener('click', () => { + localStorage.setItem('repo-clone-protocol', 'https'); + updateClonePanelUi(); + }); tabSsh?.addEventListener('click', () => { localStorage.setItem('repo-clone-protocol', 'ssh'); updateClonePanelUi(); }); - tabHttps?.addEventListener('click', () => { - localStorage.setItem('repo-clone-protocol', 'https'); + tabTea?.addEventListener('click', () => { + localStorage.setItem('repo-clone-protocol', 'tea'); updateClonePanelUi(); }); elCloneUrlInput.addEventListener('focus', () => { @@ -99,6 +140,7 @@ function initClonePanelButton(btn: HTMLButtonElement) { placement: 'bottom-end', interactive: true, hideOnClick: true, + arrow: false, }); } @@ -107,7 +149,7 @@ export function initRepoCloneButtons() { queryElems(document, '.clone-buttons-combo', initCloneSchemeUrlSelection); } -export async function updateIssuesMeta(url, action, issue_ids, id) { +export async function updateIssuesMeta(url: string, action: string, issue_ids: string, id: string) { try { const response = await POST(url, {data: new URLSearchParams({action, issue_ids, id})}); if (!response.ok) { @@ -117,3 +159,19 @@ export async function updateIssuesMeta(url, action, issue_ids, id) { console.error(error); } } + +export function sanitizeRepoName(name: string): string { + name = name.trim().replace(/[^-.\w]/g, '-'); + for (let lastName = ''; lastName !== name;) { + lastName = name; + name = name.replace(/\.+$/g, ''); + name = name.replace(/\.{2,}/g, '.'); + for (const ext of ['.git', '.wiki', '.rss', '.atom']) { + if (name.endsWith(ext)) { + name = name.substring(0, name.length - ext.length); + } + } + } + if (['.', '..', '-'].includes(name)) name = ''; + return name; +} diff --git a/web_src/js/features/repo-diff-filetree.ts b/web_src/js/features/repo-diff-filetree.ts index bc275a90f6..cc4576a846 100644 --- a/web_src/js/features/repo-diff-filetree.ts +++ b/web_src/js/features/repo-diff-filetree.ts @@ -1,6 +1,5 @@ import {createApp} from 'vue'; import DiffFileTree from '../components/DiffFileTree.vue'; -import DiffFileList from '../components/DiffFileList.vue'; export function initDiffFileTree() { const el = document.querySelector('#diff-file-tree'); @@ -9,11 +8,3 @@ export function initDiffFileTree() { const fileTreeView = createApp(DiffFileTree); fileTreeView.mount(el); } - -export function initDiffFileList() { - const fileListElement = document.querySelector('#diff-file-list'); - if (!fileListElement) return; - - const fileListView = createApp(DiffFileList); - fileListView.mount(fileListElement); -} diff --git a/web_src/js/features/repo-diff.ts b/web_src/js/features/repo-diff.ts index 58e0d88092..ad1da5c2fa 100644 --- a/web_src/js/features/repo-diff.ts +++ b/web_src/js/features/repo-diff.ts @@ -1,43 +1,36 @@ -import $ from 'jquery'; -import {initCompReactionSelector} from './comp/ReactionSelector.ts'; import {initRepoIssueContentHistory} from './repo-issue-content.ts'; -import {initDiffFileTree, initDiffFileList} from './repo-diff-filetree.ts'; +import {initDiffFileTree} from './repo-diff-filetree.ts'; import {initDiffCommitSelect} from './repo-diff-commitselect.ts'; import {validateTextareaNonEmpty} from './comp/ComboMarkdownEditor.ts'; import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.ts'; import {initImageDiff} from './imagediff.ts'; import {showErrorToast} from '../modules/toast.ts'; -import { - submitEventSubmitter, - queryElemSiblings, - hideElem, - showElem, - animateOnce, - addDelegatedEventListener, - createElementFromHTML, -} from '../utils/dom.ts'; +import {submitEventSubmitter, queryElemSiblings, hideElem, showElem, animateOnce, addDelegatedEventListener, createElementFromHTML, queryElems} from '../utils/dom.ts'; import {POST, GET} from '../modules/fetch.ts'; -import {fomanticQuery} from '../modules/fomantic/base.ts'; import {createTippy} from '../modules/tippy.ts'; +import {invertFileFolding} from './file-fold.ts'; +import {parseDom} from '../utils.ts'; +import {registerGlobalSelectorFunc} from '../modules/observer.ts'; -const {pageData, i18n} = window.config; +const {i18n} = window.config; -function initRepoDiffFileViewToggle() { - $('.file-view-toggle').on('click', function () { - for (const el of queryElemSiblings(this)) { - el.classList.remove('active'); - } - this.classList.add('active'); +function initRepoDiffFileBox(el: HTMLElement) { + // switch between "rendered" and "source", for image and CSV files + queryElems(el, '.file-view-toggle', (btn) => btn.addEventListener('click', () => { + queryElemSiblings(btn, '.file-view-toggle', (el) => el.classList.remove('active')); + btn.classList.add('active'); - const target = document.querySelector(this.getAttribute('data-toggle-selector')); - if (!target) return; + const target = document.querySelector(btn.getAttribute('data-toggle-selector')); + if (!target) throw new Error('Target element not found'); hideElem(queryElemSiblings(target)); showElem(target); - }); + })); } function initRepoDiffConversationForm() { + // FIXME: there could be various different form in a conversation-holder (for example: reply form, edit form). + // This listener is for "reply form" only, it should clearly distinguish different forms in the future. addDelegatedEventListener<HTMLFormElement, SubmitEvent>(document, 'submit', '.conversation-holder form', async (form, e) => { e.preventDefault(); const textArea = form.querySelector<HTMLTextAreaElement>('textarea'); @@ -80,7 +73,6 @@ function initRepoDiffConversationForm() { el.classList.add('tw-invisible'); } } - fomanticQuery(newConversationHolder.querySelectorAll('.ui.dropdown')).dropdown(); // the default behavior is to add a pending review, so if no submitter, it also means "pending_review" if (!submitter || submitter?.matches('button[name="pending_review"]')) { @@ -100,22 +92,21 @@ function initRepoDiffConversationForm() { } }); - $(document).on('click', '.resolve-conversation', async function (e) { + addDelegatedEventListener(document, 'click', '.resolve-conversation', async (el, e) => { e.preventDefault(); - const comment_id = $(this).data('comment-id'); - const origin = $(this).data('origin'); - const action = $(this).data('action'); - const url = $(this).data('update-url'); + const comment_id = el.getAttribute('data-comment-id'); + const origin = el.getAttribute('data-origin'); + const action = el.getAttribute('data-action'); + const url = el.getAttribute('data-update-url'); try { const response = await POST(url, {data: new URLSearchParams({origin, action, comment_id})}); const data = await response.text(); - if ($(this).closest('.conversation-holder').length) { - const $conversation = $(data); - $(this).closest('.conversation-holder').replaceWith($conversation); - $conversation.find('.dropdown').dropdown(); - initCompReactionSelector($conversation[0]); + const elConversationHolder = el.closest('.conversation-holder'); + if (elConversationHolder) { + const elNewConversation = createElementFromHTML(data); + elConversationHolder.replaceWith(elNewConversation); } else { window.location.reload(); } @@ -125,24 +116,19 @@ function initRepoDiffConversationForm() { }); } -export function initRepoDiffConversationNav() { +function initRepoDiffConversationNav() { // Previous/Next code review conversation - $(document).on('click', '.previous-conversation', (e) => { - const $conversation = $(e.currentTarget).closest('.comment-code-cloud'); - const $conversations = $('.comment-code-cloud:not(.tw-hidden)'); - const index = $conversations.index($conversation); - const previousIndex = index > 0 ? index - 1 : $conversations.length - 1; - const $previousConversation = $conversations.eq(previousIndex); - const anchor = $previousConversation.find('.comment').first()[0].getAttribute('id'); - window.location.href = `#${anchor}`; - }); - $(document).on('click', '.next-conversation', (e) => { - const $conversation = $(e.currentTarget).closest('.comment-code-cloud'); - const $conversations = $('.comment-code-cloud:not(.tw-hidden)'); - const index = $conversations.index($conversation); - const nextIndex = index < $conversations.length - 1 ? index + 1 : 0; - const $nextConversation = $conversations.eq(nextIndex); - const anchor = $nextConversation.find('.comment').first()[0].getAttribute('id'); + addDelegatedEventListener(document, 'click', '.previous-conversation, .next-conversation', (el, e) => { + e.preventDefault(); + const isPrevious = el.matches('.previous-conversation'); + const elCurConversation = el.closest('.comment-code-cloud'); + const elAllConversations = document.querySelectorAll('.comment-code-cloud:not(.tw-hidden)'); + const index = Array.from(elAllConversations).indexOf(elCurConversation); + const previousIndex = index > 0 ? index - 1 : elAllConversations.length - 1; + const nextIndex = index < elAllConversations.length - 1 ? index + 1 : 0; + const navIndex = isPrevious ? previousIndex : nextIndex; + const elNavConversation = elAllConversations[navIndex]; + const anchor = elNavConversation.querySelector('.comment').id; window.location.href = `#${anchor}`; }); } @@ -158,6 +144,7 @@ function initDiffHeaderPopup() { // Will be called when the show more (files) button has been pressed function onShowMoreFiles() { + // TODO: replace these calls with the "observer.ts" methods initRepoIssueContentHistory(); initViewedCheckboxListenerFor(); countAndUpdateViewedFiles(); @@ -165,81 +152,112 @@ function onShowMoreFiles() { initDiffHeaderPopup(); } -export async function loadMoreFiles(url) { - const target = document.querySelector('a#diff-show-more-files'); - if (target?.classList.contains('disabled') || pageData.diffFileInfo.isLoadingNewData) { - return; +async function loadMoreFiles(btn: Element): Promise<boolean> { + if (btn.classList.contains('disabled')) { + return false; } - pageData.diffFileInfo.isLoadingNewData = true; - target?.classList.add('disabled'); - + btn.classList.add('disabled'); + const url = btn.getAttribute('data-href'); try { const response = await GET(url); const resp = await response.text(); - const $resp = $(resp); + const respDoc = parseDom(resp, 'text/html'); + const respFileBoxes = respDoc.querySelector('#diff-file-boxes'); // the response is a full HTML page, we need to extract the relevant contents: - // 1. append the newly loaded file list items to the existing list - $('#diff-incomplete').replaceWith($resp.find('#diff-file-boxes').children()); - // 2. re-execute the script to append the newly loaded items to the JS variables to refresh the DiffFileTree - $('body').append($resp.find('script#diff-data-script')); - + // * append the newly loaded file list items to the existing list + document.querySelector('#diff-incomplete').replaceWith(...Array.from(respFileBoxes.children)); onShowMoreFiles(); + return true; } catch (error) { console.error('Error:', error); showErrorToast('An error occurred while loading more files.'); } finally { - target?.classList.remove('disabled'); - pageData.diffFileInfo.isLoadingNewData = false; + btn.classList.remove('disabled'); } + return false; } function initRepoDiffShowMore() { - $(document).on('click', 'a#diff-show-more-files', (e) => { + addDelegatedEventListener(document, 'click', 'a#diff-show-more-files', (el, e) => { e.preventDefault(); - - const linkLoadMore = e.target.getAttribute('data-href'); - loadMoreFiles(linkLoadMore); + loadMoreFiles(el); }); - $(document).on('click', 'a.diff-load-button', async (e) => { + addDelegatedEventListener(document, 'click', 'a.diff-load-button', async (el, e) => { e.preventDefault(); - const $target = $(e.target); - - if (e.target.classList.contains('disabled')) { - return; - } + if (el.classList.contains('disabled')) return; - e.target.classList.add('disabled'); - - const url = $target.data('href'); + el.classList.add('disabled'); + const url = el.getAttribute('data-href'); try { const response = await GET(url); const resp = await response.text(); - - if (!resp) { - return; - } - $target.parent().replaceWith($(resp).find('#diff-file-boxes .diff-file-body .file-body').children()); + const respDoc = parseDom(resp, 'text/html'); + const respFileBody = respDoc.querySelector('#diff-file-boxes .diff-file-body .file-body'); + const respFileBodyChildren = Array.from(respFileBody.children); // respFileBody.children will be empty after replaceWith + el.parentElement.replaceWith(...respFileBodyChildren); + for (const el of respFileBodyChildren) window.htmx.process(el); + // FIXME: calling onShowMoreFiles is not quite right here. + // But since onShowMoreFiles mixes "init diff box" and "init diff body" together, + // so it still needs to call it to make the "ImageDiff" and something similar work. onShowMoreFiles(); } catch (error) { console.error('Error:', error); } finally { - e.target.classList.remove('disabled'); + el.classList.remove('disabled'); } }); } +async function loadUntilFound() { + const hashTargetSelector = window.location.hash; + if (!hashTargetSelector.startsWith('#diff-') && !hashTargetSelector.startsWith('#issuecomment-')) { + return; + } + + while (true) { + // use getElementById to avoid querySelector throws an error when the hash is invalid + // eslint-disable-next-line unicorn/prefer-query-selector + const targetElement = document.getElementById(hashTargetSelector.substring(1)); + if (targetElement) { + targetElement.scrollIntoView(); + return; + } + + // the button will be refreshed after each "load more", so query it every time + const showMoreButton = document.querySelector('#diff-show-more-files'); + if (!showMoreButton) { + return; // nothing more to load + } + + // Load more files, await ensures we don't block progress + const ok = await loadMoreFiles(showMoreButton); + if (!ok) return; // failed to load more files + } +} + +function initRepoDiffHashChangeListener() { + window.addEventListener('hashchange', loadUntilFound); + loadUntilFound(); +} + export function initRepoDiffView() { - initRepoDiffConversationForm(); - if (!$('#diff-file-list').length) return; + initRepoDiffConversationForm(); // such form appears on the "conversation" page and "diff" page + + if (!document.querySelector('#diff-file-boxes')) return; + initRepoDiffConversationNav(); // "previous" and "next" buttons only appear on "diff" page initDiffFileTree(); - initDiffFileList(); initDiffCommitSelect(); initRepoDiffShowMore(); initDiffHeaderPopup(); - initRepoDiffFileViewToggle(); initViewedCheckboxListenerFor(); initExpandAndCollapseFilesButton(); + initRepoDiffHashChangeListener(); + + registerGlobalSelectorFunc('#diff-file-boxes .diff-file-box', initRepoDiffFileBox); + addDelegatedEventListener(document, 'click', '.fold-file', (el) => { + invertFileFolding(el.closest('.file-content'), el); + }); } diff --git a/web_src/js/features/repo-editor.ts b/web_src/js/features/repo-editor.ts index 64d0402d84..f3ca13460c 100644 --- a/web_src/js/features/repo-editor.ts +++ b/web_src/js/features/repo-editor.ts @@ -1,13 +1,13 @@ -import {htmlEscape} from 'escape-goat'; +import {html, htmlRaw} from '../utils/html.ts'; import {createCodeEditor} from './codeeditor.ts'; import {hideElem, queryElems, showElem, createElementFromHTML} from '../utils/dom.ts'; -import {initMarkupContent} from '../markup/content.ts'; import {attachRefIssueContextPopup} from './contextpopup.ts'; import {POST} from '../modules/fetch.ts'; import {initDropzone} from './dropzone.ts'; import {confirmModal} from './comp/ConfirmModal.ts'; import {applyAreYouSure, ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; +import {submitFormFetchAction} from './common-fetch-action.ts'; function initEditPreviewTab(elForm: HTMLFormElement) { const elTabMenu = elForm.querySelector('.repo-editor-menu'); @@ -38,9 +38,6 @@ export function initRepoEditor() { const dropzoneUpload = document.querySelector<HTMLElement>('.page-content.repository.editor.upload .dropzone'); if (dropzoneUpload) initDropzone(dropzoneUpload); - const editArea = document.querySelector<HTMLTextAreaElement>('.page-content.repository.editor textarea#edit_area'); - if (!editArea) return; - for (const el of queryElems<HTMLInputElement>(document, '.js-quick-pull-choice-option')) { el.addEventListener('input', () => { if (el.value === 'commit-to-new-branch') { @@ -55,6 +52,7 @@ export function initRepoEditor() { } const filenameInput = document.querySelector<HTMLInputElement>('#file-name'); + if (!filenameInput) return; function joinTreePath() { const parts = []; for (const el of document.querySelectorAll('.breadcrumb span.section')) { @@ -89,10 +87,10 @@ export function initRepoEditor() { if (i < parts.length - 1) { if (trimValue.length) { const linkElement = createElementFromHTML( - `<span class="section"><a href="#">${htmlEscape(value)}</a></span>`, + html`<span class="section"><a href="#">${value}</a></span>`, ); const dividerElement = createElementFromHTML( - `<div class="breadcrumb-divider">/</div>`, + html`<div class="breadcrumb-divider">/</div>`, ); links.push(linkElement); dividers.push(dividerElement); @@ -115,7 +113,7 @@ export function initRepoEditor() { if (!warningDiv) { warningDiv = document.createElement('div'); warningDiv.classList.add('ui', 'warning', 'message', 'flash-message', 'flash-warning', 'space-related'); - warningDiv.innerHTML = '<p>File path contains leading or trailing whitespace.</p>'; + warningDiv.innerHTML = html`<p>File path contains leading or trailing whitespace.</p>`; // Add display 'block' because display is set to 'none' in formantic\build\semantic.css warningDiv.style.display = 'block'; const inputContainer = document.querySelector('.repo-editor-header'); @@ -145,32 +143,34 @@ export function initRepoEditor() { }); const elForm = document.querySelector<HTMLFormElement>('.repository.editor .edit.form'); - initEditPreviewTab(elForm); - (async () => { - const editor = await createCodeEditor(editArea, filenameInput); + // on the upload page, there is no editor(textarea) + const editArea = document.querySelector<HTMLTextAreaElement>('.page-content.repository.editor textarea#edit_area'); + if (!editArea) return; - // Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage - // to enable or disable the commit button - const commitButton = document.querySelector<HTMLButtonElement>('#commit-button'); - const dirtyFileClass = 'dirty-file'; + // Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage + // to enable or disable the commit button + const commitButton = document.querySelector<HTMLButtonElement>('#commit-button'); + const dirtyFileClass = 'dirty-file'; - // Disabling the button at the start - if (document.querySelector<HTMLInputElement>('input[name="page_has_posted"]').value !== 'true') { - commitButton.disabled = true; - } + const syncCommitButtonState = () => { + const dirty = elForm.classList.contains(dirtyFileClass); + commitButton.disabled = !dirty; + }; + // Registering a custom listener for the file path and the file content + // FIXME: it is not quite right here (old bug), it causes double-init, the global areYouSure "dirty" class will also be added + applyAreYouSure(elForm, { + silent: true, + dirtyClass: dirtyFileClass, + fieldSelector: ':input:not(.commit-form-wrapper :input)', + change: syncCommitButtonState, + }); + syncCommitButtonState(); // disable the "commit" button when no content changes - // Registering a custom listener for the file path and the file content - // FIXME: it is not quite right here (old bug), it causes double-init, the global areYouSure "dirty" class will also be added - applyAreYouSure(elForm, { - silent: true, - dirtyClass: dirtyFileClass, - fieldSelector: ':input:not(.commit-form-wrapper :input)', - change($form) { - const dirty = $form[0]?.classList.contains(dirtyFileClass); - commitButton.disabled = !dirty; - }, - }); + initEditPreviewTab(elForm); + + (async () => { + const editor = await createCodeEditor(editArea, filenameInput); // Update the editor from query params, if available, // only after the dirtyFileClass initialization @@ -180,7 +180,7 @@ export function initRepoEditor() { editor.setValue(value); } - commitButton?.addEventListener('click', async (e) => { + commitButton.addEventListener('click', async (e) => { // A modal which asks if an empty file should be committed if (!editArea.value) { e.preventDefault(); @@ -189,15 +189,15 @@ export function initRepoEditor() { content: elForm.getAttribute('data-text-empty-confirm-content'), })) { ignoreAreYouSure(elForm); - elForm.submit(); + submitFormFetchAction(elForm); } } }); })(); } -export function renderPreviewPanelContent(previewPanel: Element, content: string) { - previewPanel.innerHTML = content; - initMarkupContent(); +export function renderPreviewPanelContent(previewPanel: Element, htmlContent: string) { + // the content is from the server, so it is safe to use innerHTML + previewPanel.innerHTML = html`<div class="render-content markup">${htmlRaw(htmlContent)}</div>`; attachRefIssueContextPopup(previewPanel.querySelectorAll('p .ref-issue')); } diff --git a/web_src/js/features/repo-findfile.ts b/web_src/js/features/repo-findfile.ts index 6500978bc8..59c827126f 100644 --- a/web_src/js/features/repo-findfile.ts +++ b/web_src/js/features/repo-findfile.ts @@ -4,13 +4,15 @@ import {pathEscapeSegments} from '../utils/url.ts'; import {GET} from '../modules/fetch.ts'; const threshold = 50; -let files = []; -let repoFindFileInput, repoFindFileTableBody, repoFindFileNoResult; +let files: Array<string> = []; +let repoFindFileInput: HTMLInputElement; +let repoFindFileTableBody: HTMLElement; +let repoFindFileNoResult: HTMLElement; // return the case-insensitive sub-match result as an array: [unmatched, matched, unmatched, matched, ...] // res[even] is unmatched, res[odd] is matched, see unit tests for examples // argument subLower must be a lower-cased string. -export function strSubMatch(full, subLower) { +export function strSubMatch(full: string, subLower: string) { const res = ['']; let i = 0, j = 0; const fullLower = full.toLowerCase(); @@ -38,7 +40,7 @@ export function strSubMatch(full, subLower) { return res; } -export function calcMatchedWeight(matchResult) { +export function calcMatchedWeight(matchResult: Array<any>) { let weight = 0; for (let i = 0; i < matchResult.length; i++) { if (i % 2 === 1) { // matches are on odd indices, see strSubMatch @@ -49,7 +51,7 @@ export function calcMatchedWeight(matchResult) { return weight; } -export function filterRepoFilesWeighted(files, filter) { +export function filterRepoFilesWeighted(files: Array<string>, filter: string) { let filterResult = []; if (filter) { const filterLower = filter.toLowerCase(); @@ -71,7 +73,7 @@ export function filterRepoFilesWeighted(files, filter) { return filterResult; } -function filterRepoFiles(filter) { +function filterRepoFiles(filter: string) { const treeLink = repoFindFileInput.getAttribute('data-url-tree-link'); repoFindFileTableBody.innerHTML = ''; diff --git a/web_src/js/features/repo-graph.ts b/web_src/js/features/repo-graph.ts index 6d1629a1c1..7579ee42c6 100644 --- a/web_src/js/features/repo-graph.ts +++ b/web_src/js/features/repo-graph.ts @@ -83,8 +83,8 @@ export function initRepoGraphGit() { } const flowSelectRefsDropdown = document.querySelector('#flow-select-refs-dropdown'); - fomanticQuery(flowSelectRefsDropdown).dropdown('set selected', dropdownSelected); - fomanticQuery(flowSelectRefsDropdown).dropdown({ + const $dropdown = fomanticQuery(flowSelectRefsDropdown); + $dropdown.dropdown({ clearable: true, fullTextSeach: 'exact', onRemove(toRemove: string) { @@ -110,6 +110,7 @@ export function initRepoGraphGit() { updateGraph(); }, }); + $dropdown.dropdown('set selected', dropdownSelected); graphContainer.addEventListener('mouseenter', (e: DOMEvent<MouseEvent>) => { if (e.target.matches('#rev-list li')) { diff --git a/web_src/js/features/repo-home.ts b/web_src/js/features/repo-home.ts index 9c1e317486..04a1288626 100644 --- a/web_src/js/features/repo-home.ts +++ b/web_src/js/features/repo-home.ts @@ -89,10 +89,10 @@ export function initRepoTopicBar() { url: `${appSubUrl}/explore/topics/search?q={query}`, throttle: 500, cache: false, - onResponse(res) { + onResponse(this: any, res: any) { const formattedResponse = { success: false, - results: [], + results: [] as Array<Record<string, any>>, }; const query = stripTags(this.urlData.query.trim()); let found_query = false; @@ -134,12 +134,12 @@ export function initRepoTopicBar() { return formattedResponse; }, }, - onLabelCreate(value) { + onLabelCreate(value: string) { value = value.toLowerCase().trim(); this.attr('data-value', value).contents().first().replaceWith(value); return fomanticQuery(this); }, - onAdd(addedValue, _addedText, $addedChoice) { + onAdd(addedValue: string, _addedText: any, $addedChoice: any) { addedValue = addedValue.toLowerCase().trim(); $addedChoice[0].setAttribute('data-value', addedValue); $addedChoice[0].setAttribute('data-text', addedValue); diff --git a/web_src/js/features/repo-issue-content.ts b/web_src/js/features/repo-issue-content.ts index 88672cc255..056b810be8 100644 --- a/web_src/js/features/repo-issue-content.ts +++ b/web_src/js/features/repo-issue-content.ts @@ -1,20 +1,17 @@ -import $ from 'jquery'; import {svg} from '../svg.ts'; import {showErrorToast} from '../modules/toast.ts'; import {GET, POST} from '../modules/fetch.ts'; -import {showElem} from '../utils/dom.ts'; +import {createElementFromHTML, showElem} from '../utils/dom.ts'; import {parseIssuePageInfo} from '../utils.ts'; +import {fomanticQuery} from '../modules/fomantic/base.ts'; -let i18nTextEdited; -let i18nTextOptions; -let i18nTextDeleteFromHistory; -let i18nTextDeleteFromHistoryConfirm; +let i18nTextEdited: string; +let i18nTextOptions: string; +let i18nTextDeleteFromHistory: string; +let i18nTextDeleteFromHistoryConfirm: string; -function showContentHistoryDetail(issueBaseUrl, commentId, historyId, itemTitleHtml) { - let $dialog = $('.content-history-detail-dialog'); - if ($dialog.length) return; - - $dialog = $(` +function showContentHistoryDetail(issueBaseUrl: string, commentId: string, historyId: string, itemTitleHtml: string) { + const elDetailDialog = createElementFromHTML(` <div class="ui modal content-history-detail-dialog"> ${svg('octicon-x', 16, 'close icon inside')} <div class="header tw-flex tw-items-center tw-justify-between"> @@ -29,11 +26,14 @@ function showContentHistoryDetail(issueBaseUrl, commentId, historyId, itemTitleH </div> <div class="comment-diff-data is-loading"></div> </div>`); - $dialog.appendTo($('body')); - $dialog.find('.dialog-header-options').dropdown({ + document.body.append(elDetailDialog); + const elOptionsDropdown = elDetailDialog.querySelector('.ui.dropdown.dialog-header-options'); + const $fomanticDialog = fomanticQuery(elDetailDialog); + const $fomanticDropdownOptions = fomanticQuery(elOptionsDropdown); + $fomanticDropdownOptions.dropdown({ showOnFocus: false, allowReselection: true, - async onChange(_value, _text, $item) { + async onChange(_value: string, _text: string, $item: any) { const optionItem = $item.data('option-item'); if (optionItem === 'delete') { if (window.confirm(i18nTextDeleteFromHistoryConfirm)) { @@ -46,7 +46,7 @@ function showContentHistoryDetail(issueBaseUrl, commentId, historyId, itemTitleH const resp = await response.json(); if (resp.ok) { - $dialog.modal('hide'); + $fomanticDialog.modal('hide'); } else { showErrorToast(resp.message); } @@ -60,10 +60,10 @@ function showContentHistoryDetail(issueBaseUrl, commentId, historyId, itemTitleH } }, onHide() { - $(this).dropdown('clear', true); + $fomanticDropdownOptions.dropdown('clear', true); }, }); - $dialog.modal({ + $fomanticDialog.modal({ async onShow() { try { const params = new URLSearchParams(); @@ -74,25 +74,25 @@ function showContentHistoryDetail(issueBaseUrl, commentId, historyId, itemTitleH const response = await GET(url); const resp = await response.json(); - const commentDiffData = $dialog.find('.comment-diff-data')[0]; - commentDiffData?.classList.remove('is-loading'); + const commentDiffData = elDetailDialog.querySelector('.comment-diff-data'); + commentDiffData.classList.remove('is-loading'); commentDiffData.innerHTML = resp.diffHtml; // there is only one option "item[data-option-item=delete]", so the dropdown can be entirely shown/hidden. if (resp.canSoftDelete) { - showElem($dialog.find('.dialog-header-options')); + showElem(elOptionsDropdown); } } catch (error) { console.error('Error:', error); } }, onHidden() { - $dialog.remove(); + $fomanticDialog.remove(); }, }).modal('show'); } -function showContentHistoryMenu(issueBaseUrl, $item, commentId) { - const $headerLeft = $item.find('.comment-header-left'); +function showContentHistoryMenu(issueBaseUrl: string, elCommentItem: Element, commentId: string) { + const elHeaderLeft = elCommentItem.querySelector('.comment-header-left'); const menuHtml = ` <div class="ui dropdown interact-fg content-history-menu" data-comment-id="${commentId}"> • ${i18nTextEdited}${svg('octicon-triangle-down', 14, 'dropdown icon')} @@ -100,9 +100,12 @@ function showContentHistoryMenu(issueBaseUrl, $item, commentId) { </div> </div>`; - $headerLeft.find(`.content-history-menu`).remove(); - $headerLeft.append($(menuHtml)); - $headerLeft.find('.dropdown').dropdown({ + elHeaderLeft.querySelector(`.ui.dropdown.content-history-menu`)?.remove(); // remove the old one if exists + elHeaderLeft.append(createElementFromHTML(menuHtml)); + + const elDropdown = elHeaderLeft.querySelector('.ui.dropdown.content-history-menu'); + const $fomanticDropdown = fomanticQuery(elDropdown); + $fomanticDropdown.dropdown({ action: 'hide', apiSettings: { cache: false, @@ -110,9 +113,9 @@ function showContentHistoryMenu(issueBaseUrl, $item, commentId) { }, saveRemoteData: false, onHide() { - $(this).dropdown('change values', null); + $fomanticDropdown.dropdown('change values', null); }, - onChange(value, itemHtml, $item) { + onChange(value: string, itemHtml: string, $item: any) { if (value && !$item.find('[data-history-is-deleted=1]').length) { showContentHistoryDetail(issueBaseUrl, commentId, value, itemHtml); } @@ -124,9 +127,9 @@ export async function initRepoIssueContentHistory() { const issuePageInfo = parseIssuePageInfo(); if (!issuePageInfo.issueNumber) return; - const $itemIssue = $('.repository.issue .timeline-item.comment.first'); // issue(PR) main content - const $comments = $('.repository.issue .comment-list .comment'); // includes: issue(PR) comments, review comments, code comments - if (!$itemIssue.length && !$comments.length) return; + const elIssueDescription = document.querySelector('.repository.issue .timeline-item.comment.first'); // issue(PR) main content + const elComments = document.querySelectorAll('.repository.issue .comment-list .comment'); // includes: issue(PR) comments, review comments, code comments + if (!elIssueDescription && !elComments.length) return; const issueBaseUrl = `${issuePageInfo.repoLink}/issues/${issuePageInfo.issueNumber}`; @@ -139,13 +142,13 @@ export async function initRepoIssueContentHistory() { i18nTextDeleteFromHistoryConfirm = resp.i18n.textDeleteFromHistoryConfirm; i18nTextOptions = resp.i18n.textOptions; - if (resp.editedHistoryCountMap[0] && $itemIssue.length) { - showContentHistoryMenu(issueBaseUrl, $itemIssue, '0'); + if (resp.editedHistoryCountMap[0] && elIssueDescription) { + showContentHistoryMenu(issueBaseUrl, elIssueDescription, '0'); } for (const [commentId, _editedCount] of Object.entries(resp.editedHistoryCountMap)) { if (commentId === '0') continue; - const $itemComment = $(`#issuecomment-${commentId}`); - showContentHistoryMenu(issueBaseUrl, $itemComment, commentId); + const elIssueComment = document.querySelector(`#issuecomment-${commentId}`); + if (elIssueComment) showContentHistoryMenu(issueBaseUrl, elIssueComment, commentId); } } catch (error) { console.error('Error:', error); diff --git a/web_src/js/features/repo-issue-edit.ts b/web_src/js/features/repo-issue-edit.ts index cf4c223e03..e89e5a787a 100644 --- a/web_src/js/features/repo-issue-edit.ts +++ b/web_src/js/features/repo-issue-edit.ts @@ -2,34 +2,36 @@ import {handleReply} from './repo-issue.ts'; import {getComboMarkdownEditor, initComboMarkdownEditor, ComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts'; import {POST} from '../modules/fetch.ts'; import {showErrorToast} from '../modules/toast.ts'; -import {hideElem, querySingleVisibleElem, showElem} from '../utils/dom.ts'; +import {hideElem, querySingleVisibleElem, showElem, type DOMEvent} from '../utils/dom.ts'; import {attachRefIssueContextPopup} from './contextpopup.ts'; -import {initCommentContent, initMarkupContent} from '../markup/content.ts'; import {triggerUploadStateChanged} from './comp/EditorUpload.ts'; import {convertHtmlToMarkdown} from '../markup/html2markdown.ts'; import {applyAreYouSure, reinitializeAreYouSure} from '../vendor/jquery.are-you-sure.ts'; -async function tryOnEditContent(e) { +async function tryOnEditContent(e: DOMEvent<MouseEvent>) { const clickTarget = e.target.closest('.edit-content'); if (!clickTarget) return; e.preventDefault(); - const segment = clickTarget.closest('.header').nextElementSibling; + const segment = clickTarget.closest('.comment-header').nextElementSibling; const editContentZone = segment.querySelector('.edit-content-zone'); const renderContent = segment.querySelector('.render-content'); const rawContent = segment.querySelector('.raw-content'); let comboMarkdownEditor : ComboMarkdownEditor; - const cancelAndReset = (e) => { + const cancelAndReset = (e: Event) => { e.preventDefault(); showElem(renderContent); hideElem(editContentZone); comboMarkdownEditor.dropzoneReloadFiles(); }; - const saveAndRefresh = async (e) => { + const saveAndRefresh = async (e: Event) => { e.preventDefault(); + // we are already in a form, do not bubble up to the document otherwise there will be other "form submit handlers" + // at the moment, the form submit event conflicts with initRepoDiffConversationForm (global '.conversation-holder form' event handler) + e.stopPropagation(); renderContent.classList.add('is-loading'); showElem(renderContent); hideElem(editContentZone); @@ -57,7 +59,7 @@ async function tryOnEditContent(e) { } else { renderContent.innerHTML = data.content; rawContent.textContent = comboMarkdownEditor.value(); - const refIssues = renderContent.querySelectorAll('p .ref-issue'); + const refIssues = renderContent.querySelectorAll<HTMLElement>('p .ref-issue'); attachRefIssueContextPopup(refIssues); } const content = segment; @@ -71,8 +73,6 @@ async function tryOnEditContent(e) { content.querySelector('.dropzone-attachments').outerHTML = data.attachments; } comboMarkdownEditor.dropzoneSubmitReload(); - initMarkupContent(); - initCommentContent(); } catch (error) { showErrorToast(`Failed to save the content: ${error}`); console.error(error); @@ -122,7 +122,7 @@ function extractSelectedMarkdown(container: HTMLElement) { return convertHtmlToMarkdown(el); } -async function tryOnQuoteReply(e) { +async function tryOnQuoteReply(e: Event) { const clickTarget = (e.target as HTMLElement).closest('.quote-reply'); if (!clickTarget) return; @@ -132,11 +132,11 @@ async function tryOnQuoteReply(e) { const targetMarkupToQuote = targetRawToQuote.parentElement.querySelector<HTMLElement>('.render-content.markup'); let contentToQuote = extractSelectedMarkdown(targetMarkupToQuote); if (!contentToQuote) contentToQuote = targetRawToQuote.textContent; - const quotedContent = `${contentToQuote.replace(/^/mg, '> ')}\n`; + const quotedContent = `${contentToQuote.replace(/^/mg, '> ')}\n\n`; let editor; if (clickTarget.classList.contains('quote-reply-diff')) { - const replyBtn = clickTarget.closest('.comment-code-cloud').querySelector('button.comment-form-reply'); + const replyBtn = clickTarget.closest('.comment-code-cloud').querySelector<HTMLElement>('button.comment-form-reply'); editor = await handleReply(replyBtn); } else { // for normal issue/comment page diff --git a/web_src/js/features/repo-issue-list.ts b/web_src/js/features/repo-issue-list.ts index 74d4362bfd..762fbf51bb 100644 --- a/web_src/js/features/repo-issue-list.ts +++ b/web_src/js/features/repo-issue-list.ts @@ -1,12 +1,13 @@ import {updateIssuesMeta} from './repo-common.ts'; -import {toggleElem, isElemHidden, queryElems} from '../utils/dom.ts'; -import {htmlEscape} from 'escape-goat'; +import {toggleElem, queryElems, isElemVisible} from '../utils/dom.ts'; +import {html} from '../utils/html.ts'; import {confirmModal} from './comp/ConfirmModal.ts'; import {showErrorToast} from '../modules/toast.ts'; import {createSortable} from '../modules/sortable.ts'; import {DELETE, POST} from '../modules/fetch.ts'; import {parseDom} from '../utils.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; +import type {SortableEvent} from 'sortablejs'; function initRepoIssueListCheckboxes() { const issueSelectAll = document.querySelector<HTMLInputElement>('.issue-checkbox-all'); @@ -32,8 +33,8 @@ function initRepoIssueListCheckboxes() { toggleElem('#issue-filters', !anyChecked); toggleElem('#issue-actions', anyChecked); // there are two panels but only one select-all checkbox, so move the checkbox to the visible panel - const panels = document.querySelectorAll('#issue-filters, #issue-actions'); - const visiblePanel = Array.from(panels).find((el) => !isElemHidden(el)); + const panels = document.querySelectorAll<HTMLElement>('#issue-filters, #issue-actions'); + const visiblePanel = Array.from(panels).find((el) => isElemVisible(el)); const toolbarLeft = visiblePanel.querySelector('.issue-list-toolbar-left'); toolbarLeft.prepend(issueSelectAll); }; @@ -104,7 +105,7 @@ function initDropdownUserRemoteSearch(el: Element) { $searchDropdown.dropdown('setting', { fullTextSearch: true, selectOnKeydown: false, - action: (_text, value) => { + action: (_text: string, value: string) => { window.location.href = actionJumpUrl.replace('{username}', encodeURIComponent(value)); }, }); @@ -133,14 +134,14 @@ function initDropdownUserRemoteSearch(el: Element) { $searchDropdown.dropdown('setting', 'apiSettings', { cache: false, url: `${searchUrl}&q={query}`, - onResponse(resp) { + onResponse(resp: any) { // the content is provided by backend IssuePosters handler processedResults.length = 0; for (const item of resp.results) { - let html = `<img class="ui avatar tw-align-middle" src="${htmlEscape(item.avatar_link)}" aria-hidden="true" alt="" width="20" height="20"><span class="gt-ellipsis">${htmlEscape(item.username)}</span>`; - if (item.full_name) html += `<span class="search-fullname tw-ml-2">${htmlEscape(item.full_name)}</span>`; + let nameHtml = html`<img class="ui avatar tw-align-middle" src="${item.avatar_link}" aria-hidden="true" alt width="20" height="20"><span class="gt-ellipsis">${item.username}</span>`; + if (item.full_name) nameHtml += html`<span class="search-fullname tw-ml-2">${item.full_name}</span>`; if (selectedUsername.toLowerCase() === item.username.toLowerCase()) selectedUsername = item.username; - processedResults.push({value: item.username, name: html}); + processedResults.push({value: item.username, name: nameHtml}); } resp.results = processedResults; return resp; @@ -153,7 +154,7 @@ function initDropdownUserRemoteSearch(el: Element) { const dropdownSetup = {...$searchDropdown.dropdown('internal', 'setup')}; const dropdownTemplates = $searchDropdown.dropdown('setting', 'templates'); $searchDropdown.dropdown('internal', 'setup', dropdownSetup); - dropdownSetup.menu = function (values) { + dropdownSetup.menu = function (values: any) { // remove old dynamic items for (const el of elMenu.querySelectorAll(':scope > .dynamic-item')) { el.remove(); @@ -193,7 +194,7 @@ function initPinRemoveButton() { } } -async function pinMoveEnd(e) { +async function pinMoveEnd(e: SortableEvent) { const url = e.item.getAttribute('data-move-url'); const id = Number(e.item.getAttribute('data-issue-id')); await POST(url, {data: {id, position: e.newIndex + 1}}); diff --git a/web_src/js/features/repo-issue-pr-form.ts b/web_src/js/features/repo-issue-pr-form.ts deleted file mode 100644 index 94a2857340..0000000000 --- a/web_src/js/features/repo-issue-pr-form.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {createApp} from 'vue'; -import PullRequestMergeForm from '../components/PullRequestMergeForm.vue'; - -export function initRepoPullRequestMergeForm() { - const el = document.querySelector('#pull-request-merge-form'); - if (!el) return; - - const view = createApp(PullRequestMergeForm); - view.mount(el); -} diff --git a/web_src/js/features/repo-issue-pr-status.ts b/web_src/js/features/repo-issue-pr-status.ts deleted file mode 100644 index 8426b389f0..0000000000 --- a/web_src/js/features/repo-issue-pr-status.ts +++ /dev/null @@ -1,10 +0,0 @@ -export function initRepoPullRequestCommitStatus() { - for (const btn of document.querySelectorAll('.commit-status-hide-checks')) { - const panel = btn.closest('.commit-status-panel'); - const list = panel.querySelector<HTMLElement>('.commit-status-list'); - btn.addEventListener('click', () => { - list.style.maxHeight = list.style.maxHeight ? '' : '0px'; // toggle - btn.textContent = btn.getAttribute(list.style.maxHeight ? 'data-show-all' : 'data-hide-all'); - }); - } -} diff --git a/web_src/js/features/repo-issue-pull.ts b/web_src/js/features/repo-issue-pull.ts new file mode 100644 index 0000000000..c415dad08f --- /dev/null +++ b/web_src/js/features/repo-issue-pull.ts @@ -0,0 +1,133 @@ +import {createApp} from 'vue'; +import PullRequestMergeForm from '../components/PullRequestMergeForm.vue'; +import {GET, POST} from '../modules/fetch.ts'; +import {fomanticQuery} from '../modules/fomantic/base.ts'; +import {createElementFromHTML} from '../utils/dom.ts'; + +function initRepoPullRequestUpdate(el: HTMLElement) { + const prUpdateButtonContainer = el.querySelector('#update-pr-branch-with-base'); + if (!prUpdateButtonContainer) return; + + const prUpdateButton = prUpdateButtonContainer.querySelector<HTMLButtonElement>(':scope > button'); + const prUpdateDropdown = prUpdateButtonContainer.querySelector(':scope > .ui.dropdown'); + prUpdateButton.addEventListener('click', async function (e) { + e.preventDefault(); + const redirect = this.getAttribute('data-redirect'); + this.classList.add('is-loading'); + let response: Response; + try { + response = await POST(this.getAttribute('data-do')); + } catch (error) { + console.error(error); + } finally { + this.classList.remove('is-loading'); + } + let data: Record<string, any>; + try { + data = await response?.json(); // the response is probably not a JSON + } catch (error) { + console.error(error); + } + if (data?.redirect) { + window.location.href = data.redirect; + } else if (redirect) { + window.location.href = redirect; + } else { + window.location.reload(); + } + }); + + fomanticQuery(prUpdateDropdown).dropdown({ + onChange(_text: string, _value: string, $choice: any) { + const choiceEl = $choice[0]; + const url = choiceEl.getAttribute('data-do'); + if (url) { + const buttonText = prUpdateButton.querySelector('.button-text'); + if (buttonText) { + buttonText.textContent = choiceEl.textContent; + } + prUpdateButton.setAttribute('data-do', url); + } + }, + }); +} + +function initRepoPullRequestCommitStatus(el: HTMLElement) { + for (const btn of el.querySelectorAll('.commit-status-hide-checks')) { + const panel = btn.closest('.commit-status-panel'); + const list = panel.querySelector<HTMLElement>('.commit-status-list'); + btn.addEventListener('click', () => { + list.style.maxHeight = list.style.maxHeight ? '' : '0px'; // toggle + btn.textContent = btn.getAttribute(list.style.maxHeight ? 'data-show-all' : 'data-hide-all'); + }); + } +} + +function initRepoPullRequestMergeForm(box: HTMLElement) { + const el = box.querySelector('#pull-request-merge-form'); + if (!el) return; + + const view = createApp(PullRequestMergeForm); + view.mount(el); +} + +function executeScripts(elem: HTMLElement) { + for (const oldScript of elem.querySelectorAll('script')) { + // TODO: that's the only way to load the data for the merge form. In the future + // we need to completely decouple the page data and embedded script + // eslint-disable-next-line github/no-dynamic-script-tag + const newScript = document.createElement('script'); + for (const attr of oldScript.attributes) { + if (attr.name === 'type' && attr.value === 'module') continue; + newScript.setAttribute(attr.name, attr.value); + } + newScript.text = oldScript.text; + document.body.append(newScript); + } +} + +export function initRepoPullMergeBox(el: HTMLElement) { + initRepoPullRequestCommitStatus(el); + initRepoPullRequestUpdate(el); + initRepoPullRequestMergeForm(el); + + const reloadingIntervalValue = el.getAttribute('data-pull-merge-box-reloading-interval'); + if (!reloadingIntervalValue) return; + + const reloadingInterval = parseInt(reloadingIntervalValue); + const pullLink = el.getAttribute('data-pull-link'); + let timerId: number; + + let reloadMergeBox: () => Promise<void>; + const stopReloading = () => { + if (!timerId) return; + clearTimeout(timerId); + timerId = null; + }; + const startReloading = () => { + if (timerId) return; + setTimeout(reloadMergeBox, reloadingInterval); + }; + const onVisibilityChange = () => { + if (document.hidden) { + stopReloading(); + } else { + startReloading(); + } + }; + reloadMergeBox = async () => { + const resp = await GET(`${pullLink}/merge_box`); + stopReloading(); + if (!resp.ok) { + startReloading(); + return; + } + document.removeEventListener('visibilitychange', onVisibilityChange); + const newElem = createElementFromHTML(await resp.text()); + executeScripts(newElem); + el.replaceWith(newElem); + }; + + document.addEventListener('visibilitychange', onVisibilityChange); + startReloading(); +} diff --git a/web_src/js/features/repo-issue-sidebar-combolist.ts b/web_src/js/features/repo-issue-sidebar-combolist.ts index 24d620547f..f25c0a77c6 100644 --- a/web_src/js/features/repo-issue-sidebar-combolist.ts +++ b/web_src/js/features/repo-issue-sidebar-combolist.ts @@ -1,6 +1,6 @@ import {fomanticQuery} from '../modules/fomantic/base.ts'; import {POST} from '../modules/fetch.ts'; -import {queryElemChildren, queryElems, toggleElem} from '../utils/dom.ts'; +import {addDelegatedEventListener, queryElemChildren, queryElems, toggleElem} from '../utils/dom.ts'; // if there are draft comments, confirm before reloading, to avoid losing comments function issueSidebarReloadConfirmDraftComment() { @@ -22,7 +22,7 @@ function issueSidebarReloadConfirmDraftComment() { window.location.reload(); } -class IssueSidebarComboList { +export class IssueSidebarComboList { updateUrl: string; updateAlgo: string; selectionMode: string; @@ -30,9 +30,11 @@ class IssueSidebarComboList { elList: HTMLElement; elComboValue: HTMLInputElement; initialValues: string[]; + container: HTMLElement; - constructor(private container: HTMLElement) { - this.updateUrl = this.container.getAttribute('data-update-url'); + constructor(container: HTMLElement) { + this.container = container; + this.updateUrl = container.getAttribute('data-update-url'); this.updateAlgo = container.getAttribute('data-update-algo'); this.selectionMode = container.getAttribute('data-selection-mode'); if (!['single', 'multiple'].includes(this.selectionMode)) throw new Error(`Invalid data-update-on: ${this.selectionMode}`); @@ -46,7 +48,7 @@ class IssueSidebarComboList { return Array.from(this.elDropdown.querySelectorAll('.menu > .item.checked'), (el) => el.getAttribute('data-value')); } - updateUiList(changedValues) { + updateUiList(changedValues: Array<string>) { const elEmptyTip = this.elList.querySelector('.item.empty-list'); queryElemChildren(this.elList, '.item:not(.empty-list)', (el) => el.remove()); for (const value of changedValues) { @@ -60,7 +62,7 @@ class IssueSidebarComboList { toggleElem(elEmptyTip, !hasItems); } - async updateToBackend(changedValues) { + async updateToBackend(changedValues: Array<string>) { if (this.updateAlgo === 'diff') { for (const value of this.initialValues) { if (!changedValues.includes(value)) { @@ -93,9 +95,7 @@ class IssueSidebarComboList { } } - async onItemClick(e) { - const elItem = (e.target as HTMLElement).closest('.item'); - if (!elItem) return; + async onItemClick(elItem: HTMLElement, e: Event) { e.preventDefault(); if (elItem.hasAttribute('data-can-change') && elItem.getAttribute('data-can-change') !== 'true') return; @@ -144,16 +144,13 @@ class IssueSidebarComboList { } this.initialValues = this.collectCheckedValues(); - this.elDropdown.addEventListener('click', (e) => this.onItemClick(e)); + addDelegatedEventListener(this.elDropdown, 'click', '.item', (el, e) => this.onItemClick(el, e)); fomanticQuery(this.elDropdown).dropdown('setting', { action: 'nothing', // do not hide the menu if user presses Enter fullTextSearch: 'exact', + hideDividers: 'empty', onHide: () => this.onHide(), }); } } - -export function initIssueSidebarComboList(container: HTMLElement) { - new IssueSidebarComboList(container).init(); -} diff --git a/web_src/js/features/repo-issue-sidebar.md b/web_src/js/features/repo-issue-sidebar.md index 6de013f1c2..e1ce0927e1 100644 --- a/web_src/js/features/repo-issue-sidebar.md +++ b/web_src/js/features/repo-issue-sidebar.md @@ -22,10 +22,13 @@ A sidebar combo (dropdown+list) is like this: When the selected items change, the `combo-value` input will be updated. If there is `data-update-url`, it also calls backend to attach/detach the changed items. -Also, the changed items will be syncronized to the `ui list` items. +Also, the changed items will be synchronized to the `ui list` items. The items with the same data-scope only allow one selected at a time. The dropdown selection could work in 2 modes: * single: only one item could be selected, it updates immediately when the item is selected. * multiple: multiple items could be selected, it defers the update until the dropdown is hidden. + +When using "scrolling menu", the items must be in the same level, +otherwise keyboard (ArrowUp/ArrowDown/Enter) won't work. diff --git a/web_src/js/features/repo-issue-sidebar.ts b/web_src/js/features/repo-issue-sidebar.ts index ef2b7d143c..290e1ae000 100644 --- a/web_src/js/features/repo-issue-sidebar.ts +++ b/web_src/js/features/repo-issue-sidebar.ts @@ -1,17 +1,15 @@ -import $ from 'jquery'; import {POST} from '../modules/fetch.ts'; import {queryElems, toggleElem} from '../utils/dom.ts'; -import {initIssueSidebarComboList} from './repo-issue-sidebar-combolist.ts'; +import {IssueSidebarComboList} from './repo-issue-sidebar-combolist.ts'; function initBranchSelector() { // TODO: RemoveIssueRef: see "repo/issue/branch_selector_field.tmpl" - const elSelectBranch = document.querySelector('.ui.dropdown.select-branch'); + const elSelectBranch = document.querySelector('.ui.dropdown.select-branch.branch-selector-dropdown'); if (!elSelectBranch) return; const urlUpdateIssueRef = elSelectBranch.getAttribute('data-url-update-issueref'); - const $selectBranch = $(elSelectBranch); - const $branchMenu = $selectBranch.find('.reference-list-menu'); - $branchMenu.find('.item:not(.no-select)').on('click', async function (e) { + const elBranchMenu = elSelectBranch.querySelector('.reference-list-menu'); + queryElems(elBranchMenu, '.item:not(.no-select)', (el) => el.addEventListener('click', async function (e) { e.preventDefault(); const selectedValue = this.getAttribute('data-id'); // eg: "refs/heads/my-branch" const selectedText = this.getAttribute('data-name'); // eg: "my-branch" @@ -29,7 +27,7 @@ function initBranchSelector() { document.querySelector<HTMLInputElement>(selectedHiddenSelector).value = selectedValue; elSelectBranch.querySelector('.text-branch-name').textContent = selectedText; } - }); + })); } function initRepoIssueDue() { @@ -50,5 +48,5 @@ export function initRepoIssueSidebar() { initRepoIssueDue(); // init the combo list: a dropdown for selecting items, and a list for showing selected items and related actions - queryElems<HTMLElement>(document, '.issue-sidebar-combo', (el) => initIssueSidebarComboList(el)); + queryElems<HTMLElement>(document, '.issue-sidebar-combo', (el) => new IssueSidebarComboList(el).init()); } diff --git a/web_src/js/features/repo-issue.ts b/web_src/js/features/repo-issue.ts index a9dda39a7f..49e8fc40a2 100644 --- a/web_src/js/features/repo-issue.ts +++ b/web_src/js/features/repo-issue.ts @@ -1,5 +1,4 @@ -import $ from 'jquery'; -import {htmlEscape} from 'escape-goat'; +import {html, htmlEscape} from '../utils/html.ts'; import {createTippy, showTemporaryTooltip} from '../modules/tippy.ts'; import { addDelegatedEventListener, @@ -18,38 +17,40 @@ import {showErrorToast} from '../modules/toast.ts'; import {initRepoIssueSidebar} from './repo-issue-sidebar.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; import {ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts'; +import {registerGlobalInitFunc} from '../modules/observer.ts'; const {appSubUrl} = window.config; -export function initRepoIssueSidebarList() { +export function initRepoIssueSidebarDependency() { + const elDropdown = document.querySelector('#new-dependency-drop-list'); + if (!elDropdown) return; + const issuePageInfo = parseIssuePageInfo(); - const crossRepoSearch = $('#crossRepoSearch').val(); + const crossRepoSearch = elDropdown.getAttribute('data-issue-cross-repo-search'); let issueSearchUrl = `${issuePageInfo.repoLink}/issues/search?q={query}&type=${issuePageInfo.issueDependencySearchType}`; if (crossRepoSearch === 'true') { issueSearchUrl = `${appSubUrl}/issues/search?q={query}&priority_repo_id=${issuePageInfo.repoId}&type=${issuePageInfo.issueDependencySearchType}`; } - fomanticQuery('#new-dependency-drop-list').dropdown({ + fomanticQuery(elDropdown).dropdown({ fullTextSearch: true, apiSettings: { + cache: false, + rawResponse: true, url: issueSearchUrl, - onResponse(response) { - const filteredResponse = {success: true, results: []}; - const currIssueId = $('#new-dependency-drop-list').data('issue-id'); + onResponse(response: any) { + const filteredResponse = {success: true, results: [] as Array<Record<string, any>>}; + const currIssueId = elDropdown.getAttribute('data-issue-id'); // Parse the response from the api to work with our dropdown - $.each(response, (_i, issue) => { + for (const issue of response) { // Don't list current issue in the dependency list. - if (issue.id === currIssueId) { - return; - } + if (String(issue.id) === currIssueId) continue; filteredResponse.results.push({ - name: `<div class="gt-ellipsis">#${issue.number} ${htmlEscape(issue.title)}</div> -<div class="text small tw-break-anywhere">${htmlEscape(issue.repository.full_name)}</div>`, value: issue.id, + name: html`<div class="gt-ellipsis">#${issue.number} ${issue.title}</div><div class="text small tw-break-anywhere">${issue.repository.full_name}</div>`, }); - }); + } return filteredResponse; }, - cache: false, }, }); } @@ -181,24 +182,6 @@ export function initRepoIssueCommentDelete() { }); } -export function initRepoIssueDependencyDelete() { - // Delete Issue dependency - $(document).on('click', '.delete-dependency-button', (e) => { - const id = e.currentTarget.getAttribute('data-id'); - const type = e.currentTarget.getAttribute('data-type'); - - $('.remove-dependency').modal({ - closable: false, - duration: 200, - onApprove: () => { - $('#removeDependencyID').val(id); - $('#dependencyType').val(type); - $('#removeDependencyForm').trigger('submit'); - }, - }).modal('show'); - }); -} - export function initRepoIssueCodeCommentCancel() { // Cancel inline code comment document.addEventListener('click', (e: DOMEvent<MouseEvent>) => { @@ -214,59 +197,6 @@ export function initRepoIssueCodeCommentCancel() { }); } -export function initRepoPullRequestUpdate() { - // Pull Request update button - const pullUpdateButton = document.querySelector('.update-button > button'); - if (!pullUpdateButton) return; - - pullUpdateButton.addEventListener('click', async function (e) { - e.preventDefault(); - const redirect = this.getAttribute('data-redirect'); - this.classList.add('is-loading'); - let response: Response; - try { - response = await POST(this.getAttribute('data-do')); - } catch (error) { - console.error(error); - } finally { - this.classList.remove('is-loading'); - } - let data: Record<string, any>; - try { - data = await response?.json(); // the response is probably not a JSON - } catch (error) { - console.error(error); - } - if (data?.redirect) { - window.location.href = data.redirect; - } else if (redirect) { - window.location.href = redirect; - } else { - window.location.reload(); - } - }); - - $('.update-button > .dropdown').dropdown({ - onChange(_text, _value, $choice) { - const choiceEl = $choice[0]; - const url = choiceEl.getAttribute('data-do'); - if (url) { - const buttonText = pullUpdateButton.querySelector('.button-text'); - if (buttonText) { - buttonText.textContent = choiceEl.textContent; - } - pullUpdateButton.setAttribute('data-do', url); - } - }, - }); -} - -export function initRepoPullRequestMergeInstruction() { - $('.show-instruction').on('click', () => { - toggleElem($('.instruct-content')); - }); -} - export function initRepoPullRequestAllowMaintainerEdit() { const wrapper = document.querySelector('#allow-edits-from-maintainers'); if (!wrapper) return; @@ -293,54 +223,8 @@ export function initRepoPullRequestAllowMaintainerEdit() { }); } -export function initRepoIssueReferenceRepositorySearch() { - $('.issue_reference_repository_search') - .dropdown({ - apiSettings: { - url: `${appSubUrl}/repo/search?q={query}&limit=20`, - onResponse(response) { - const filteredResponse = {success: true, results: []}; - $.each(response.data, (_r, repo) => { - filteredResponse.results.push({ - name: htmlEscape(repo.repository.full_name), - value: repo.repository.full_name, - }); - }); - return filteredResponse; - }, - cache: false, - }, - onChange(_value, _text, $choice) { - const $form = $choice.closest('form'); - if (!$form.length) return; - - $form[0].setAttribute('action', `${appSubUrl}/${_text}/issues/new`); - }, - fullTextSearch: true, - }); -} - -export function initRepoIssueWipTitle() { - $('.title_wip_desc > a').on('click', (e) => { - e.preventDefault(); - - const $issueTitle = $('#issue_title'); - $issueTitle.trigger('focus'); - const value = ($issueTitle.val() as string).trim().toUpperCase(); - - const wipPrefixes = $('.title_wip_desc').data('wip-prefixes'); - for (const prefix of wipPrefixes) { - if (value.startsWith(prefix.toUpperCase())) { - return; - } - } - - $issueTitle.val(`${wipPrefixes[0]} ${$issueTitle.val()}`); - }); -} - export function initRepoIssueComments() { - if (!$('.repository.view.issue .timeline').length) return; + if (!document.querySelector('.repository.view.issue .timeline')) return; document.addEventListener('click', (e: DOMEvent<MouseEvent>) => { const urlTarget = document.querySelector(':target'); @@ -352,15 +236,15 @@ export function initRepoIssueComments() { if (!/^(issue|pull)(comment)?-\d+$/.test(urlTargetId)) return; if (!e.target.closest(`#${urlTargetId}`)) { - const scrollPosition = $(window).scrollTop(); - window.location.hash = ''; - $(window).scrollTop(scrollPosition); + // if the user clicks outside the comment, remove the hash from the url + // use empty hash and state to avoid scrolling + window.location.hash = ' '; window.history.pushState(null, null, ' '); } }); } -export async function handleReply(el) { +export async function handleReply(el: HTMLElement) { const form = el.closest('.comment-code-cloud').querySelector('.comment-form'); const textarea = form.querySelector('textarea'); @@ -373,25 +257,13 @@ export async function handleReply(el) { export function initRepoPullRequestReview() { if (window.location.hash && window.location.hash.startsWith('#issuecomment-')) { - // set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing - if (window.history.scrollRestoration !== 'manual') { - window.history.scrollRestoration = 'manual'; - } const commentDiv = document.querySelector(window.location.hash); if (commentDiv) { // get the name of the parent id const groupID = commentDiv.closest('div[id^="code-comments-"]')?.getAttribute('id'); if (groupID && groupID.startsWith('code-comments-')) { const id = groupID.slice(14); - const ancestorDiffBox = commentDiv.closest('.diff-file-box'); - // on pages like conversation, there is no diff header - const diffHeader = ancestorDiffBox?.querySelector('.diff-file-header'); - - // offset is for scrolling - let offset = 30; - if (diffHeader) { - offset += $('.diff-detail-box').outerHeight() + $(diffHeader).outerHeight(); - } + const ancestorDiffBox = commentDiv.closest<HTMLElement>('.diff-file-box'); hideElem(`#show-outdated-${id}`); showElem(`#code-comments-${id}, #code-preview-${id}, #hide-outdated-${id}`); @@ -399,48 +271,45 @@ export function initRepoPullRequestReview() { if (ancestorDiffBox?.getAttribute('data-folded') === 'true') { setFileFolding(ancestorDiffBox, ancestorDiffBox.querySelector('.fold-file'), false); } - - window.scrollTo({ - top: $(commentDiv).offset().top - offset, - behavior: 'instant', - }); } + // set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing + if (window.history.scrollRestoration !== 'manual') window.history.scrollRestoration = 'manual'; + // wait for a while because some elements (eg: image, editor, etc.) may change the viewport's height. + setTimeout(() => commentDiv.scrollIntoView({block: 'start'}), 100); } } - $(document).on('click', '.show-outdated', function (e) { + addDelegatedEventListener(document, 'click', '.show-outdated', (el, e) => { e.preventDefault(); - const id = this.getAttribute('data-comment'); - hideElem(this); + const id = el.getAttribute('data-comment'); + hideElem(el); showElem(`#code-comments-${id}`); showElem(`#code-preview-${id}`); showElem(`#hide-outdated-${id}`); }); - $(document).on('click', '.hide-outdated', function (e) { + addDelegatedEventListener(document, 'click', '.hide-outdated', (el, e) => { e.preventDefault(); - const id = this.getAttribute('data-comment'); - hideElem(this); + const id = el.getAttribute('data-comment'); + hideElem(el); hideElem(`#code-comments-${id}`); hideElem(`#code-preview-${id}`); showElem(`#show-outdated-${id}`); }); - $(document).on('click', 'button.comment-form-reply', async function (e) { + addDelegatedEventListener(document, 'click', 'button.comment-form-reply', (el, e) => { e.preventDefault(); - await handleReply(this); + handleReply(el); }); // The following part is only for diff views - if (!$('.repository.pull.diff').length) return; - - const $reviewBtn = $('.js-btn-review'); - const $panel = $reviewBtn.parent().find('.review-box-panel'); - const $closeBtn = $panel.find('.close'); + if (!document.querySelector('.repository.pull.diff')) return; - if ($reviewBtn.length && $panel.length) { - const tippy = createTippy($reviewBtn[0], { - content: $panel[0], + const elReviewBtn = document.querySelector('.js-btn-review'); + const elReviewPanel = document.querySelector('.review-box-panel.tippy-target'); + if (elReviewBtn && elReviewPanel) { + const tippy = createTippy(elReviewBtn, { + content: elReviewPanel, theme: 'default', placement: 'bottom', trigger: 'click', @@ -448,11 +317,7 @@ export function initRepoPullRequestReview() { interactive: true, hideOnClick: true, }); - - $closeBtn.on('click', (e) => { - e.preventDefault(); - tippy.hide(); - }); + elReviewPanel.querySelector('.close').addEventListener('click', () => tippy.hide()); } addDelegatedEventListener(document, 'click', '.add-code-comment', async (el, e) => { @@ -493,43 +358,79 @@ export function initRepoPullRequestReview() { } export function initRepoIssueReferenceIssue() { + const elDropdown = document.querySelector('.issue_reference_repository_search'); + if (!elDropdown) return; + const form = elDropdown.closest('form'); + fomanticQuery(elDropdown).dropdown({ + fullTextSearch: true, + apiSettings: { + cache: false, + rawResponse: true, + url: `${appSubUrl}/repo/search?q={query}&limit=20`, + onResponse(response: any) { + const filteredResponse = {success: true, results: [] as Array<Record<string, any>>}; + for (const repo of response.data) { + filteredResponse.results.push({ + name: htmlEscape(repo.repository.full_name), + value: repo.repository.full_name, + }); + } + return filteredResponse; + }, + }, + onChange(_value: string, _text: string, _$choice: any) { + form.setAttribute('action', `${appSubUrl}/${_text}/issues/new`); + }, + }); + // Reference issue - $(document).on('click', '.reference-issue', function (e) { - const target = this.getAttribute('data-target'); + addDelegatedEventListener(document, 'click', '.reference-issue', (el, e) => { + e.preventDefault(); + const target = el.getAttribute('data-target'); const content = document.querySelector(`#${target}`)?.textContent ?? ''; - const poster = this.getAttribute('data-poster-username'); - const reference = toAbsoluteUrl(this.getAttribute('data-reference')); - const modalSelector = this.getAttribute('data-modal'); + const poster = el.getAttribute('data-poster-username'); + const reference = toAbsoluteUrl(el.getAttribute('data-reference')); + const modalSelector = el.getAttribute('data-modal'); const modal = document.querySelector(modalSelector); - const textarea = modal.querySelector('textarea[name="content"]'); + const textarea = modal.querySelector<HTMLTextAreaElement>('textarea[name="content"]'); textarea.value = `${content}\n\n_Originally posted by @${poster} in ${reference}_`; - $(modal).modal('show'); - e.preventDefault(); + fomanticQuery(modal).modal('show'); }); } +export function initRepoIssueWipNewTitle() { + // Toggle WIP for new PR + queryElems(document, '.title_wip_desc > a', (el) => el.addEventListener('click', (e) => { + e.preventDefault(); + const wipPrefixes = JSON.parse(el.closest('.title_wip_desc').getAttribute('data-wip-prefixes')); + const titleInput = document.querySelector<HTMLInputElement>('#issue_title'); + const titleValue = titleInput.value; + for (const prefix of wipPrefixes) { + if (titleValue.startsWith(prefix.toUpperCase())) { + return; + } + } + titleInput.value = `${wipPrefixes[0]} ${titleValue}`; + })); +} + export function initRepoIssueWipToggle() { - // Toggle WIP - $('.toggle-wip a, .toggle-wip button').on('click', async (e) => { + // Toggle WIP for existing PR + registerGlobalInitFunc('initPullRequestWipToggle', (toggleWip) => toggleWip.addEventListener('click', async (e) => { e.preventDefault(); - const toggleWip = e.currentTarget.closest('.toggle-wip'); const title = toggleWip.getAttribute('data-title'); const wipPrefix = toggleWip.getAttribute('data-wip-prefix'); const updateUrl = toggleWip.getAttribute('data-update-url'); - try { - const params = new URLSearchParams(); - params.append('title', title?.startsWith(wipPrefix) ? title.slice(wipPrefix.length).trim() : `${wipPrefix.trim()} ${title}`); - - const response = await POST(updateUrl, {data: params}); - if (!response.ok) { - throw new Error('Failed to toggle WIP status'); - } - window.location.reload(); - } catch (error) { - console.error(error); + const params = new URLSearchParams(); + params.append('title', title?.startsWith(wipPrefix) ? title.slice(wipPrefix.length).trim() : `${wipPrefix.trim()} ${title}`); + const response = await POST(updateUrl, {data: params}); + if (!response.ok) { + showErrorToast(`Failed to toggle 'work in progress' status`); + return; } - }); + window.location.reload(); + })); } export function initRepoIssueTitleEdit() { @@ -602,11 +503,11 @@ export function initRepoIssueBranchSelect() { }); } -async function initSingleCommentEditor($commentForm) { +async function initSingleCommentEditor(commentForm: HTMLFormElement) { // pages: // * normal new issue/pr page: no status-button, no comment-button (there is only a normal submit button which can submit empty content) // * issue/pr view page: with comment form, has status-button and comment-button - const editor = await initComboMarkdownEditor($commentForm[0].querySelector('.combo-markdown-editor')); + const editor = await initComboMarkdownEditor(commentForm.querySelector('.combo-markdown-editor')); const statusButton = document.querySelector<HTMLButtonElement>('#status-button'); const commentButton = document.querySelector<HTMLButtonElement>('#comment-button'); const syncUiState = () => { @@ -624,27 +525,27 @@ async function initSingleCommentEditor($commentForm) { syncUiState(); } -function initIssueTemplateCommentEditors($commentForm) { +function initIssueTemplateCommentEditors(commentForm: HTMLFormElement) { // pages: // * new issue with issue template - const $comboFields = $commentForm.find('.combo-editor-dropzone'); + const comboFields = commentForm.querySelectorAll<HTMLElement>('.combo-editor-dropzone'); const initCombo = async (elCombo: HTMLElement) => { - const $formField = $(elCombo.querySelector('.form-field-real')); + const fieldTextarea = elCombo.querySelector<HTMLTextAreaElement>('.form-field-real'); const dropzoneContainer = elCombo.querySelector<HTMLElement>('.form-field-dropzone'); const markdownEditor = elCombo.querySelector<HTMLElement>('.combo-markdown-editor'); const editor = await initComboMarkdownEditor(markdownEditor); - editor.container.addEventListener(ComboMarkdownEditor.EventEditorContentChanged, () => $formField.val(editor.value())); + editor.container.addEventListener(ComboMarkdownEditor.EventEditorContentChanged, () => fieldTextarea.value = editor.value()); - $formField.on('focus', async () => { + fieldTextarea.addEventListener('focus', async () => { // deactivate all markdown editors - showElem($commentForm.find('.combo-editor-dropzone .form-field-real')); - hideElem($commentForm.find('.combo-editor-dropzone .combo-markdown-editor')); - hideElem($commentForm.find('.combo-editor-dropzone .form-field-dropzone')); + showElem(commentForm.querySelectorAll('.combo-editor-dropzone .form-field-real')); + hideElem(commentForm.querySelectorAll('.combo-editor-dropzone .combo-markdown-editor')); + hideElem(commentForm.querySelectorAll('.combo-editor-dropzone .form-field-dropzone')); // activate this markdown editor - hideElem($formField); + hideElem(fieldTextarea); showElem(markdownEditor); showElem(dropzoneContainer); @@ -653,21 +554,21 @@ function initIssueTemplateCommentEditors($commentForm) { }); }; - for (const el of $comboFields) { + for (const el of comboFields) { initCombo(el); } } export function initRepoCommentFormAndSidebar() { - const $commentForm = $('.comment.form'); - if (!$commentForm.length) return; + const commentForm = document.querySelector<HTMLFormElement>('.comment.form'); + if (!commentForm) return; - if ($commentForm.find('.field.combo-editor-dropzone').length) { + if (commentForm.querySelector('.field.combo-editor-dropzone')) { // at the moment, if a form has multiple combo-markdown-editors, it must be an issue template form - initIssueTemplateCommentEditors($commentForm); - } else if ($commentForm.find('.combo-markdown-editor').length) { + initIssueTemplateCommentEditors(commentForm); + } else if (commentForm.querySelector('.combo-markdown-editor')) { // it's quite unclear about the "comment form" elements, sometimes it's for issue comment, sometimes it's for file editor/uploader message - initSingleCommentEditor($commentForm); + initSingleCommentEditor(commentForm); } initRepoIssueSidebar(); diff --git a/web_src/js/features/repo-legacy.ts b/web_src/js/features/repo-legacy.ts index 04267d1dda..249d181b25 100644 --- a/web_src/js/features/repo-legacy.ts +++ b/web_src/js/features/repo-legacy.ts @@ -1,77 +1,47 @@ -import $ from 'jquery'; +import {registerGlobalInitFunc} from '../modules/observer.ts'; import { initRepoCommentFormAndSidebar, initRepoIssueBranchSelect, initRepoIssueCodeCommentCancel, initRepoIssueCommentDelete, - initRepoIssueComments, initRepoIssueDependencyDelete, initRepoIssueReferenceIssue, - initRepoIssueTitleEdit, initRepoIssueWipToggle, - initRepoPullRequestUpdate, + initRepoIssueComments, initRepoIssueReferenceIssue, + initRepoIssueTitleEdit, initRepoIssueWipNewTitle, initRepoIssueWipToggle, } from './repo-issue.ts'; import {initUnicodeEscapeButton} from './repo-unicode-escape.ts'; import {initRepoCloneButtons} from './repo-common.ts'; import {initCitationFileCopyContent} from './citation.ts'; import {initCompLabelEdit} from './comp/LabelEdit.ts'; -import {initRepoDiffConversationNav} from './repo-diff.ts'; import {initCompReactionSelector} from './comp/ReactionSelector.ts'; import {initRepoSettings} from './repo-settings.ts'; -import {initRepoPullRequestMergeForm} from './repo-issue-pr-form.ts'; -import {initRepoPullRequestCommitStatus} from './repo-issue-pr-status.ts'; -import {hideElem, queryElemChildren, showElem} from '../utils/dom.ts'; +import {hideElem, queryElemChildren, queryElems, showElem} from '../utils/dom.ts'; import {initRepoIssueCommentEdit} from './repo-issue-edit.ts'; import {initRepoMilestone} from './repo-milestone.ts'; import {initRepoNew} from './repo-new.ts'; import {createApp} from 'vue'; import RepoBranchTagSelector from '../components/RepoBranchTagSelector.vue'; +import {initRepoPullMergeBox} from './repo-issue-pull.ts'; -function initRepoBranchTagSelector(selector: string) { - for (const elRoot of document.querySelectorAll(selector)) { +function initRepoBranchTagSelector() { + registerGlobalInitFunc('initRepoBranchTagSelector', async (elRoot: HTMLInputElement) => { createApp(RepoBranchTagSelector, {elRoot}).mount(elRoot); - } -} - -export function initBranchSelectorTabs() { - const elSelectBranch = document.querySelector('.ui.dropdown.select-branch'); - if (!elSelectBranch) return; - - $(elSelectBranch).find('.reference.column').on('click', function () { - hideElem($(elSelectBranch).find('.scrolling.reference-list-menu')); - showElem(this.getAttribute('data-target')); - queryElemChildren(this.parentNode, '.branch-tag-item', (el) => el.classList.remove('active')); - this.classList.add('active'); - return false; }); } -function initRepoCommonBranchOrTagDropdown(selector: string) { - $(selector).each(function () { - const $dropdown = $(this); - $dropdown.find('.reference.column').on('click', function () { - hideElem($dropdown.find('.scrolling.reference-list-menu')); - showElem($($(this).data('target'))); - return false; - }); - }); -} - -function initRepoCommonFilterSearchDropdown(selector: string) { - const $dropdown = $(selector); - if (!$dropdown.length) return; - - $dropdown.dropdown({ - fullTextSearch: 'exact', - selectOnKeydown: false, - onChange(_text, _value, $choice) { - if ($choice[0].getAttribute('data-url')) { - window.location.href = $choice[0].getAttribute('data-url'); - } - }, - message: {noResults: $dropdown[0].getAttribute('data-no-results')}, - }); +export function initBranchSelectorTabs() { + const elSelectBranches = document.querySelectorAll('.ui.dropdown.select-branch'); + for (const elSelectBranch of elSelectBranches) { + queryElems(elSelectBranch, '.reference.column', (el) => el.addEventListener('click', () => { + hideElem(elSelectBranch.querySelectorAll('.scrolling.reference-list-menu')); + showElem(el.getAttribute('data-target')); + queryElemChildren(el.parentNode, '.branch-tag-item', (el) => el.classList.remove('active')); + el.classList.add('active'); + })); + } } export function initRepository() { - if (!$('.page-content.repository').length) return; + const pageContent = document.querySelector('.page-content.repository'); + if (!pageContent) return; - initRepoBranchTagSelector('.js-branch-tag-selector'); + initRepoBranchTagSelector(); initRepoCommentFormAndSidebar(); // Labels @@ -79,19 +49,13 @@ export function initRepository() { initRepoMilestone(); initRepoNew(); - // Compare or pull request - const $repoDiff = $('.repository.diff'); - if ($repoDiff.length) { - initRepoCommonBranchOrTagDropdown('.choose.branch .dropdown'); - initRepoCommonFilterSearchDropdown('.choose.branch .dropdown'); - } - initRepoCloneButtons(); initCitationFileCopyContent(); initRepoSettings(); + initRepoIssueWipNewTitle(); // Issues - if ($('.repository.view.issue').length > 0) { + if (pageContent.matches('.page-content.repository.view.issue')) { initRepoIssueCommentEdit(); initRepoIssueBranchSelect(); @@ -99,30 +63,13 @@ export function initRepository() { initRepoIssueWipToggle(); initRepoIssueComments(); - initRepoDiffConversationNav(); initRepoIssueReferenceIssue(); initRepoIssueCommentDelete(); - initRepoIssueDependencyDelete(); initRepoIssueCodeCommentCancel(); - initRepoPullRequestUpdate(); initCompReactionSelector(); - initRepoPullRequestMergeForm(); - initRepoPullRequestCommitStatus(); - } - - // Pull request - const $repoComparePull = $('.repository.compare.pull'); - if ($repoComparePull.length > 0) { - // show pull request form - $repoComparePull.find('button.show-form').on('click', function (e) { - e.preventDefault(); - hideElem($(this).parent()); - - const $form = $repoComparePull.find('.pullrequest-form'); - showElem($form); - }); + registerGlobalInitFunc('initRepoPullMergeBox', initRepoPullMergeBox); } initUnicodeEscapeButton(); diff --git a/web_src/js/features/repo-migrate.ts b/web_src/js/features/repo-migrate.ts index b75289feec..0788f83215 100644 --- a/web_src/js/features/repo-migrate.ts +++ b/web_src/js/features/repo-migrate.ts @@ -1,11 +1,11 @@ -import {hideElem, showElem} from '../utils/dom.ts'; +import {hideElem, showElem, type DOMEvent} from '../utils/dom.ts'; import {GET, POST} from '../modules/fetch.ts'; export function initRepoMigrationStatusChecker() { const repoMigrating = document.querySelector('#repo_migrating'); if (!repoMigrating) return; - document.querySelector('#repo_migrating_retry')?.addEventListener('click', doMigrationRetry); + document.querySelector<HTMLButtonElement>('#repo_migrating_retry')?.addEventListener('click', doMigrationRetry); const repoLink = repoMigrating.getAttribute('data-migrating-repo-link'); @@ -55,7 +55,7 @@ export function initRepoMigrationStatusChecker() { syncTaskStatus(); // no await } -async function doMigrationRetry(e) { +async function doMigrationRetry(e: DOMEvent<MouseEvent>) { await POST(e.target.getAttribute('data-migrating-task-retry-url')); window.location.reload(); } diff --git a/web_src/js/features/repo-migration.ts b/web_src/js/features/repo-migration.ts index fb9c822f98..4914e47267 100644 --- a/web_src/js/features/repo-migration.ts +++ b/web_src/js/features/repo-migration.ts @@ -1,4 +1,5 @@ import {hideElem, showElem, toggleElem} from '../utils/dom.ts'; +import {sanitizeRepoName} from './repo-common.ts'; const service = document.querySelector<HTMLInputElement>('#service_type'); const user = document.querySelector<HTMLInputElement>('#auth_username'); @@ -25,13 +26,19 @@ export function initRepoMigration() { }); lfs?.addEventListener('change', setLFSSettingsVisibility); - const cloneAddr = document.querySelector<HTMLInputElement>('#clone_addr'); - cloneAddr?.addEventListener('change', () => { - const repoName = document.querySelector<HTMLInputElement>('#repo_name'); - if (cloneAddr.value && !repoName?.value) { // Only modify if repo_name input is blank - repoName.value = /^(.*\/)?((.+?)(\.git)?)$/.exec(cloneAddr.value)[3]; - } - }); + const elCloneAddr = document.querySelector<HTMLInputElement>('#clone_addr'); + const elRepoName = document.querySelector<HTMLInputElement>('#repo_name'); + if (elCloneAddr && elRepoName) { + let repoNameChanged = false; + elRepoName.addEventListener('input', () => {repoNameChanged = true}); + elCloneAddr.addEventListener('input', () => { + if (repoNameChanged) return; + let repoNameFromUrl = elCloneAddr.value.split(/[?#]/)[0]; + repoNameFromUrl = /^(.*\/)?((.+?)\/?)$/.exec(repoNameFromUrl)[3]; + repoNameFromUrl = repoNameFromUrl.split(/[?#]/)[0]; + elRepoName.value = sanitizeRepoName(repoNameFromUrl); + }); + } } function checkAuth() { diff --git a/web_src/js/features/repo-new.ts b/web_src/js/features/repo-new.ts index 436288325a..e2aa13f490 100644 --- a/web_src/js/features/repo-new.ts +++ b/web_src/js/features/repo-new.ts @@ -1,14 +1,99 @@ -import $ from 'jquery'; +import {hideElem, querySingleVisibleElem, showElem, toggleElem} from '../utils/dom.ts'; +import {htmlEscape} from '../utils/html.ts'; +import {fomanticQuery} from '../modules/fomantic/base.ts'; +import {sanitizeRepoName} from './repo-common.ts'; -export function initRepoNew() { - // Repo Creation - if ($('.repository.new.repo').length > 0) { - $('input[name="gitignores"], input[name="license"]').on('change', () => { - const gitignores = $('input[name="gitignores"]').val(); - const license = $('input[name="license"]').val(); - if (gitignores || license) { - document.querySelector<HTMLInputElement>('input[name="auto_init"]').checked = true; - } +const {appSubUrl} = window.config; + +function initRepoNewTemplateSearch(form: HTMLFormElement) { + const elSubmitButton = querySingleVisibleElem<HTMLInputElement>(form, '.ui.primary.button'); + const elCreateRepoErrorMessage = form.querySelector('#create-repo-error-message'); + const elRepoOwnerDropdown = form.querySelector('#repo_owner_dropdown'); + const elRepoTemplateDropdown = form.querySelector<HTMLInputElement>('#repo_template_search'); + const inputRepoTemplate = form.querySelector<HTMLInputElement>('#repo_template'); + const elTemplateUnits = form.querySelector('#template_units'); + const elNonTemplate = form.querySelector('#non_template'); + const checkTemplate = function () { + const hasSelectedTemplate = inputRepoTemplate.value !== '' && inputRepoTemplate.value !== '0'; + toggleElem(elTemplateUnits, hasSelectedTemplate); + toggleElem(elNonTemplate, !hasSelectedTemplate); + }; + inputRepoTemplate.addEventListener('change', checkTemplate); + checkTemplate(); + + const $repoOwnerDropdown = fomanticQuery(elRepoOwnerDropdown); + const $repoTemplateDropdown = fomanticQuery(elRepoTemplateDropdown); + const onChangeOwner = function () { + const ownerId = $repoOwnerDropdown.dropdown('get value'); + const $ownerItem = $repoOwnerDropdown.dropdown('get item', ownerId); + hideElem(elCreateRepoErrorMessage); + elSubmitButton.disabled = false; + if ($ownerItem?.length) { + const elOwnerItem = $ownerItem[0]; + elCreateRepoErrorMessage.textContent = elOwnerItem.getAttribute('data-create-repo-disallowed-prompt') ?? ''; + const hasError = Boolean(elCreateRepoErrorMessage.textContent); + toggleElem(elCreateRepoErrorMessage, hasError); + elSubmitButton.disabled = hasError; + } + $repoTemplateDropdown.dropdown('setting', { + apiSettings: { + url: `${appSubUrl}/repo/search?q={query}&template=true&priority_owner_id=${ownerId}`, + onResponse(response: any) { + const results = []; + results.push({name: '', value: ''}); // empty item means not using template + for (const tmplRepo of response.data) { + results.push({ + name: htmlEscape(tmplRepo.repository.full_name), + value: String(tmplRepo.repository.id), + }); + } + $repoTemplateDropdown.fomanticExt.onResponseKeepSelectedItem($repoTemplateDropdown, inputRepoTemplate.value); + return {results}; + }, + cache: false, + }, }); - } + }; + $repoOwnerDropdown.dropdown('setting', 'onChange', onChangeOwner); + onChangeOwner(); +} + +export function initRepoNew() { + const pageContent = document.querySelector('.page-content.repository.new-repo'); + if (!pageContent) return; + + const form = document.querySelector<HTMLFormElement>('.new-repo-form'); + const inputGitIgnores = form.querySelector<HTMLInputElement>('input[name="gitignores"]'); + const inputLicense = form.querySelector<HTMLInputElement>('input[name="license"]'); + const inputAutoInit = form.querySelector<HTMLInputElement>('input[name="auto_init"]'); + const updateUiAutoInit = () => { + inputAutoInit.checked = Boolean(inputGitIgnores.value || inputLicense.value); + }; + inputGitIgnores.addEventListener('change', updateUiAutoInit); + inputLicense.addEventListener('change', updateUiAutoInit); + updateUiAutoInit(); + + const inputRepoName = form.querySelector<HTMLInputElement>('input[name="repo_name"]'); + const inputPrivate = form.querySelector<HTMLInputElement>('input[name="private"]'); + const updateUiRepoName = () => { + const helps = form.querySelectorAll(`.help[data-help-for-repo-name]`); + hideElem(helps); + let help = form.querySelector(`.help[data-help-for-repo-name="${CSS.escape(inputRepoName.value)}"]`); + if (!help) help = form.querySelector(`.help[data-help-for-repo-name=""]`); + showElem(help); + const repoNamePreferPrivate: Record<string, boolean> = {'.profile': false, '.profile-private': true}; + const preferPrivate = repoNamePreferPrivate[inputRepoName.value]; + // inputPrivate might be disabled because site admin "force private" + if (preferPrivate !== undefined && !inputPrivate.closest('.disabled, [disabled]')) { + inputPrivate.checked = preferPrivate; + } + }; + inputRepoName.addEventListener('input', updateUiRepoName); + inputRepoName.addEventListener('change', () => { + inputRepoName.value = sanitizeRepoName(inputRepoName.value); + updateUiRepoName(); + }); + updateUiRepoName(); + + initRepoNewTemplateSearch(form); } diff --git a/web_src/js/features/repo-projects.ts b/web_src/js/features/repo-projects.ts index 11f5c19c8d..ad0feb6101 100644 --- a/web_src/js/features/repo-projects.ts +++ b/web_src/js/features/repo-projects.ts @@ -2,8 +2,9 @@ import {contrastColor} from '../utils/color.ts'; import {createSortable} from '../modules/sortable.ts'; import {POST, request} from '../modules/fetch.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; -import {queryElemChildren, queryElems} from '../utils/dom.ts'; +import {queryElemChildren, queryElems, toggleElem} from '../utils/dom.ts'; import type {SortableEvent} from 'sortablejs'; +import {toggleFullScreen} from '../utils.ts'; function updateIssueCount(card: HTMLElement): void { const parent = card.parentElement; @@ -34,8 +35,8 @@ async function moveIssue({item, from, to, oldIndex}: SortableEvent): Promise<voi } async function initRepoProjectSortable(): Promise<void> { - // the HTML layout is: #project-board > .board > .project-column .cards > .issue-card - const mainBoard = document.querySelector('#project-board > .board.sortable'); + // the HTML layout is: #project-board.board > .project-column .cards > .issue-card + const mainBoard = document.querySelector('#project-board'); let boardColumns = mainBoard.querySelectorAll<HTMLElement>('.project-column'); createSortable(mainBoard, { group: 'project-column', @@ -113,7 +114,6 @@ function initRepoProjectColumnEdit(writableProjectBoard: Element): void { window.location.reload(); // newly added column, need to reload the page return; } - fomanticQuery(elModal).modal('hide'); // update the newly saved column title and color in the project board (to avoid reload) const elEditButton = writableProjectBoard.querySelector<HTMLButtonElement>(`.show-project-column-modal-edit[${attrDataColumnId}="${columnId}"]`); @@ -133,13 +133,32 @@ function initRepoProjectColumnEdit(writableProjectBoard: Element): void { elBoardColumn.style.removeProperty('color'); queryElemChildren<HTMLElement>(elBoardColumn, '.divider', (divider) => divider.style.removeProperty('color')); } + + fomanticQuery(elModal).modal('hide'); } finally { elForm.classList.remove('is-loading'); } }); } +function initRepoProjectToggleFullScreen(): void { + const enterFullscreenBtn = document.querySelector('.screen-full'); + const exitFullscreenBtn = document.querySelector('.screen-normal'); + if (!enterFullscreenBtn || !exitFullscreenBtn) return; + + const toggleFullscreenState = (isFullScreen: boolean) => { + toggleFullScreen('.projects-view', isFullScreen); + toggleElem(enterFullscreenBtn, !isFullScreen); + toggleElem(exitFullscreenBtn, isFullScreen); + }; + + enterFullscreenBtn.addEventListener('click', () => toggleFullscreenState(true)); + exitFullscreenBtn.addEventListener('click', () => toggleFullscreenState(false)); +} + export function initRepoProject(): void { + initRepoProjectToggleFullScreen(); + const writableProjectBoard = document.querySelector('#project-board[data-project-borad-writable="true"]'); if (!writableProjectBoard) return; diff --git a/web_src/js/features/repo-settings.ts b/web_src/js/features/repo-settings.ts index 1d2d447205..be1821664f 100644 --- a/web_src/js/features/repo-settings.ts +++ b/web_src/js/features/repo-settings.ts @@ -1,9 +1,9 @@ -import $ from 'jquery'; import {minimatch} from 'minimatch'; import {createMonaco} from './codeeditor.ts'; -import {onInputDebounce, queryElems, toggleElem} from '../utils/dom.ts'; +import {onInputDebounce, queryElems, toggleClass, toggleElem} from '../utils/dom.ts'; import {POST} from '../modules/fetch.ts'; import {initRepoSettingsBranchesDrag} from './repo-settings-branches.ts'; +import {fomanticQuery} from '../modules/fomantic/base.ts'; const {appSubUrl, csrfToken} = window.config; @@ -11,11 +11,12 @@ function initRepoSettingsCollaboration() { // Change collaborator access mode for (const dropdownEl of queryElems(document, '.page-content.repository .ui.dropdown.access-mode')) { const textEl = dropdownEl.querySelector(':scope > .text'); - $(dropdownEl).dropdown({ - async action(text, value) { + const $dropdown = fomanticQuery(dropdownEl); + $dropdown.dropdown({ + async action(text: string, value: string) { dropdownEl.classList.add('is-loading', 'loading-icon-2px'); const lastValue = dropdownEl.getAttribute('data-last-value'); - $(dropdownEl).dropdown('hide'); + $dropdown.dropdown('hide'); try { const uid = dropdownEl.getAttribute('data-uid'); await POST(dropdownEl.getAttribute('data-url'), {data: new URLSearchParams({uid, 'mode': value})}); @@ -32,9 +33,9 @@ function initRepoSettingsCollaboration() { // set to the really selected value, defer to next tick to make sure `action` has finished // its work because the calling order might be onHide -> action setTimeout(() => { - const $item = $(dropdownEl).dropdown('get item', dropdownEl.getAttribute('data-last-value')); + const $item = $dropdown.dropdown('get item', dropdownEl.getAttribute('data-last-value')); if ($item) { - $(dropdownEl).dropdown('set selected', dropdownEl.getAttribute('data-last-value')); + $dropdown.dropdown('set selected', dropdownEl.getAttribute('data-last-value')); } else { textEl.textContent = '(none)'; // prevent from misleading users when the access mode is undefined } @@ -48,52 +49,52 @@ function initRepoSettingsSearchTeamBox() { const searchTeamBox = document.querySelector('#search-team-box'); if (!searchTeamBox) return; - $(searchTeamBox).search({ + fomanticQuery(searchTeamBox).search({ minCharacters: 2, + searchFields: ['name', 'description'], + showNoResults: false, + rawResponse: true, apiSettings: { url: `${appSubUrl}/org/${searchTeamBox.getAttribute('data-org-name')}/teams/-/search?q={query}`, headers: {'X-Csrf-Token': csrfToken}, - onResponse(response) { - const items = []; - $.each(response.data, (_i, item) => { + onResponse(response: any) { + const items: Array<Record<string, any>> = []; + for (const item of response.data) { items.push({ title: item.name, description: `${item.permission} access`, // TODO: translate this string }); - }); - + } return {results: items}; }, }, - searchFields: ['name', 'description'], - showNoResults: false, }); } function initRepoSettingsGitHook() { - if (!$('.edit.githook').length) return; + if (!document.querySelector('.page-content.repository.settings.edit.githook')) return; const filename = document.querySelector('.hook-filename').textContent; - createMonaco($('#content')[0] as HTMLTextAreaElement, filename, {language: 'shell'}); + createMonaco(document.querySelector<HTMLTextAreaElement>('#content'), filename, {language: 'shell'}); } function initRepoSettingsBranches() { if (!document.querySelector('.repository.settings.branches')) return; - for (const el of document.querySelectorAll('.toggle-target-enabled')) { + for (const el of document.querySelectorAll<HTMLInputElement>('.toggle-target-enabled')) { el.addEventListener('change', function () { const target = document.querySelector(this.getAttribute('data-target')); target?.classList.toggle('disabled', !this.checked); }); } - for (const el of document.querySelectorAll('.toggle-target-disabled')) { + for (const el of document.querySelectorAll<HTMLInputElement>('.toggle-target-disabled')) { el.addEventListener('change', function () { const target = document.querySelector(this.getAttribute('data-target')); if (this.checked) target?.classList.add('disabled'); // only disable, do not auto enable }); } - document.querySelector('#dismiss_stale_approvals')?.addEventListener('change', function () { + document.querySelector<HTMLInputElement>('#dismiss_stale_approvals')?.addEventListener('change', function () { document.querySelector('#ignore_stale_approvals_box')?.classList.toggle('disabled', this.checked); }); @@ -107,7 +108,7 @@ function initRepoSettingsBranches() { let matched = false; const statusCheck = el.getAttribute('data-status-check'); for (const pattern of validPatterns) { - if (minimatch(statusCheck, pattern)) { + if (minimatch(statusCheck, pattern, {noext: true})) { // https://github.com/go-gitea/gitea/issues/33121 disable extended glob syntax matched = true; break; } @@ -120,32 +121,23 @@ function initRepoSettingsBranches() { } function initRepoSettingsOptions() { - if ($('.repository.settings.options').length > 0) { - // Enable or select internal/external wiki system and issue tracker. - $('.enable-system').on('change', function (this: HTMLInputElement) { // eslint-disable-line @typescript-eslint/no-deprecated - if (this.checked) { - $($(this).data('target')).removeClass('disabled'); - if (!$(this).data('context')) $($(this).data('context')).addClass('disabled'); - } else { - $($(this).data('target')).addClass('disabled'); - if (!$(this).data('context')) $($(this).data('context')).removeClass('disabled'); - } - }); - $('.enable-system-radio').on('change', function (this: HTMLInputElement) { // eslint-disable-line @typescript-eslint/no-deprecated - if (this.value === 'false') { - $($(this).data('target')).addClass('disabled'); - if ($(this).data('context') !== undefined) $($(this).data('context')).removeClass('disabled'); - } else if (this.value === 'true') { - $($(this).data('target')).removeClass('disabled'); - if ($(this).data('context') !== undefined) $($(this).data('context')).addClass('disabled'); - } - }); - const $trackerIssueStyleRadios = $('.js-tracker-issue-style'); - $trackerIssueStyleRadios.on('change input', () => { - const checkedVal = $trackerIssueStyleRadios.filter(':checked').val(); - $('#tracker-issue-style-regex-box').toggleClass('disabled', checkedVal !== 'regexp'); - }); - } + const pageContent = document.querySelector('.page-content.repository.settings.options'); + if (!pageContent) return; + + // Enable or select internal/external wiki system and issue tracker. + queryElems<HTMLInputElement>(pageContent, '.enable-system', (el) => el.addEventListener('change', () => { + toggleClass(el.getAttribute('data-target'), 'disabled', !el.checked); + toggleClass(el.getAttribute('data-context'), 'disabled', el.checked); + })); + queryElems<HTMLInputElement>(pageContent, '.enable-system-radio', (el) => el.addEventListener('change', () => { + toggleClass(el.getAttribute('data-target'), 'disabled', el.value === 'false'); + toggleClass(el.getAttribute('data-context'), 'disabled', el.value === 'true'); + })); + + queryElems<HTMLInputElement>(pageContent, '.js-tracker-issue-style', (el) => el.addEventListener('change', () => { + const checkedVal = el.value; + pageContent.querySelector('#tracker-issue-style-regex-box').classList.toggle('disabled', checkedVal !== 'regexp'); + })); } export function initRepoSettings() { diff --git a/web_src/js/features/repo-template.ts b/web_src/js/features/repo-template.ts deleted file mode 100644 index fbd7b656ed..0000000000 --- a/web_src/js/features/repo-template.ts +++ /dev/null @@ -1,51 +0,0 @@ -import $ from 'jquery'; -import {htmlEscape} from 'escape-goat'; -import {hideElem, showElem} from '../utils/dom.ts'; - -const {appSubUrl} = window.config; - -export function initRepoTemplateSearch() { - const $repoTemplate = $('#repo_template'); - const checkTemplate = function () { - const $templateUnits = $('#template_units'); - const $nonTemplate = $('#non_template'); - if ($repoTemplate.val() !== '' && $repoTemplate.val() !== '0') { - showElem($templateUnits); - hideElem($nonTemplate); - } else { - hideElem($templateUnits); - showElem($nonTemplate); - } - }; - $repoTemplate.on('change', checkTemplate); - checkTemplate(); - - const changeOwner = function () { - $('#repo_template_search') - .dropdown({ - apiSettings: { - url: `${appSubUrl}/repo/search?q={query}&template=true&priority_owner_id=${$('#uid').val()}`, - onResponse(response) { - const filteredResponse = {success: true, results: []}; - filteredResponse.results.push({ - name: '', - value: '', - }); - // Parse the response from the api to work with our dropdown - $.each(response.data, (_r, repo) => { - filteredResponse.results.push({ - name: htmlEscape(repo.repository.full_name), - value: repo.repository.id, - }); - }); - return filteredResponse; - }, - cache: false, - }, - - fullTextSearch: true, - }); - }; - $('#uid').on('change', changeOwner); - changeOwner(); -} diff --git a/web_src/js/features/repo-view-file-tree.ts b/web_src/js/features/repo-view-file-tree.ts new file mode 100644 index 0000000000..f52b64cc51 --- /dev/null +++ b/web_src/js/features/repo-view-file-tree.ts @@ -0,0 +1,37 @@ +import {createApp} from 'vue'; +import {toggleElem} from '../utils/dom.ts'; +import {POST} from '../modules/fetch.ts'; +import ViewFileTree from '../components/ViewFileTree.vue'; +import {registerGlobalEventFunc} from '../modules/observer.ts'; + +const {appSubUrl} = window.config; + +async function toggleSidebar(btn: HTMLElement) { + const elToggleShow = document.querySelector('.repo-view-file-tree-toggle-show'); + const elFileTreeContainer = document.querySelector('.repo-view-file-tree-container'); + const shouldShow = btn.getAttribute('data-toggle-action') === 'show'; + toggleElem(elFileTreeContainer, shouldShow); + toggleElem(elToggleShow, !shouldShow); + + // FIXME: need to remove "full height" style from parent element + + if (!elFileTreeContainer.hasAttribute('data-user-is-signed-in')) return; + await POST(`${appSubUrl}/user/settings/update_preferences`, { + data: {codeViewShowFileTree: shouldShow}, + }); +} + +export async function initRepoViewFileTree() { + const sidebar = document.querySelector<HTMLElement>('.repo-view-file-tree-container'); + const repoViewContent = document.querySelector('.repo-view-content'); + if (!sidebar || !repoViewContent) return; + + registerGlobalEventFunc('click', 'onRepoViewFileTreeToggle', toggleSidebar); + + const fileTree = sidebar.querySelector('#view-file-tree'); + createApp(ViewFileTree, { + repoLink: fileTree.getAttribute('data-repo-link'), + treePath: fileTree.getAttribute('data-tree-path'), + currentRefNameSubURL: fileTree.getAttribute('data-current-ref-name-sub-url'), + }).mount(fileTree); +} diff --git a/web_src/js/features/repo-wiki.ts b/web_src/js/features/repo-wiki.ts index 484c628f9f..6ae0947077 100644 --- a/web_src/js/features/repo-wiki.ts +++ b/web_src/js/features/repo-wiki.ts @@ -1,8 +1,8 @@ -import {initMarkupContent} from '../markup/content.ts'; import {validateTextareaNonEmpty, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts'; import {fomanticMobileScreen} from '../modules/fomantic.ts'; import {POST} from '../modules/fetch.ts'; import type {ComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts'; +import {html, htmlRaw} from '../utils/html.ts'; async function initRepoWikiFormEditor() { const editArea = document.querySelector<HTMLTextAreaElement>('.repository.wiki .combo-markdown-editor textarea'); @@ -31,8 +31,7 @@ async function initRepoWikiFormEditor() { const response = await POST(editor.previewUrl, {data: formData}); const data = await response.text(); lastContent = newContent; - previewTarget.innerHTML = `<div class="markup ui segment">${data}</div>`; - initMarkupContent(); + previewTarget.innerHTML = html`<div class="render-content markup ui segment">${htmlRaw(data)}</div>`; } catch (error) { console.error('Error rendering preview:', error); } finally { @@ -70,7 +69,7 @@ async function initRepoWikiFormEditor() { }); } -function collapseWikiTocForMobile(collapse) { +function collapseWikiTocForMobile(collapse: boolean) { if (collapse) { document.querySelector('.wiki-content-toc details')?.removeAttribute('open'); } diff --git a/web_src/js/features/scoped-access-token.ts b/web_src/js/features/scoped-access-token.ts deleted file mode 100644 index c498d4c011..0000000000 --- a/web_src/js/features/scoped-access-token.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {createApp} from 'vue'; - -export async function initScopedAccessTokenCategories() { - const el = document.querySelector('#scoped-access-token-selector'); - if (!el) return; - - const {default: ScopedAccessTokenSelector} = await import(/* webpackChunkName: "scoped-access-token-selector" */'../components/ScopedAccessTokenSelector.vue'); - try { - const View = createApp(ScopedAccessTokenSelector, { - isAdmin: JSON.parse(el.getAttribute('data-is-admin')), - noAccessLabel: el.getAttribute('data-no-access-label'), - readLabel: el.getAttribute('data-read-label'), - writeLabel: el.getAttribute('data-write-label'), - }); - View.mount(el); - } catch (err) { - console.error('ScopedAccessTokenSelector failed to load', err); - el.textContent = el.getAttribute('data-locale-component-failed-to-load'); - } -} diff --git a/web_src/js/features/sshkey-helper.ts b/web_src/js/features/sshkey-helper.ts index 9234e3ec44..860bc5b294 100644 --- a/web_src/js/features/sshkey-helper.ts +++ b/web_src/js/features/sshkey-helper.ts @@ -1,6 +1,6 @@ export function initSshKeyFormParser() { // Parse SSH Key - document.querySelector('#ssh-key-content')?.addEventListener('input', function () { + document.querySelector<HTMLTextAreaElement>('#ssh-key-content')?.addEventListener('input', function () { const arrays = this.value.split(' '); const title = document.querySelector<HTMLInputElement>('#ssh-key-title'); if (!title.value && arrays.length === 3 && arrays[2] !== '') { diff --git a/web_src/js/features/stopwatch.ts b/web_src/js/features/stopwatch.ts index af52be4e24..07f9c435b8 100644 --- a/web_src/js/features/stopwatch.ts +++ b/web_src/js/features/stopwatch.ts @@ -1,6 +1,6 @@ import {createTippy} from '../modules/tippy.ts'; import {GET} from '../modules/fetch.ts'; -import {hideElem, showElem} from '../utils/dom.ts'; +import {hideElem, queryElems, showElem} from '../utils/dom.ts'; import {logoutFromWorker} from '../modules/worker.ts'; const {appSubUrl, notificationSettings, enableTimeTracking, assetVersionEncoded} = window.config; @@ -38,7 +38,7 @@ export function initStopwatch() { } let usingPeriodicPoller = false; - const startPeriodicPoller = (timeout) => { + const startPeriodicPoller = (timeout: number) => { if (timeout <= 0 || !Number.isFinite(timeout)) return; usingPeriodicPoller = true; setTimeout(() => updateStopwatchWithCallback(startPeriodicPoller, timeout), timeout); @@ -103,7 +103,7 @@ export function initStopwatch() { startPeriodicPoller(notificationSettings.MinTimeout); } -async function updateStopwatchWithCallback(callback, timeout) { +async function updateStopwatchWithCallback(callback: (timeout: number) => void, timeout: number) { const isSet = await updateStopwatch(); if (!isSet) { @@ -125,7 +125,7 @@ async function updateStopwatch() { return updateStopwatchData(data); } -function updateStopwatchData(data) { +function updateStopwatchData(data: any) { const watch = data[0]; const btnEls = document.querySelectorAll('.active-stopwatch'); if (!watch) { @@ -134,7 +134,7 @@ function updateStopwatchData(data) { const {repo_owner_name, repo_name, issue_index, seconds} = watch; const issueUrl = `${appSubUrl}/${repo_owner_name}/${repo_name}/issues/${issue_index}`; document.querySelector('.stopwatch-link')?.setAttribute('href', issueUrl); - document.querySelector('.stopwatch-commit')?.setAttribute('action', `${issueUrl}/times/stopwatch/toggle`); + document.querySelector('.stopwatch-commit')?.setAttribute('action', `${issueUrl}/times/stopwatch/stop`); document.querySelector('.stopwatch-cancel')?.setAttribute('action', `${issueUrl}/times/stopwatch/cancel`); const stopwatchIssue = document.querySelector('.stopwatch-issue'); if (stopwatchIssue) stopwatchIssue.textContent = `${repo_owner_name}/${repo_name}#${issue_index}`; @@ -144,23 +144,10 @@ function updateStopwatchData(data) { return Boolean(data.length); } -// TODO: This flickers on page load, we could avoid this by making a custom -// element to render time periods. Feeding a datetime in backend does not work -// when time zone between server and client differs. -function updateStopwatchTime(seconds) { - if (!Number.isFinite(seconds)) return; - const datetime = (new Date(Date.now() - seconds * 1000)).toISOString(); - for (const parent of document.querySelectorAll('.header-stopwatch-dot')) { - const existing = parent.querySelector(':scope > relative-time'); - if (existing) { - existing.setAttribute('datetime', datetime); - } else { - const el = document.createElement('relative-time'); - el.setAttribute('format', 'micro'); - el.setAttribute('datetime', datetime); - el.setAttribute('lang', 'en-US'); - el.setAttribute('title', ''); // make <relative-time> show no title and therefor no tooltip - parent.append(el); - } - } +// TODO: This flickers on page load, we could avoid this by making a custom element to render time periods. +function updateStopwatchTime(seconds: number) { + const hours = seconds / 3600 || 0; + const minutes = seconds / 60 || 0; + const timeText = hours >= 1 ? `${Math.round(hours)}h` : `${Math.round(minutes)}m`; + queryElems(document, '.header-stopwatch-dot', (el) => el.textContent = timeText); } diff --git a/web_src/js/features/tablesort.ts b/web_src/js/features/tablesort.ts index 15ea358fa3..0648ffd067 100644 --- a/web_src/js/features/tablesort.ts +++ b/web_src/js/features/tablesort.ts @@ -9,7 +9,7 @@ export function initTableSort() { } } -function tableSort(normSort, revSort, isDefault) { +function tableSort(normSort: string, revSort: string, isDefault: string) { if (!normSort) return false; if (!revSort) revSort = ''; diff --git a/web_src/js/features/tribute.ts b/web_src/js/features/tribute.ts index fa65bcbb28..43c21ebe6d 100644 --- a/web_src/js/features/tribute.ts +++ b/web_src/js/features/tribute.ts @@ -1,14 +1,16 @@ import {emojiKeys, emojiHTML, emojiString} from './emoji.ts'; -import {htmlEscape} from 'escape-goat'; +import {html, htmlRaw} from '../utils/html.ts'; -function makeCollections({mentions, emoji}) { - const collections = []; +type TributeItem = Record<string, any>; - if (emoji) { - collections.push({ +export async function attachTribute(element: HTMLElement) { + const {default: Tribute} = await import(/* webpackChunkName: "tribute" */'tributejs'); + + const collections = [ + { // emojis trigger: ':', requireLeadingSpace: true, - values: (query, cb) => { + values: (query: string, cb: (matches: Array<string>) => void) => { const matches = []; for (const name of emojiKeys) { if (name.includes(query)) { @@ -18,39 +20,30 @@ function makeCollections({mentions, emoji}) { } cb(matches); }, - lookup: (item) => item, - selectTemplate: (item) => { + lookup: (item: TributeItem) => item, + selectTemplate: (item: TributeItem) => { if (item === undefined) return null; return emojiString(item.original); }, - menuItemTemplate: (item) => { - return `<div class="tribute-item">${emojiHTML(item.original)}<span>${htmlEscape(item.original)}</span></div>`; + menuItemTemplate: (item: TributeItem) => { + return html`<div class="tribute-item">${htmlRaw(emojiHTML(item.original))}<span>${item.original}</span></div>`; }, - }); - } - - if (mentions) { - collections.push({ + }, { // mentions values: window.config.mentionValues ?? [], requireLeadingSpace: true, - menuItemTemplate: (item) => { - return ` + menuItemTemplate: (item: TributeItem) => { + const fullNameHtml = item.original.fullname && item.original.fullname !== '' ? html`<span class="fullname">${item.original.fullname}</span>` : ''; + return html` <div class="tribute-item"> - <img src="${htmlEscape(item.original.avatar)}" width="21" height="21"/> - <span class="name">${htmlEscape(item.original.name)}</span> - ${item.original.fullname && item.original.fullname !== '' ? `<span class="fullname">${htmlEscape(item.original.fullname)}</span>` : ''} + <img alt src="${item.original.avatar}" width="21" height="21"/> + <span class="name">${item.original.name}</span> + ${htmlRaw(fullNameHtml)} </div> `; }, - }); - } + }, + ]; - return collections; -} - -export async function attachTribute(element, {mentions, emoji}) { - const {default: Tribute} = await import(/* webpackChunkName: "tribute" */'tributejs'); - const collections = makeCollections({mentions, emoji}); // @ts-expect-error TS2351: This expression is not constructable (strange, why) const tribute = new Tribute({collection: collections, noMatchTemplate: ''}); tribute.attach(element); diff --git a/web_src/js/features/user-auth-webauthn.ts b/web_src/js/features/user-auth-webauthn.ts index 70516c280d..1f336b9741 100644 --- a/web_src/js/features/user-auth-webauthn.ts +++ b/web_src/js/features/user-auth-webauthn.ts @@ -1,5 +1,5 @@ import {encodeURLEncodedBase64, decodeURLEncodedBase64} from '../utils.ts'; -import {showElem} from '../utils/dom.ts'; +import {hideElem, showElem} from '../utils/dom.ts'; import {GET, POST} from '../modules/fetch.ts'; const {appSubUrl} = window.config; @@ -11,6 +11,12 @@ export async function initUserAuthWebAuthn() { return; } + // webauthn is only supported on secure contexts + if (!window.isSecureContext) { + hideElem(elSignInPasskeyBtn); + return; + } + if (!detectWebAuthnSupport()) { return; } @@ -114,7 +120,7 @@ async function login2FA() { } } -async function verifyAssertion(assertedCredential) { +async function verifyAssertion(assertedCredential: any) { // TODO: Credential type does not work // Move data into Arrays in case it is super long const authData = new Uint8Array(assertedCredential.response.authenticatorData); const clientDataJSON = new Uint8Array(assertedCredential.response.clientDataJSON); @@ -148,7 +154,7 @@ async function verifyAssertion(assertedCredential) { window.location.href = reply?.redirect ?? `${appSubUrl}/`; } -async function webauthnRegistered(newCredential) { +async function webauthnRegistered(newCredential: any) { // TODO: Credential type does not work const attestationObject = new Uint8Array(newCredential.response.attestationObject); const clientDataJSON = new Uint8Array(newCredential.response.clientDataJSON); const rawId = new Uint8Array(newCredential.rawId); diff --git a/web_src/js/features/user-settings.ts b/web_src/js/features/user-settings.ts index c097df7b6c..6fbb56e540 100644 --- a/web_src/js/features/user-settings.ts +++ b/web_src/js/features/user-settings.ts @@ -1,19 +1,9 @@ import {hideElem, showElem} from '../utils/dom.ts'; -import {initCompCropper} from './comp/Cropper.ts'; - -function initUserSettingsAvatarCropper() { - const fileInput = document.querySelector<HTMLInputElement>('#new-avatar'); - const container = document.querySelector<HTMLElement>('.user.settings.profile .cropper-panel'); - const imageSource = container.querySelector<HTMLImageElement>('.cropper-source'); - initCompCropper({container, fileInput, imageSource}); -} export function initUserSettings() { if (!document.querySelector('.user.settings.profile')) return; - initUserSettingsAvatarCropper(); - - const usernameInput = document.querySelector('#username'); + const usernameInput = document.querySelector<HTMLInputElement>('#username'); if (!usernameInput) return; usernameInput.addEventListener('input', function () { const prompt = document.querySelector('#name-change-prompt'); diff --git a/web_src/js/globals.d.ts b/web_src/js/globals.d.ts index c08ff9976b..e4b540122d 100644 --- a/web_src/js/globals.d.ts +++ b/web_src/js/globals.d.ts @@ -25,19 +25,15 @@ declare module 'htmx.org/dist/htmx.esm.js' { export default value; } -declare module 'uint8-to-base64' { - export function encode(arrayBuffer: Uint8Array): string; - export function decode(base64str: string): Uint8Array; -} - declare module 'swagger-ui-dist/swagger-ui-es-bundle.js' { const value = await import('swagger-ui-dist'); export default value.SwaggerUIBundle; } interface JQuery { - api: any, // fomantic areYouSure: any, // jquery.are-you-sure + fomanticExt: any; // fomantic extension + api: any, // fomantic dimmer: any, // fomantic dropdown: any; // fomantic modal: any; // fomantic @@ -57,21 +53,24 @@ interface Element { type Writable<T> = { -readonly [K in keyof T]: T[K] }; interface Window { + __webpack_public_path__: string; config: import('./web_src/js/types.ts').Config; $: typeof import('@types/jquery'), jQuery: typeof import('@types/jquery'), htmx: Omit<typeof import('htmx.org/dist/htmx.esm.js').default, 'config'> & { config?: Writable<typeof import('htmx.org').default.config>, + process?: (elt: Element | string) => void, }, - ui?: any, _globalHandlerErrors: Array<ErrorEvent & PromiseRejectionEvent> & { _inited: boolean, push: (e: ErrorEvent & PromiseRejectionEvent) => void | number, }, - __webpack_public_path__: string; + codeEditors: any[], // export editor for customization + + // various captcha plugins grecaptcha: any, turnstile: any, hcaptcha: any, - codeEditors: any[], - updateCloneStates: () => void, + + // do not add more properties here unless it is a must } diff --git a/web_src/js/htmx.ts b/web_src/js/htmx.ts index 3f9a5a815c..c23c3a21fa 100644 --- a/web_src/js/htmx.ts +++ b/web_src/js/htmx.ts @@ -1,5 +1,5 @@ import {showErrorToast} from './modules/toast.ts'; -import 'idiomorph/dist/idiomorph-ext.js'; // https://github.com/bigskysoftware/idiomorph#htmx +import 'idiomorph/htmx'; import type {HtmxResponseInfo} from 'htmx.org'; type HtmxEvent = Event & {detail: HtmxResponseInfo}; diff --git a/web_src/js/index.ts b/web_src/js/index.ts index 51d8c96fbd..347aad2709 100644 --- a/web_src/js/index.ts +++ b/web_src/js/index.ts @@ -2,8 +2,7 @@ import './bootstrap.ts'; import './htmx.ts'; -import {initDashboardRepoList} from './components/DashboardRepoList.vue'; - +import {initDashboardRepoList} from './features/dashboard.ts'; import {initGlobalCopyToClipboardListener} from './features/clipboard.ts'; import {initContextPopups} from './features/contextpopup.ts'; import {initRepoGraphGit} from './features/repo-graph.ts'; @@ -12,7 +11,6 @@ import {initImageDiff} from './features/imagediff.ts'; import {initRepoMigration} from './features/repo-migration.ts'; import {initRepoProject} from './features/repo-projects.ts'; import {initTableSort} from './features/tablesort.ts'; -import {initAutoFocusEnd} from './features/autofocus-end.ts'; import {initAdminUserListSearchForm} from './features/admin/users.ts'; import {initAdminConfigs} from './features/admin/config.ts'; import {initMarkupAnchors} from './markup/anchors.ts'; @@ -20,28 +18,20 @@ import {initNotificationCount, initNotificationsTable} from './features/notifica import {initRepoIssueContentHistory} from './features/repo-issue-content.ts'; import {initStopwatch} from './features/stopwatch.ts'; import {initFindFileInRepo} from './features/repo-findfile.ts'; -import {initCommentContent, initMarkupContent} from './markup/content.ts'; -import {initPdfViewer} from './render/pdf.ts'; - +import {initMarkupContent} from './markup/content.ts'; +import {initRepoFileView} from './features/file-view.ts'; import {initUserAuthOauth2, initUserCheckAppUrl} from './features/user-auth.ts'; -import { - initRepoIssueReferenceRepositorySearch, - initRepoIssueWipTitle, - initRepoPullRequestMergeInstruction, - initRepoPullRequestAllowMaintainerEdit, - initRepoPullRequestReview, initRepoIssueSidebarList, initRepoIssueFilterItemLabel, -} from './features/repo-issue.ts'; +import {initRepoPullRequestAllowMaintainerEdit, initRepoPullRequestReview, initRepoIssueSidebarDependency, initRepoIssueFilterItemLabel} from './features/repo-issue.ts'; import {initRepoEllipsisButton, initCommitStatuses} from './features/repo-commit.ts'; import {initRepoTopicBar} from './features/repo-home.ts'; import {initAdminCommon} from './features/admin/common.ts'; -import {initRepoTemplateSearch} from './features/repo-template.ts'; import {initRepoCodeView} from './features/repo-code.ts'; import {initSshKeyFormParser} from './features/sshkey-helper.ts'; import {initUserSettings} from './features/user-settings.ts'; import {initRepoActivityTopAuthorsChart, initRepoArchiveLinks} from './features/repo-common.ts'; import {initRepoMigrationStatusChecker} from './features/repo-migrate.ts'; import {initRepoDiffView} from './features/repo-diff.ts'; -import {initOrgTeamSearchRepoBox, initOrgTeamSettings} from './features/org-team.ts'; +import {initOrgTeam} from './features/org-team.ts'; import {initUserAuthWebAuthn, initUserAuthWebAuthnRegister} from './features/user-auth-webauthn.ts'; import {initRepoRelease, initRepoReleaseNew} from './features/repo-release.ts'; import {initRepoEditor} from './features/repo-editor.ts'; @@ -54,7 +44,7 @@ import {initRepoWikiForm} from './features/repo-wiki.ts'; import {initRepository, initBranchSelectorTabs} from './features/repo-legacy.ts'; import {initCopyContent} from './features/copycontent.ts'; import {initCaptcha} from './features/captcha.ts'; -import {initRepositoryActionView} from './components/RepoActionView.vue'; +import {initRepositoryActionView} from './features/repo-actions.ts'; import {initGlobalTooltips} from './modules/tippy.ts'; import {initGiteaFomantic} from './modules/fomantic.ts'; import {initSubmitEventPolyfill, onDomReady} from './utils/dom.ts'; @@ -64,63 +54,25 @@ import {initRepoContributors} from './features/contributors.ts'; import {initRepoCodeFrequency} from './features/code-frequency.ts'; import {initRepoRecentCommits} from './features/recent-commits.ts'; import {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.ts'; -import {initDirAuto} from './modules/dirauto.ts'; +import {initGlobalSelectorObserver} from './modules/observer.ts'; import {initRepositorySearch} from './features/repo-search.ts'; import {initColorPickers} from './features/colorpicker.ts'; import {initAdminSelfCheck} from './features/admin/selfcheck.ts'; import {initOAuth2SettingsDisableCheckbox} from './features/oauth2-settings.ts'; import {initGlobalFetchAction} from './features/common-fetch-action.ts'; -import {initScopedAccessTokenCategories} from './features/scoped-access-token.ts'; -import { - initFootLanguageMenu, - initGlobalDropdown, - initGlobalTabularMenu, - initHeadNavbarContentToggle, -} from './features/common-page.ts'; -import { - initGlobalButtonClickOnEnter, - initGlobalButtons, - initGlobalDeleteButton, -} from './features/common-button.ts'; -import { - initGlobalComboMarkdownEditor, - initGlobalEnterQuickSubmit, - initGlobalFormDirtyLeaveConfirm, -} from './features/common-form.ts'; +import {initFootLanguageMenu, initGlobalAvatarUploader, initGlobalDropdown, initGlobalInput, initGlobalTabularMenu, initHeadNavbarContentToggle} from './features/common-page.ts'; +import {initGlobalButtonClickOnEnter, initGlobalButtons, initGlobalDeleteButton} from './features/common-button.ts'; +import {initGlobalComboMarkdownEditor, initGlobalEnterQuickSubmit, initGlobalFormDirtyLeaveConfirm} from './features/common-form.ts'; +import {callInitFunctions} from './modules/init.ts'; +import {initRepoViewFileTree} from './features/repo-view-file-tree.ts'; initGiteaFomantic(); -initDirAuto(); initSubmitEventPolyfill(); -function callInitFunctions(functions: (() => any)[]) { - // Start performance trace by accessing a URL by "https://localhost/?_ui_performance_trace=1" or "https://localhost/?key=value&_ui_performance_trace=1" - // It is a quick check, no side effect so no need to do slow URL parsing. - const initStart = performance.now(); - if (window.location.search.includes('_ui_performance_trace=1')) { - let results: {name: string, dur: number}[] = []; - for (const func of functions) { - const start = performance.now(); - func(); - results.push({name: func.name, dur: performance.now() - start}); - } - results = results.sort((a, b) => b.dur - a.dur); - for (let i = 0; i < 20 && i < results.length; i++) { - // eslint-disable-next-line no-console - console.log(`performance trace: ${results[i].name} ${results[i].dur.toFixed(3)}`); - } - } else { - for (const func of functions) { - func(); - } - } - const initDur = performance.now() - initStart; - if (initDur > 500) { - console.error(`slow init functions took ${initDur.toFixed(3)}ms`); - } -} - onDomReady(() => { - callInitFunctions([ + const initStartTime = performance.now(); + const initPerformanceTracer = callInitFunctions([ + initGlobalAvatarUploader, initGlobalDropdown, initGlobalTabularMenu, initGlobalFetchAction, @@ -132,6 +84,7 @@ onDomReady(() => { initGlobalFormDirtyLeaveConfirm, initGlobalComboMarkdownEditor, initGlobalDeleteButton, + initGlobalInput, initCommonOrganization, initCommonIssueListQuickGoto, @@ -144,7 +97,6 @@ onDomReady(() => { initHeadNavbarContentToggle, initFootLanguageMenu, - initCommentContent, initContextPopups, initHeatmap, initImageDiff, @@ -153,7 +105,6 @@ onDomReady(() => { initSshKeyFormParser, initStopwatch, initTableSort, - initAutoFocusEnd, initFindFileInRepo, initCopyContent, @@ -167,8 +118,7 @@ onDomReady(() => { initNotificationCount, initNotificationsTable, - initOrgTeamSearchRepoBox, - initOrgTeamSettings, + initOrgTeam, initRepoActivityTopAuthorsChart, initRepoArchiveLinks, @@ -182,19 +132,16 @@ onDomReady(() => { initRepoIssueContentHistory, initRepoIssueList, initRepoIssueFilterItemLabel, - initRepoIssueSidebarList, - initRepoIssueReferenceRepositorySearch, - initRepoIssueWipTitle, + initRepoIssueSidebarDependency, initRepoMigration, initRepoMigrationStatusChecker, initRepoProject, - initRepoPullRequestMergeInstruction, initRepoPullRequestAllowMaintainerEdit, initRepoPullRequestReview, initRepoRelease, initRepoReleaseNew, - initRepoTemplateSearch, initRepoTopicBar, + initRepoViewFileTree, initRepoWikiForm, initRepository, initRepositoryActionView, @@ -212,10 +159,19 @@ onDomReady(() => { initUserAuthWebAuthnRegister, initUserSettings, initRepoDiffView, - initPdfViewer, - initScopedAccessTokenCategories, initColorPickers, initOAuth2SettingsDisableCheckbox, + + initRepoFileView, ]); + + // it must be the last one, then the "querySelectorAll" only needs to be executed once for global init functions. + initGlobalSelectorObserver(initPerformanceTracer); + if (initPerformanceTracer) initPerformanceTracer.printResults(); + + const initDur = performance.now() - initStartTime; + if (initDur > 500) { + console.error(`slow init functions took ${initDur.toFixed(3)}ms`); + } }); diff --git a/web_src/js/markup/anchors.ts b/web_src/js/markup/anchors.ts index 483d72bd5b..a0d49911fe 100644 --- a/web_src/js/markup/anchors.ts +++ b/web_src/js/markup/anchors.ts @@ -5,21 +5,24 @@ const removePrefix = (str: string): string => str.replace(/^user-content-/, ''); const hasPrefix = (str: string): boolean => str.startsWith('user-content-'); // scroll to anchor while respecting the `user-content` prefix that exists on the target -function scrollToAnchor(encodedId: string): void { - if (!encodedId) return; - const id = decodeURIComponent(encodedId); - const prefixedId = addPrefix(id); - let el = document.querySelector(`#${prefixedId}`); +function scrollToAnchor(encodedId?: string): void { + // FIXME: need to rewrite this function with new a better markup anchor generation logic, too many tricks here + let elemId: string; + try { + elemId = decodeURIComponent(encodedId ?? ''); + } catch {} // ignore the errors, since the "encodedId" is from user's input + if (!elemId) return; + + const prefixedId = addPrefix(elemId); + // eslint-disable-next-line unicorn/prefer-query-selector + let el = document.getElementById(prefixedId); // check for matching user-generated `a[name]` - if (!el) { - el = document.querySelector(`a[name="${CSS.escape(prefixedId)}"]`); - } + el = el ?? document.querySelector(`a[name="${CSS.escape(prefixedId)}"]`); // compat for links with old 'user-content-' prefixed hashes - if (!el && hasPrefix(id)) { - return document.querySelector(`#${id}`)?.scrollIntoView(); - } + // eslint-disable-next-line unicorn/prefer-query-selector + el = (!el && hasPrefix(elemId)) ? document.getElementById(elemId) : el; el?.scrollIntoView(); } diff --git a/web_src/js/markup/asciicast.ts b/web_src/js/markup/asciicast.ts index 97b18743a1..125bba447b 100644 --- a/web_src/js/markup/asciicast.ts +++ b/web_src/js/markup/asciicast.ts @@ -1,17 +1,17 @@ -export async function renderAsciicast() { - const els = document.querySelectorAll('.asciinema-player-container'); - if (!els.length) return; +import {queryElems} from '../utils/dom.ts'; - const [player] = await Promise.all([ - import(/* webpackChunkName: "asciinema-player" */'asciinema-player'), - import(/* webpackChunkName: "asciinema-player" */'asciinema-player/dist/bundle/asciinema-player.css'), - ]); +export async function initMarkupRenderAsciicast(elMarkup: HTMLElement): Promise<void> { + queryElems(elMarkup, '.asciinema-player-container', async (el) => { + const [player] = await Promise.all([ + // @ts-expect-error: module exports no types + import(/* webpackChunkName: "asciinema-player" */'asciinema-player'), + import(/* webpackChunkName: "asciinema-player" */'asciinema-player/dist/bundle/asciinema-player.css'), + ]); - for (const el of els) { player.create(el.getAttribute('data-asciinema-player-src'), el, { // poster (a preview frame) to display until the playback is started. // Set it to 1 hour (also means the end if the video is shorter) to make the preview frame show more. poster: 'npt:1:0:0', }); - } + }); } diff --git a/web_src/js/markup/codecopy.ts b/web_src/js/markup/codecopy.ts index f45b7a8e04..b37aa3a236 100644 --- a/web_src/js/markup/codecopy.ts +++ b/web_src/js/markup/codecopy.ts @@ -1,4 +1,5 @@ import {svg} from '../svg.ts'; +import {queryElems} from '../utils/dom.ts'; export function makeCodeCopyButton(): HTMLButtonElement { const button = document.createElement('button'); @@ -7,15 +8,15 @@ export function makeCodeCopyButton(): HTMLButtonElement { return button; } -export function renderCodeCopy(): void { - const els = document.querySelectorAll('.markup .code-block code'); - if (!els.length) return; - - for (const el of els) { - if (!el.textContent) continue; +export function initMarkupCodeCopy(elMarkup: HTMLElement): void { + // .markup .code-block code + queryElems(elMarkup, '.code-block code', (el) => { + if (!el.textContent) return; const btn = makeCodeCopyButton(); // remove final trailing newline introduced during HTML rendering btn.setAttribute('data-clipboard-text', el.textContent.replace(/\r?\n$/, '')); - el.after(btn); - } + // we only want to use `.code-block-container` if it exists, no matter `.code-block` exists or not. + const btnContainer = el.closest('.code-block-container') ?? el.closest('.code-block'); + btnContainer.append(btn); + }); } diff --git a/web_src/js/markup/content.ts b/web_src/js/markup/content.ts index b9190b15ce..55db4aa810 100644 --- a/web_src/js/markup/content.ts +++ b/web_src/js/markup/content.ts @@ -1,18 +1,17 @@ -import {renderMermaid} from './mermaid.ts'; -import {renderMath} from './math.ts'; -import {renderCodeCopy} from './codecopy.ts'; -import {renderAsciicast} from './asciicast.ts'; +import {initMarkupCodeMermaid} from './mermaid.ts'; +import {initMarkupCodeMath} from './math.ts'; +import {initMarkupCodeCopy} from './codecopy.ts'; +import {initMarkupRenderAsciicast} from './asciicast.ts'; import {initMarkupTasklist} from './tasklist.ts'; +import {registerGlobalSelectorFunc} from '../modules/observer.ts'; // code that runs for all markup content export function initMarkupContent(): void { - renderMermaid(); - renderMath(); - renderCodeCopy(); - renderAsciicast(); -} - -// code that only runs for comments -export function initCommentContent(): void { - initMarkupTasklist(); + registerGlobalSelectorFunc('.markup', (el: HTMLElement) => { + initMarkupCodeCopy(el); + initMarkupTasklist(el); + initMarkupCodeMermaid(el); + initMarkupCodeMath(el); + initMarkupRenderAsciicast(el); + }); } diff --git a/web_src/js/markup/html2markdown.ts b/web_src/js/markup/html2markdown.ts index fc2083e86d..5866d0d259 100644 --- a/web_src/js/markup/html2markdown.ts +++ b/web_src/js/markup/html2markdown.ts @@ -1,7 +1,9 @@ -import {htmlEscape} from 'escape-goat'; +import {html, htmlRaw} from '../utils/html.ts'; + +type Processor = (el: HTMLElement) => string | HTMLElement | void; type Processors = { - [tagName: string]: (el: HTMLElement) => string | HTMLElement | void; + [tagName: string]: Processor; } type ProcessorContext = { @@ -11,7 +13,7 @@ type ProcessorContext = { } function prepareProcessors(ctx:ProcessorContext): Processors { - const processors = { + const processors: Processors = { H1(el: HTMLElement) { const level = parseInt(el.tagName.slice(1)); el.textContent = `${'#'.repeat(level)} ${el.textContent.trim()}`; @@ -36,10 +38,10 @@ function prepareProcessors(ctx:ProcessorContext): Processors { IMG(el: HTMLElement) { const alt = el.getAttribute('alt') || 'image'; const src = el.getAttribute('src'); - const widthAttr = el.hasAttribute('width') ? ` width="${htmlEscape(el.getAttribute('width') || '')}"` : ''; - const heightAttr = el.hasAttribute('height') ? ` height="${htmlEscape(el.getAttribute('height') || '')}"` : ''; + const widthAttr = el.hasAttribute('width') ? htmlRaw` width="${el.getAttribute('width') || ''}"` : ''; + const heightAttr = el.hasAttribute('height') ? htmlRaw` height="${el.getAttribute('height') || ''}"` : ''; if (widthAttr || heightAttr) { - return `<img alt="${htmlEscape(alt)}"${widthAttr}${heightAttr} src="${htmlEscape(src)}">`; + return html`<img alt="${alt}"${widthAttr}${heightAttr} src="${src}">`; } return ``; }, diff --git a/web_src/js/markup/math.ts b/web_src/js/markup/math.ts index 4777805e3c..bc118137a1 100644 --- a/web_src/js/markup/math.ts +++ b/web_src/js/markup/math.ts @@ -1,4 +1,5 @@ import {displayError} from './common.ts'; +import {queryElems} from '../utils/dom.ts'; function targetElement(el: Element): {target: Element, displayAsBlock: boolean} { // The target element is either the parent "code block with loading indicator", or itself @@ -11,27 +12,25 @@ function targetElement(el: Element): {target: Element, displayAsBlock: boolean} }; } -export async function renderMath(): Promise<void> { - const els = document.querySelectorAll('.markup code.language-math'); - if (!els.length) return; +export async function initMarkupCodeMath(elMarkup: HTMLElement): Promise<void> { + // .markup code.language-math' + queryElems(elMarkup, 'code.language-math', async (el) => { + const [{default: katex}] = await Promise.all([ + import(/* webpackChunkName: "katex" */'katex'), + import(/* webpackChunkName: "katex" */'katex/dist/katex.css'), + ]); - const [{default: katex}] = await Promise.all([ - import(/* webpackChunkName: "katex" */'katex'), - import(/* webpackChunkName: "katex" */'katex/dist/katex.css'), - ]); + const MAX_CHARS = 1000; + const MAX_SIZE = 25; + const MAX_EXPAND = 1000; - const MAX_CHARS = 1000; - const MAX_SIZE = 25; - const MAX_EXPAND = 1000; - - for (const el of els) { const {target, displayAsBlock} = targetElement(el); - if (target.hasAttribute('data-render-done')) continue; + if (target.hasAttribute('data-render-done')) return; const source = el.textContent; if (source.length > MAX_CHARS) { displayError(target, new Error(`Math source of ${source.length} characters exceeds the maximum allowed length of ${MAX_CHARS}.`)); - continue; + return; } try { const tempEl = document.createElement(displayAsBlock ? 'p' : 'span'); @@ -44,5 +43,5 @@ export async function renderMath(): Promise<void> { } catch (error) { displayError(target, error); } - } + }); } diff --git a/web_src/js/markup/mermaid.ts b/web_src/js/markup/mermaid.ts index 2dbed280c2..33d9a1ed9b 100644 --- a/web_src/js/markup/mermaid.ts +++ b/web_src/js/markup/mermaid.ts @@ -1,6 +1,8 @@ import {isDarkTheme} from '../utils.ts'; import {makeCodeCopyButton} from './codecopy.ts'; import {displayError} from './common.ts'; +import {queryElems} from '../utils/dom.ts'; +import {html, htmlRaw} from '../utils/html.ts'; const {mermaidMaxSourceCharacters} = window.config; @@ -10,34 +12,32 @@ body {margin: 0; padding: 0; overflow: hidden} #mermaid {display: block; margin: 0 auto} blockquote, dd, dl, figure, h1, h2, h3, h4, h5, h6, hr, p, pre {margin: 0}`; -export async function renderMermaid(): Promise<void> { - const els = document.querySelectorAll('.markup code.language-mermaid'); - if (!els.length) return; +export async function initMarkupCodeMermaid(elMarkup: HTMLElement): Promise<void> { + // .markup code.language-mermaid + queryElems(elMarkup, 'code.language-mermaid', async (el) => { + const {default: mermaid} = await import(/* webpackChunkName: "mermaid" */'mermaid'); - const {default: mermaid} = await import(/* webpackChunkName: "mermaid" */'mermaid'); + mermaid.initialize({ + startOnLoad: false, + theme: isDarkTheme() ? 'dark' : 'neutral', + securityLevel: 'strict', + suppressErrorRendering: true, + }); - mermaid.initialize({ - startOnLoad: false, - theme: isDarkTheme() ? 'dark' : 'neutral', - securityLevel: 'strict', - suppressErrorRendering: true, - }); - - for (const el of els) { const pre = el.closest('pre'); - if (pre.hasAttribute('data-render-done')) continue; + if (pre.hasAttribute('data-render-done')) return; const source = el.textContent; if (mermaidMaxSourceCharacters >= 0 && source.length > mermaidMaxSourceCharacters) { displayError(pre, new Error(`Mermaid source of ${source.length} characters exceeds the maximum allowed length of ${mermaidMaxSourceCharacters}.`)); - continue; + return; } try { await mermaid.parse(source); } catch (err) { displayError(pre, err); - continue; + return; } try { @@ -46,8 +46,8 @@ export async function renderMermaid(): Promise<void> { const {svg} = await mermaid.render('mermaid', source); const iframe = document.createElement('iframe'); - iframe.classList.add('markup-render', 'tw-invisible'); - iframe.srcdoc = `<html><head><style>${iframeCss}</style></head><body>${svg}</body></html>`; + iframe.classList.add('markup-content-iframe', 'tw-invisible'); + iframe.srcdoc = html`<html><head><style>${htmlRaw(iframeCss)}</style></head><body>${htmlRaw(svg)}</body></html>`; const mermaidBlock = document.createElement('div'); mermaidBlock.classList.add('mermaid-block', 'is-loading', 'tw-hidden'); @@ -85,5 +85,5 @@ export async function renderMermaid(): Promise<void> { } catch (err) { displayError(pre, err); } - } + }); } diff --git a/web_src/js/markup/tasklist.ts b/web_src/js/markup/tasklist.ts index 95db7fc845..dc4bbd9519 100644 --- a/web_src/js/markup/tasklist.ts +++ b/web_src/js/markup/tasklist.ts @@ -7,80 +7,80 @@ const preventListener = (e: Event) => e.preventDefault(); * Attaches `input` handlers to markdown rendered tasklist checkboxes in comments. * * When a checkbox value changes, the corresponding [ ] or [x] in the markdown string - * is set accordingly and sent to the server. On success it updates the raw-content on + * is set accordingly and sent to the server. On success, it updates the raw-content on * error it resets the checkbox to its original value. */ -export function initMarkupTasklist(): void { - for (const el of document.querySelectorAll(`.markup[data-can-edit=true]`) || []) { - const container = el.parentNode; - const checkboxes = el.querySelectorAll<HTMLInputElement>(`.task-list-item input[type=checkbox]`); +export function initMarkupTasklist(elMarkup: HTMLElement): void { + if (!elMarkup.matches('[data-can-edit=true]')) return; - for (const checkbox of checkboxes) { - if (checkbox.hasAttribute('data-editable')) { - return; - } + const container = elMarkup.parentNode; + const checkboxes = elMarkup.querySelectorAll<HTMLInputElement>(`.task-list-item input[type=checkbox]`); - checkbox.setAttribute('data-editable', 'true'); - checkbox.addEventListener('input', async () => { - const checkboxCharacter = checkbox.checked ? 'x' : ' '; - const position = parseInt(checkbox.getAttribute('data-source-position')) + 1; + for (const checkbox of checkboxes) { + if (checkbox.hasAttribute('data-editable')) { + return; + } - const rawContent = container.querySelector('.raw-content'); - const oldContent = rawContent.textContent; + checkbox.setAttribute('data-editable', 'true'); + checkbox.addEventListener('input', async () => { + const checkboxCharacter = checkbox.checked ? 'x' : ' '; + const position = parseInt(checkbox.getAttribute('data-source-position')) + 1; - const encoder = new TextEncoder(); - const buffer = encoder.encode(oldContent); - // Indexes may fall off the ends and return undefined. - if (buffer[position - 1] !== '['.codePointAt(0) || - buffer[position] !== ' '.codePointAt(0) && buffer[position] !== 'x'.codePointAt(0) || - buffer[position + 1] !== ']'.codePointAt(0)) { - // Position is probably wrong. Revert and don't allow change. - checkbox.checked = !checkbox.checked; - throw new Error(`Expected position to be space or x and surrounded by brackets, but it's not: position=${position}`); - } - buffer.set(encoder.encode(checkboxCharacter), position); - const newContent = new TextDecoder().decode(buffer); + const rawContent = container.querySelector('.raw-content'); + const oldContent = rawContent.textContent; - if (newContent === oldContent) { - return; - } + const encoder = new TextEncoder(); + const buffer = encoder.encode(oldContent); + // Indexes may fall off the ends and return undefined. + if (buffer[position - 1] !== '['.codePointAt(0) || + buffer[position] !== ' '.codePointAt(0) && buffer[position] !== 'x'.codePointAt(0) || + buffer[position + 1] !== ']'.codePointAt(0)) { + // Position is probably wrong. Revert and don't allow change. + checkbox.checked = !checkbox.checked; + throw new Error(`Expected position to be space or x and surrounded by brackets, but it's not: position=${position}`); + } + buffer.set(encoder.encode(checkboxCharacter), position); + const newContent = new TextDecoder().decode(buffer); - // Prevent further inputs until the request is done. This does not use the - // `disabled` attribute because it causes the border to flash on click. - for (const checkbox of checkboxes) { - checkbox.addEventListener('click', preventListener); - } + if (newContent === oldContent) { + return; + } - try { - const editContentZone = container.querySelector<HTMLDivElement>('.edit-content-zone'); - const updateUrl = editContentZone.getAttribute('data-update-url'); - const context = editContentZone.getAttribute('data-context'); - const contentVersion = editContentZone.getAttribute('data-content-version'); + // Prevent further inputs until the request is done. This does not use the + // `disabled` attribute because it causes the border to flash on click. + for (const checkbox of checkboxes) { + checkbox.addEventListener('click', preventListener); + } - const requestBody = new FormData(); - requestBody.append('ignore_attachments', 'true'); - requestBody.append('content', newContent); - requestBody.append('context', context); - requestBody.append('content_version', contentVersion); - const response = await POST(updateUrl, {data: requestBody}); - const data = await response.json(); - if (response.status === 400) { - showErrorToast(data.errorMessage); - return; - } - editContentZone.setAttribute('data-content-version', data.contentVersion); - rawContent.textContent = newContent; - } catch (err) { - checkbox.checked = !checkbox.checked; - console.error(err); - } + try { + const editContentZone = container.querySelector<HTMLDivElement>('.edit-content-zone'); + const updateUrl = editContentZone.getAttribute('data-update-url'); + const context = editContentZone.getAttribute('data-context'); + const contentVersion = editContentZone.getAttribute('data-content-version'); - // Enable input on checkboxes again - for (const checkbox of checkboxes) { - checkbox.removeEventListener('click', preventListener); + const requestBody = new FormData(); + requestBody.append('ignore_attachments', 'true'); + requestBody.append('content', newContent); + requestBody.append('context', context); + requestBody.append('content_version', contentVersion); + const response = await POST(updateUrl, {data: requestBody}); + const data = await response.json(); + if (response.status === 400) { + showErrorToast(data.errorMessage); + return; } - }); - } + editContentZone.setAttribute('data-content-version', data.contentVersion); + rawContent.textContent = newContent; + } catch (err) { + checkbox.checked = !checkbox.checked; + console.error(err); + } + + // Enable input on checkboxes again + for (const checkbox of checkboxes) { + checkbox.removeEventListener('click', preventListener); + } + }); // Enable the checkboxes as they are initially disabled by the markdown renderer for (const checkbox of checkboxes) { diff --git a/web_src/js/modules/diff-file.test.ts b/web_src/js/modules/diff-file.test.ts new file mode 100644 index 0000000000..f0438538a0 --- /dev/null +++ b/web_src/js/modules/diff-file.test.ts @@ -0,0 +1,51 @@ +import {diffTreeStoreSetViewed, reactiveDiffTreeStore} from './diff-file.ts'; + +test('diff-tree', () => { + const store = reactiveDiffTreeStore({ + 'TreeRoot': { + 'FullName': '', + 'DisplayName': '', + 'EntryMode': '', + 'IsViewed': false, + 'NameHash': '....', + 'DiffStatus': '', + 'FileIcon': '', + 'Children': [ + { + 'FullName': 'dir1', + 'DisplayName': 'dir1', + 'EntryMode': 'tree', + 'IsViewed': false, + 'NameHash': '....', + 'DiffStatus': '', + 'FileIcon': '', + 'Children': [ + { + 'FullName': 'dir1/test.txt', + 'DisplayName': 'test.txt', + 'DiffStatus': 'added', + 'NameHash': '....', + 'EntryMode': '', + 'IsViewed': false, + 'FileIcon': '', + 'Children': null, + }, + ], + }, + { + 'FullName': 'other.txt', + 'DisplayName': 'other.txt', + 'NameHash': '........', + 'DiffStatus': 'added', + 'EntryMode': '', + 'IsViewed': false, + 'FileIcon': '', + 'Children': null, + }, + ], + }, + }, '', ''); + diffTreeStoreSetViewed(store, 'dir1/test.txt', true); + expect(store.fullNameMap['dir1/test.txt'].IsViewed).toBe(true); + expect(store.fullNameMap['dir1'].IsViewed).toBe(true); +}); diff --git a/web_src/js/modules/diff-file.ts b/web_src/js/modules/diff-file.ts new file mode 100644 index 0000000000..2cec7bc6b3 --- /dev/null +++ b/web_src/js/modules/diff-file.ts @@ -0,0 +1,82 @@ +import {reactive} from 'vue'; +import type {Reactive} from 'vue'; + +const {pageData} = window.config; + +export type DiffStatus = '' | 'added' | 'modified' | 'deleted' | 'renamed' | 'copied' | 'typechange'; + +export type DiffTreeEntry = { + FullName: string, + DisplayName: string, + NameHash: string, + DiffStatus: DiffStatus, + EntryMode: string, + IsViewed: boolean, + Children: DiffTreeEntry[], + FileIcon: string, + ParentEntry?: DiffTreeEntry, +} + +type DiffFileTreeData = { + TreeRoot: DiffTreeEntry, +}; + +type DiffFileTree = { + folderIcon: string; + folderOpenIcon: string; + diffFileTree: DiffFileTreeData; + fullNameMap?: Record<string, DiffTreeEntry> + fileTreeIsVisible: boolean; + selectedItem: string; +} + +let diffTreeStoreReactive: Reactive<DiffFileTree>; +export function diffTreeStore() { + if (!diffTreeStoreReactive) { + diffTreeStoreReactive = reactiveDiffTreeStore(pageData.DiffFileTree, pageData.FolderIcon, pageData.FolderOpenIcon); + } + return diffTreeStoreReactive; +} + +export function diffTreeStoreSetViewed(store: Reactive<DiffFileTree>, fullName: string, viewed: boolean) { + const entry = store.fullNameMap[fullName]; + if (!entry) return; + entry.IsViewed = viewed; + for (let parent = entry.ParentEntry; parent; parent = parent.ParentEntry) { + parent.IsViewed = isEntryViewed(parent); + } +} + +function fillFullNameMap(map: Record<string, DiffTreeEntry>, entry: DiffTreeEntry) { + map[entry.FullName] = entry; + if (!entry.Children) return; + entry.IsViewed = isEntryViewed(entry); + for (const child of entry.Children) { + child.ParentEntry = entry; + fillFullNameMap(map, child); + } +} + +export function reactiveDiffTreeStore(data: DiffFileTreeData, folderIcon: string, folderOpenIcon: string): Reactive<DiffFileTree> { + const store = reactive({ + diffFileTree: data, + folderIcon, + folderOpenIcon, + fileTreeIsVisible: false, + selectedItem: '', + fullNameMap: {}, + }); + fillFullNameMap(store.fullNameMap, data.TreeRoot); + return store; +} + +function isEntryViewed(entry: DiffTreeEntry): boolean { + if (entry.Children) { + let count = 0; + for (const child of entry.Children) { + if (child.IsViewed) count++; + } + return count === entry.Children.length; + } + return entry.IsViewed; +} diff --git a/web_src/js/modules/dirauto.ts b/web_src/js/modules/dirauto.ts deleted file mode 100644 index 7058a59b09..0000000000 --- a/web_src/js/modules/dirauto.ts +++ /dev/null @@ -1,44 +0,0 @@ -import {isDocumentFragmentOrElementNode} from '../utils/dom.ts'; - -type DirElement = HTMLInputElement | HTMLTextAreaElement; - -// for performance considerations, it only uses performant syntax -function attachDirAuto(el: DirElement) { - if (el.type !== 'hidden' && - el.type !== 'checkbox' && - el.type !== 'radio' && - el.type !== 'range' && - el.type !== 'color') { - el.dir = 'auto'; - } -} - -export function initDirAuto(): void { - const observer = new MutationObserver((mutationList) => { - const len = mutationList.length; - for (let i = 0; i < len; i++) { - const mutation = mutationList[i]; - const len = mutation.addedNodes.length; - for (let i = 0; i < len; i++) { - const addedNode = mutation.addedNodes[i] as HTMLElement; - if (!isDocumentFragmentOrElementNode(addedNode)) continue; - if (addedNode.nodeName === 'INPUT' || addedNode.nodeName === 'TEXTAREA') { - attachDirAuto(addedNode as DirElement); - } - const children = addedNode.querySelectorAll<DirElement>('input, textarea'); - const len = children.length; - for (let childIdx = 0; childIdx < len; childIdx++) { - attachDirAuto(children[childIdx]); - } - } - } - }); - - const docNodes = document.querySelectorAll<DirElement>('input, textarea'); - const len = docNodes.length; - for (let i = 0; i < len; i++) { - attachDirAuto(docNodes[i]); - } - - observer.observe(document, {subtree: true, childList: true}); -} diff --git a/web_src/js/modules/fomantic.ts b/web_src/js/modules/fomantic.ts index af47c8fb51..4b1dbc4f62 100644 --- a/web_src/js/modules/fomantic.ts +++ b/web_src/js/modules/fomantic.ts @@ -1,5 +1,4 @@ import $ from 'jquery'; -import {initFomanticApiPatch} from './fomantic/api.ts'; import {initAriaCheckboxPatch} from './fomantic/checkbox.ts'; import {initAriaFormFieldPatch} from './fomantic/form.ts'; import {initAriaDropdownPatch} from './fomantic/dropdown.ts'; @@ -7,13 +6,13 @@ import {initAriaModalPatch} from './fomantic/modal.ts'; import {initFomanticTransition} from './fomantic/transition.ts'; import {initFomanticDimmer} from './fomantic/dimmer.ts'; import {svg} from '../svg.ts'; +import {initFomanticTab} from './fomantic/tab.ts'; export const fomanticMobileScreen = window.matchMedia('only screen and (max-width: 767.98px)'); export function initGiteaFomantic() { - // Silence fomantic's error logging when tabs are used without a target content element - $.fn.tab.settings.silent = true; - + // our extensions + $.fn.fomanticExt = {}; // By default, use "exact match" for full text search $.fn.dropdown.settings.fullTextSearch = 'exact'; // Do not use "cursor: pointer" for dropdown labels @@ -26,7 +25,7 @@ export function initGiteaFomantic() { initFomanticTransition(); initFomanticDimmer(); - initFomanticApiPatch(); + initFomanticTab(); // Use the patches to improve accessibility, these patches are designed to be as independent as possible, make it easy to modify or remove in the future. initAriaCheckboxPatch(); diff --git a/web_src/js/modules/fomantic/api.ts b/web_src/js/modules/fomantic/api.ts deleted file mode 100644 index 97430450e2..0000000000 --- a/web_src/js/modules/fomantic/api.ts +++ /dev/null @@ -1,41 +0,0 @@ -import $ from 'jquery'; -import type {FomanticInitFunction} from '../../types.ts'; - -export function initFomanticApiPatch() { - // - // Fomantic API module has some very buggy behaviors: - // - // If encodeParameters=true, it calls `urlEncodedValue` to encode the parameter. - // However, `urlEncodedValue` just tries to "guess" whether the parameter is already encoded, by decoding the parameter and encoding it again. - // - // There are 2 problems: - // 1. It may guess wrong, and skip encoding a parameter which looks like encoded. - // 2. If the parameter can't be decoded, `decodeURIComponent` will throw an error, and the whole request will fail. - // - // This patch only fixes the second error behavior at the moment. - // - const patchKey = '_giteaFomanticApiPatch'; - const oldApi = $.api; - $.api = $.fn.api = function(...args: Parameters<FomanticInitFunction>) { - const apiCall = oldApi.bind(this); - const ret = oldApi.apply(this, args); - - if (typeof args[0] !== 'string') { - const internalGet = apiCall('internal', 'get'); - if (!internalGet.urlEncodedValue[patchKey]) { - const oldUrlEncodedValue = internalGet.urlEncodedValue; - internalGet.urlEncodedValue = function (value: any) { - try { - return oldUrlEncodedValue(value); - } catch { - // if Fomantic API module's `urlEncodedValue` throws an error, we encode it by ourselves. - return encodeURIComponent(value); - } - }; - internalGet.urlEncodedValue[patchKey] = true; - } - } - return ret; - }; - $.api.settings = oldApi.settings; -} diff --git a/web_src/js/modules/fomantic/dimmer.ts b/web_src/js/modules/fomantic/dimmer.ts index 4e05cac0cd..cbdfac23cb 100644 --- a/web_src/js/modules/fomantic/dimmer.ts +++ b/web_src/js/modules/fomantic/dimmer.ts @@ -3,7 +3,7 @@ import {queryElemChildren} from '../../utils/dom.ts'; export function initFomanticDimmer() { // stand-in for removed dimmer module - $.fn.dimmer = function (arg0: string, arg1: any) { + $.fn.dimmer = function (this: any, arg0: string, arg1: any) { if (arg0 === 'add content') { const $el = arg1; const existingDimmer = document.querySelector('body > .ui.dimmer'); diff --git a/web_src/js/modules/fomantic/dropdown.test.ts b/web_src/js/modules/fomantic/dropdown.test.ts index 587e0bca7c..dd3497c8fc 100644 --- a/web_src/js/modules/fomantic/dropdown.test.ts +++ b/web_src/js/modules/fomantic/dropdown.test.ts @@ -23,7 +23,27 @@ test('hideScopedEmptyDividers-simple', () => { `); }); -test('hideScopedEmptyDividers-hidden1', () => { +test('hideScopedEmptyDividers-items-all-filtered', () => { + const container = createElementFromHTML(`<div> +<div class="any"></div> +<div class="divider"></div> +<div class="item filtered">a</div> +<div class="item filtered">b</div> +<div class="divider"></div> +<div class="any"></div> +</div>`); + hideScopedEmptyDividers(container); + expect(container.innerHTML).toEqual(` +<div class="any"></div> +<div class="divider hidden transition"></div> +<div class="item filtered">a</div> +<div class="item filtered">b</div> +<div class="divider"></div> +<div class="any"></div> +`); +}); + +test('hideScopedEmptyDividers-hide-last', () => { const container = createElementFromHTML(`<div> <div class="item">a</div> <div class="divider" data-scope="b"></div> @@ -37,7 +57,7 @@ test('hideScopedEmptyDividers-hidden1', () => { `); }); -test('hideScopedEmptyDividers-hidden2', () => { +test('hideScopedEmptyDividers-scoped-items', () => { const container = createElementFromHTML(`<div> <div class="item" data-scope="">a</div> <div class="divider" data-scope="b"></div> diff --git a/web_src/js/modules/fomantic/dropdown.ts b/web_src/js/modules/fomantic/dropdown.ts index 6d0f12cb43..ccc22073d7 100644 --- a/web_src/js/modules/fomantic/dropdown.ts +++ b/web_src/js/modules/fomantic/dropdown.ts @@ -1,6 +1,7 @@ import $ from 'jquery'; import {generateAriaId} from './base.ts'; import type {FomanticInitFunction} from '../../types.ts'; +import {queryElems} from '../../utils/dom.ts'; const ariaPatchKey = '_giteaAriaPatchDropdown'; const fomanticDropdownFn = $.fn.dropdown; @@ -9,24 +10,35 @@ const fomanticDropdownFn = $.fn.dropdown; export function initAriaDropdownPatch() { if ($.fn.dropdown === ariaDropdownFn) throw new Error('initAriaDropdownPatch could only be called once'); $.fn.dropdown = ariaDropdownFn; + $.fn.fomanticExt.onResponseKeepSelectedItem = onResponseKeepSelectedItem; + $.fn.fomanticExt.onDropdownAfterFiltered = onDropdownAfterFiltered; (ariaDropdownFn as FomanticInitFunction).settings = fomanticDropdownFn.settings; } // the patched `$.fn.dropdown` function, it passes the arguments to Fomantic's `$.fn.dropdown` function, and: -// * it does the one-time attaching on the first call -// * it delegates the `onLabelCreate` to the patched `onLabelCreate` to add necessary aria attributes -function ariaDropdownFn(...args: Parameters<FomanticInitFunction>) { +// * it does the one-time element event attaching on the first call +// * it delegates the module internal functions like `onLabelCreate` to the patched functions to add more features. +function ariaDropdownFn(this: any, ...args: Parameters<FomanticInitFunction>) { const ret = fomanticDropdownFn.apply(this, args); - // if the `$().dropdown()` call is without arguments, or it has non-string (object) argument, - // it means that this call will reset the dropdown internal settings, then we need to re-delegate the callbacks. - const needDelegate = (!args.length || typeof args[0] !== 'string'); - for (const el of this) { + for (let el of this) { + // dropdown will replace '<select class="ui dropdown"/>' to '<div class="ui dropdown"><select (hidden)></select><div class="menu">...</div></div>' + // so we need to correctly find the closest '.ui.dropdown' element, it is the real fomantic dropdown module. + el = el.closest('.ui.dropdown'); if (!el[ariaPatchKey]) { - attachInit(el); + // the elements don't belong to the dropdown "module" and won't be reset + // so we only need to initialize them once. + attachInitElements(el); } - if (needDelegate) { - delegateOne($(el)); + + // if the `$().dropdown()` is called without arguments, or it has non-string (object) argument, + // it means that such call will reset the dropdown "module" including internal settings, + // then we need to re-delegate the callbacks. + const $dropdown = $(el); + const dropdownModule = $dropdown.data('module-dropdown'); + if (!dropdownModule.giteaDelegated) { + dropdownModule.giteaDelegated = true; + delegateDropdownModule($dropdown); } } return ret; @@ -36,7 +48,7 @@ function ariaDropdownFn(...args: Parameters<FomanticInitFunction>) { // the elements inside the dropdown menu item should not be focusable, the focus should always be on the dropdown primary element. function updateMenuItem(dropdown: HTMLElement, item: HTMLElement) { if (!item.id) item.id = generateAriaId(); - item.setAttribute('role', dropdown[ariaPatchKey].listItemRole); + item.setAttribute('role', (dropdown as any)[ariaPatchKey].listItemRole); item.setAttribute('tabindex', '-1'); for (const el of item.querySelectorAll('a, input, button')) el.setAttribute('tabindex', '-1'); } @@ -59,37 +71,17 @@ function updateSelectionLabel(label: HTMLElement) { } } -function processMenuItems($dropdown, dropdownCall) { - const hideEmptyDividers = dropdownCall('setting', 'hideDividers') === 'empty'; +function onDropdownAfterFiltered(this: any) { + const $dropdown = $(this).closest('.ui.dropdown'); // "this" can be the "ui dropdown" or "<select>" + const hideEmptyDividers = $dropdown.dropdown('setting', 'hideDividers') === 'empty'; const itemsMenu = $dropdown[0].querySelector('.scrolling.menu') || $dropdown[0].querySelector('.menu'); - if (hideEmptyDividers) hideScopedEmptyDividers(itemsMenu); + if (hideEmptyDividers && itemsMenu) hideScopedEmptyDividers(itemsMenu); } // delegate the dropdown's template functions and callback functions to add aria attributes. -function delegateOne($dropdown: any) { +function delegateDropdownModule($dropdown: any) { const dropdownCall = fomanticDropdownFn.bind($dropdown); - // If there is a "search input" in the "menu", Fomantic will only "focus the input" but not "toggle the menu" when the "dropdown icon" is clicked. - // Actually, Fomantic UI doesn't support such layout/usage. It needs to patch the "focusSearch" / "blurSearch" functions to make sure it toggles the menu. - const oldFocusSearch = dropdownCall('internal', 'focusSearch'); - const oldBlurSearch = dropdownCall('internal', 'blurSearch'); - // * If the "dropdown icon" is clicked, Fomantic calls "focusSearch", so show the menu - dropdownCall('internal', 'focusSearch', function () { dropdownCall('show'); oldFocusSearch.call(this) }); - // * If the "dropdown icon" is clicked again when the menu is visible, Fomantic calls "blurSearch", so hide the menu - dropdownCall('internal', 'blurSearch', function () { oldBlurSearch.call(this); dropdownCall('hide') }); - - const oldFilterItems = dropdownCall('internal', 'filterItems'); - dropdownCall('internal', 'filterItems', function (...args: any[]) { - oldFilterItems.call(this, ...args); - processMenuItems($dropdown, dropdownCall); - }); - - const oldShow = dropdownCall('internal', 'show'); - dropdownCall('internal', 'show', function (...args: any[]) { - oldShow.call(this, ...args); - processMenuItems($dropdown, dropdownCall); - }); - // the "template" functions are used for dynamic creation (eg: AJAX) const dropdownTemplates = {...dropdownCall('setting', 'templates'), t: performance.now()}; const dropdownTemplatesMenuOld = dropdownTemplates.menu; @@ -108,7 +100,7 @@ function delegateOne($dropdown: any) { // the `onLabelCreate` is used to add necessary aria attributes for dynamically created selection labels const dropdownOnLabelCreateOld = dropdownCall('setting', 'onLabelCreate'); - dropdownCall('setting', 'onLabelCreate', function(value: any, text: string) { + dropdownCall('setting', 'onLabelCreate', function(this: any, value: any, text: string) { const $label = dropdownOnLabelCreateOld.call(this, value, text); updateSelectionLabel($label[0]); return $label; @@ -141,7 +133,7 @@ function attachStaticElements(dropdown: HTMLElement, focusable: HTMLElement, men $(menu).find('> .item').each((_, item) => updateMenuItem(dropdown, item)); // this role could only be changed after its content is ready, otherwise some browsers+readers (like Chrome+AppleVoice) crash - menu.setAttribute('role', dropdown[ariaPatchKey].listPopupRole); + menu.setAttribute('role', (dropdown as any)[ariaPatchKey].listPopupRole); // prepare selection label items for (const label of dropdown.querySelectorAll<HTMLElement>('.ui.label')) { @@ -149,8 +141,8 @@ function attachStaticElements(dropdown: HTMLElement, focusable: HTMLElement, men } // make the primary element (focusable) aria-friendly - focusable.setAttribute('role', focusable.getAttribute('role') ?? dropdown[ariaPatchKey].focusableRole); - focusable.setAttribute('aria-haspopup', dropdown[ariaPatchKey].listPopupRole); + focusable.setAttribute('role', focusable.getAttribute('role') ?? (dropdown as any)[ariaPatchKey].focusableRole); + focusable.setAttribute('aria-haspopup', (dropdown as any)[ariaPatchKey].listPopupRole); focusable.setAttribute('aria-controls', menu.id); focusable.setAttribute('aria-expanded', 'false'); @@ -161,9 +153,8 @@ function attachStaticElements(dropdown: HTMLElement, focusable: HTMLElement, men } } -function attachInit(dropdown: HTMLElement) { - dropdown[ariaPatchKey] = {}; - if (dropdown.classList.contains('custom')) return; +function attachInitElements(dropdown: HTMLElement) { + (dropdown as any)[ariaPatchKey] = {}; // Dropdown has 2 different focusing behaviors // * with search input: the input is focused, and it works with aria-activedescendant pointing another sibling element. @@ -202,9 +193,9 @@ function attachInit(dropdown: HTMLElement) { // Since #19861 we have prepared the "combobox" solution, but didn't get enough time to put it into practice and test before. const isComboBox = dropdown.querySelectorAll('input').length > 0; - dropdown[ariaPatchKey].focusableRole = isComboBox ? 'combobox' : 'menu'; - dropdown[ariaPatchKey].listPopupRole = isComboBox ? 'listbox' : ''; - dropdown[ariaPatchKey].listItemRole = isComboBox ? 'option' : 'menuitem'; + (dropdown as any)[ariaPatchKey].focusableRole = isComboBox ? 'combobox' : 'menu'; + (dropdown as any)[ariaPatchKey].listPopupRole = isComboBox ? 'listbox' : ''; + (dropdown as any)[ariaPatchKey].listItemRole = isComboBox ? 'option' : 'menuitem'; attachDomEvents(dropdown, focusable, menu); attachStaticElements(dropdown, focusable, menu); @@ -227,7 +218,7 @@ function attachDomEvents(dropdown: HTMLElement, focusable: HTMLElement, menu: HT // if the popup is visible and has an active/selected item, use its id as aria-activedescendant if (menuVisible) { focusable.setAttribute('aria-activedescendant', active.id); - } else if (dropdown[ariaPatchKey].listPopupRole === 'menu') { + } else if ((dropdown as any)[ariaPatchKey].listPopupRole === 'menu') { // for menu, when the popup is hidden, no need to keep the aria-activedescendant, and clear the active/selected item focusable.removeAttribute('aria-activedescendant'); active.classList.remove('active', 'selected'); @@ -237,12 +228,13 @@ function attachDomEvents(dropdown: HTMLElement, focusable: HTMLElement, menu: HT dropdown.addEventListener('keydown', (e: KeyboardEvent) => { // here it must use keydown event before dropdown's keyup handler, otherwise there is no Enter event in our keyup handler if (e.key === 'Enter') { - const dropdownCall = fomanticDropdownFn.bind($(dropdown)); - let $item = dropdownCall('get item', dropdownCall('get value')); - if (!$item) $item = $(menu).find('> .item.selected'); // when dropdown filters items by input, there is no "value", so query the "selected" item + const elItem = menu.querySelector<HTMLElement>(':scope > .item.selected, .menu > .item.selected'); // if the selected item is clickable, then trigger the click event. // we can not click any item without check, because Fomantic code might also handle the Enter event. that would result in double click. - if ($item?.[0]?.matches('a, .js-aria-clickable')) $item[0].click(); + if (elItem?.matches('a, .js-aria-clickable') && !elItem.matches('.tw-hidden, .filtered')) { + e.preventDefault(); + elItem.click(); + } } }); @@ -251,7 +243,7 @@ function attachDomEvents(dropdown: HTMLElement, focusable: HTMLElement, menu: HT // when the popup is hiding, it's better to have a small "delay", because there is a Fomantic UI animation // without the delay for hiding, the UI will be somewhat laggy and sometimes may get stuck in the animation. const deferredRefreshAriaActiveItem = (delay = 0) => { setTimeout(refreshAriaActiveItem, delay) }; - dropdown[ariaPatchKey].deferredRefreshAriaActiveItem = deferredRefreshAriaActiveItem; + (dropdown as any)[ariaPatchKey].deferredRefreshAriaActiveItem = deferredRefreshAriaActiveItem; dropdown.addEventListener('keyup', (e) => { if (e.key.startsWith('Arrow')) deferredRefreshAriaActiveItem(); }); // if the dropdown has been opened by focus, do not trigger the next click event again. @@ -303,9 +295,11 @@ export function hideScopedEmptyDividers(container: Element) { const visibleItems: Element[] = []; const curScopeVisibleItems: Element[] = []; let curScope: string = '', lastVisibleScope: string = ''; - const isScopedDivider = (item: Element) => item.matches('.divider') && item.hasAttribute('data-scope'); + const isDivider = (item: Element) => item.classList.contains('divider'); + const isScopedDivider = (item: Element) => isDivider(item) && item.hasAttribute('data-scope'); const hideDivider = (item: Element) => item.classList.add('hidden', 'transition'); // dropdown has its own classes to hide items - + const showDivider = (item: Element) => item.classList.remove('hidden', 'transition'); + const isHidden = (item: Element) => item.classList.contains('hidden') || item.classList.contains('filtered') || item.classList.contains('tw-hidden'); const handleScopeSwitch = (itemScope: string) => { if (curScopeVisibleItems.length === 1 && isScopedDivider(curScopeVisibleItems[0])) { hideDivider(curScopeVisibleItems[0]); @@ -321,13 +315,16 @@ export function hideScopedEmptyDividers(container: Element) { curScopeVisibleItems.length = 0; }; + // reset hidden dividers + queryElems(container, '.divider', showDivider); + // hide the scope dividers if the scope items are empty for (const item of container.children) { const itemScope = item.getAttribute('data-scope') || ''; if (itemScope !== curScope) { handleScopeSwitch(itemScope); } - if (!item.classList.contains('filtered') && !item.classList.contains('tw-hidden')) { + if (!isHidden(item)) { curScopeVisibleItems.push(item as HTMLElement); } } @@ -335,19 +332,35 @@ export function hideScopedEmptyDividers(container: Element) { // hide all leading and trailing dividers while (visibleItems.length) { - if (!visibleItems[0].matches('.divider')) break; + if (!isDivider(visibleItems[0])) break; hideDivider(visibleItems[0]); visibleItems.shift(); } while (visibleItems.length) { - if (!visibleItems[visibleItems.length - 1].matches('.divider')) break; + if (!isDivider(visibleItems[visibleItems.length - 1])) break; hideDivider(visibleItems[visibleItems.length - 1]); visibleItems.pop(); } // hide all duplicate dividers, hide current divider if next sibling is still divider // no need to update "visibleItems" array since this is the last loop - for (const item of visibleItems) { - if (!item.matches('.divider')) continue; - if (item.nextElementSibling?.matches('.divider')) hideDivider(item); + for (let i = 0; i < visibleItems.length - 1; i++) { + if (!visibleItems[i].matches('.divider')) continue; + if (visibleItems[i + 1].matches('.divider')) hideDivider(visibleItems[i]); } } + +function onResponseKeepSelectedItem(dropdown: typeof $|HTMLElement, selectedValue: string) { + // There is a bug in fomantic dropdown when using "apiSettings" to fetch data + // * when there is a selected item, the dropdown insists on hiding the selected one from the list: + // * in the "filter" function: ('[data-value="'+value+'"]').addClass(className.filtered) + // + // When user selects one item, and click the dropdown again, + // then the dropdown only shows other items and will select another (wrong) one. + // It can't be easily fix by using setTimeout(patch, 0) in `onResponse` because the `onResponse` is called before another `setTimeout(..., timeLeft)` + // Fortunately, the "timeLeft" is controlled by "loadingDuration" which is always zero at the moment, so we can use `setTimeout(..., 10)` + const elDropdown = (dropdown instanceof HTMLElement) ? dropdown : (dropdown as any)[0]; + setTimeout(() => { + queryElems(elDropdown, `.menu .item[data-value="${CSS.escape(selectedValue)}"].filtered`, (el) => el.classList.remove('filtered')); + $(elDropdown).dropdown('set selected', selectedValue ?? ''); + }, 10); +} diff --git a/web_src/js/modules/fomantic/modal.ts b/web_src/js/modules/fomantic/modal.ts index fb80047d01..a96c7785e1 100644 --- a/web_src/js/modules/fomantic/modal.ts +++ b/web_src/js/modules/fomantic/modal.ts @@ -1,5 +1,7 @@ import $ from 'jquery'; import type {FomanticInitFunction} from '../../types.ts'; +import {queryElems} from '../../utils/dom.ts'; +import {hideToastsFrom} from '../toast.ts'; const fomanticModalFn = $.fn.modal; @@ -8,11 +10,13 @@ export function initAriaModalPatch() { if ($.fn.modal === ariaModalFn) throw new Error('initAriaModalPatch could only be called once'); $.fn.modal = ariaModalFn; (ariaModalFn as FomanticInitFunction).settings = fomanticModalFn.settings; + $.fn.fomanticExt.onModalBeforeHidden = onModalBeforeHidden; + $.fn.modal.settings.onApprove = onModalApproveDefault; } // the patched `$.fn.modal` modal function // * it does the one-time attaching on the first call -function ariaModalFn(...args: Parameters<FomanticInitFunction>) { +function ariaModalFn(this: any, ...args: Parameters<FomanticInitFunction>) { const ret = fomanticModalFn.apply(this, args); if (args[0] === 'show' || args[0]?.autoShow) { for (const el of this) { @@ -27,3 +31,33 @@ function ariaModalFn(...args: Parameters<FomanticInitFunction>) { } return ret; } + +function onModalBeforeHidden(this: any) { + const $modal = $(this); + const elModal = $modal[0]; + hideToastsFrom(elModal.closest('.ui.dimmer') ?? document.body); + + // reset the form after the modal is hidden, after other modal events and handlers (e.g. "onApprove", form submit) + setTimeout(() => { + queryElems(elModal, 'form', (form: HTMLFormElement) => form.reset()); + }, 0); +} + +function onModalApproveDefault(this: any) { + const $modal = $(this); + const selectors = $modal.modal('setting', 'selector'); + const elModal = $modal[0]; + const elApprove = elModal.querySelector(selectors.approve); + const elForm = elApprove?.closest('form'); + if (!elForm) return true; // no form, just allow closing the modal + + // "form-fetch-action" can handle network errors gracefully, + // so keep the modal dialog to make users can re-submit the form if anything wrong happens. + if (elForm.matches('.form-fetch-action')) return false; + + // There is an abuse for the "modal" + "form" combination, the "Approve" button is a traditional form submit button in the form. + // Then "approve" and "submit" occur at the same time, the modal will be closed immediately before the form is submitted. + // So here we prevent the modal from closing automatically by returning false, add the "is-loading" class to the form element. + elForm.classList.add('is-loading'); + return false; +} diff --git a/web_src/js/modules/fomantic/tab.ts b/web_src/js/modules/fomantic/tab.ts new file mode 100644 index 0000000000..ceae9dd098 --- /dev/null +++ b/web_src/js/modules/fomantic/tab.ts @@ -0,0 +1,19 @@ +import $ from 'jquery'; +import {queryElemSiblings} from '../../utils/dom.ts'; + +export function initFomanticTab() { + $.fn.tab = function (this: any) { + for (const elBtn of this) { + const tabName = elBtn.getAttribute('data-tab'); + if (!tabName) continue; + elBtn.addEventListener('click', () => { + const elTab = document.querySelector(`.ui.tab[data-tab="${tabName}"]`); + queryElemSiblings(elTab, `.ui.tab`, (el) => el.classList.remove('active')); + queryElemSiblings(elBtn, `[data-tab]`, (el) => el.classList.remove('active')); + elBtn.classList.add('active'); + elTab.classList.add('active'); + }); + } + return this; + }; +} diff --git a/web_src/js/modules/init.ts b/web_src/js/modules/init.ts new file mode 100644 index 0000000000..538fafd83f --- /dev/null +++ b/web_src/js/modules/init.ts @@ -0,0 +1,26 @@ +export class InitPerformanceTracer { + results: {name: string, dur: number}[] = []; + recordCall(name: string, func: ()=>void) { + const start = performance.now(); + func(); + this.results.push({name, dur: performance.now() - start}); + } + printResults() { + this.results = this.results.sort((a, b) => b.dur - a.dur); + for (let i = 0; i < 20 && i < this.results.length; i++) { + console.info(`performance trace: ${this.results[i].name} ${this.results[i].dur.toFixed(3)}`); + } + } +} + +export function callInitFunctions(functions: (() => any)[]): InitPerformanceTracer | null { + // Start performance trace by accessing a URL by "https://localhost/?_ui_performance_trace=1" or "https://localhost/?key=value&_ui_performance_trace=1" + // It is a quick check, no side effect so no need to do slow URL parsing. + const perfTracer = !window.location.search.includes('_ui_performance_trace=1') ? null : new InitPerformanceTracer(); + if (perfTracer) { + for (const func of functions) perfTracer.recordCall(func.name, func); + } else { + for (const func of functions) func(); + } + return perfTracer; +} diff --git a/web_src/js/modules/observer.ts b/web_src/js/modules/observer.ts new file mode 100644 index 0000000000..3305c2f29d --- /dev/null +++ b/web_src/js/modules/observer.ts @@ -0,0 +1,112 @@ +import {isDocumentFragmentOrElementNode} from '../utils/dom.ts'; +import type {Promisable} from 'type-fest'; +import type {InitPerformanceTracer} from './init.ts'; + +let globalSelectorObserverInited = false; + +type SelectorHandler = {selector: string, handler: (el: HTMLElement) => void}; +const selectorHandlers: SelectorHandler[] = []; + +type GlobalEventFunc<T extends HTMLElement, E extends Event> = (el: T, e: E) => Promisable<void>; +const globalEventFuncs: Record<string, GlobalEventFunc<HTMLElement, Event>> = {}; + +type GlobalInitFunc<T extends HTMLElement> = (el: T) => Promisable<void>; +const globalInitFuncs: Record<string, GlobalInitFunc<HTMLElement>> = {}; + +// It handles the global events for all `<div data-global-click="onSomeElemClick"></div>` elements. +export function registerGlobalEventFunc<T extends HTMLElement, E extends Event>(event: string, name: string, func: GlobalEventFunc<T, E>) { + globalEventFuncs[`${event}:${name}`] = func as GlobalEventFunc<HTMLElement, Event>; +} + +// It handles the global init functions by a selector, for example: +// > registerGlobalSelectorObserver('.ui.dropdown:not(.custom)', (el) => { initDropdown(el, ...) }); +// ATTENTION: For most cases, it's recommended to use registerGlobalInitFunc instead, +// Because this selector-based approach is less efficient and less maintainable. +// But if there are already a lot of elements on many pages, this selector-based approach is more convenient for exiting code. +export function registerGlobalSelectorFunc(selector: string, handler: (el: HTMLElement) => void) { + selectorHandlers.push({selector, handler}); + // Then initAddedElementObserver will call this handler for all existing elements after all handlers are added. + // This approach makes the init stage only need to do one "querySelectorAll". + if (!globalSelectorObserverInited) return; + for (const el of document.querySelectorAll<HTMLElement>(selector)) { + handler(el); + } +} + +// It handles the global init functions for all `<div data-global-int="initSomeElem"></div>` elements. +export function registerGlobalInitFunc<T extends HTMLElement>(name: string, handler: GlobalInitFunc<T>) { + globalInitFuncs[name] = handler as GlobalInitFunc<HTMLElement>; + // The "global init" functions are managed internally and called by callGlobalInitFunc + // They must be ready before initGlobalSelectorObserver is called. + if (globalSelectorObserverInited) throw new Error('registerGlobalInitFunc() must be called before initGlobalSelectorObserver()'); +} + +function callGlobalInitFunc(el: HTMLElement) { + const initFunc = el.getAttribute('data-global-init'); + const func = globalInitFuncs[initFunc]; + if (!func) throw new Error(`Global init function "${initFunc}" not found`); + + // when an element node is removed and added again, it should not be re-initialized again. + type GiteaGlobalInitElement = Partial<HTMLElement> & {_giteaGlobalInited: boolean}; + if ((el as GiteaGlobalInitElement)._giteaGlobalInited) return; + (el as GiteaGlobalInitElement)._giteaGlobalInited = true; + + func(el); +} + +function attachGlobalEvents() { + // add global "[data-global-click]" event handler + document.addEventListener('click', (e) => { + const elem = (e.target as HTMLElement).closest<HTMLElement>('[data-global-click]'); + if (!elem) return; + const funcName = elem.getAttribute('data-global-click'); + const func = globalEventFuncs[`click:${funcName}`]; + if (!func) throw new Error(`Global event function "click:${funcName}" not found`); + func(elem, e); + }); +} + +export function initGlobalSelectorObserver(perfTracer?: InitPerformanceTracer): void { + if (globalSelectorObserverInited) throw new Error('initGlobalSelectorObserver() already called'); + globalSelectorObserverInited = true; + + attachGlobalEvents(); + + selectorHandlers.push({selector: '[data-global-init]', handler: callGlobalInitFunc}); + const observer = new MutationObserver((mutationList) => { + const len = mutationList.length; + for (let i = 0; i < len; i++) { + const mutation = mutationList[i]; + const len = mutation.addedNodes.length; + for (let i = 0; i < len; i++) { + const addedNode = mutation.addedNodes[i] as HTMLElement; + if (!isDocumentFragmentOrElementNode(addedNode)) continue; + + for (const {selector, handler} of selectorHandlers) { + if (addedNode.matches(selector)) { + handler(addedNode); + } + for (const el of addedNode.querySelectorAll<HTMLElement>(selector)) { + handler(el); + } + } + } + } + }); + if (perfTracer) { + for (const {selector, handler} of selectorHandlers) { + perfTracer.recordCall(`initGlobalSelectorObserver ${selector}`, () => { + for (const el of document.querySelectorAll<HTMLElement>(selector)) { + handler(el); + } + }); + } + } else { + for (const {selector, handler} of selectorHandlers) { + for (const el of document.querySelectorAll<HTMLElement>(selector)) { + handler(el); + } + } + } + observer.observe(document, {subtree: true, childList: true}); +} diff --git a/web_src/js/modules/stores.ts b/web_src/js/modules/stores.ts deleted file mode 100644 index 942a7bc508..0000000000 --- a/web_src/js/modules/stores.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {reactive} from 'vue'; -import type {Reactive} from 'vue'; - -let diffTreeStoreReactive: Reactive<Record<string, any>>; -export function diffTreeStore() { - if (!diffTreeStoreReactive) { - diffTreeStoreReactive = reactive(window.config.pageData.diffFileInfo); - window.config.pageData.diffFileInfo = diffTreeStoreReactive; - } - return diffTreeStoreReactive; -} diff --git a/web_src/js/modules/tippy.ts b/web_src/js/modules/tippy.ts index aaaf580de1..2a1d998d76 100644 --- a/web_src/js/modules/tippy.ts +++ b/web_src/js/modules/tippy.ts @@ -2,6 +2,7 @@ import tippy, {followCursor} from 'tippy.js'; import {isDocumentFragmentOrElementNode} from '../utils/dom.ts'; import {formatDatetime} from '../utils/time.ts'; import type {Content, Instance, Placement, Props} from 'tippy.js'; +import {html} from '../utils/html.ts'; type TippyOpts = { role?: string, @@ -9,7 +10,7 @@ type TippyOpts = { } & Partial<Props>; const visibleInstances = new Set<Instance>(); -const arrowSvg = `<svg width="16" height="7"><path d="m0 7 8-7 8 7Z" class="tippy-svg-arrow-outer"/><path d="m0 8 8-7 8 7Z" class="tippy-svg-arrow-inner"/></svg>`; +const arrowSvg = html`<svg width="16" height="7"><path d="m0 7 8-7 8 7Z" class="tippy-svg-arrow-outer"/><path d="m0 8 8-7 8 7Z" class="tippy-svg-arrow-inner"/></svg>`; export function createTippy(target: Element, opts: TippyOpts = {}): Instance { // the callback functions should be destructured from opts, @@ -40,18 +41,20 @@ export function createTippy(target: Element, opts: TippyOpts = {}): Instance { } } visibleInstances.add(instance); + target.setAttribute('aria-controls', instance.popper.id); return onShow?.(instance); }, - arrow: arrow || (theme === 'bare' ? false : arrowSvg), + arrow: arrow ?? (theme === 'bare' ? false : arrowSvg), // HTML role attribute, ideally the default role would be "popover" but it does not exist role: role || 'menu', // CSS theme, either "default", "tooltip", "menu", "box-with-header" or "bare" theme: theme || role || 'default', + offset: [0, arrow ? 10 : 6], plugins: [followCursor], ...other, } satisfies Partial<Props>); - if (role === 'menu') { + if (instance.props.role === 'menu') { target.setAttribute('aria-haspopup', 'true'); } @@ -121,7 +124,7 @@ function switchTitleToTooltip(target: Element): void { * Some browsers like PaleMoon don't support "addEventListener('mouseenter', capture)" * The tippy by default uses "mouseenter" event to show, so we use "mouseover" event to switch to tippy */ -function lazyTooltipOnMouseHover(e: Event): void { +function lazyTooltipOnMouseHover(this: HTMLElement, e: Event): void { e.target.removeEventListener('mouseover', lazyTooltipOnMouseHover, true); attachTooltip(this); } @@ -179,13 +182,25 @@ export function initGlobalTooltips(): void { } export function showTemporaryTooltip(target: Element, content: Content): void { - // if the target is inside a dropdown, the menu will be hidden soon - // so display the tooltip on the dropdown instead - target = target.closest('.ui.dropdown') || target; - const tippy = target._tippy ?? attachTooltip(target, content); - tippy.setContent(content); - if (!tippy.state.isShown) tippy.show(); - tippy.setProps({ + // if the target is inside a dropdown or tippy popup, the menu will be hidden soon + // so display the tooltip on the "aria-controls" element or dropdown instead + let refClientRect: DOMRect; + const popupTippyId = target.closest(`[data-tippy-root]`)?.id; + if (popupTippyId) { + // for example, the "Copy Permalink" button in the "File View" page for the selected lines + target = document.body; + refClientRect = document.querySelector(`[aria-controls="${CSS.escape(popupTippyId)}"]`)?.getBoundingClientRect(); + refClientRect = refClientRect ?? new DOMRect(0, 0, 0, 0); // fallback to empty rect if not found, tippy doesn't accept null + } else { + // for example, the "Copy Link" button in the issue header dropdown menu + target = target.closest('.ui.dropdown') ?? target; + refClientRect = target.getBoundingClientRect(); + } + const tooltipTippy = target._tippy ?? attachTooltip(target, content); + tooltipTippy.setContent(content); + tooltipTippy.setProps({getReferenceClientRect: () => refClientRect}); + if (!tooltipTippy.state.isShown) tooltipTippy.show(); + tooltipTippy.setProps({ onHidden: (tippy) => { // reset the default tooltip content, if no default, then this temporary tooltip could be destroyed if (!attachTooltip(target)) { diff --git a/web_src/js/modules/toast.ts b/web_src/js/modules/toast.ts index 36e2321743..ed807a4977 100644 --- a/web_src/js/modules/toast.ts +++ b/web_src/js/modules/toast.ts @@ -1,6 +1,6 @@ -import {htmlEscape} from 'escape-goat'; +import {htmlEscape} from '../utils/html.ts'; import {svg} from '../svg.ts'; -import {animateOnce, showElem} from '../utils/dom.ts'; +import {animateOnce, queryElems, showElem} from '../utils/dom.ts'; import Toastify from 'toastify-js'; // don't use "async import", because when network error occurs, the "async import" also fails and nothing is shown import type {Intent} from '../types.ts'; import type {SvgName} from '../svg.ts'; @@ -37,17 +37,20 @@ const levels: ToastLevels = { type ToastOpts = { useHtmlBody?: boolean, - preventDuplicates?: boolean, + preventDuplicates?: boolean | string, } & Options; +type ToastifyElement = HTMLElement & {_giteaToastifyInstance?: Toast }; + // See https://github.com/apvarun/toastify-js#api for options function showToast(message: string, level: Intent, {gravity, position, duration, useHtmlBody, preventDuplicates = true, ...other}: ToastOpts = {}): Toast { const body = useHtmlBody ? String(message) : htmlEscape(message); - const key = `${level}-${body}`; + const parent = document.querySelector('.ui.dimmer.active') ?? document.body; + const duplicateKey = preventDuplicates ? (preventDuplicates === true ? `${level}-${body}` : preventDuplicates) : ''; - // prevent showing duplicate toasts with same level and message, and give a visual feedback for end users + // prevent showing duplicate toasts with the same level and message, and give visual feedback for end users if (preventDuplicates) { - const toastEl = document.querySelector(`.toastify[data-toast-unique-key="${CSS.escape(key)}"]`); + const toastEl = parent.querySelector(`:scope > .toastify.on[data-toast-unique-key="${CSS.escape(duplicateKey)}"]`); if (toastEl) { const toastDupNumEl = toastEl.querySelector('.toast-duplicate-number'); showElem(toastDupNumEl); @@ -59,6 +62,7 @@ function showToast(message: string, level: Intent, {gravity, position, duration, const {icon, background, duration: levelDuration} = levels[level ?? 'info']; const toast = Toastify({ + selector: parent, text: ` <div class='toast-icon'>${svg(icon)}</div> <div class='toast-body'><span class="toast-duplicate-number tw-hidden">1</span>${body}</div> @@ -74,7 +78,8 @@ function showToast(message: string, level: Intent, {gravity, position, duration, toast.showToast(); toast.toastElement.querySelector('.toast-close').addEventListener('click', () => toast.hideToast()); - toast.toastElement.setAttribute('data-toast-unique-key', key); + toast.toastElement.setAttribute('data-toast-unique-key', duplicateKey); + (toast.toastElement as ToastifyElement)._giteaToastifyInstance = toast; return toast; } @@ -89,3 +94,15 @@ export function showWarningToast(message: string, opts?: ToastOpts): Toast { export function showErrorToast(message: string, opts?: ToastOpts): Toast { return showToast(message, 'error', opts); } + +function hideToastByElement(el: Element): void { + (el as ToastifyElement)?._giteaToastifyInstance?.hideToast(); +} + +export function hideToastsFrom(parent: Element): void { + queryElems(parent, ':scope > .toastify.on', hideToastByElement); +} + +export function hideToastsAll(): void { + queryElems(document, '.toastify.on', hideToastByElement); +} diff --git a/web_src/js/render/pdf.ts b/web_src/js/render/pdf.ts deleted file mode 100644 index f31f161e6e..0000000000 --- a/web_src/js/render/pdf.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {htmlEscape} from 'escape-goat'; - -export async function initPdfViewer() { - const els = document.querySelectorAll('.pdf-content'); - if (!els.length) return; - - const pdfobject = await import(/* webpackChunkName: "pdfobject" */'pdfobject'); - - for (const el of els) { - const src = el.getAttribute('data-src'); - const fallbackText = el.getAttribute('data-fallback-button-text'); - pdfobject.embed(src, el, { - fallbackLink: htmlEscape` - <a role="button" class="ui basic button pdf-fallback-button" href="[url]">${fallbackText}</a> - `, - }); - el.classList.remove('is-loading'); - } -} diff --git a/web_src/js/render/plugin.ts b/web_src/js/render/plugin.ts new file mode 100644 index 0000000000..a8dd0a7c05 --- /dev/null +++ b/web_src/js/render/plugin.ts @@ -0,0 +1,10 @@ +export type FileRenderPlugin = { + // unique plugin name + name: string; + + // test if plugin can handle a specified file + canHandle: (filename: string, mimeType: string) => boolean; + + // render file content + render: (container: HTMLElement, fileUrl: string, options?: any) => Promise<void>; +} diff --git a/web_src/js/render/plugins/3d-viewer.ts b/web_src/js/render/plugins/3d-viewer.ts new file mode 100644 index 0000000000..2a0929359d --- /dev/null +++ b/web_src/js/render/plugins/3d-viewer.ts @@ -0,0 +1,60 @@ +import type {FileRenderPlugin} from '../plugin.ts'; +import {extname} from '../../utils.ts'; + +// support common 3D model file formats, use online-3d-viewer library for rendering + +// eslint-disable-next-line multiline-comment-style +/* a simple text STL file example: +solid SimpleTriangle + facet normal 0 0 1 + outer loop + vertex 0 0 0 + vertex 1 0 0 + vertex 0 1 0 + endloop + endfacet +endsolid SimpleTriangle +*/ + +export function newRenderPlugin3DViewer(): FileRenderPlugin { + // Some extensions are text-based formats: + // .3mf .amf .brep: XML + // .fbx: XML or BINARY + // .dae .gltf: JSON + // .ifc, .igs, .iges, .stp, .step are: TEXT + // .stl .ply: TEXT or BINARY + // .obj .off .wrl: TEXT + // So we need to be able to render when the file is recognized as plaintext file by backend. + // + // It needs more logic to make it overall right (render a text 3D model automatically): + // we need to distinguish the ambiguous filename extensions. + // For example: "*.obj, *.off, *.step" might be or not be a 3D model file. + // So when it is a text file, we can't assume that "we only render it by 3D plugin", + // otherwise the end users would be impossible to view its real content when the file is not a 3D model. + const SUPPORTED_EXTENSIONS = [ + '.3dm', '.3ds', '.3mf', '.amf', '.bim', '.brep', + '.dae', '.fbx', '.fcstd', '.glb', '.gltf', + '.ifc', '.igs', '.iges', '.stp', '.step', + '.stl', '.obj', '.off', '.ply', '.wrl', + ]; + + return { + name: '3d-model-viewer', + + canHandle(filename: string, _mimeType: string): boolean { + const ext = extname(filename).toLowerCase(); + return SUPPORTED_EXTENSIONS.includes(ext); + }, + + async render(container: HTMLElement, fileUrl: string): Promise<void> { + // TODO: height and/or max-height? + const OV = await import(/* webpackChunkName: "online-3d-viewer" */'online-3d-viewer'); + const viewer = new OV.EmbeddedViewer(container, { + backgroundColor: new OV.RGBAColor(59, 68, 76, 0), + defaultColor: new OV.RGBColor(65, 131, 196), + edgeSettings: new OV.EdgeSettings(false, new OV.RGBColor(0, 0, 0), 1), + }); + viewer.LoadModelFromUrlList([fileUrl]); + }, + }; +} diff --git a/web_src/js/render/plugins/pdf-viewer.ts b/web_src/js/render/plugins/pdf-viewer.ts new file mode 100644 index 0000000000..40623be055 --- /dev/null +++ b/web_src/js/render/plugins/pdf-viewer.ts @@ -0,0 +1,20 @@ +import type {FileRenderPlugin} from '../plugin.ts'; + +export function newRenderPluginPdfViewer(): FileRenderPlugin { + return { + name: 'pdf-viewer', + + canHandle(filename: string, _mimeType: string): boolean { + return filename.toLowerCase().endsWith('.pdf'); + }, + + async render(container: HTMLElement, fileUrl: string): Promise<void> { + const PDFObject = await import(/* webpackChunkName: "pdfobject" */'pdfobject'); + // TODO: the PDFObject library does not support dynamic height adjustment, + container.style.height = `${window.innerHeight - 100}px`; + if (!PDFObject.default.embed(fileUrl, container)) { + throw new Error('Unable to render the PDF file'); + } + }, + }; +} diff --git a/web_src/js/standalone/devtest.ts b/web_src/js/standalone/devtest.ts index 3489697a2f..e6baf6c9ce 100644 --- a/web_src/js/standalone/devtest.ts +++ b/web_src/js/standalone/devtest.ts @@ -1,7 +1,7 @@ import {showInfoToast, showWarningToast, showErrorToast} from '../modules/toast.ts'; function initDevtestToast() { - const levelMap = {info: showInfoToast, warning: showWarningToast, error: showErrorToast}; + const levelMap: Record<string, any> = {info: showInfoToast, warning: showWarningToast, error: showErrorToast}; for (const el of document.querySelectorAll('.toast-test-button')) { el.addEventListener('click', () => { const level = el.getAttribute('data-toast-level'); diff --git a/web_src/js/standalone/swagger.ts b/web_src/js/standalone/swagger.ts index 63b676b2ea..4b17ba21a8 100644 --- a/web_src/js/standalone/swagger.ts +++ b/web_src/js/standalone/swagger.ts @@ -14,7 +14,7 @@ window.addEventListener('load', async () => { return 0; }); - const ui = SwaggerUI({ + SwaggerUI({ spec, dom_id: '#swagger-ui', deepLinking: true, @@ -27,6 +27,4 @@ window.addEventListener('load', async () => { SwaggerUI.plugins.DownloadUrl, ], }); - - window.ui = ui; }); diff --git a/web_src/js/svg.test.ts b/web_src/js/svg.test.ts index 7f3e0496ec..715b739a82 100644 --- a/web_src/js/svg.test.ts +++ b/web_src/js/svg.test.ts @@ -16,12 +16,11 @@ test('svgParseOuterInner', () => { test('SvgIcon', () => { const root = document.createElement('div'); - createApp({render: () => h(SvgIcon, {name: 'octicon-link', size: 24, class: 'base', className: 'extra'})}).mount(root); + createApp({render: () => h(SvgIcon, {name: 'octicon-link', size: 24, class: 'base'})}).mount(root); const node = root.firstChild as Element; expect(node.nodeName).toEqual('svg'); expect(node.getAttribute('width')).toEqual('24'); expect(node.getAttribute('height')).toEqual('24'); expect(node.classList.contains('octicon-link')).toBeTruthy(); expect(node.classList.contains('base')).toBeTruthy(); - expect(node.classList.contains('extra')).toBeTruthy(); }); diff --git a/web_src/js/svg.ts b/web_src/js/svg.ts index 90b12fa87d..50c9536f37 100644 --- a/web_src/js/svg.ts +++ b/web_src/js/svg.ts @@ -1,5 +1,6 @@ -import {h} from 'vue'; +import {defineComponent, h, type PropType} from 'vue'; import {parseDom, serializeXml} from './utils.ts'; +import {html, htmlRaw} from './utils/html.ts'; import giteaDoubleChevronLeft from '../../public/assets/img/svg/gitea-double-chevron-left.svg'; import giteaDoubleChevronRight from '../../public/assets/img/svg/gitea-double-chevron-right.svg'; import giteaEmptyCheckbox from '../../public/assets/img/svg/gitea-empty-checkbox.svg'; @@ -28,12 +29,15 @@ import octiconEye from '../../public/assets/img/svg/octicon-eye.svg'; import octiconFile from '../../public/assets/img/svg/octicon-file.svg'; import octiconFileDirectoryFill from '../../public/assets/img/svg/octicon-file-directory-fill.svg'; import octiconFileDirectoryOpenFill from '../../public/assets/img/svg/octicon-file-directory-open-fill.svg'; +import octiconFileSubmodule from '../../public/assets/img/svg/octicon-file-submodule.svg'; +import octiconFileSymlinkFile from '../../public/assets/img/svg/octicon-file-symlink-file.svg'; import octiconFilter from '../../public/assets/img/svg/octicon-filter.svg'; import octiconGear from '../../public/assets/img/svg/octicon-gear.svg'; import octiconGitBranch from '../../public/assets/img/svg/octicon-git-branch.svg'; import octiconGitCommit from '../../public/assets/img/svg/octicon-git-commit.svg'; import octiconGitMerge from '../../public/assets/img/svg/octicon-git-merge.svg'; import octiconGitPullRequest from '../../public/assets/img/svg/octicon-git-pull-request.svg'; +import octiconGitPullRequestClosed from '../../public/assets/img/svg/octicon-git-pull-request-closed.svg'; import octiconGitPullRequestDraft from '../../public/assets/img/svg/octicon-git-pull-request-draft.svg'; import octiconGrabber from '../../public/assets/img/svg/octicon-grabber.svg'; import octiconHeading from '../../public/assets/img/svg/octicon-heading.svg'; @@ -104,12 +108,15 @@ const svgs = { 'octicon-file': octiconFile, 'octicon-file-directory-fill': octiconFileDirectoryFill, 'octicon-file-directory-open-fill': octiconFileDirectoryOpenFill, + 'octicon-file-submodule': octiconFileSubmodule, + 'octicon-file-symlink-file': octiconFileSymlinkFile, 'octicon-filter': octiconFilter, 'octicon-gear': octiconGear, 'octicon-git-branch': octiconGitBranch, 'octicon-git-commit': octiconGitCommit, 'octicon-git-merge': octiconGitMerge, 'octicon-git-pull-request': octiconGitPullRequest, + 'octicon-git-pull-request-closed': octiconGitPullRequestClosed, 'octicon-git-pull-request-draft': octiconGitPullRequestDraft, 'octicon-grabber': octiconGrabber, 'octicon-heading': octiconHeading, @@ -192,19 +199,18 @@ export function svgParseOuterInner(name: SvgName) { return {svgOuter, svgInnerHtml}; } -export const SvgIcon = { +export const SvgIcon = defineComponent({ name: 'SvgIcon', props: { - name: {type: String, required: true}, + name: {type: String as PropType<SvgName>, required: true}, size: {type: Number, default: 16}, - className: {type: String, default: ''}, symbolId: {type: String}, }, render() { let {svgOuter, svgInnerHtml} = svgParseOuterInner(this.name); // https://vuejs.org/guide/extras/render-function.html#creating-vnodes // the `^` is used for attr, set SVG attributes like 'width', `aria-hidden`, `viewBox`, etc - const attrs = {}; + const attrs: Record<string, any> = {}; for (const attr of svgOuter.attributes) { if (attr.name === 'class') continue; attrs[`^${attr.name}`] = attr.value; @@ -212,18 +218,10 @@ export const SvgIcon = { attrs[`^width`] = this.size; attrs[`^height`] = this.size; - // make the <SvgIcon class="foo" class-name="bar"> classes work together - const classes = []; - for (const cls of svgOuter.classList) { - classes.push(cls); - } - // TODO: drop the `className/class-name` prop in the future, only use "class" prop - if (this.className) { - classes.push(...this.className.split(/\s+/).filter(Boolean)); - } + const classes = Array.from(svgOuter.classList); if (this.symbolId) { classes.push('tw-hidden', 'svg-symbol-container'); - svgInnerHtml = `<symbol id="${this.symbolId}" viewBox="${attrs['^viewBox']}">${svgInnerHtml}</symbol>`; + svgInnerHtml = html`<symbol id="${this.symbolId}" viewBox="${attrs['^viewBox']}">${htmlRaw(svgInnerHtml)}</symbol>`; } // create VNode return h('svg', { @@ -232,4 +230,4 @@ export const SvgIcon = { innerHTML: svgInnerHtml, }); }, -}; +}); diff --git a/web_src/js/types.ts b/web_src/js/types.ts index e7c9ac0df4..1b5e652f66 100644 --- a/web_src/js/types.ts +++ b/web_src/js/types.ts @@ -22,6 +22,8 @@ export type Config = { i18n: Record<string, string>, } +export type IntervalId = ReturnType<typeof setInterval>; + export type Intent = 'error' | 'warning' | 'info'; export type RequestData = string | FormData | URLSearchParams | Record<string, any>; @@ -30,6 +32,11 @@ export type RequestOpts = { data?: RequestData, } & RequestInit; +export type RepoOwnerPathInfo = { + ownerName: string, + repoName: string, +} + export type IssuePathInfo = { ownerName: string, repoName: string, diff --git a/web_src/js/utils.test.ts b/web_src/js/utils.test.ts index ac9d4fab91..f1025471a4 100644 --- a/web_src/js/utils.test.ts +++ b/web_src/js/utils.test.ts @@ -1,9 +1,15 @@ import { - basename, extname, isObject, stripTags, parseIssueHref, + dirname, basename, extname, isObject, stripTags, parseIssueHref, parseUrl, translateMonth, translateDay, blobToDataURI, - toAbsoluteUrl, encodeURLEncodedBase64, decodeURLEncodedBase64, isImageFile, isVideoFile, parseIssueNewHref, + toAbsoluteUrl, encodeURLEncodedBase64, decodeURLEncodedBase64, isImageFile, isVideoFile, parseRepoOwnerPathInfo, } from './utils.ts'; +test('dirname', () => { + expect(dirname('/path/to/file.js')).toEqual('/path/to'); + expect(dirname('/path/to')).toEqual('/path'); + expect(dirname('file.js')).toEqual(''); +}); + test('basename', () => { expect(basename('/path/to/file.js')).toEqual('file.js'); expect(basename('/path/to/file')).toEqual('file'); @@ -45,10 +51,14 @@ test('parseIssueHref', () => { expect(parseIssueHref('')).toEqual({ownerName: undefined, repoName: undefined, type: undefined, index: undefined}); }); -test('parseIssueNewHref', () => { - expect(parseIssueNewHref('/owner/repo/issues/new')).toEqual({ownerName: 'owner', repoName: 'repo', pathType: 'issues'}); - expect(parseIssueNewHref('/owner/repo/issues/new?query')).toEqual({ownerName: 'owner', repoName: 'repo', pathType: 'issues'}); - expect(parseIssueNewHref('/sub/owner/repo/issues/new#hash')).toEqual({ownerName: 'owner', repoName: 'repo', pathType: 'issues'}); +test('parseRepoOwnerPathInfo', () => { + expect(parseRepoOwnerPathInfo('/owner/repo/issues/new')).toEqual({ownerName: 'owner', repoName: 'repo'}); + expect(parseRepoOwnerPathInfo('/owner/repo/releases')).toEqual({ownerName: 'owner', repoName: 'repo'}); + expect(parseRepoOwnerPathInfo('/other')).toEqual({}); + window.config.appSubUrl = '/sub'; + expect(parseRepoOwnerPathInfo('/sub/owner/repo/issues/new')).toEqual({ownerName: 'owner', repoName: 'repo'}); + expect(parseRepoOwnerPathInfo('/sub/owner/repo/compare/feature/branch-1...fix/branch-2')).toEqual({ownerName: 'owner', repoName: 'repo'}); + window.config.appSubUrl = ''; }); test('parseUrl', () => { diff --git a/web_src/js/utils.ts b/web_src/js/utils.ts index 997a4d1ff3..e33b1413e8 100644 --- a/web_src/js/utils.ts +++ b/web_src/js/utils.ts @@ -1,5 +1,12 @@ import {decode, encode} from 'uint8-to-base64'; -import type {IssuePageInfo, IssuePathInfo} from './types.ts'; +import type {IssuePageInfo, IssuePathInfo, RepoOwnerPathInfo} from './types.ts'; +import {toggleClass, toggleElem} from './utils/dom.ts'; + +// transform /path/to/file.ext to /path/to +export function dirname(path: string): string { + const lastSlashIndex = path.lastIndexOf('/'); + return lastSlashIndex < 0 ? '' : path.substring(0, lastSlashIndex); +} // transform /path/to/file.ext to file.ext export function basename(path: string): string { @@ -32,15 +39,17 @@ export function stripTags(text: string): string { } export function parseIssueHref(href: string): IssuePathInfo { + // FIXME: it should use pathname and trim the appSubUrl ahead const path = (href || '').replace(/[#?].*$/, ''); const [_, ownerName, repoName, pathType, indexString] = /([^/]+)\/([^/]+)\/(issues|pulls)\/([0-9]+)/.exec(path) || []; return {ownerName, repoName, pathType, indexString}; } -export function parseIssueNewHref(href: string): IssuePathInfo { - const path = (href || '').replace(/[#?].*$/, ''); - const [_, ownerName, repoName, pathType, indexString] = /([^/]+)\/([^/]+)\/(issues|pulls)\/new/.exec(path) || []; - return {ownerName, repoName, pathType, indexString}; +export function parseRepoOwnerPathInfo(pathname: string): RepoOwnerPathInfo { + const appSubUrl = window.config.appSubUrl; + if (appSubUrl && pathname.startsWith(appSubUrl)) pathname = pathname.substring(appSubUrl.length); + const [_, ownerName, repoName] = /([^/]+)\/([^/]+)/.exec(pathname) || []; + return {ownerName, repoName}; } export function parseIssuePageInfo(): IssuePageInfo { @@ -164,10 +173,31 @@ export function sleep(ms: number): Promise<void> { return new Promise((resolve) => setTimeout(resolve, ms)); } -export function isImageFile({name, type}: {name: string, type?: string}): boolean { +export function isImageFile({name, type}: {name?: string, type?: string}): boolean { return /\.(avif|jpe?g|png|gif|webp|svg|heic)$/i.test(name || '') || type?.startsWith('image/'); } -export function isVideoFile({name, type}: {name: string, type?: string}): boolean { +export function isVideoFile({name, type}: {name?: string, type?: string}): boolean { return /\.(mpe?g|mp4|mkv|webm)$/i.test(name || '') || type?.startsWith('video/'); } + +export function toggleFullScreen(fullscreenElementsSelector: string, isFullScreen: boolean, sourceParentSelector?: string): void { + // hide other elements + const headerEl = document.querySelector('#navbar'); + const contentEl = document.querySelector('.page-content'); + const footerEl = document.querySelector('.page-footer'); + toggleElem(headerEl, !isFullScreen); + toggleElem(contentEl, !isFullScreen); + toggleElem(footerEl, !isFullScreen); + + const sourceParentEl = sourceParentSelector ? document.querySelector(sourceParentSelector) : contentEl; + + const fullScreenEl = document.querySelector(fullscreenElementsSelector); + const outerEl = document.querySelector('.full.height'); + toggleClass(fullscreenElementsSelector, 'fullscreen', isFullScreen); + if (isFullScreen) { + outerEl.append(fullScreenEl); + } else { + sourceParentEl.append(fullScreenEl); + } +} diff --git a/web_src/js/utils/dom.test.ts b/web_src/js/utils/dom.test.ts index 6e71596850..057ea9808c 100644 --- a/web_src/js/utils/dom.test.ts +++ b/web_src/js/utils/dom.test.ts @@ -1,4 +1,10 @@ -import {createElementFromAttrs, createElementFromHTML, queryElemChildren, querySingleVisibleElem} from './dom.ts'; +import { + createElementFromAttrs, + createElementFromHTML, + queryElemChildren, + querySingleVisibleElem, + toggleElem, +} from './dom.ts'; test('createElementFromHTML', () => { expect(createElementFromHTML('<a>foo<span>bar</span></a>').outerHTML).toEqual('<a>foo<span>bar</span></a>'); @@ -19,10 +25,14 @@ test('createElementFromAttrs', () => { }); test('querySingleVisibleElem', () => { - let el = createElementFromHTML('<div><span>foo</span></div>'); + let el = createElementFromHTML('<div></div>'); + expect(querySingleVisibleElem(el, 'span')).toBeNull(); + el = createElementFromHTML('<div><span>foo</span></div>'); expect(querySingleVisibleElem(el, 'span').textContent).toEqual('foo'); el = createElementFromHTML('<div><span style="display: none;">foo</span><span>bar</span></div>'); expect(querySingleVisibleElem(el, 'span').textContent).toEqual('bar'); + el = createElementFromHTML('<div><span class="some-class tw-hidden">foo</span><span>bar</span></div>'); + expect(querySingleVisibleElem(el, 'span').textContent).toEqual('bar'); el = createElementFromHTML('<div><span>foo</span><span>bar</span></div>'); expect(() => querySingleVisibleElem(el, 'span')).toThrowError('Expected exactly one visible element'); }); @@ -32,3 +42,13 @@ test('queryElemChildren', () => { const children = queryElemChildren(el, '.a'); expect(children.length).toEqual(1); }); + +test('toggleElem', () => { + const el = createElementFromHTML('<p><div>a</div><div class="tw-hidden">b</div></p>'); + toggleElem(el.children); + expect(el.outerHTML).toEqual('<p><div class="tw-hidden">a</div><div class="">b</div></p>'); + toggleElem(el.children, false); + expect(el.outerHTML).toEqual('<p><div class="tw-hidden">a</div><div class="tw-hidden">b</div></p>'); + toggleElem(el.children, true); + expect(el.outerHTML).toEqual('<p><div class="">a</div><div class="">b</div></p>'); +}); diff --git a/web_src/js/utils/dom.ts b/web_src/js/utils/dom.ts index e24cb29bac..8b540cebb1 100644 --- a/web_src/js/utils/dom.ts +++ b/web_src/js/utils/dom.ts @@ -9,55 +9,50 @@ type ElementsCallback<T extends Element> = (el: T) => Promisable<any>; type ElementsCallbackWithArgs = (el: Element, ...args: any[]) => Promisable<any>; export type DOMEvent<E extends Event, T extends Element = HTMLElement> = E & { target: Partial<T>; }; -function elementsCall(el: ElementArg, func: ElementsCallbackWithArgs, ...args: any[]) { +function elementsCall(el: ElementArg, func: ElementsCallbackWithArgs, ...args: any[]): ArrayLikeIterable<Element> { if (typeof el === 'string' || el instanceof String) { el = document.querySelectorAll(el as string); } if (el instanceof Node) { func(el, ...args); + return [el]; } else if (el.length !== undefined) { // this works for: NodeList, HTMLCollection, Array, jQuery - for (const e of (el as ArrayLikeIterable<Element>)) { - func(e, ...args); - } - } else { - throw new Error('invalid argument to be shown/hidden'); + const elems = el as ArrayLikeIterable<Element>; + for (const elem of elems) func(elem, ...args); + return elems; } + throw new Error('invalid argument to be shown/hidden'); +} + +export function toggleClass(el: ElementArg, className: string, force?: boolean): ArrayLikeIterable<Element> { + return elementsCall(el, (e: Element) => { + if (force === true) { + e.classList.add(className); + } else if (force === false) { + e.classList.remove(className); + } else if (force === undefined) { + e.classList.toggle(className); + } else { + throw new Error('invalid force argument'); + } + }); } /** - * @param el Element + * @param el ElementArg * @param force force=true to show or force=false to hide, undefined to toggle */ -function toggleShown(el: Element, force: boolean) { - if (force === true) { - el.classList.remove('tw-hidden'); - } else if (force === false) { - el.classList.add('tw-hidden'); - } else if (force === undefined) { - el.classList.toggle('tw-hidden'); - } else { - throw new Error('invalid force argument'); - } +export function toggleElem(el: ElementArg, force?: boolean): ArrayLikeIterable<Element> { + return toggleClass(el, 'tw-hidden', force === undefined ? force : !force); } -export function showElem(el: ElementArg) { - elementsCall(el, toggleShown, true); +export function showElem(el: ElementArg): ArrayLikeIterable<Element> { + return toggleElem(el, true); } -export function hideElem(el: ElementArg) { - elementsCall(el, toggleShown, false); -} - -export function toggleElem(el: ElementArg, force?: boolean) { - elementsCall(el, toggleShown, force); -} - -export function isElemHidden(el: ElementArg) { - const res: boolean[] = []; - elementsCall(el, (e) => res.push(e.classList.contains('tw-hidden'))); - if (res.length > 1) throw new Error(`isElemHidden doesn't work for multiple elements`); - return res[0]; +export function hideElem(el: ElementArg): ArrayLikeIterable<Element> { + return toggleElem(el, false); } function applyElemsCallback<T extends Element>(elems: ArrayLikeIterable<T>, fn?: ElementsCallback<T>): ArrayLikeIterable<T> { @@ -87,7 +82,7 @@ export function queryElemChildren<T extends Element>(parent: Element | ParentNod } // it works like parent.querySelectorAll: all descendants are selected -// in the future, all "queryElems(document, ...)" should be refactored to use a more specific parent +// in the future, all "queryElems(document, ...)" should be refactored to use a more specific parent if the targets are not for page-level components. export function queryElems<T extends HTMLElement>(parent: Element | ParentNode, selector: string, fn?: ElementsCallback<T>): ArrayLikeIterable<T> { return applyElemsCallback<T>(parent.querySelectorAll(selector), fn); } @@ -166,6 +161,7 @@ export function autosize(textarea: HTMLTextAreaElement, {viewportMarginBottom = function resizeToFit() { if (isUserResized) return; if (textarea.offsetWidth <= 0 && textarea.offsetHeight <= 0) return; + const previousMargin = textarea.style.marginBottom; try { const {top, bottom} = overflowOffset(); @@ -181,6 +177,9 @@ export function autosize(textarea: HTMLTextAreaElement, {viewportMarginBottom = const curHeight = parseFloat(computedStyle.height); const maxHeight = curHeight + bottom - adjustedViewportMarginBottom; + // In Firefox, setting auto height momentarily may cause the page to scroll up + // unexpectedly, prevent this by setting a temporary margin. + textarea.style.marginBottom = `${textarea.clientHeight}px`; textarea.style.height = 'auto'; let newHeight = textarea.scrollHeight + borderAddOn; @@ -201,6 +200,12 @@ export function autosize(textarea: HTMLTextAreaElement, {viewportMarginBottom = textarea.style.height = `${newHeight}px`; lastStyleHeight = textarea.style.height; } finally { + // restore previous margin + if (previousMargin) { + textarea.style.marginBottom = previousMargin; + } else { + textarea.style.removeProperty('margin-bottom'); + } // ensure that the textarea is fully scrolled to the end, when the cursor // is at the end during an input event if (textarea.selectionStart === textarea.selectionEnd && @@ -255,12 +260,12 @@ export function loadElem(el: LoadableElement, src: string) { // it can't use other transparent polyfill patches because PaleMoon also doesn't support "addEventListener(capture)" const needSubmitEventPolyfill = typeof SubmitEvent === 'undefined'; -export function submitEventSubmitter(e) { +export function submitEventSubmitter(e: any) { e = e.originalEvent ?? e; // if the event is wrapped by jQuery, use "originalEvent", otherwise, use the event itself return needSubmitEventPolyfill ? (e.target._submitter || null) : e.submitter; } -function submitEventPolyfillListener(e) { +function submitEventPolyfillListener(e: DOMEvent<Event>) { const form = e.target.closest('form'); if (!form) return; form._submitter = e.target.closest('button:not([type]), button[type="submit"], input[type="submit"]'); @@ -273,14 +278,12 @@ export function initSubmitEventPolyfill() { document.body.addEventListener('focus', submitEventPolyfillListener); } -/** - * Check if an element is visible, equivalent to jQuery's `:visible` pseudo. - * Note: This function doesn't account for all possible visibility scenarios. - */ -export function isElemVisible(element: HTMLElement): boolean { - if (!element) return false; - // checking element.style.display is not necessary for browsers, but it is required by some tests with happy-dom because happy-dom doesn't really do layout - return Boolean((element.offsetWidth || element.offsetHeight || element.getClientRects().length) && element.style.display !== 'none'); +export function isElemVisible(el: HTMLElement): boolean { + // Check if an element is visible, equivalent to jQuery's `:visible` pseudo. + // This function DOESN'T account for all possible visibility scenarios, its behavior is covered by the tests of "querySingleVisibleElem" + if (!el) return false; + // checking el.style.display is not necessary for browsers, but it is required by some tests with happy-dom because happy-dom doesn't really do layout + return !el.classList.contains('tw-hidden') && Boolean((el.offsetWidth || el.offsetHeight || el.getClientRects().length) && el.style.display !== 'none'); } // replace selected text in a textarea while preserving editor history, e.g. CTRL-Z works after this @@ -311,6 +314,7 @@ export function replaceTextareaSelection(textarea: HTMLTextAreaElement, text: st export function createElementFromHTML<T extends HTMLElement>(htmlString: string): T { htmlString = htmlString.trim(); // some tags like "tr" are special, it must use a correct parent container to create + // eslint-disable-next-line github/unescaped-html-literal -- FIXME: maybe we need to use other approaches to create elements from HTML, e.g. using DOMParser if (htmlString.startsWith('<tr')) { const container = document.createElement('table'); container.innerHTML = htmlString; @@ -355,10 +359,19 @@ export function querySingleVisibleElem<T extends HTMLElement>(parent: Element, s return candidates.length ? candidates[0] as T : null; } -export function addDelegatedEventListener<T extends HTMLElement, E extends Event>(parent: Node, type: string, selector: string, listener: (elem: T, e: E) => void | Promise<any>, options?: boolean | AddEventListenerOptions) { +export function addDelegatedEventListener<T extends HTMLElement, E extends Event>(parent: Node, type: string, selector: string, listener: (elem: T, e: E) => Promisable<void>, options?: boolean | AddEventListenerOptions) { parent.addEventListener(type, (e: Event) => { const elem = (e.target as HTMLElement).closest(selector); - if (!elem) return; + // It strictly checks "parent contains the target elem" to avoid side effects of selector running on outside the parent. + // Keep in mind that the elem could have been removed from parent by other event handlers before this event handler is called. + // For example: tippy popup item, the tippy popup could be hidden and removed from DOM before this. + // It is caller's responsibility make sure the elem is still in parent's DOM when this event handler is called. + if (!elem || (parent !== document && !parent.contains(elem))) return; listener(elem as T, e as E); }, options); } + +/** Returns whether a click event is a left-click without any modifiers held */ +export function isPlainClick(e: MouseEvent) { + return e.button === 0 && !e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey; +} diff --git a/web_src/js/utils/html.test.ts b/web_src/js/utils/html.test.ts new file mode 100644 index 0000000000..3028b7bb0a --- /dev/null +++ b/web_src/js/utils/html.test.ts @@ -0,0 +1,8 @@ +import {html, htmlEscape, htmlRaw} from './html.ts'; + +test('html', async () => { + expect(html`<a>${'<>&\'"'}</a>`).toBe(`<a><>&'"</a>`); + expect(html`<a>${htmlRaw('<img>')}</a>`).toBe(`<a><img></a>`); + expect(html`<a>${htmlRaw`<img ${'&'}>`}</a>`).toBe(`<a><img &></a>`); + expect(htmlEscape(`<a></a>`)).toBe(`<a></a>`); +}); diff --git a/web_src/js/utils/html.ts b/web_src/js/utils/html.ts new file mode 100644 index 0000000000..22e5703c34 --- /dev/null +++ b/web_src/js/utils/html.ts @@ -0,0 +1,32 @@ +export function htmlEscape(s: string, ...args: Array<any>): string { + if (args.length !== 0) throw new Error('use html or htmlRaw instead of htmlEscape'); // check legacy usages + return s.replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(/</g, '<') + .replace(/>/g, '>'); +} + +class rawObject { + private readonly value: string; + constructor(v: string) { this.value = v } + toString(): string { return this.value } +} + +export function html(tmpl: TemplateStringsArray, ...parts: Array<any>): string { + let output = tmpl[0]; + for (let i = 0; i < parts.length; i++) { + const value = parts[i]; + const valueEscaped = (value instanceof rawObject) ? value.toString() : htmlEscape(String(parts[i])); + output = output + valueEscaped + tmpl[i + 1]; + } + return output; +} + +export function htmlRaw(s: string|TemplateStringsArray, ...tmplParts: Array<any>): rawObject { + if (typeof s === 'string') { + if (tmplParts.length !== 0) throw new Error("either htmlRaw('str') or htmlRaw`tmpl`"); + return new rawObject(s); + } + return new rawObject(html(s, ...tmplParts)); +} diff --git a/web_src/js/utils/image.test.ts b/web_src/js/utils/image.test.ts index da0605f1d0..49856c891c 100644 --- a/web_src/js/utils/image.test.ts +++ b/web_src/js/utils/image.test.ts @@ -4,7 +4,7 @@ const pngNoPhys = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA const pngPhys = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAEElEQVQI12OQNZcAIgYIBQAL8gGxdzzM0A=='; const pngEmpty = 'data:image/png;base64,'; -async function dataUriToBlob(datauri) { +async function dataUriToBlob(datauri: string) { return await (await globalThis.fetch(datauri)).blob(); } diff --git a/web_src/js/utils/time.ts b/web_src/js/utils/time.ts index 6951ebfedb..c63498345f 100644 --- a/web_src/js/utils/time.ts +++ b/web_src/js/utils/time.ts @@ -54,7 +54,7 @@ export type DayDataObject = { } export function fillEmptyStartDaysWithZeroes(startDays: number[], data: DayDataObject): DayData[] { - const result = {}; + const result: Record<string, any> = {}; for (const startDay of startDays) { result[startDay] = data[startDay] || {'week': startDay, 'additions': 0, 'deletions': 0, 'commits': 0}; diff --git a/web_src/js/webcomponents/absolute-date.test.ts b/web_src/js/webcomponents/absolute-date.test.ts index a3866829a7..bf591358bd 100644 --- a/web_src/js/webcomponents/absolute-date.test.ts +++ b/web_src/js/webcomponents/absolute-date.test.ts @@ -20,7 +20,7 @@ test('toAbsoluteLocaleDate', () => { // test different timezone const oldTZ = process.env.TZ; process.env.TZ = 'America/New_York'; - expect(new Date('2024-03-15').toLocaleString()).toEqual('3/14/2024, 8:00:00 PM'); - expect(toAbsoluteLocaleDate('2024-03-15')).toEqual('3/15/2024, 12:00:00 AM'); + expect(new Date('2024-03-15').toLocaleString('en-US')).toEqual('3/14/2024, 8:00:00 PM'); + expect(toAbsoluteLocaleDate('2024-03-15', 'en-US')).toEqual('3/15/2024, 12:00:00 AM'); process.env.TZ = oldTZ; }); diff --git a/web_src/js/webcomponents/absolute-date.ts b/web_src/js/webcomponents/absolute-date.ts index 8eb1c3e37e..23a8606673 100644 --- a/web_src/js/webcomponents/absolute-date.ts +++ b/web_src/js/webcomponents/absolute-date.ts @@ -15,7 +15,7 @@ window.customElements.define('absolute-date', class extends HTMLElement { initialized = false; update = () => { - const opt: Intl.DateTimeFormatOptions = {}; + const opt: Record<string, string> = {}; for (const attr of ['year', 'month', 'weekday', 'day']) { if (this.getAttribute(attr)) opt[attr] = this.getAttribute(attr); } diff --git a/web_src/js/webcomponents/overflow-menu.ts b/web_src/js/webcomponents/overflow-menu.ts index 4e729a268a..ae93f2b758 100644 --- a/web_src/js/webcomponents/overflow-menu.ts +++ b/web_src/js/webcomponents/overflow-menu.ts @@ -1,6 +1,6 @@ import {throttle} from 'throttle-debounce'; import {createTippy} from '../modules/tippy.ts'; -import {isDocumentFragmentOrElementNode} from '../utils/dom.ts'; +import {addDelegatedEventListener, isDocumentFragmentOrElementNode} from '../utils/dom.ts'; import octiconKebabHorizontal from '../../../public/assets/img/svg/octicon-kebab-horizontal.svg'; window.customElements.define('overflow-menu', class extends HTMLElement { @@ -12,10 +12,14 @@ window.customElements.define('overflow-menu', class extends HTMLElement { mutationObserver: MutationObserver; lastWidth: number; + updateButtonActivationState() { + if (!this.button || !this.tippyContent) return; + this.button.classList.toggle('active', Boolean(this.tippyContent.querySelector('.item.active'))); + } + updateItems = throttle(100, () => { if (!this.tippyContent) { const div = document.createElement('div'); - div.classList.add('tippy-target'); div.tabIndex = -1; // for initial focus, programmatic focus only div.addEventListener('keydown', (e) => { if (e.key === 'Tab') { @@ -64,9 +68,10 @@ window.customElements.define('overflow-menu', class extends HTMLElement { } } }); - this.append(div); + div.classList.add('tippy-target'); + this.handleItemClick(div, '.tippy-target > .item'); this.tippyContent = div; - } + } // end if: no tippyContent and create a new one const itemFlexSpace = this.menuItemsEl.querySelector<HTMLSpanElement>('.item-flex-space'); const itemOverFlowMenuButton = this.querySelector<HTMLButtonElement>('.overflow-menu-button'); @@ -88,7 +93,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { const menuRight = this.offsetLeft + this.offsetWidth; const menuItems = this.menuItemsEl.querySelectorAll<HTMLElement>('.item, .item-flex-space'); let afterFlexSpace = false; - for (const item of menuItems) { + for (const [idx, item] of menuItems.entries()) { if (item.classList.contains('item-flex-space')) { afterFlexSpace = true; continue; @@ -96,7 +101,10 @@ window.customElements.define('overflow-menu', class extends HTMLElement { if (afterFlexSpace) item.setAttribute('data-after-flex-space', 'true'); const itemRight = item.offsetLeft + item.offsetWidth; if (menuRight - itemRight < 38) { // roughly the width of .overflow-menu-button with some extra space - this.tippyItems.push(item); + const onlyLastItem = idx === menuItems.length - 1 && this.tippyItems.length === 0; + const lastItemFit = onlyLastItem && menuRight - itemRight > 0; + const moveToPopup = !onlyLastItem || !lastItemFit; + if (moveToPopup) this.tippyItems.push(item); } } itemFlexSpace?.style.removeProperty('display'); @@ -107,6 +115,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { const btn = this.querySelector('.overflow-menu-button'); btn?._tippy?.destroy(); btn?.remove(); + this.button = null; return; } @@ -126,18 +135,17 @@ window.customElements.define('overflow-menu', class extends HTMLElement { // update existing tippy if (this.button?._tippy) { this.button._tippy.setContent(this.tippyContent); + this.updateButtonActivationState(); return; } // create button initially - const btn = document.createElement('button'); - btn.classList.add('overflow-menu-button'); - btn.setAttribute('aria-label', window.config.i18n.more_items); - btn.innerHTML = octiconKebabHorizontal; - this.append(btn); - this.button = btn; - - createTippy(btn, { + this.button = document.createElement('button'); + this.button.classList.add('overflow-menu-button'); + this.button.setAttribute('aria-label', window.config.i18n.more_items); + this.button.innerHTML = octiconKebabHorizontal; + this.append(this.button); + createTippy(this.button, { trigger: 'click', hideOnClick: true, interactive: true, @@ -151,6 +159,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { }, 0); }, }); + this.updateButtonActivationState(); }); init() { @@ -187,6 +196,14 @@ window.customElements.define('overflow-menu', class extends HTMLElement { } }); this.resizeObserver.observe(this); + this.handleItemClick(this, '.overflow-menu-items > .item'); + } + + handleItemClick(el: Element, selector: string) { + addDelegatedEventListener(el, 'click', selector, () => { + this.button?._tippy?.hide(); + this.updateButtonActivationState(); + }); } connectedCallback() { diff --git a/web_src/js/webcomponents/polyfill.test.ts b/web_src/js/webcomponents/polyfill.test.ts new file mode 100644 index 0000000000..4fb4621547 --- /dev/null +++ b/web_src/js/webcomponents/polyfill.test.ts @@ -0,0 +1,7 @@ +import {weakRefClass} from './polyfills.ts'; + +test('polyfillWeakRef', () => { + const WeakRef = weakRefClass(); + const r = new WeakRef(123); + expect(r.deref()).toEqual(123); +}); diff --git a/web_src/js/webcomponents/polyfills.ts b/web_src/js/webcomponents/polyfills.ts index 4a84ee9562..9575324b5a 100644 --- a/web_src/js/webcomponents/polyfills.ts +++ b/web_src/js/webcomponents/polyfills.ts @@ -16,3 +16,19 @@ try { return intlNumberFormat(locales, options); }; } + +export function weakRefClass() { + const weakMap = new WeakMap(); + return class { + constructor(target: any) { + weakMap.set(this, target); + } + deref() { + return weakMap.get(this); + } + }; +} + +if (!window.WeakRef) { + window.WeakRef = weakRefClass() as any; +} diff --git a/web_src/svg/gitea-feishu.svg b/web_src/svg/gitea-feishu.svg new file mode 100644 index 0000000000..57941978d1 --- /dev/null +++ b/web_src/svg/gitea-feishu.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="7 7 26 26" width="20" height="20"><path d="M21.069 20.504l.063-.06.125-.122.085-.084.256-.254.348-.344.299-.296.281-.278.293-.289.269-.266.374-.37.218-.206.419-.359.404-.306.598-.386.617-.33.606-.265.348-.127.177-.058a14.78 14.78 0 0 0-2.793-5.603c-.252-.318-.639-.502-1.047-.502H12.221c-.196 0-.277.249-.119.364a31.49 31.49 0 0 1 8.943 10.162c.008-.007.016-.015.025-.023z" fill="#00d6b9"/><path d="M16.791 30c5.57 0 10.423-3.074 12.955-7.618.089-.159.175-.321.258-.484a6.12 6.12 0 0 1-.425.699c-.055.078-.111.155-.17.23a6.29 6.29 0 0 1-.225.274c-.062.07-.123.138-.188.206a5.61 5.61 0 0 1-.407.384 5.53 5.53 0 0 1-.24.195 7.12 7.12 0 0 1-.292.21c-.063.043-.126.084-.191.122s-.134.081-.204.119c-.14.078-.282.149-.428.215a5.53 5.53 0 0 1-.385.157 5.81 5.81 0 0 1-.43.138 5.91 5.91 0 0 1-.661.143c-.162.025-.325.044-.491.055-.173.012-.348.016-.525.014-.193-.003-.388-.015-.585-.037-.144-.015-.289-.037-.433-.062-.126-.022-.252-.049-.38-.079l-.2-.051-.555-.155-.275-.081-.41-.125-.334-.107-.317-.104-.215-.073-.26-.091-.186-.066-.367-.134-.212-.081-.284-.11-.299-.119-.193-.079-.24-.1-.185-.078-.192-.084-.166-.073-.152-.067-.153-.07-.159-.073-.2-.093-.208-.099-.222-.108-.189-.093c-3.335-1.668-6.295-3.89-8.822-6.583-.126-.134-.349-.045-.349.138l.005 9.52v.773c0 .448.222.87.595 1.118C10.946 29.092 13.762 30 16.791 30z" fill="#3370ff"/><path d="M29.746 22.382h0l.051-.093-.051.093zm.231-.435l.014-.025.007-.012-.021.037z" fill="#133c92"/><path d="M33.151 16.582c-1.129-.556-2.399-.869-3.744-.869a8.45 8.45 0 0 0-2.303.317l-.252.075-.177.058-.348.127-.606.265-.617.33-.598.386-.404.306-.419.359-.218.206-.374.37-.269.266-.293.289-.281.278-.299.296-.348.344-.256.254-.085.084-.125.122-.063.06-.095.09-.105.099c-.924.848-1.956 1.581-3.072 2.175l.2.093.159.073.153.07.152.067.166.073.192.084.185.078.24.1.193.079.299.119.284.11.212.081.367.134.186.066.26.09.215.073.317.104.334.107.41.125.275.081.555.155.2.051.379.079.433.062.585.037.525-.014.491-.055a5.61 5.61 0 0 0 .66-.143l.43-.138.385-.158.427-.215.204-.119.191-.122.292-.21.24-.195.407-.384.188-.206.225-.274.17-.23a6.13 6.13 0 0 0 .421-.693l.144-.288 1.305-2.599-.003.006a8.07 8.07 0 0 1 1.697-2.439z" fill="#133c9a"/></svg> diff --git a/web_src/svg/material-folder-generic.svg b/web_src/svg/material-folder-generic.svg new file mode 100644 index 0000000000..a6c6262c17 --- /dev/null +++ b/web_src/svg/material-folder-generic.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 4H4c-1.11 0-2 .89-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" fill="#42a5f5"/></svg>
\ No newline at end of file diff --git a/web_src/svg/material-folder-symlink.svg b/web_src/svg/material-folder-symlink.svg new file mode 100644 index 0000000000..2db7bcd4de --- /dev/null +++ b/web_src/svg/material-folder-symlink.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 4H4c-1.11 0-2 .89-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" fill="#42a5f5" opacity=".745"/><path d="M16.972 10.757v2.641h-6.561v5.281h6.561v2.641l6.562-5.281-6.562-5.282z" opacity=".81" fill="#c5e5fd"/></svg>
\ No newline at end of file |