Merge pull request '[gitea] week 15 cherry pick' (#3091) from algernon/forgejo:wcp/week-15 into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3091
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
This commit is contained in:
Earl Warren 2024-04-09 05:31:44 +00:00
commit 26fc7c3461
117 changed files with 3706 additions and 4312 deletions

View file

@ -24,6 +24,7 @@
--repo-header-issue-min-height: 41px;
--min-height-textarea: 132px; /* padding + 6 lines + border = calc(1.57142em + 6lh + 2px), but lh is not fully supported */
--tab-size: 4;
--checkbox-size: 16px; /* height and width of checkbox and radio inputs */
}
:root * {
@ -44,7 +45,7 @@ html, body {
}
body {
line-height: 1.4285rem;
line-height: 20px;
font-family: var(--fonts-regular);
color: var(--color-text);
background-color: var(--color-body);
@ -316,61 +317,6 @@ a.label,
background-color: var(--color-label-bg);
}
/* fix Fomantic's line-height cutting off "g" on Windows Chrome with Segoe UI */
.ui.input > input {
line-height: var(--line-height-default);
text-align: start; /* Override fomantic's `text-align: left` to make RTL work via HTML `dir="auto"` */
}
/* fix Fomantic's line-height causing vertical scrollbars to appear */
ul.ui.list li,
ol.ui.list li,
.ui.list > .item,
.ui.list .list > .item {
line-height: var(--line-height-default);
}
.ui.input.focus > input,
.ui.input > input:focus {
border-color: var(--color-primary);
}
.ui.action.input .ui.ui.button {
border-color: var(--color-input-border);
padding-top: 0; /* the ".action.input" is "flex + stretch", so let the buttons layout themselves */
padding-bottom: 0;
}
/* currently used for search bar dropdowns in repo search and explore code */
.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection {
min-width: 10em;
}
.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(:focus) {
border-right: none;
}
.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(.active):hover {
border-color: var(--color-input-border);
}
.ui.action.input:not([class*="left action"]) .ui.dropdown.selection.upward.visible {
border-bottom-left-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.ui.action.input:not([class*="left action"]) > input,
.ui.action.input:not([class*="left action"]) > input:hover {
border-right: none;
}
.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection,
.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection:hover,
.ui.action.input:not([class*="left action"]) > input:focus + .button,
.ui.action.input:not([class*="left action"]) > input:focus + .button:hover,
.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button,
.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button:hover {
border-left-color: var(--color-primary);
}
.ui.action.input:not([class*="left action"]) > input:focus {
border-right-color: var(--color-primary);
}
.ui.menu {
display: flex;
}
@ -514,21 +460,6 @@ ol.ui.list li,
color: var(--color-text-light-2);
}
.ui.list .list > .item .header,
.ui.list > .item .header {
color: var(--color-text-dark);
}
.ui.list .list > .item > .content,
.ui.list > .item > .content {
color: var(--color-text);
}
.ui.list .list > .item .description,
.ui.list > .item .description {
color: var(--color-text);
}
/* replace item margin on secondary menu items with gap and remove both the
negative margins on the menu as well as margin on the items */
.ui.secondary.menu {
@ -647,10 +578,6 @@ img.ui.avatar,
aspect-ratio: 1;
}
.ui.divided.list > .item {
border-color: var(--color-secondary);
}
.ui.error.message .header,
.ui.warning.message .header {
color: inherit;
@ -1457,11 +1384,6 @@ table th[data-sortt-desc] .svg {
vertical-align: -0.15em;
}
/* for the jquery.minicolors plugin */
.minicolors-panel {
background: var(--color-secondary-dark-1) !important;
}
.ui.tabular.menu {
border-color: var(--color-secondary);
}
@ -1625,16 +1547,6 @@ table th[data-sortt-desc] .svg {
align-items: stretch;
}
.ui.ui.icon.input .icon {
display: flex;
align-items: center;
justify-content: center;
}
.ui.icon.input > i.icon {
transition: none;
}
.flex-items-block > .item,
.flex-text-block {
display: flex;

View file

@ -0,0 +1,47 @@
.js-color-picker-input {
display: flex;
position: relative;
}
.js-color-picker-input input {
padding-top: 8px !important;
padding-bottom: 8px !important;
padding-left: 32px !important;
}
.js-color-picker-input .preview-square {
position: absolute;
aspect-ratio: 1;
height: 16px;
left: 10px;
top: 50%;
transform: translateY(-50%);
border-radius: 2px;
background: repeating-linear-gradient(45deg, #aaa 25%, transparent 25%, transparent 75%, #aaa 75%, #aaa), repeating-linear-gradient(45deg, #aaa 25%, #fff 25%, #fff 75%, #aaa 75%, #aaa); /* stylelint-disable-line scale-unlimited/declaration-strict-value */
background-position: 0 0, 4px 4px;
background-size: 8px 8px;
}
.js-color-picker-input .preview-square::after {
content: "";
position: absolute;
width: 100%;
height: 100%;
border-radius: inherit;
background-color: currentcolor;
}
hex-color-picker {
width: 180px;
height: 120px;
}
hex-color-picker::part(hue-pointer),
hex-color-picker::part(saturation-pointer) {
width: 22px;
height: 22px;
}
hex-color-picker::part(hue) {
flex-basis: 16px;
}

View file

@ -102,26 +102,3 @@
.card-ghost * {
opacity: 0;
}
.color-field .minicolors.minicolors-theme-default {
display: block;
}
.color-field .minicolors.minicolors-theme-default .minicolors-input {
height: 38px;
padding-left: 2rem;
}
.color-field .minicolors.minicolors-theme-default .minicolors-swatch {
top: 10px;
}
.edit-project-column-modal .color.picker.column,
.new-project-column-modal .color.picker.column {
display: flex;
}
.edit-project-column-modal .color.picker.column .minicolors,
.new-project-column-modal .color.picker.column .minicolors {
flex: 1;
}

View file

@ -32,10 +32,7 @@ textarea,
.ui.form input[type="text"],
.ui.form input[type="time"],
.ui.form input[type="url"],
.ui.selection.dropdown,
.ui.checkbox label::before,
.ui.checkbox input:checked ~ label::before,
.ui.checkbox input:not([type="radio"]):indeterminate ~ label::before {
.ui.selection.dropdown {
background: var(--color-input-background);
border-color: var(--color-input-border);
color: var(--color-input-text);
@ -63,12 +60,7 @@ textarea:hover,
.ui.form input[type="text"]:hover,
.ui.form input[type="time"]:hover,
.ui.form input[type="url"]:hover,
.ui.selection.dropdown:hover,
.ui.checkbox label:hover::before,
.ui.checkbox label:active::before,
.ui.radio.checkbox label::after,
.ui.radio.checkbox input:focus ~ label::before,
.ui.radio.checkbox input:checked ~ label::before {
.ui.selection.dropdown:hover {
background: var(--color-input-background);
border-color: var(--color-input-border-hover);
color: var(--color-input-text);
@ -91,11 +83,7 @@ textarea:focus,
.ui.form input[type="text"]:focus,
.ui.form input[type="time"]:focus,
.ui.form input[type="url"]:focus,
.ui.selection.dropdown:focus,
.ui.checkbox input:focus ~ label::before,
.ui.checkbox input:not([type="radio"]):indeterminate:focus ~ label::before,
.ui.checkbox input:checked:focus ~ label::before,
.ui.radio.checkbox input:focus:checked ~ label::before {
.ui.selection.dropdown:focus {
background: var(--color-input-background);
border-color: var(--color-primary);
color: var(--color-input-text);
@ -106,58 +94,21 @@ textarea:focus,
.ui.form .inline.fields .field > label,
.ui.form .inline.fields .field > p,
.ui.form .inline.field > label,
.ui.form .inline.field > p,
.ui.checkbox label,
.ui.checkbox + label,
.ui.checkbox label:hover,
.ui.checkbox + label:hover,
.ui.checkbox input:focus ~ label,
.ui.checkbox input:active ~ label {
.ui.form .inline.field > p {
color: var(--color-text);
}
.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 {
color: var(--color-red);
}
.ui.input,
.ui.checkbox input:focus ~ label::after,
.ui.checkbox input:checked ~ label::after,
.ui.checkbox label:active::after,
.ui.checkbox input:not([type="radio"]):indeterminate ~ label::after,
.ui.checkbox input:not([type="radio"]):indeterminate:focus ~ label::after,
.ui.checkbox input:checked:focus ~ label::after,
.ui.disabled.checkbox label,
.ui.checkbox input[disabled] ~ label {
.ui.input {
color: var(--color-input-text);
}
.ui.radio.checkbox input:focus ~ label::after,
.ui.radio.checkbox input:checked ~ label::after,
.ui.radio.checkbox input:focus:checked ~ label::after {
background: var(--color-input-text);
}
.ui.toggle.checkbox label::before {
background: var(--color-input-toggle-background);
}
.ui.toggle.checkbox label,
.ui.toggle.checkbox input:checked ~ label,
.ui.toggle.checkbox input:focus:checked ~ label {
color: var(--color-text) !important;
}
.ui.toggle.checkbox input:checked ~ label::before,
.ui.toggle.checkbox input:focus:checked ~ label::before {
background: var(--color-primary) !important;
}
/* match <select> padding to <input> */
.ui.form select {
padding: 0.67857143em 1em;

View file

@ -63,3 +63,20 @@ only use:
display: none !important;
}
}
.tab-size-1 { tab-size: 1 !important; }
.tab-size-2 { tab-size: 2 !important; }
.tab-size-3 { tab-size: 3 !important; }
.tab-size-4 { tab-size: 4 !important; }
.tab-size-5 { tab-size: 5 !important; }
.tab-size-6 { tab-size: 6 !important; }
.tab-size-7 { tab-size: 7 !important; }
.tab-size-8 { tab-size: 8 !important; }
.tab-size-9 { tab-size: 9 !important; }
.tab-size-10 { tab-size: 10 !important; }
.tab-size-11 { tab-size: 11 !important; }
.tab-size-12 { tab-size: 12 !important; }
.tab-size-13 { tab-size: 13 !important; }
.tab-size-14 { tab-size: 14 !important; }
.tab-size-15 { tab-size: 15 !important; }
.tab-size-16 { tab-size: 16 !important; }

View file

@ -6,12 +6,15 @@
@import "./modules/container.css";
@import "./modules/divider.css";
@import "./modules/header.css";
@import "./modules/input.css";
@import "./modules/label.css";
@import "./modules/list.css";
@import "./modules/segment.css";
@import "./modules/grid.css";
@import "./modules/message.css";
@import "./modules/table.css";
@import "./modules/card.css";
@import "./modules/checkbox.css";
@import "./modules/modal.css";
@import "./modules/select.css";

View file

@ -6,7 +6,6 @@
.is-loading {
pointer-events: none !important;
position: relative !important;
overflow: hidden !important;
}
.is-loading > * {
@ -35,10 +34,14 @@
border-radius: var(--border-radius-circle);
}
.is-loading.small-loading-icon::after {
.is-loading.loading-icon-2px::after {
border-width: 2px;
}
.is-loading.loading-icon-3px::after {
border-width: 3px;
}
/* for single form button, the loading state should be on the button, but not go semi-transparent, just replace the text on the button with the loader. */
form.single-button-form.is-loading > * {
opacity: 1;
@ -63,7 +66,7 @@ form.single-button-form.is-loading .button {
background: transparent;
}
/* TODO: not needed, use "is-loading small-loading-icon" instead */
/* TODO: not needed, use "is-loading loading-icon-2px" instead */
code.language-math.is-loading::after {
padding: 0;
border-width: 2px;

View file

@ -0,0 +1,120 @@
/* 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. */
input[type="checkbox"],
input[type="radio"] {
width: var(--checkbox-size);
height: var(--checkbox-size);
}
.ui.checkbox {
position: relative;
display: inline-block;
vertical-align: baseline;
min-height: var(--checkbox-size);
line-height: var(--checkbox-size);
min-width: var(--checkbox-size);
padding: 1px;
}
.ui.checkbox input[type="checkbox"],
.ui.checkbox input[type="radio"] {
position: absolute;
top: 0;
left: 0;
width: var(--checkbox-size);
height: var(--checkbox-size);
}
.ui.checkbox input[type="checkbox"]:enabled,
.ui.checkbox input[type="radio"]:enabled,
.ui.checkbox label:enabled {
cursor: pointer;
}
.ui.checkbox label {
cursor: auto;
position: relative;
display: block;
user-select: none;
}
.ui.checkbox label,
.ui.radio.checkbox label {
margin-left: 1.85714em;
}
.ui.checkbox + label {
vertical-align: middle;
}
.ui.disabled.checkbox label,
.ui.checkbox input[disabled] ~ label {
cursor: default !important;
opacity: 0.5;
pointer-events: none;
}
.ui.radio.checkbox {
min-height: var(--checkbox-size);
}
/* "switch" styled checkbox */
.ui.toggle.checkbox {
min-height: 1.5rem;
}
.ui.toggle.checkbox input {
width: 3.5rem;
height: 1.5rem;
opacity: 0;
z-index: 3;
}
.ui.toggle.checkbox label {
min-height: 1.5rem;
padding-left: 4.5rem;
padding-top: 0.15em;
}
.ui.toggle.checkbox label::before {
display: block;
position: absolute;
content: "";
z-index: 1;
top: 0;
width: 3.5rem;
height: 1.5rem;
border-radius: 500rem;
left: 0;
}
.ui.toggle.checkbox label::after {
background: var(--color-white);
position: absolute;
content: "";
opacity: 1;
z-index: 2;
width: 1.5rem;
height: 1.5rem;
top: 0;
left: 0;
border-radius: 500rem;
transition: background 0.3s ease, left 0.3s ease;
}
.ui.toggle.checkbox input ~ label::after {
left: -0.05rem;
}
.ui.toggle.checkbox input:checked ~ label::after {
left: 2.15rem;
}
.ui.toggle.checkbox input:focus ~ label::before,
.ui.toggle.checkbox label::before {
background: var(--color-input-toggle-background);
}
.ui.toggle.checkbox label,
.ui.toggle.checkbox input:checked ~ label,
.ui.toggle.checkbox input:focus:checked ~ label {
color: var(--color-text) !important;
}
.ui.toggle.checkbox input:checked ~ label::before,
.ui.toggle.checkbox input:focus:checked ~ label::before {
background: var(--color-primary) !important;
}

View file

@ -135,6 +135,12 @@ h4.ui.header .sub.header {
font-weight: var(--font-weight-normal);
}
/* open dropdown menus to the left in right-attached headers */
.ui.attached.header > .ui.right .ui.dropdown .menu {
right: 0;
left: auto;
}
/* if a .top.attached.header is followed by a .segment, add some margin */
.ui.segments + .ui.top.attached.header,
.ui.attached.segment + .ui.top.attached.header {

View file

@ -0,0 +1,197 @@
/* based on Fomantic UI input module, with just the parts extracted that we use. If you find any
unused rules here after refactoring, please remove them. */
.ui.input {
position: relative;
font-weight: var(--font-weight-normal);
display: inline-flex;
color: var(--color-input-text);
}
.ui.input > input {
margin: 0;
max-width: 100%;
flex: 1 0 auto;
outline: none;
font-family: var(--fonts-regular);
padding: 0.67857143em 1em;
border: 1px solid var(--color-input-border);
color: var(--color-input-text);
border-radius: 0.28571429rem;
line-height: var(--line-height-default);
text-align: start;
}
.ui.disabled.input,
.ui.input:not(.disabled) input[disabled] {
opacity: var(--opacity-disabled);
}
.ui.disabled.input > input,
.ui.input:not(.disabled) input[disabled] {
pointer-events: none;
}
.ui.input.focus > input,
.ui.input > input:focus {
border-color: var(--color-primary);
}
.ui.input.error > input {
background: var(--color-error-bg);
border-color: var(--color-error-border);
color: var(--color-error-text);
}
.ui.icon.input > i.icon {
display: flex;
align-items: center;
justify-content: center;
cursor: default;
position: absolute;
text-align: center;
top: 0;
right: 0;
margin: 0;
height: 100%;
width: 2.67142857em;
opacity: 0.5;
border-radius: 0 0.28571429rem 0.28571429rem 0;
pointer-events: none;
padding: 4px;
}
.ui.icon.input > i.icon.is-loading {
position: absolute !important;
height: 28px;
top: 4px;
}
.ui.icon.input > i.icon.is-loading > * {
visibility: hidden;
}
.ui.ui.ui.ui.icon.input > textarea,
.ui.ui.ui.ui.icon.input > input {
padding-right: 2.67142857em;
}
.ui.icon.input > i.link.icon {
cursor: pointer;
}
.ui.icon.input > i.circular.icon {
top: 0.35em;
right: 0.5em;
}
.ui[class*="left icon"].input > i.icon {
right: auto;
left: 1px;
border-radius: 0.28571429rem 0 0 0.28571429rem;
}
.ui[class*="left icon"].input > i.circular.icon {
right: auto;
left: 0.5em;
}
.ui.ui.ui.ui[class*="left icon"].input > textarea,
.ui.ui.ui.ui[class*="left icon"].input > input {
padding-left: 2.67142857em;
padding-right: 1em;
}
.ui.icon.input > textarea:focus ~ .icon,
.ui.icon.input > input:focus ~ .icon {
opacity: 1;
}
.ui.icon.input > textarea ~ i.icon {
height: 3em;
}
.ui.form .field.error > .ui.action.input > .ui.button,
.ui.action.input.error > .ui.button {
border-top: 1px solid var(--color-error-border);
border-bottom: 1px solid var(--color-error-border);
}
.ui.action.input > .button,
.ui.action.input > .buttons {
display: flex;
align-items: center;
flex: 0 0 auto;
}
.ui.action.input > .button,
.ui.action.input > .buttons > .button {
padding-top: 0.78571429em;
padding-bottom: 0.78571429em;
margin: 0;
}
.ui.action.input:not([class*="left action"]) > input {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right-color: transparent;
}
.ui.action.input > .dropdown:first-child,
.ui.action.input > .button:first-child,
.ui.action.input > .buttons:first-child > .button {
border-radius: 0.28571429rem 0 0 0.28571429rem;
}
.ui.action.input > .dropdown:not(:first-child),
.ui.action.input > .button:not(:first-child),
.ui.action.input > .buttons:not(:first-child) > .button {
border-radius: 0;
}
.ui.action.input > .dropdown:last-child,
.ui.action.input > .button:last-child,
.ui.action.input > .buttons:last-child > .button {
border-radius: 0 0.28571429rem 0.28571429rem 0;
}
.ui.fluid.input {
display: flex;
}
.ui.fluid.input > input {
width: 0 !important;
}
.ui.tiny.input {
font-size: 0.85714286em;
}
.ui.small.input {
font-size: 0.92857143em;
}
.ui.action.input .ui.ui.button {
border-color: var(--color-input-border);
padding-top: 0; /* the ".action.input" is "flex + stretch", so let the buttons layout themselves */
padding-bottom: 0;
}
/* currently used for search bar dropdowns in repo search and explore code */
.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection {
min-width: 10em;
}
.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(:focus) {
border-right: none;
}
.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(.active):hover {
border-color: var(--color-input-border);
}
.ui.action.input:not([class*="left action"]) .ui.dropdown.selection.upward.visible {
border-bottom-left-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.ui.action.input:not([class*="left action"]) > input,
.ui.action.input:not([class*="left action"]) > input:hover {
border-right: none;
}
.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection,
.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection:hover,
.ui.action.input:not([class*="left action"]) > input:focus + .button,
.ui.action.input:not([class*="left action"]) > input:focus + .button:hover,
.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button,
.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button:hover {
border-left-color: var(--color-primary);
}
.ui.action.input:not([class*="left action"]) > input:focus {
border-right-color: var(--color-primary);
}

View file

@ -0,0 +1,187 @@
/* based on Fomantic UI list module, with just the parts extracted that we use. If you find any
unused rules here after refactoring, please remove them. */
.ui.list {
list-style-type: none;
margin: 1em 0;
padding: 0;
font-size: 1em;
}
.ui.list:first-child {
margin-top: 0;
padding-top: 0;
}
.ui.list:last-child {
margin-bottom: 0;
padding-bottom: 0;
}
.ui.list > .item,
.ui.list .list > .item {
display: list-item;
table-layout: fixed;
list-style-type: none;
list-style-position: outside;
}
.ui.list > .list > .item::after,
.ui.list > .item::after {
content: "";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
.ui.list .list:not(.icon) {
clear: both;
margin: 0;
padding: 0.75em 0 0.25em 0.5em;
}
.ui.list .list > .item {
padding: 0.14285714em 0;
}
.ui.list .list > .item > i.icon,
.ui.list > .item > i.icon {
display: table-cell;
min-width: 1.55em;
padding-top: 0;
transition: color 0.1s ease;
padding-right: 0.28571429em;
vertical-align: top;
}
.ui.list .list > .item > i.icon:only-child,
.ui.list > .item > i.icon:only-child {
display: inline-block;
min-width: auto;
vertical-align: top;
}
.ui.list .list > .item > .image,
.ui.list > .item > .image {
display: table-cell;
background-color: transparent;
vertical-align: top;
}
.ui.list .list > .item > .image:not(:only-child):not(img),
.ui.list > .item > .image:not(:only-child):not(img) {
padding-right: 0.5em;
}
.ui.list .list > .item > .image img,
.ui.list > .item > .image img {
vertical-align: top;
}
.ui.list .list > .item > img.image,
.ui.list .list > .item > .image:only-child,
.ui.list > .item > img.image,
.ui.list > .item > .image:only-child {
display: inline-block;
}
.ui.list .list > .item > .content,
.ui.list > .item > .content {
color: var(--color-text);
}
.ui.list .list > .item > .image + .content,
.ui.list .list > .item > i.icon + .content,
.ui.list > .item > .image + .content,
.ui.list > .item > i.icon + .content {
display: table-cell;
width: 100%;
padding: 0 0 0 0.5em;
vertical-align: top;
}
.ui.list .list > .item > img.image + .content,
.ui.list > .item > img.image + .content {
display: inline-block;
width: auto;
}
.ui.list .list > .item > .content > .list,
.ui.list > .item > .content > .list {
margin-left: 0;
padding-left: 0;
}
.ui.list .list > .item .header,
.ui.list > .item .header {
display: block;
margin: 0;
font-family: var(--fonts-regular);
font-weight: var(--font-weight-medium);
color: var(--color-text-dark);
}
.ui.list .list > .item .description,
.ui.list > .item .description {
display: block;
color: var(--color-text);
}
.ui.list > .item a,
.ui.list .list > .item a {
cursor: pointer;
}
.ui.menu .ui.list > .item,
.ui.menu .ui.list .list > .item {
display: list-item;
table-layout: fixed;
background-color: transparent;
list-style-type: none;
list-style-position: outside;
padding: 0.21428571em 0;
}
.ui.menu .ui.list .list > .item::before,
.ui.menu .ui.list > .item::before {
border: none;
background: none;
}
.ui.menu .ui.list .list > .item:first-child,
.ui.menu .ui.list > .item:first-child {
padding-top: 0;
}
.ui.menu .ui.list .list > .item:last-child,
.ui.menu .ui.list > .item:last-child {
padding-bottom: 0;
}
.ui.list .list > .disabled.item,
.ui.list > .disabled.item {
pointer-events: none;
opacity: var(--opacity-disabled);
}
.ui.list .list > a.item:hover > .icons,
.ui.list > a.item:hover > .icons,
.ui.list .list > a.item:hover > i.icon,
.ui.list > a.item:hover > i.icon {
color: var(--color-text-dark);
}
.ui.divided.list > .item {
border-top: 1px solid var(--color-secondary);
}
.ui.divided.list .list > .item {
border-top: none;
}
.ui.divided.list .item .list > .item {
border-top: none;
}
.ui.divided.list .list > .item:first-child,
.ui.divided.list > .item:first-child {
border-top: none;
}
.ui.divided.list .list > .item:first-child {
border-top-width: 1px;
}
.ui.relaxed.list > .item:not(:first-child) {
padding-top: 0.42857143em;
}
.ui.relaxed.list > .item:not(:last-child) {
padding-bottom: 0.42857143em;
}

View file

@ -140,3 +140,8 @@
.secondary-nav {
background: var(--color-secondary-nav-bg) !important; /* important because of .ui.secondary.menu */
}
.issue-navbar {
display: flex;
justify-content: space-between;
}

View file

@ -29,6 +29,17 @@
z-index: 1;
}
/* bare theme, no styling at all, except box-shadow */
.tippy-box[data-theme="bare"] {
border: none;
box-shadow: 0 6px 18px var(--color-shadow);
}
.tippy-box[data-theme="bare"] .tippy-content {
padding: 0;
background: transparent;
}
/* tooltip theme for text tooltips */
.tippy-box[data-theme="tooltip"] {

View file

@ -89,10 +89,6 @@
text-align: center;
}
.organization.options input {
min-width: 300px;
}
.page-content.organization .org-avatar {
margin-right: 15px;
}

View file

@ -2299,104 +2299,6 @@
padding-top: 15px;
}
.edit-label.modal .form .color.picker.column,
.new-label.modal .form .color.picker.column {
display: flex;
}
.edit-label.modal .form .color.picker.column .minicolors,
.new-label.modal .form .color.picker.column .minicolors {
flex: 1;
}
.edit-label.modal .form .minicolors-swatch.minicolors-sprite,
.new-label.modal .form .minicolors-swatch.minicolors-sprite {
top: 10px;
left: 10px;
width: 15px;
height: 15px;
}
.tab-size-1 {
tab-size: 1 !important;
-moz-tab-size: 1 !important;
}
.tab-size-2 {
tab-size: 2 !important;
-moz-tab-size: 2 !important;
}
.tab-size-3 {
tab-size: 3 !important;
-moz-tab-size: 3 !important;
}
.tab-size-4 {
tab-size: 4 !important;
-moz-tab-size: 4 !important;
}
.tab-size-5 {
tab-size: 5 !important;
-moz-tab-size: 5 !important;
}
.tab-size-6 {
tab-size: 6 !important;
-moz-tab-size: 6 !important;
}
.tab-size-7 {
tab-size: 7 !important;
-moz-tab-size: 7 !important;
}
.tab-size-8 {
tab-size: 8 !important;
-moz-tab-size: 8 !important;
}
.tab-size-9 {
tab-size: 9 !important;
-moz-tab-size: 9 !important;
}
.tab-size-10 {
tab-size: 10 !important;
-moz-tab-size: 10 !important;
}
.tab-size-11 {
tab-size: 11 !important;
-moz-tab-size: 11 !important;
}
.tab-size-12 {
tab-size: 12 !important;
-moz-tab-size: 12 !important;
}
.tab-size-13 {
tab-size: 13 !important;
-moz-tab-size: 13 !important;
}
.tab-size-14 {
tab-size: 14 !important;
-moz-tab-size: 14 !important;
}
.tab-size-15 {
tab-size: 15 !important;
-moz-tab-size: 15 !important;
}
.tab-size-16 {
tab-size: 16 !important;
-moz-tab-size: 16 !important;
}
.stats-table {
display: table;
width: 100%;
@ -2573,6 +2475,7 @@ tbody.commit-list {
#repo-topics .repo-topic {
font-weight: var(--font-weight-normal);
cursor: pointer;
margin: 0;
}
#new-dependency-drop-list.ui.selection.dropdown {
@ -2990,6 +2893,7 @@ tbody.commit-list {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
}
@media (max-width: 767.98px) {

View file

@ -9,6 +9,7 @@
.issue-list-toolbar-left {
display: flex;
align-items: center;
}
.issue-list-toolbar-right .filter.menu {

File diff suppressed because it is too large Load diff

View file

@ -1184,883 +1184,6 @@ $.api.settings = {
})( jQuery, window, document );
/*!
* # Fomantic-UI - Checkbox
* 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.checkbox = 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
;
$allModules
.each(function() {
var
settings = $.extend(true, {}, $.fn.checkbox.settings, parameters),
className = settings.className,
namespace = settings.namespace,
selector = settings.selector,
error = settings.error,
eventNamespace = '.' + namespace,
moduleNamespace = 'module-' + namespace,
$module = $(this),
$label = $(this).children(selector.label),
$input = $(this).children(selector.input),
input = $input[0],
initialLoad = false,
shortcutPressed = false,
instance = $module.data(moduleNamespace),
observer,
element = this,
module
;
module = {
initialize: function() {
module.verbose('Initializing checkbox', settings);
module.create.label();
module.bind.events();
module.set.tabbable();
module.hide.input();
module.observeChanges();
module.instantiate();
module.setup();
},
instantiate: function() {
module.verbose('Storing instance of module', module);
instance = module;
$module
.data(moduleNamespace, module)
;
},
destroy: function() {
module.verbose('Destroying module');
module.unbind.events();
module.show.input();
$module.removeData(moduleNamespace);
},
fix: {
reference: function() {
if( $module.is(selector.input) ) {
module.debug('Behavior called on <input> adjusting invoked element');
$module = $module.closest(selector.checkbox);
module.refresh();
}
}
},
setup: function() {
module.set.initialLoad();
if( module.is.indeterminate() ) {
module.debug('Initial value is indeterminate');
module.indeterminate();
}
else if( module.is.checked() ) {
module.debug('Initial value is checked');
module.check();
}
else {
module.debug('Initial value is unchecked');
module.uncheck();
}
module.remove.initialLoad();
},
refresh: function() {
$label = $module.children(selector.label);
$input = $module.children(selector.input);
input = $input[0];
},
hide: {
input: function() {
module.verbose('Modifying <input> z-index to be unselectable');
$input.addClass(className.hidden);
}
},
show: {
input: function() {
module.verbose('Modifying <input> z-index to be selectable');
$input.removeClass(className.hidden);
}
},
observeChanges: function() {
if('MutationObserver' in window) {
observer = new MutationObserver(function(mutations) {
module.debug('DOM tree modified, updating selector cache');
module.refresh();
});
observer.observe(element, {
childList : true,
subtree : true
});
module.debug('Setting up mutation observer', observer);
}
},
attachEvents: function(selector, event) {
var
$element = $(selector)
;
event = $.isFunction(module[event])
? module[event]
: module.toggle
;
if($element.length > 0) {
module.debug('Attaching checkbox events to element', selector, event);
$element
.on('click' + eventNamespace, event)
;
}
else {
module.error(error.notFound);
}
},
preventDefaultOnInputTarget: function() {
if(typeof event !== 'undefined' && event !== null && $(event.target).is(selector.input)) {
module.verbose('Preventing default check action after manual check action');
event.preventDefault();
}
},
event: {
change: function(event) {
if( !module.should.ignoreCallbacks() ) {
settings.onChange.call(input);
}
},
click: function(event) {
var
$target = $(event.target)
;
if( $target.is(selector.input) ) {
module.verbose('Using default check action on initialized checkbox');
return;
}
if( $target.is(selector.link) ) {
module.debug('Clicking link inside checkbox, skipping toggle');
return;
}
module.toggle();
$input.focus();
event.preventDefault();
},
keydown: function(event) {
var
key = event.which,
keyCode = {
enter : 13,
space : 32,
escape : 27,
left : 37,
up : 38,
right : 39,
down : 40
}
;
var r = module.get.radios(),
rIndex = r.index($module),
rLen = r.length,
checkIndex = false;
if(key == keyCode.left || key == keyCode.up) {
checkIndex = (rIndex === 0 ? rLen : rIndex) - 1;
} else if(key == keyCode.right || key == keyCode.down) {
checkIndex = rIndex === rLen-1 ? 0 : rIndex+1;
}
if (!module.should.ignoreCallbacks() && checkIndex !== false) {
if(settings.beforeUnchecked.apply(input)===false) {
module.verbose('Option not allowed to be unchecked, cancelling key navigation');
return false;
}
if (settings.beforeChecked.apply($(r[checkIndex]).children(selector.input)[0])===false) {
module.verbose('Next option should not allow check, cancelling key navigation');
return false;
}
}
if(key == keyCode.escape) {
module.verbose('Escape key pressed blurring field');
$input.blur();
shortcutPressed = true;
}
else if(!event.ctrlKey && ( key == keyCode.space || (key == keyCode.enter && settings.enableEnterKey)) ) {
module.verbose('Enter/space key pressed, toggling checkbox');
module.toggle();
shortcutPressed = true;
}
else {
shortcutPressed = false;
}
},
keyup: function(event) {
if(shortcutPressed) {
event.preventDefault();
}
}
},
check: function() {
if( !module.should.allowCheck() ) {
return;
}
module.debug('Checking checkbox', $input);
module.set.checked();
if( !module.should.ignoreCallbacks() ) {
settings.onChecked.call(input);
module.trigger.change();
}
module.preventDefaultOnInputTarget();
},
uncheck: function() {
if( !module.should.allowUncheck() ) {
return;
}
module.debug('Unchecking checkbox');
module.set.unchecked();
if( !module.should.ignoreCallbacks() ) {
settings.onUnchecked.call(input);
module.trigger.change();
}
module.preventDefaultOnInputTarget();
},
indeterminate: function() {
if( module.should.allowIndeterminate() ) {
module.debug('Checkbox is already indeterminate');
return;
}
module.debug('Making checkbox indeterminate');
module.set.indeterminate();
if( !module.should.ignoreCallbacks() ) {
settings.onIndeterminate.call(input);
module.trigger.change();
}
},
determinate: function() {
if( module.should.allowDeterminate() ) {
module.debug('Checkbox is already determinate');
return;
}
module.debug('Making checkbox determinate');
module.set.determinate();
if( !module.should.ignoreCallbacks() ) {
settings.onDeterminate.call(input);
module.trigger.change();
}
},
enable: function() {
if( module.is.enabled() ) {
module.debug('Checkbox is already enabled');
return;
}
module.debug('Enabling checkbox');
module.set.enabled();
if( !module.should.ignoreCallbacks() ) {
settings.onEnable.call(input);
// preserve legacy callbacks
settings.onEnabled.call(input);
module.trigger.change();
}
},
disable: function() {
if( module.is.disabled() ) {
module.debug('Checkbox is already disabled');
return;
}
module.debug('Disabling checkbox');
module.set.disabled();
if( !module.should.ignoreCallbacks() ) {
settings.onDisable.call(input);
// preserve legacy callbacks
settings.onDisabled.call(input);
module.trigger.change();
}
},
get: {
radios: function() {
var
name = module.get.name()
;
return $('input[name="' + name + '"]').closest(selector.checkbox);
},
otherRadios: function() {
return module.get.radios().not($module);
},
name: function() {
return $input.attr('name');
}
},
is: {
initialLoad: function() {
return initialLoad;
},
radio: function() {
return ($input.hasClass(className.radio) || $input.attr('type') == 'radio');
},
indeterminate: function() {
return $input.prop('indeterminate') !== undefined && $input.prop('indeterminate');
},
checked: function() {
return $input.prop('checked') !== undefined && $input.prop('checked');
},
disabled: function() {
return $input.prop('disabled') !== undefined && $input.prop('disabled');
},
enabled: function() {
return !module.is.disabled();
},
determinate: function() {
return !module.is.indeterminate();
},
unchecked: function() {
return !module.is.checked();
}
},
should: {
allowCheck: function() {
if(module.is.determinate() && module.is.checked() && !module.is.initialLoad() ) {
module.debug('Should not allow check, checkbox is already checked');
return false;
}
if(!module.should.ignoreCallbacks() && settings.beforeChecked.apply(input) === false) {
module.debug('Should not allow check, beforeChecked cancelled');
return false;
}
return true;
},
allowUncheck: function() {
if(module.is.determinate() && module.is.unchecked() && !module.is.initialLoad() ) {
module.debug('Should not allow uncheck, checkbox is already unchecked');
return false;
}
if(!module.should.ignoreCallbacks() && settings.beforeUnchecked.apply(input) === false) {
module.debug('Should not allow uncheck, beforeUnchecked cancelled');
return false;
}
return true;
},
allowIndeterminate: function() {
if(module.is.indeterminate() && !module.is.initialLoad() ) {
module.debug('Should not allow indeterminate, checkbox is already indeterminate');
return false;
}
if(!module.should.ignoreCallbacks() && settings.beforeIndeterminate.apply(input) === false) {
module.debug('Should not allow indeterminate, beforeIndeterminate cancelled');
return false;
}
return true;
},
allowDeterminate: function() {
if(module.is.determinate() && !module.is.initialLoad() ) {
module.debug('Should not allow determinate, checkbox is already determinate');
return false;
}
if(!module.should.ignoreCallbacks() && settings.beforeDeterminate.apply(input) === false) {
module.debug('Should not allow determinate, beforeDeterminate cancelled');
return false;
}
return true;
},
ignoreCallbacks: function() {
return (initialLoad && !settings.fireOnInit);
}
},
can: {
change: function() {
return !( $module.hasClass(className.disabled) || $module.hasClass(className.readOnly) || $input.prop('disabled') || $input.prop('readonly') );
},
uncheck: function() {
return (typeof settings.uncheckable === 'boolean')
? settings.uncheckable
: !module.is.radio()
;
}
},
set: {
initialLoad: function() {
initialLoad = true;
},
checked: function() {
module.verbose('Setting class to checked');
$module
.removeClass(className.indeterminate)
.addClass(className.checked)
;
if( module.is.radio() ) {
module.uncheckOthers();
}
if(!module.is.indeterminate() && module.is.checked()) {
module.debug('Input is already checked, skipping input property change');
return;
}
module.verbose('Setting state to checked', input);
$input
.prop('indeterminate', false)
.prop('checked', true)
;
},
unchecked: function() {
module.verbose('Removing checked class');
$module
.removeClass(className.indeterminate)
.removeClass(className.checked)
;
if(!module.is.indeterminate() && module.is.unchecked() ) {
module.debug('Input is already unchecked');
return;
}
module.debug('Setting state to unchecked');
$input
.prop('indeterminate', false)
.prop('checked', false)
;
},
indeterminate: function() {
module.verbose('Setting class to indeterminate');
$module
.addClass(className.indeterminate)
;
if( module.is.indeterminate() ) {
module.debug('Input is already indeterminate, skipping input property change');
return;
}
module.debug('Setting state to indeterminate');
$input
.prop('indeterminate', true)
;
},
determinate: function() {
module.verbose('Removing indeterminate class');
$module
.removeClass(className.indeterminate)
;
if( module.is.determinate() ) {
module.debug('Input is already determinate, skipping input property change');
return;
}
module.debug('Setting state to determinate');
$input
.prop('indeterminate', false)
;
},
disabled: function() {
module.verbose('Setting class to disabled');
$module
.addClass(className.disabled)
;
if( module.is.disabled() ) {
module.debug('Input is already disabled, skipping input property change');
return;
}
module.debug('Setting state to disabled');
$input
.prop('disabled', 'disabled')
;
},
enabled: function() {
module.verbose('Removing disabled class');
$module.removeClass(className.disabled);
if( module.is.enabled() ) {
module.debug('Input is already enabled, skipping input property change');
return;
}
module.debug('Setting state to enabled');
$input
.prop('disabled', false)
;
},
tabbable: function() {
module.verbose('Adding tabindex to checkbox');
if( $input.attr('tabindex') === undefined) {
$input.attr('tabindex', 0);
}
}
},
remove: {
initialLoad: function() {
initialLoad = false;
}
},
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);
}
}
},
create: {
label: function() {
if($input.prevAll(selector.label).length > 0) {
$input.prev(selector.label).detach().insertAfter($input);
module.debug('Moving existing label', $label);
}
else if( !module.has.label() ) {
$label = $('<label>').insertAfter($input);
module.debug('Creating label', $label);
}
}
},
has: {
label: function() {
return ($label.length > 0);
}
},
bind: {
events: function() {
module.verbose('Attaching checkbox events');
$module
.on('click' + eventNamespace, module.event.click)
.on('change' + eventNamespace, module.event.change)
.on('keydown' + eventNamespace, selector.input, module.event.keydown)
.on('keyup' + eventNamespace, selector.input, module.event.keyup)
;
}
},
unbind: {
events: function() {
module.debug('Removing events');
$module
.off(eventNamespace)
;
}
},
uncheckOthers: function() {
var
$radios = module.get.otherRadios()
;
module.debug('Unchecking other radios', $radios);
$radios.removeClass(className.checked);
},
toggle: function() {
if( !module.can.change() ) {
if(!module.is.radio()) {
module.debug('Checkbox is read-only or disabled, ignoring toggle');
}
return;
}
if( module.is.indeterminate() || module.is.unchecked() ) {
module.debug('Currently unchecked');
module.check();
}
else if( module.is.checked() && module.can.uncheck() ) {
module.debug('Currently checked');
module.uncheck();
}
},
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
;
};
$.fn.checkbox.settings = {
name : 'Checkbox',
namespace : 'checkbox',
silent : false,
debug : false,
verbose : true,
performance : true,
// delegated event context
uncheckable : 'auto',
fireOnInit : false,
enableEnterKey : true,
onChange : function(){},
beforeChecked : function(){},
beforeUnchecked : function(){},
beforeDeterminate : function(){},
beforeIndeterminate : function(){},
onChecked : function(){},
onUnchecked : function(){},
onDeterminate : function() {},
onIndeterminate : function() {},
onEnable : function(){},
onDisable : function(){},
// preserve misspelled callbacks (will be removed in 3.0)
onEnabled : function(){},
onDisabled : function(){},
className : {
checked : 'checked',
indeterminate : 'indeterminate',
disabled : 'disabled',
hidden : 'hidden',
radio : 'radio',
readOnly : 'read-only'
},
error : {
method : 'The method you called is not defined'
},
selector : {
checkbox : '.ui.checkbox',
label : 'label, .box',
input : 'input[type="checkbox"], input[type="radio"]',
link : 'a[href]'
}
};
})( jQuery, window, document );
/*!

View file

@ -23,12 +23,9 @@
"components": [
"api",
"button",
"checkbox",
"dimmer",
"dropdown",
"form",
"input",
"list",
"menu",
"modal",
"search",

View file

@ -350,10 +350,10 @@ export default sfc; // activate the IDE's Vue plugin
<span class="ui grey label tw-ml-2">{{ reposTotalCount }}</span>
</div>
</h4>
<div class="ui top attached segment repos-search gt-rounded-top">
<div class="ui fluid action left icon input" :class="{loading: isLoading}">
<div 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"><svg-icon name="octicon-search" :size="16"/></i>
<i class="icon loading-icon-3px" :class="{'is-loading': isLoading}"><svg-icon name="octicon-search" :size="16"/></i>
<div class="ui dropdown icon button" :title="textFilter">
<svg-icon name="octicon-filter" :size="16"/>
<div class="menu">

View file

@ -218,17 +218,24 @@ export function initAdminCommon() {
});
// Select actions
const $checkboxes = $('.select.table .ui.checkbox');
const checkboxes = document.querySelectorAll('.select.table .ui.checkbox input');
$('.select.action').on('click', function () {
switch ($(this).data('action')) {
case 'select-all':
$checkboxes.checkbox('check');
for (const checkbox of checkboxes) {
checkbox.checked = true;
}
break;
case 'deselect-all':
$checkboxes.checkbox('uncheck');
for (const checkbox of checkboxes) {
checkbox.checked = false;
}
break;
case 'inverse':
$checkboxes.checkbox('toggle');
for (const checkbox of checkboxes) {
checkbox.checked = !checkbox.checked;
}
break;
}
});
@ -236,11 +243,11 @@ export function initAdminCommon() {
e.preventDefault();
this.classList.add('is-loading', 'disabled');
const data = new FormData();
$checkboxes.each(function () {
if ($(this).checkbox('is checked')) {
data.append('ids[]', this.getAttribute('data-id'));
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');
});

View file

@ -1,12 +1,66 @@
import $ from 'jquery';
import {createTippy} from '../modules/tippy.js';
export async function createColorPicker(els) {
export async function initColorPickers() {
const els = document.getElementsByClassName('js-color-picker-input');
if (!els.length) return;
await Promise.all([
import(/* webpackChunkName: "minicolors" */'@claviska/jquery-minicolors'),
import(/* webpackChunkName: "minicolors" */'@claviska/jquery-minicolors/jquery.minicolors.css'),
import(/* webpackChunkName: "colorpicker" */'vanilla-colorful/hex-color-picker.js'),
import(/* webpackChunkName: "colorpicker" */'../../css/features/colorpicker.css'),
]);
return $(els).minicolors();
for (const el of els) {
initPicker(el);
}
}
function updateSquare(el, newValue) {
el.style.color = /#[0-9a-f]{6}/i.test(newValue) ? newValue : 'transparent';
}
function updatePicker(el, newValue) {
el.setAttribute('color', newValue);
}
function initPicker(el) {
const input = el.querySelector('input');
const square = document.createElement('div');
square.classList.add('preview-square');
updateSquare(square, input.value);
el.append(square);
const picker = document.createElement('hex-color-picker');
picker.addEventListener('color-changed', (e) => {
input.value = e.detail.value;
input.focus();
updateSquare(square, e.detail.value);
});
input.addEventListener('input', (e) => {
updateSquare(square, e.target.value);
updatePicker(picker, e.target.value);
});
createTippy(input, {
trigger: 'focus click',
theme: 'bare',
hideOnClick: true,
content: picker,
placement: 'bottom-start',
interactive: true,
onShow() {
updatePicker(picker, input.value);
},
});
// init precolors
for (const colorEl of el.querySelectorAll('.precolors .color')) {
colorEl.addEventListener('click', (e) => {
const newValue = e.target.getAttribute('data-color-hex');
input.value = newValue;
input.dispatchEvent(new Event('input', {bubbles: true}));
updateSquare(square, newValue);
});
}
}

View file

@ -2,7 +2,6 @@ import $ from 'jquery';
import '../vendor/jquery.are-you-sure.js';
import {clippie} from 'clippie';
import {createDropzone} from './dropzone.js';
import {initCompColorPicker} from './comp/ColorPicker.js';
import {showGlobalErrorMessage} from '../bootstrap.js';
import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js';
import {svg} from '../svg.js';
@ -110,7 +109,7 @@ async function fetchActionDoRequest(actionElem, url, opt) {
showErrorToast(`${i18n.network_error} ${e}`);
}
}
actionElem.classList.remove('is-loading', 'small-loading-icon');
actionElem.classList.remove('is-loading', 'loading-icon-2px');
}
async function formFetchAction(e) {
@ -122,7 +121,7 @@ async function formFetchAction(e) {
formEl.classList.add('is-loading');
if (formEl.clientHeight < 50) {
formEl.classList.add('small-loading-icon');
formEl.classList.add('loading-icon-2px');
}
const formMethod = formEl.getAttribute('method') || 'get';
@ -196,8 +195,6 @@ export function initGlobalCommon() {
$uiDropdowns.filter('.upward').dropdown('setting', 'direction', 'upward');
$uiDropdowns.filter('.downward').dropdown('setting', 'direction', 'downward');
$('.ui.checkbox').checkbox();
$('.tabular.menu .item').tab();
initSubmitEventPolyfill();
@ -379,10 +376,7 @@ function initGlobalShowModal() {
$attrTarget.text(attrib.value); // FIXME: it should be more strict here, only handle div/span/p
}
}
const $colorPickers = $modal.find('.color-picker');
if ($colorPickers.length > 0) {
initCompColorPicker(); // FIXME: this might cause duplicate init
}
$modal.modal('setting', {
onApprove: () => {
// "form-fetch-action" can handle network errors gracefully,

View file

@ -1,16 +0,0 @@
import $ from 'jquery';
import {createColorPicker} from '../colorpicker.js';
export function initCompColorPicker() {
(async () => {
await createColorPicker(document.querySelectorAll('.color-picker'));
for (const el of document.querySelectorAll('.precolors .color')) {
el.addEventListener('click', (e) => {
const color = e.target.getAttribute('data-color-hex');
const parent = e.target.closest('.color.picker');
$(parent.querySelector('.color-picker')).minicolors('value', color);
});
}
})();
}

View file

@ -1,5 +1,4 @@
import $ from 'jquery';
import {initCompColorPicker} from './ColorPicker.js';
function isExclusiveScopeName(name) {
return /.*[^/]\/[^/].*/.test(name);
@ -28,13 +27,17 @@ function updateExclusiveLabelEdit(form) {
export function initCompLabelEdit(selector) {
if (!$(selector).length) return;
initCompColorPicker();
// Create label
$('.new-label.button').on('click', () => {
updateExclusiveLabelEdit('.new-label');
$('.new-label.modal').modal({
onApprove() {
const form = document.querySelector('.new-label.form');
if (!form.checkValidity()) {
form.reportValidity();
return false;
}
$('.new-label.form').trigger('submit');
},
}).modal('show');
@ -60,10 +63,18 @@ export function initCompLabelEdit(selector) {
updateExclusiveLabelEdit('.edit-label');
$('.edit-label .label-desc-input').val(this.getAttribute('data-description'));
$('.edit-label .color-picker').minicolors('value', this.getAttribute('data-color'));
const colorInput = document.querySelector('.edit-label .js-color-picker-input input');
colorInput.value = this.getAttribute('data-color');
colorInput.dispatchEvent(new Event('input', {bubbles: true}));
$('.edit-label.modal').modal({
onApprove() {
const form = document.querySelector('.edit-label.form');
if (!form.checkValidity()) {
form.reportValidity();
return false;
}
$('.edit-label.form').trigger('submit');
},
}).modal('show');

View file

@ -19,7 +19,7 @@ export function initCopyContent() {
// the text to copy is not in the DOM or it is an image which should be
// fetched to copy in full resolution
if (link) {
btn.classList.add('is-loading', 'small-loading-icon');
btn.classList.add('is-loading', 'loading-icon-2px');
try {
const res = await GET(link, {credentials: 'include', redirect: 'follow'});
const contentType = res.headers.get('content-type');
@ -33,7 +33,7 @@ export function initCopyContent() {
} catch {
return showTemporaryTooltip(btn, i18n.copy_error);
} finally {
btn.classList.remove('is-loading', 'small-loading-icon');
btn.classList.remove('is-loading', 'loading-icon-2px');
}
} else { // text, read from DOM
const lineEls = document.querySelectorAll('.file-view .lines-code');

View file

@ -110,15 +110,15 @@ export function initImageDiff() {
const $imagesAfter = imageInfos[0].$images;
const $imagesBefore = imageInfos[1].$images;
initSideBySide(createContext($imagesAfter[0], $imagesBefore[0]));
initSideBySide(this, createContext($imagesAfter[0], $imagesBefore[0]));
if ($imagesAfter.length > 0 && $imagesBefore.length > 0) {
initSwipe(createContext($imagesAfter[1], $imagesBefore[1]));
initOverlay(createContext($imagesAfter[2], $imagesBefore[2]));
}
$container.find('> .image-diff-tabs').removeClass('is-loading');
this.querySelector(':scope > .image-diff-tabs')?.classList.remove('is-loading');
function initSideBySide(sizes) {
function initSideBySide(container, sizes) {
let factor = 1;
if (sizes.max.width > (diffContainerWidth - 24) / 2) {
factor = (diffContainerWidth - 24) / 2 / sizes.max.width;
@ -126,13 +126,24 @@ export function initImageDiff() {
const widthChanged = sizes.$image1.length !== 0 && sizes.$image2.length !== 0 && sizes.$image1[0].naturalWidth !== sizes.$image2[0].naturalWidth;
const heightChanged = sizes.$image1.length !== 0 && sizes.$image2.length !== 0 && sizes.$image1[0].naturalHeight !== sizes.$image2[0].naturalHeight;
if (sizes.$image1.length !== 0) {
$container.find('.bounds-info-after .bounds-info-width').text(`${sizes.$image1[0].naturalWidth}px`).addClass(widthChanged ? 'green' : '');
$container.find('.bounds-info-after .bounds-info-height').text(`${sizes.$image1[0].naturalHeight}px`).addClass(heightChanged ? 'green' : '');
if (sizes.$image1?.length) {
const boundsInfoAfterWidth = container.querySelector('.bounds-info-after .bounds-info-width');
boundsInfoAfterWidth.textContent = `${sizes.$image1[0].naturalWidth}px`;
if (widthChanged) boundsInfoAfterWidth.classList.add('green');
const boundsInfoAfterHeight = container.querySelector('.bounds-info-after .bounds-info-height');
boundsInfoAfterHeight.textContent = `${sizes.$image1[0].naturalHeight}px`;
if (heightChanged) boundsInfoAfterHeight.classList.add('green');
}
if (sizes.$image2.length !== 0) {
$container.find('.bounds-info-before .bounds-info-width').text(`${sizes.$image2[0].naturalWidth}px`).addClass(widthChanged ? 'red' : '');
$container.find('.bounds-info-before .bounds-info-height').text(`${sizes.$image2[0].naturalHeight}px`).addClass(heightChanged ? 'red' : '');
if (sizes.$image2?.length) {
const boundsInfoBeforeWidth = container.querySelector('.bounds-info-before .bounds-info-width');
boundsInfoBeforeWidth.textContent = `${sizes.$image2[0].naturalWidth}px`;
if (widthChanged) boundsInfoBeforeWidth.classList.add('red');
const boundsInfoBeforeHeight = container.querySelector('.bounds-info-before .bounds-info-height');
boundsInfoBeforeHeight.textContent = `${sizes.$image2[0].naturalHeight}px`;
if (heightChanged) boundsInfoBeforeHeight.classList.add('red');
}
const image1 = sizes.$image1[0];

View file

@ -1,5 +1,6 @@
import $ from 'jquery';
import {GET} from '../modules/fetch.js';
import {toggleElem} from '../utils/dom.js';
const {appSubUrl, notificationSettings, assetVersionEncoded} = window.config;
let notificationSequenceNumber = 0;
@ -177,14 +178,11 @@ async function updateNotificationCount() {
const data = await response.json();
const $notificationCount = $('.notification_count');
if (data.new === 0) {
$notificationCount.addClass('tw-hidden');
} else {
$notificationCount.removeClass('tw-hidden');
}
toggleElem('.notification_count', data.new !== 0);
$notificationCount.text(`${data.new}`);
for (const el of document.getElementsByClassName('notification_count')) {
el.textContent = `${data.new}`;
}
return `${data.new}`;
} catch (error) {

View file

@ -25,7 +25,9 @@ function getLineEls() {
}
function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
$linesEls.closest('tr').removeClass('active');
for (const el of $linesEls) {
el.closest('tr').classList.remove('active');
}
// add hashchange to permalink
const refInNewIssue = document.querySelector('a.ref-in-new-issue');
@ -72,7 +74,7 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
classes.push(`[rel=L${i}]`);
}
$linesEls.filter(classes.join(',')).each(function () {
$(this).closest('tr').addClass('active');
this.closest('tr').classList.add('active');
});
changeHash(`#L${a}-L${b}`);
@ -82,7 +84,7 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
return;
}
}
$selectionEndEl.closest('tr').addClass('active');
$selectionEndEl[0].closest('tr').classList.add('active');
changeHash(`#${$selectionEndEl[0].getAttribute('rel')}`);
updateIssueHref($selectionEndEl[0].getAttribute('rel'));

View file

@ -7,7 +7,7 @@ import {validateTextareaNonEmpty} from './comp/ComboMarkdownEditor.js';
import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.js';
import {initImageDiff} from './imagediff.js';
import {showErrorToast} from '../modules/toast.js';
import {submitEventSubmitter} from '../utils/dom.js';
import {submitEventSubmitter, queryElemSiblings, hideElem, showElem} from '../utils/dom.js';
import {POST, GET} from '../modules/fetch.js';
const {pageData, i18n} = window.config;
@ -16,7 +16,6 @@ function initRepoDiffReviewButton() {
const reviewBox = document.getElementById('review-box');
if (!reviewBox) return;
const $reviewBox = $(reviewBox);
const counter = reviewBox.querySelector('.review-comments-counter');
if (!counter) return;
@ -27,23 +26,27 @@ function initRepoDiffReviewButton() {
const num = parseInt(counter.getAttribute('data-pending-comment-number')) + 1 || 1;
counter.setAttribute('data-pending-comment-number', num);
counter.textContent = num;
// Force the browser to reflow the DOM. This is to ensure that the browser replay the animation
$reviewBox.removeClass('pulse');
$reviewBox.width();
$reviewBox.addClass('pulse');
reviewBox.classList.remove('pulse');
requestAnimationFrame(() => {
reviewBox.classList.add('pulse');
});
});
});
}
function initRepoDiffFileViewToggle() {
$('.file-view-toggle').on('click', function () {
const $this = $(this);
$this.parent().children().removeClass('active');
$this.addClass('active');
for (const el of queryElemSiblings(this)) {
el.classList.remove('active');
}
this.classList.add('active');
const $target = $($this.data('toggle-selector'));
$target.parent().children().addClass('tw-hidden');
$target.removeClass('tw-hidden');
const target = document.querySelector(this.getAttribute('data-toggle-selector'));
if (!target) return;
hideElem(queryElemSiblings(target));
showElem(target);
});
}
@ -57,9 +60,9 @@ function initRepoDiffConversationForm() {
return;
}
if ($form.hasClass('is-loading')) return;
if (e.target.classList.contains('is-loading')) return;
try {
$form.addClass('is-loading');
e.target.classList.add('is-loading');
const formData = new FormData($form[0]);
// If the form is submitted by a button, append the button's name and value to the form data.
@ -76,10 +79,14 @@ function initRepoDiffConversationForm() {
const {path, side, idx} = $newConversationHolder.data();
$form.closest('.conversation-holder').replaceWith($newConversationHolder);
let selector;
if ($form.closest('tr').data('line-type') === 'same') {
$(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).addClass('tw-invisible');
selector = `[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`;
} else {
$(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).addClass('tw-invisible');
selector = `[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`;
}
for (const el of document.querySelectorAll(selector)) {
el.classList.add('tw-invisible');
}
$newConversationHolder.find('.dropdown').dropdown();
initCompReactionSelector($newConversationHolder);
@ -87,7 +94,7 @@ function initRepoDiffConversationForm() {
console.error('error when submitting conversation', e);
showErrorToast(i18n.network_error);
} finally {
$form.removeClass('is-loading');
e.target.classList.remove('is-loading');
}
});
@ -147,13 +154,13 @@ function onShowMoreFiles() {
}
export async function loadMoreFiles(url) {
const $target = $('a#diff-show-more-files');
if ($target.hasClass('disabled') || pageData.diffFileInfo.isLoadingNewData) {
const target = document.querySelector('a#diff-show-more-files');
if (target?.classList.contains('disabled') || pageData.diffFileInfo.isLoadingNewData) {
return;
}
pageData.diffFileInfo.isLoadingNewData = true;
$target.addClass('disabled');
target?.classList.add('disabled');
try {
const response = await GET(url);
@ -170,7 +177,7 @@ export async function loadMoreFiles(url) {
console.error('Error:', error);
showErrorToast('An error occurred while loading more files.');
} finally {
$target.removeClass('disabled');
target?.classList.remove('disabled');
pageData.diffFileInfo.isLoadingNewData = false;
}
}
@ -187,11 +194,11 @@ function initRepoDiffShowMore() {
e.preventDefault();
const $target = $(e.target);
if ($target.hasClass('disabled')) {
if (e.target.classList.contains('disabled')) {
return;
}
$target.addClass('disabled');
e.target.classList.add('disabled');
const url = $target.data('href');
@ -207,7 +214,7 @@ function initRepoDiffShowMore() {
} catch (error) {
console.error('Error:', error);
} finally {
$target.removeClass('disabled');
e.target.classList.remove('disabled');
}
});
}

View file

@ -147,8 +147,8 @@ export function initRepoEditor() {
silent: true,
dirtyClass: dirtyFileClass,
fieldSelector: ':input:not(.commit-form-wrapper :input)',
change() {
const dirty = $(this).hasClass(dirtyFileClass);
change($form) {
const dirty = $form[0]?.classList.contains(dirtyFileClass);
commitButton.disabled = !dirty;
},
});

View file

@ -1,55 +1,53 @@
import $ from 'jquery';
import {stripTags} from '../utils.js';
import {hideElem, showElem} from '../utils/dom.js';
import {hideElem, queryElemChildren, showElem} from '../utils/dom.js';
import {POST} from '../modules/fetch.js';
import {showErrorToast} from '../modules/toast.js';
const {appSubUrl} = window.config;
export function initRepoTopicBar() {
const mgrBtn = document.getElementById('manage_topic');
if (!mgrBtn) return;
const editDiv = document.getElementById('topic_edit');
const viewDiv = document.getElementById('repo-topics');
const saveBtn = document.getElementById('save_topic');
const topicDropdown = editDiv.querySelector('.dropdown');
const $topicDropdown = $(topicDropdown);
const $topicForm = $(editDiv);
const $topicDropdownSearch = $topicDropdown.find('input.search');
const topicPrompts = {
countPrompt: topicDropdown.getAttribute('data-text-count-prompt') ?? undefined,
formatPrompt: topicDropdown.getAttribute('data-text-format-prompt') ?? undefined,
};
const topicDropdown = editDiv.querySelector('.ui.dropdown');
let lastErrorToast;
mgrBtn.addEventListener('click', () => {
hideElem(viewDiv);
showElem(editDiv);
$topicDropdownSearch.trigger('focus');
topicDropdown.querySelector('input.search').focus();
});
$('#cancel_topic_edit').on('click', () => {
document.querySelector('#cancel_topic_edit').addEventListener('click', () => {
lastErrorToast?.hideToast();
hideElem(editDiv);
showElem(viewDiv);
mgrBtn.focus();
});
saveBtn.addEventListener('click', async () => {
const topics = $('input[name=topics]').val();
document.getElementById('save_topic').addEventListener('click', async (e) => {
lastErrorToast?.hideToast();
const topics = editDiv.querySelector('input[name=topics]').value;
const data = new FormData();
data.append('topics', topics);
const response = await POST(saveBtn.getAttribute('data-link'), {data});
const response = await POST(e.target.getAttribute('data-link'), {data});
if (response.ok) {
const responseData = await response.json();
if (responseData.status === 'ok') {
$(viewDiv).children('.topic').remove();
queryElemChildren(viewDiv, '.repo-topic', (el) => el.remove());
if (topics.length) {
const topicArray = topics.split(',');
topicArray.sort();
for (const topic of topicArray) {
// it should match the code in repo/home.tmpl
const link = document.createElement('a');
link.classList.add('ui', 'repo-topic', 'large', 'label', 'topic', 'tw-m-0');
link.classList.add('repo-topic', 'ui', 'large', 'label');
link.href = `${appSubUrl}/explore/repos?q=${encodeURIComponent(topic)}&topic=1`;
link.textContent = topic;
mgrBtn.parentNode.insertBefore(link, mgrBtn); // insert all new topics before manage button
@ -59,27 +57,23 @@ export function initRepoTopicBar() {
showElem(viewDiv);
}
} else if (response.status === 422) {
// how to test: input topic like " invalid topic " (with spaces), and select it from the list, then "Save"
const responseData = await response.json();
lastErrorToast = showErrorToast(responseData.message, {duration: 5000});
if (responseData.invalidTopics.length > 0) {
topicPrompts.formatPrompt = responseData.message;
const {invalidTopics} = responseData;
const $topicLabels = $topicDropdown.children('a.ui.label');
const topicLabels = queryElemChildren(topicDropdown, 'a.ui.label');
for (const [index, value] of topics.split(',').entries()) {
if (invalidTopics.includes(value)) {
$topicLabels.eq(index).removeClass('green').addClass('red');
topicLabels[index].classList.remove('green');
topicLabels[index].classList.add('red');
}
}
} else {
topicPrompts.countPrompt = responseData.message;
}
}
// Always validate the form
$topicForm.form('validate form');
});
$topicDropdown.dropdown({
$(topicDropdown).dropdown({
allowAdditions: true,
forceSelection: false,
fullTextSearch: 'exact',
@ -102,9 +96,9 @@ export function initRepoTopicBar() {
const query = stripTags(this.urlData.query.trim());
let found_query = false;
const current_topics = [];
$topicDropdown.find('a.label.visible').each((_, el) => {
for (const el of queryElemChildren(topicDropdown, 'a.ui.label.visible')) {
current_topics.push(el.getAttribute('data-value'));
});
}
if (res.topics) {
let found = false;
@ -146,38 +140,8 @@ export function initRepoTopicBar() {
},
onAdd(addedValue, _addedText, $addedChoice) {
addedValue = addedValue.toLowerCase().trim();
$($addedChoice)[0].setAttribute('data-value', addedValue);
$($addedChoice)[0].setAttribute('data-text', addedValue);
},
});
$.fn.form.settings.rules.validateTopic = function (_values, regExp) {
const $topics = $topicDropdown.children('a.ui.label');
const status = !$topics.length || $topics.last()[0].getAttribute('data-value').match(regExp);
if (!status) {
$topics.last().removeClass('green').addClass('red');
}
return status && !$topicDropdown.children('a.ui.label.red').length;
};
$topicForm.form({
on: 'change',
inline: true,
fields: {
topics: {
identifier: 'topics',
rules: [
{
type: 'validateTopic',
value: /^\s*[a-z0-9][-.a-z0-9]{0,35}\s*$/,
prompt: topicPrompts.formatPrompt,
},
{
type: 'maxCount[25]',
prompt: topicPrompts.countPrompt,
},
],
},
$addedChoice[0].setAttribute('data-value', addedValue);
$addedChoice[0].setAttribute('data-text', addedValue);
},
});
}

View file

@ -2,6 +2,7 @@ import $ from 'jquery';
import {svg} from '../svg.js';
import {showErrorToast} from '../modules/toast.js';
import {GET, POST} from '../modules/fetch.js';
import {showElem} from '../utils/dom.js';
const {appSubUrl} = window.config;
let i18nTextEdited;
@ -73,10 +74,12 @@ function showContentHistoryDetail(issueBaseUrl, commentId, historyId, itemTitleH
const response = await GET(url);
const resp = await response.json();
$dialog.find('.comment-diff-data').removeClass('is-loading').html(resp.diffHtml);
const commentDiffData = $dialog.find('.comment-diff-data')[0];
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) {
$dialog.find('.dialog-header-options').removeClass('tw-hidden');
showElem($dialog.find('.dialog-header-options'));
}
} catch (error) {
console.error('Error:', error);

View file

@ -6,6 +6,7 @@ import {confirmModal} from './comp/ConfirmModal.js';
import {showErrorToast} from '../modules/toast.js';
import {createSortable} from '../modules/sortable.js';
import {DELETE, POST} from '../modules/fetch.js';
import {parseDom} from '../utils.js';
function initRepoIssueListCheckboxes() {
const issueSelectAll = document.querySelector('.issue-checkbox-all');
@ -129,22 +130,29 @@ function initRepoIssueListAuthorDropdown() {
const dropdownTemplates = $searchDropdown.dropdown('setting', 'templates');
$searchDropdown.dropdown('internal', 'setup', dropdownSetup);
dropdownSetup.menu = function (values) {
const $menu = $searchDropdown.find('> .menu');
$menu.find('> .dynamic-item').remove(); // remove old dynamic items
const menu = $searchDropdown.find('> .menu')[0];
// remove old dynamic items
for (const el of menu.querySelectorAll(':scope > .dynamic-item')) {
el.remove();
}
const newMenuHtml = dropdownTemplates.menu(values, $searchDropdown.dropdown('setting', 'fields'), true /* html */, $searchDropdown.dropdown('setting', 'className'));
if (newMenuHtml) {
const $newMenuItems = $(newMenuHtml);
$newMenuItems.addClass('dynamic-item');
const newMenuItems = parseDom(newMenuHtml, 'text/html').querySelectorAll('body > div');
for (const newMenuItem of newMenuItems) {
newMenuItem.classList.add('dynamic-item');
}
const div = document.createElement('div');
div.classList.add('divider', 'dynamic-item');
$menu[0].append(div, ...$newMenuItems);
menu.append(div, ...newMenuItems);
}
$searchDropdown.dropdown('refresh');
// defer our selection to the next tick, because dropdown will set the selection item after this `menu` function
setTimeout(() => {
$menu.find('.item.active, .item.selected').removeClass('active selected');
$menu.find(`.item[data-value="${selectedUserId}"]`).addClass('selected');
for (const el of menu.querySelectorAll('.item.active, .item.selected')) {
el.classList.remove('active', 'selected');
}
menu.querySelector(`.item[data-value="${selectedUserId}"]`)?.classList.add('selected');
}, 0);
};
}

View file

@ -158,17 +158,22 @@ export function initRepoIssueSidebarList() {
export function initRepoIssueCommentDelete() {
// Delete comment
$(document).on('click', '.delete-comment', async function () {
const $this = $(this);
if (window.confirm($this.data('locale'))) {
document.addEventListener('click', async (e) => {
if (!e.target.matches('.delete-comment')) return;
e.preventDefault();
const deleteButton = e.target;
if (window.confirm(deleteButton.getAttribute('data-locale'))) {
try {
const response = await POST($this.data('url'));
const response = await POST(deleteButton.getAttribute('data-url'));
if (!response.ok) throw new Error('Failed to delete comment');
const $conversationHolder = $this.closest('.conversation-holder');
const $parentTimelineItem = $this.closest('.timeline-item');
const $parentTimelineGroup = $this.closest('.timeline-item-group');
const conversationHolder = deleteButton.closest('.conversation-holder');
const parentTimelineItem = deleteButton.closest('.timeline-item');
const parentTimelineGroup = deleteButton.closest('.timeline-item-group');
// Check if this was a pending comment.
if ($conversationHolder.find('.pending-label').length) {
if (conversationHolder?.querySelector('.pending-label')) {
const counter = document.querySelector('#review-box .review-comments-counter');
let num = parseInt(counter?.getAttribute('data-pending-comment-number')) - 1 || 0;
num = Math.max(num, 0);
@ -176,29 +181,32 @@ export function initRepoIssueCommentDelete() {
counter.textContent = String(num);
}
$(`#${$this.data('comment-id')}`).remove();
if ($conversationHolder.length && !$conversationHolder.find('.comment').length) {
const path = $conversationHolder.data('path');
const side = $conversationHolder.data('side');
const idx = $conversationHolder.data('idx');
const lineType = $conversationHolder.closest('tr').data('line-type');
document.getElementById(deleteButton.getAttribute('data-comment-id'))?.remove();
if (conversationHolder && !conversationHolder.querySelector('.comment')) {
const path = conversationHolder.getAttribute('data-path');
const side = conversationHolder.getAttribute('data-side');
const idx = conversationHolder.getAttribute('data-idx');
const lineType = conversationHolder.closest('tr').getAttribute('data-line-type');
if (lineType === 'same') {
$(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).removeClass('tw-invisible');
document.querySelector(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).classList.remove('tw-invisible');
} else {
$(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).removeClass('tw-invisible');
document.querySelector(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).classList.remove('tw-invisible');
}
$conversationHolder.remove();
conversationHolder.remove();
}
// Check if there is no review content, move the time avatar upward to avoid overlapping the content below.
if (!$parentTimelineGroup.find('.timeline-item.comment').length && !$parentTimelineItem.find('.conversation-holder').length) {
const $timelineAvatar = $parentTimelineGroup.find('.timeline-avatar');
$timelineAvatar.removeClass('timeline-avatar-offset');
if (!parentTimelineGroup?.querySelector('.timeline-item.comment') && !parentTimelineItem?.querySelector('.conversation-holder')) {
const timelineAvatar = parentTimelineGroup?.querySelector('.timeline-avatar');
timelineAvatar?.classList.remove('timeline-avatar-offset');
}
} catch (error) {
console.error(error);
}
}
return false;
});
}
@ -222,32 +230,35 @@ export function initRepoIssueDependencyDelete() {
export function initRepoIssueCodeCommentCancel() {
// Cancel inline code comment
$(document).on('click', '.cancel-code-comment', (e) => {
const $form = $(e.currentTarget).closest('form');
if ($form.length > 0 && $form.hasClass('comment-form')) {
$form.addClass('tw-hidden');
showElem($form.closest('.comment-code-cloud').find('button.comment-form-reply'));
document.addEventListener('click', (e) => {
if (!e.target.matches('.cancel-code-comment')) return;
const form = e.target.closest('form');
if (form?.classList.contains('comment-form')) {
hideElem(form);
showElem(form.closest('.comment-code-cloud')?.querySelectorAll('button.comment-form-reply'));
} else {
$form.closest('.comment-code-cloud').remove();
form.closest('.comment-code-cloud')?.remove();
}
});
}
export function initRepoPullRequestUpdate() {
// Pull Request update button
const $pullUpdateButton = $('.update-button > button');
$pullUpdateButton.on('click', async function (e) {
const pullUpdateButton = document.querySelector('.update-button > button');
if (!pullUpdateButton) return;
pullUpdateButton.addEventListener('click', async function (e) {
e.preventDefault();
const $this = $(this);
const redirect = $this.data('redirect');
$this.addClass('is-loading');
const redirect = this.getAttribute('data-redirect');
this.classList.add('is-loading');
let response;
try {
response = await POST($this.data('do'));
response = await POST(this.getAttribute('data-do'));
} catch (error) {
console.error(error);
} finally {
$this.removeClass('is-loading');
this.classList.remove('is-loading');
}
let data;
try {
@ -266,10 +277,13 @@ export function initRepoPullRequestUpdate() {
$('.update-button > .dropdown').dropdown({
onChange(_text, _value, $choice) {
const $url = $choice.data('do');
if ($url) {
$pullUpdateButton.find('.button-text').text($choice.text());
$pullUpdateButton.data('do', $url);
const url = $choice[0].getAttribute('data-do');
if (url) {
const buttonText = pullUpdateButton.querySelector('.button-text');
if (buttonText) {
buttonText.textContent = $choice.text();
}
pullUpdateButton.setAttribute('data-do', url);
}
},
});
@ -282,32 +296,26 @@ export function initRepoPullRequestMergeInstruction() {
}
export function initRepoPullRequestAllowMaintainerEdit() {
const checkbox = document.getElementById('allow-edits-from-maintainers');
if (!checkbox) return;
const wrapper = document.getElementById('allow-edits-from-maintainers');
if (!wrapper) return;
const $checkbox = $(checkbox);
const promptError = checkbox.getAttribute('data-prompt-error');
$checkbox.checkbox({
'onChange': async () => {
const checked = $checkbox.checkbox('is checked');
let url = checkbox.getAttribute('data-url');
url += '/set_allow_maintainer_edit';
$checkbox.checkbox('set disabled');
try {
const response = await POST(url, {
data: {allow_maintainer_edit: checked},
});
if (!response.ok) {
throw new Error('Failed to update maintainer edit permission');
}
} catch (error) {
console.error(error);
showTemporaryTooltip(checkbox, promptError);
} finally {
$checkbox.checkbox('set enabled');
wrapper.querySelector('input[type="checkbox"]')?.addEventListener('change', async (e) => {
const checked = e.target.checked;
const url = `${wrapper.getAttribute('data-url')}/set_allow_maintainer_edit`;
wrapper.classList.add('is-loading');
e.target.disabled = true;
try {
const response = await POST(url, {data: {allow_maintainer_edit: checked}});
if (!response.ok) {
throw new Error('Failed to update maintainer edit permission');
}
},
} catch (error) {
console.error(error);
showTemporaryTooltip(wrapper, wrapper.getAttribute('data-prompt-error'));
} finally {
wrapper.classList.remove('is-loading');
e.target.disabled = false;
}
});
}
@ -373,10 +381,10 @@ export function initRepoIssueComments() {
$('.re-request-review').on('click', async function (e) {
e.preventDefault();
const url = $(this).data('update-url');
const issueId = $(this).data('issue-id');
const id = $(this).data('id');
const isChecked = $(this).hasClass('checked');
const url = this.getAttribute('data-update-url');
const issueId = this.getAttribute('data-issue-id');
const id = this.getAttribute('data-id');
const isChecked = this.classList.contains('checked');
await updateIssuesMeta(url, isChecked ? 'detach' : 'attach', issueId, id);
window.location.reload();
@ -403,7 +411,7 @@ export function initRepoIssueComments() {
export async function handleReply($el) {
hideElem($el);
const $form = $el.closest('.comment-code-cloud').find('.comment-form');
$form.removeClass('tw-hidden');
showElem($form);
const $textarea = $form.find('textarea');
let editor = getComboMarkdownEditor($textarea);
@ -460,20 +468,20 @@ export function initRepoPullRequestReview() {
$(document).on('click', '.show-outdated', function (e) {
e.preventDefault();
const id = $(this).data('comment');
$(this).addClass('tw-hidden');
$(`#code-comments-${id}`).removeClass('tw-hidden');
$(`#code-preview-${id}`).removeClass('tw-hidden');
$(`#hide-outdated-${id}`).removeClass('tw-hidden');
const id = this.getAttribute('data-comment');
hideElem(this);
showElem(`#code-comments-${id}`);
showElem(`#code-preview-${id}`);
showElem(`#hide-outdated-${id}`);
});
$(document).on('click', '.hide-outdated', function (e) {
e.preventDefault();
const id = $(this).data('comment');
$(this).addClass('tw-hidden');
$(`#code-comments-${id}`).addClass('tw-hidden');
$(`#code-preview-${id}`).addClass('tw-hidden');
$(`#show-outdated-${id}`).removeClass('tw-hidden');
const id = this.getAttribute('data-comment');
hideElem(this);
hideElem(`#code-comments-${id}`);
hideElem(`#code-preview-${id}`);
showElem(`#show-outdated-${id}`);
});
$(document).on('click', 'button.comment-form-reply', async function (e) {
@ -510,18 +518,19 @@ export function initRepoPullRequestReview() {
}
$(document).on('click', '.add-code-comment', async function (e) {
if ($(e.target).hasClass('btn-add-single')) return; // https://github.com/go-gitea/gitea/issues/4745
if (e.target.classList.contains('btn-add-single')) return; // https://github.com/go-gitea/gitea/issues/4745
e.preventDefault();
const isSplit = $(this).closest('.code-diff').hasClass('code-diff-split');
const side = $(this).data('side');
const idx = $(this).data('idx');
const path = $(this).closest('[data-path]').data('path');
const $tr = $(this).closest('tr');
const lineType = $tr.data('line-type');
const isSplit = this.closest('.code-diff')?.classList.contains('code-diff-split');
const side = this.getAttribute('data-side');
const idx = this.getAttribute('data-idx');
const path = this.closest('[data-path]')?.getAttribute('data-path');
const tr = this.closest('tr');
const lineType = tr.getAttribute('data-line-type');
let $ntr = $tr.next();
if (!$ntr.hasClass('add-comment')) {
const ntr = tr.nextElementSibling;
let $ntr = $(ntr);
if (!ntr?.classList.contains('add-comment')) {
$ntr = $(`
<tr class="add-comment" data-line-type="${lineType}">
${isSplit ? `
@ -531,7 +540,7 @@ export function initRepoPullRequestReview() {
<td class="add-comment-left add-comment-right" colspan="5"></td>
`}
</tr>`);
$tr.after($ntr);
$(tr).after($ntr);
}
const $td = $ntr.find(`.add-comment-${side}`);
@ -617,13 +626,13 @@ export function initRepoIssueTitleEdit() {
const editTitleToggle = function () {
toggleElem($issueTitle);
toggleElem($('.not-in-edit'));
toggleElem($('#edit-title-input'));
toggleElem($('#pull-desc'));
toggleElem($('#pull-desc-edit'));
toggleElem($('.in-edit'));
toggleElem($('.new-issue-button'));
$('#issue-title-wrapper').toggleClass('edit-active');
toggleElem('.not-in-edit');
toggleElem('#edit-title-input');
toggleElem('#pull-desc');
toggleElem('#pull-desc-edit');
toggleElem('.in-edit');
toggleElem('.new-issue-button');
document.getElementById('issue-title-wrapper')?.classList.toggle('edit-active');
$editInput[0].focus();
$editInput[0].select();
return false;

View file

@ -94,47 +94,46 @@ async function initRepoProjectSortable() {
}
export function initRepoProject() {
if (!$('.repository.projects').length) {
if (!document.querySelector('.repository.projects')) {
return;
}
const _promise = initRepoProjectSortable();
$('.edit-project-column-modal').each(function () {
const $projectHeader = $(this).closest('.project-column-header');
const $projectTitleLabel = $projectHeader.find('.project-column-title');
const $projectTitleInput = $(this).find('.project-column-title-input');
const $projectColorInput = $(this).find('#new_project_column_color');
const $boardColumn = $(this).closest('.project-column');
for (const modal of document.getElementsByClassName('edit-project-column-modal')) {
const projectHeader = modal.closest('.project-column-header');
const projectTitleLabel = projectHeader?.querySelector('.project-column-title');
const projectTitleInput = modal.querySelector('.project-column-title-input');
const projectColorInput = modal.querySelector('#new_project_column_color');
const boardColumn = modal.closest('.project-column');
const bgColor = boardColumn?.style.backgroundColor;
const bgColor = $boardColumn[0].style.backgroundColor;
if (bgColor) {
setLabelColor($projectHeader, rgbToHex(bgColor));
setLabelColor(projectHeader, rgbToHex(bgColor));
}
$(this).find('.edit-project-column-button').on('click', async function (e) {
modal.querySelector('.edit-project-column-button')?.addEventListener('click', async function (e) {
e.preventDefault();
try {
await PUT($(this).data('url'), {
await PUT(this.getAttribute('data-url'), {
data: {
title: $projectTitleInput.val(),
color: $projectColorInput.val(),
title: projectTitleInput?.value,
color: projectColorInput?.value,
},
});
} catch (error) {
console.error(error);
} finally {
$projectTitleLabel.text($projectTitleInput.val());
$projectTitleInput.closest('form').removeClass('dirty');
if ($projectColorInput.val()) {
setLabelColor($projectHeader, $projectColorInput.val());
projectTitleLabel.textContent = projectTitleInput?.value;
projectTitleInput.closest('form')?.classList.remove('dirty');
if (projectColorInput?.value) {
setLabelColor(projectHeader, projectColorInput.value);
}
$boardColumn[0].style = `background: ${$projectColorInput.val()} !important`;
boardColumn.style = `background: ${projectColorInput.value} !important`;
$('.ui.modal').modal('hide');
}
});
});
}
$('.default-project-column-modal').each(function () {
const $boardColumn = $(this).closest('.project-column');
@ -187,9 +186,11 @@ export function initRepoProject() {
function setLabelColor(label, color) {
const {r, g, b} = tinycolor(color).toRgb();
if (useLightTextOnBackground(r, g, b)) {
label.removeClass('dark-label').addClass('light-label');
label.classList.remove('dark-label');
label.classList.add('light-label');
} else {
label.removeClass('light-label').addClass('dark-label');
label.classList.remove('light-label');
label.classList.add('dark-label');
}
}

View file

@ -77,18 +77,24 @@ export function initRepoSettingGitHook() {
}
export function initRepoSettingBranches() {
if (!$('.repository.settings.branches').length) return;
$('.toggle-target-enabled').on('change', function () {
const $target = $(this.getAttribute('data-target'));
$target.toggleClass('disabled', !this.checked);
});
$('.toggle-target-disabled').on('change', function () {
const $target = $(this.getAttribute('data-target'));
if (this.checked) $target.addClass('disabled'); // only disable, do not auto enable
});
$('#dismiss_stale_approvals').on('change', function () {
const $target = $('#ignore_stale_approvals_box');
$target.toggleClass('disabled', this.checked);
if (!document.querySelector('.repository.settings.branches')) return;
for (const el of document.getElementsByClassName('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.getElementsByClassName('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.getElementById('dismiss_stale_approvals')?.addEventListener('change', function () {
document.getElementById('ignore_stale_approvals_box')?.classList.toggle('disabled', this.checked);
});
// show the `Matched` mark for the status checks that match the pattern
@ -106,7 +112,6 @@ export function initRepoSettingBranches() {
break;
}
}
toggleElem(el, matched);
}
};

View file

@ -86,6 +86,7 @@ import {initRepoRecentCommits} from './features/recent-commits.js';
import {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.js';
import {initDirAuto} from './modules/dirauto.js';
import {initRepositorySearch} from './features/repo-search.js';
import {initColorPickers} from './features/colorpicker.js';
// Init Gitea's Fomantic settings
initGiteaFomantic();
@ -188,4 +189,5 @@ onDomReady(() => {
initRepoDiffView();
initPdfViewer();
initScopedAccessTokenCategories();
initColorPickers();
});

View file

@ -11,8 +11,6 @@ export const fomanticMobileScreen = window.matchMedia('only screen and (max-widt
export function initGiteaFomantic() {
// Silence fomantic's error logging when tabs are used without a target content element
$.fn.tab.settings.silent = true;
// Disable the behavior of fomantic to toggle the checkbox when you press enter on a checkbox element.
$.fn.checkbox.settings.enableEnterKey = false;
// By default, use "exact match" for full text search
$.fn.dropdown.settings.fullTextSearch = 'exact';

View file

@ -41,24 +41,19 @@ The ideal checkboxes should be:
<label><input type="checkbox"> ... </label>
```
However, related CSS styles aren't supported (not implemented) yet, so at the moment,
almost all the checkboxes are still using Fomantic UI checkbox.
## Fomantic UI Checkbox
However, the templates still have the Fomantic-style HTML layout:
```html
<div class="ui checkbox">
<input type="checkbox"> <!-- class "hidden" will be added by $.checkbox() -->
<input type="checkbox">
<label>...</label>
</div>
```
Then the JS `$.checkbox()` should be called to make it work with keyboard and label-clicking,
then it works like the ideal checkboxes.
There is still a problem: Fomantic UI checkbox is not friendly to screen readers,
so we add IDs to all the Fomantic UI checkboxes automatically by JS.
If the `label` part is empty, then the checkbox needs to get the `aria-label` attribute manually.
We call `initAriaCheckboxPatch` to link the `input` and `label` which makes clicking the
label etc. work. There is still a problem: These checkboxes are not friendly to screen readers,
so we add IDs to all the Fomantic UI checkboxes automatically by JS. If the `label` part is empty,
then the checkbox needs to get the `aria-label` attribute manually.
# Fomantic Dropdown

View file

@ -1,38 +1,24 @@
import $ from 'jquery';
import {generateAriaId} from './base.js';
const ariaPatchKey = '_giteaAriaPatchCheckbox';
const fomanticCheckboxFn = $.fn.checkbox;
// use our own `$.fn.checkbox` to patch Fomantic's checkbox module
export function initAriaCheckboxPatch() {
if ($.fn.checkbox === ariaCheckboxFn) throw new Error('initAriaCheckboxPatch could only be called once');
$.fn.checkbox = ariaCheckboxFn;
ariaCheckboxFn.settings = fomanticCheckboxFn.settings;
}
// link the label and the input element so it's clickable and accessible
for (const el of document.querySelectorAll('.ui.checkbox')) {
if (el.hasAttribute('data-checkbox-patched')) continue;
const label = el.querySelector('label');
const input = el.querySelector('input');
if (!label || !input) continue;
const inputId = input.getAttribute('id');
const labelFor = label.getAttribute('for');
// the patched `$.fn.checkbox` checkbox function
// * it does the one-time attaching on the first call
function ariaCheckboxFn(...args) {
const ret = fomanticCheckboxFn.apply(this, args);
for (const el of this) {
if (el[ariaPatchKey]) continue;
attachInit(el);
if (inputId && !labelFor) { // missing "for"
label.setAttribute('for', inputId);
} else if (!inputId && !labelFor) { // missing both "id" and "for"
const id = generateAriaId();
input.setAttribute('id', id);
label.setAttribute('for', id);
} else {
continue;
}
el.setAttribute('data-checkbox-patched', 'true');
}
return ret;
}
function attachInit(el) {
// Fomantic UI checkbox needs to be something like: <div class="ui checkbox"><label /><input /></div>
// It doesn't work well with <label><input />...</label>
// To make it work with aria, the "id"/"for" attributes are necessary, so add them automatically if missing.
// In the future, refactor to use native checkbox directly, then this patch could be removed.
el[ariaPatchKey] = {}; // record that this element has been patched
const label = el.querySelector('label');
const input = el.querySelector('input');
if (!label || !input || input.getAttribute('id')) return;
const id = generateAriaId();
input.setAttribute('id', id);
label.setAttribute('for', id);
}

View file

@ -207,7 +207,7 @@ function attachDomEvents(dropdown, focusable, menu) {
if (!$item) $item = $(menu).find('> .item.selected'); // when dropdown filters items by input, there is no "value", so query the "selected" item
// 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 && ($item[0].matches('a') || $item.hasClass('js-aria-clickable'))) $item[0].click();
if ($item?.[0]?.matches('a, .js-aria-clickable')) $item[0].click();
}
});

View file

@ -3,11 +3,12 @@ import {isDocumentFragmentOrElementNode} from '../utils/dom.js';
import {formatDatetime} from '../utils/time.js';
const visibleInstances = new Set();
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>`;
export function createTippy(target, opts = {}) {
// the callback functions should be destructured from opts,
// because we should use our own wrapper functions to handle them, do not let the user override them
const {onHide, onShow, onDestroy, role, theme, ...other} = opts;
const {onHide, onShow, onDestroy, role, theme, arrow, ...other} = opts;
const instance = tippy(target, {
appendTo: document.body,
@ -35,9 +36,9 @@ export function createTippy(target, opts = {}) {
visibleInstances.add(instance);
return onShow?.(instance);
},
arrow: `<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>`,
arrow: arrow || (theme === 'bare' ? false : arrowSvg),
role: role || 'menu', // HTML role attribute
theme: theme || role || 'menu', // CSS theme, either "tooltip", "menu" or "box-with-header"
theme: theme || role || 'menu', // CSS theme, either "tooltip", "menu", "box-with-header" or "bare"
plugins: [followCursor],
...other,
});

View file

@ -39,6 +39,7 @@ function showToast(message, level, {gravity, position, duration, useHtmlBody, ..
toast.showToast();
toast.toastElement.querySelector('.toast-close').addEventListener('click', () => toast.hideToast());
return toast;
}
export function showInfoToast(message, opts) {

View file

@ -51,8 +51,22 @@ export function isElemHidden(el) {
return res[0];
}
export function queryElemSiblings(el, selector) {
return Array.from(el.parentNode.children).filter((child) => child !== el && child.matches(selector));
function applyElemsCallback(elems, fn) {
if (fn) {
for (const el of elems) {
fn(el);
}
}
return elems;
}
export function queryElemSiblings(el, selector = '*', fn) {
return applyElemsCallback(Array.from(el.parentNode.children).filter((child) => child !== el && child.matches(selector)), fn);
}
// it works like jQuery.children: only the direct children are selected
export function queryElemChildren(parent, selector = '*', fn) {
return applyElemsCallback(parent.querySelectorAll(`:scope > ${selector}`), fn);
}
export function onDomReady(cb) {