feat: vendor gitea 1.16.2

This commit is contained in:
2026-06-02 08:36:01 +00:00
parent 247cd60f69
commit 54798b4615
2145 changed files with 162965 additions and 126447 deletions
@@ -6,14 +6,6 @@
overflow-x: auto;
}
.runner-container .runner-new-text {
color: var(--color-white);
}
.runner-container #runner-new:hover .runner-new-text {
color: var(--color-white) !important;
}
.runner-container .task-status-success {
background-color: var(--color-green);
color: var(--color-white);
@@ -49,3 +49,11 @@
gap: 1rem;
margin-bottom: 1rem;
}
.web-banner-content-editor .render-content.render-preview {
/* use the styles from ".ui.message" */
padding: 1em 1.5em;
border: 1px solid var(--color-info-border);
background: var(--color-info-bg);
color: var(--color-text);
}
+115 -386
View File
@@ -12,12 +12,15 @@
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
/* line-height: use the default value as "modules/normalize.css" */
--line-height-default: normal;
--line-height-default: normal; /* line-height: use the default value as "modules/normalize.css" */
--line-height-code: 20px; /* line-height for code, also used by the editor */
/* images */
--checkbox-mask-checked: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 18 18" width="16" height="16"><path fill-rule="evenodd" d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"></path></svg>');
--checkbox-mask-indeterminate: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2 7.75A.75.75 0 012.75 7h10a.75.75 0 010 1.5h-10A.75.75 0 012 7.75z"></path></svg>');
--checkbox-mask-checked: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="9" viewBox="0 0 12 9"><path fill-rule="evenodd" d="M11.78.22a.75.75 0 0 1 0 1.061L4.52 8.541a.75.75 0 0 1-1.062 0L.202 5.285a.75.75 0 0 1 1.061-1.061l2.725 2.723L10.718.22a.75.75 0 0 1 1.062 0"/></svg>');
--checkbox-mask-indeterminate: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="10" height="2" viewBox="0 0 10 2"><path fill-rule="evenodd" d="M0 1a1 1 0 0 1 1-1h8a1 1 0 1 1 0 2H1a1 1 0 0 1-1-1" clip-rule="evenodd"/></svg>');
--octicon-alert-fill: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575ZM8 5a.75.75 0 0 0-.75.75v2.5a.75.75 0 0 0 1.5 0v-2.5A.75.75 0 0 0 8 5Zm1 6a1 1 0 1 0-2 0 1 1 0 0 0 2 0Z"/></svg>');
--octicon-chevron-right: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M6.22 3.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L9.94 8 6.22 4.28a.75.75 0 0 1 0-1.06Z"></path></svg>');
--octicon-x: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.75.75 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.75.75 0 0 1-.734-.215L8 9.06l-3.22 3.22a.75.75 0 0 1-1.042-.018.75.75 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06"/></svg>');
--select-arrows: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="m4.074 9.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.043 9H4.251a.25.25 0 0 0-.177.427m0-1.957L7.47 4.073a.25.25 0 0 1 .354 0L11.22 7.47a.25.25 0 0 1-.177.426H4.251a.25.25 0 0 1-.177-.426"/></svg>');
/* other variables */
--border-radius: 4px;
--border-radius-medium: 6px;
@@ -26,13 +29,14 @@
--height-loading: 16rem;
--min-height-textarea: 132px; /* padding + 6 lines + border = calc(1.57142em + 6lh + 2px), but lh is not fully supported */
--tab-size: 4;
--checkbox-size: 15px; /* height and width of checkbox and radio inputs */
--checkbox-size: 14px; /* height and width of checkbox and radio inputs */
--page-spacing: 16px; /* space between page elements */
--page-margin-x: 32px; /* minimum space on left and right side of page */
--page-space-bottom: 64px; /* space between last page element and footer */
--transition-hover-fade: opacity 0.2s ease; /* fade transition for elements that show on hover */
/* z-index */
--z-index-modal: 1001; /* modal dialog, hard-coded from Fomantic modal.css */
--z-index-modal: 1001; /* modal dialog */
--z-index-toast: 1002; /* should be larger than modal */
--font-size-label: 12px; /* font size of individual labels */
@@ -40,7 +44,8 @@
--gap-inline: 0.25rem; /* gap for inline texts and elements, for example: the spaces for sentence with labels, button text, etc */
--gap-block: 0.5rem; /* gap for element blocks, for example: spaces between buttons, menu image & title, header icon & title etc */
--background-view-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAG0lEQVQYlWN4+vTpf3SMDTAMBYXYBLFpHgoKAeiOf0SGE9kbAAAAAElFTkSuQmCC") right bottom var(--color-primary-light-7);
--background-view-image: repeating-conic-gradient(var(--color-transparency-grid-dark) 0 25%, var(--color-transparency-grid-light) 0 50%) 0 0 / 18px 18px;
--box-shadow-kbd: inset 0 -1px 0 var(--color-secondary);
}
@media (min-width: 768px) and (max-width: 1200px) {
@@ -97,19 +102,18 @@ samp {
pre,
code,
kbd,
samp,
.tw-font-mono {
font-size: 0.95em; /* compensate for monospace fonts being usually slightly larger */
}
/* there are many <code> blocks in non-markup(.markup code) / non-code-diff(code.code-inner) containers (for example: translation strings, etc),
so we need to make <code> have default global styles, ".markup code" has its own styles and doesn't conflict, but `.code-inner` is special.
TODO: in the future, we should use `div` instead of `code` for `.code-inner` because it is a container for highlighted code line */
code:not(.code-inner) {
/* there are many <code> blocks in non-markup(.markup code) / non-code-diff(code.code-inner) containers, for example: translation strings, etc,
so we need to make <code> have default global styles, ".markup code" has its own styles and these styles sometimes conflict.
TODO: in the future, we should use `div` instead of `code` for `.code-inner` because it is a container for highlighted code line, then drop this ":not" patch */
code:where(:not(.code-inner)) {
padding: 1px 4px;
border-radius: var(--border-radius);
background-color: var(--color-label-bg);
background-color: var(--color-markup-code-inline);
}
b,
@@ -236,14 +240,13 @@ progress::-moz-progress-bar {
background: var(--color-hover);
}
::selection {
::selection,
relative-time::part(root)::selection {
background: var(--color-primary-light-1);
color: var(--color-white);
}
::placeholder,
.ui.dropdown:not(.button) > .default.text,
.ui.default.dropdown:not(.button) > .text {
::placeholder {
color: var(--color-placeholder-text) !important;
opacity: 1 !important;
}
@@ -260,6 +263,12 @@ progress::-moz-progress-bar {
user-select: none;
}
.container-disabled {
opacity: var(--opacity-disabled);
pointer-events: none;
user-select: none;
}
a {
color: var(--color-primary);
cursor: pointer;
@@ -308,35 +317,34 @@ a.label,
text-decoration-line: none !important;
}
.ui.search > .results {
background: var(--color-body);
border-color: var(--color-secondary);
overflow-wrap: anywhere; /* allow text to wrap as fomantic limits this to 18em width */
.native-select {
position: relative;
}
.ui.search > .results .result {
background: var(--color-body);
border-color: var(--color-secondary);
display: flex;
align-items: center;
.native-select > select {
appearance: none; /* hide default triangle */
padding: 6px 26px 6px 10px;
border: 1px solid var(--color-light-border) !important;
color: var(--color-text);
border-radius: var(--border-radius);
}
.ui.search > .results .result .title {
color: var(--color-text-dark);
}
.ui.search > .results .result .description {
color: var(--color-text-light-2);
}
.ui.search > .results .result .image {
width: auto;
height: auto;
}
.ui.search > .results .result:hover,
.ui.category.search > .results .category .result:hover {
background: var(--color-hover);
/* ::before and ::after pseudo elements don't work on select elements,
so we need to put it on the parent wrapper element. */
.native-select::after {
position: absolute;
pointer-events: none; /* make the click event can pass through the svg to the select element */
top: 50%;
transform: translateY(-50%);
right: 5px;
content: "";
width: 16px;
height: 16px;
mask-size: cover;
-webkit-mask-size: cover;
mask-image: var(--select-arrows);
-webkit-mask-image: var(--select-arrows);
background: currentcolor;
}
.empty-placeholder {
@@ -367,81 +375,6 @@ a.label,
color: var(--color-text-light-2);
}
.ui.dropdown .menu {
background: var(--color-menu);
border-color: var(--color-secondary);
}
.ui.dropdown .menu > .header {
text-transform: none; /* reset fomantic's "uppercase" */
}
.ui.dropdown .menu > .header:not(.ui) {
color: var(--color-text);
font-size: 0.95em; /* reset fomantic's small font-size */
}
.ui.dropdown .menu > .item {
color: var(--color-text);
line-height: var(--line-height-default);
}
.ui.dropdown .menu > .item:hover {
color: var(--color-text);
background: var(--color-hover);
}
.ui.dropdown .menu > .item:active {
color: var(--color-text);
background: var(--color-active);
}
.ui.dropdown .menu .active.item {
color: var(--color-text);
background: var(--color-active);
border-radius: 0;
font-weight: var(--font-weight-normal);
}
/* fix misaligned images in webhook dropdown */
.ui.dropdown .menu > .item > img {
margin-top: -0.25rem;
margin-bottom: -0.25rem;
}
.ui.dropdown .menu > .item > svg {
margin-right: .78rem; /* use the same margin as for <img> */
}
.ui.selection.dropdown .menu > .item {
border-color: var(--color-secondary);
}
.ui.selection.visible.dropdown > .text:not(.default) {
color: var(--color-text);
}
.ui.dropdown.selected,
.ui.dropdown .menu .selected.item {
color: var(--color-text);
background: var(--color-hover);
}
.ui.dropdown .menu > .message:not(.ui) {
color: var(--color-text-light-2);
}
/* extend fomantic style '.ui.dropdown > .text > img' to include svg.img */
.ui.dropdown > .text > .img {
margin-left: 0;
float: none;
margin-right: 0.78571429rem;
}
.ui.dropdown > .text > .description,
.ui.dropdown .menu > .item > .description {
color: var(--color-text-light-2);
}
/* styles from removed fomantic transition module */
.hidden.transition {
visibility: hidden;
@@ -452,36 +385,6 @@ a.label,
visibility: visible !important;
}
.ui.selection.active.dropdown,
.ui.selection.active.dropdown:hover,
.ui.selection.active.dropdown .menu,
.ui.selection.active.dropdown:hover .menu {
border-color: var(--color-primary);
}
.ui.pointing.dropdown > .menu:not(.hidden)::after {
background: var(--color-menu);
box-shadow: -1px -1px 0 0 var(--color-secondary);
}
.ui.pointing.upward.dropdown .menu::after,
.ui.top.pointing.upward.dropdown .menu::after {
box-shadow: 1px 1px 0 0 var(--color-secondary);
}
.ui.comments .comment .text {
margin: 0;
}
.ui.comments .comment .text,
.ui.comments .comment .author {
color: var(--color-text);
}
.ui.comments .comment a.author:hover {
color: var(--color-primary);
}
.ui.comments .comment .metadata {
color: var(--color-text-light-2);
}
@@ -494,12 +397,6 @@ img.ui.avatar,
aspect-ratio: 1;
}
.ui.error.message .header,
.ui.warning.message .header {
color: inherit;
filter: saturate(2);
}
.full.height {
flex-grow: 1;
padding-bottom: var(--page-space-bottom);
@@ -534,87 +431,6 @@ img.ui.avatar,
margin-top: calc(var(--page-spacing) - 1rem);
}
.text.primary {
color: var(--color-primary) !important;
}
.text.red {
color: var(--color-red) !important;
}
.text.orange {
color: var(--color-orange) !important;
}
.text.yellow {
color: var(--color-yellow) !important;
}
.text.green {
color: var(--color-green) !important;
}
.text.blue {
color: var(--color-blue) !important;
}
.text.purple {
color: var(--color-purple) !important;
}
.text.black {
color: var(--color-text) !important;
}
.text.grey {
color: var(--color-text-light) !important;
}
.text.light {
color: var(--color-text-light) !important;
}
.text.light-2 {
color: var(--color-text-light-2) !important;
}
.text.light-3 {
color: var(--color-text-light-3) !important;
}
.text.light.grey {
color: var(--color-grey-light) !important;
}
.text.gold {
color: var(--color-gold) !important;
}
.text.small {
font-size: 0.75em;
}
/* popover box shadows */
.ui.dropdown .menu,
.ui.upward.dropdown > .menu,
.ui.menu .dropdown.item .menu,
.ui.selection.active.dropdown .menu,
.ui.upward.selection.dropdown .menu,
.ui.selection.active.dropdown:hover .menu,
.ui.upward.active.selection.dropdown:hover .menu {
box-shadow: 0 6px 18px var(--color-shadow);
}
.ui.floating.dropdown .menu {
box-shadow: 0 6px 18px var(--color-shadow) !important;
}
.ui .text.truncate {
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
}
.ui .message.flash-message {
text-align: center;
}
@@ -666,27 +482,11 @@ img.ui.avatar,
border: 1px solid;
}
.ui.dropdown .menu.context-user-switch .scrolling.menu {
border-radius: 0 !important;
box-shadow: none !important;
border-bottom: 1px solid var(--color-secondary);
max-width: 80vw;
}
.user-menu > .item {
width: 100%;
border-radius: 0 !important;
}
.scrolling.menu .item.selected {
font-weight: var(--font-weight-semibold) !important;
}
.ui.dropdown .scrolling.menu {
border-color: var(--color-secondary);
border-radius: 0 0 var(--border-radius) var(--border-radius) !important;
}
.color-preview {
display: inline-block;
margin-left: 0.4em;
@@ -700,10 +500,10 @@ img.ui.avatar,
}
blockquote.attention-note {
border-left-color: var(--color-blue-dark-1);
border-left-color: var(--color-info-text);
}
strong.attention-note, svg.attention-note {
color: var(--color-blue-dark-1);
color: var(--color-info-text);
}
blockquote.attention-tip {
@@ -714,10 +514,10 @@ strong.attention-tip, svg.attention-tip {
}
blockquote.attention-important {
border-left-color: var(--color-violet-dark-1);
border-left-color: var(--color-priority-text);
}
strong.attention-important, svg.attention-important {
color: var(--color-violet-dark-1);
color: var(--color-priority-text);
}
blockquote.attention-warning {
@@ -728,10 +528,10 @@ strong.attention-warning, svg.attention-warning {
}
blockquote.attention-caution {
border-left-color: var(--color-red-dark-1);
border-left-color: var(--color-error-text);
}
strong.attention-caution, svg.attention-caution {
color: var(--color-red-dark-1);
color: var(--color-error-text);
}
/* FIXME: this is a longstanding dirty patch since 2015, it only makes the pages more messy and shouldn't be used */
@@ -742,6 +542,58 @@ strong.attention-caution, svg.attention-caution {
overflow-menu {
border-bottom: 1px solid var(--color-secondary) !important;
display: flex;
position: relative;
}
overflow-menu .overflow-menu-popup {
position: absolute;
top: calc(100% + 8px);
right: 0;
z-index: 100;
background-color: var(--color-menu);
color: var(--color-text);
border: 1px solid var(--color-secondary);
border-radius: var(--border-radius);
box-shadow: 0 6px 18px var(--color-shadow);
padding: 4px 0;
}
overflow-menu .overflow-menu-popup::before,
overflow-menu .overflow-menu-popup::after {
content: "";
position: absolute;
right: 10px;
border: 8px solid transparent;
}
overflow-menu .overflow-menu-popup::before {
bottom: 100%;
border-bottom-color: var(--color-secondary);
}
overflow-menu .overflow-menu-popup::after {
bottom: calc(100% - 1px);
border-bottom-color: var(--color-menu);
}
overflow-menu .overflow-menu-popup > .item {
display: flex;
align-items: center;
padding: 9px 18px !important;
color: var(--color-text) !important;
background: transparent !important;
text-decoration: none;
gap: 10px;
width: 100%;
}
overflow-menu .overflow-menu-popup > .item:hover,
overflow-menu .overflow-menu-popup > .item:focus {
background: var(--color-hover) !important;
}
overflow-menu .overflow-menu-popup > .item.active {
background: var(--color-active) !important;
}
overflow-menu .overflow-menu-items {
@@ -791,10 +643,6 @@ overflow-menu .ui.label {
color: var(--color-primary-contrast);
}
.archived-icon {
color: var(--color-secondary-dark-2) !important;
}
.oauth2-authorize-application-box {
margin-top: 3em !important;
}
@@ -818,13 +666,9 @@ overflow-menu .ui.label {
min-width: 50px;
}
.lines-num span.bottom-line::after {
border-bottom: 1px solid var(--color-secondary);
}
.lines-num span::after {
content: attr(data-line-number);
line-height: 20px !important;
line-height: var(--line-height-code) !important;
padding: 0 10px;
display: block;
}
@@ -835,10 +679,11 @@ overflow-menu .ui.label {
}
.lines-num,
.lines-escape,
.lines-code {
font-size: 12px;
font-family: var(--fonts-monospace);
line-height: 20px;
line-height: var(--line-height-code);
padding-top: 0;
padding-bottom: 0;
vertical-align: top;
@@ -895,7 +740,7 @@ overflow-menu .ui.label {
width: min(26vw, 300px);
display: block;
padding: 0 0 0 6px;
line-height: 20px;
line-height: var(--line-height-code);
box-sizing: content-box;
}
@@ -919,7 +764,7 @@ overflow-menu .ui.label {
.blame-avatar {
display: flex;
align-items: center;
margin-right: 4px;
margin-right: 6px;
}
tr.top-line-blame {
@@ -930,11 +775,6 @@ tr.top-line-blame:first-of-type {
border-top: none; /* merge code lines belonging to the same commit into one block */
}
.lines-code .bottom-line,
.lines-commit .bottom-line {
border-bottom: 1px solid var(--color-secondary);
}
.migrate .svg.gitea-git {
color: var(--color-git);
}
@@ -962,22 +802,6 @@ table th[data-sortt-desc] .svg {
margin-left: 0.25rem;
}
.ui.dropdown .menu .item {
border-radius: 0;
}
.ui.dropdown .menu .item:first-of-type {
border-radius: var(--border-radius) var(--border-radius) 0 0;
}
.ui.dropdown .menu .item:last-of-type {
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
.ui.multiple.dropdown > .label {
box-shadow: 0 0 0 1px var(--color-secondary) inset;
}
/* for "image" emojis like ":git:" ":gitea:" and ":github:" (see CUSTOM_EMOJIS config option) */
.emoji img {
border-width: 0 !important;
@@ -1026,60 +850,9 @@ table th[data-sortt-desc] .svg {
min-height: 0;
}
.ui.dropdown:not(.button) {
line-height: var(--line-height-default); /* the dropdown doesn't have default line-height, use this to make the dropdown icon align with plain dropdown */
}
/* dropdown has some kinds of icons:
- "> .dropdown.icon": the arrow for opening the dropdown
- "> .remove.icon": the "x" icon for clearing the dropdown, only used in selection dropdown
- "> .ui.label > .delete.icon": the "x" icon for removing a label item in multiple selection dropdown
*/
.ui.dropdown.mini.button,
.ui.dropdown.tiny.button {
padding-right: 20px;
}
.ui.dropdown.button {
padding-right: 22px;
}
.ui.dropdown.large.button {
padding-right: 24px;
}
/* Gitea uses SVG images instead of Fomantic builtin "<i>" font icons, so we need to reset the icon styles */
.ui.ui.dropdown > .icon.icon {
position: initial; /* plain dropdown and button dropdown use flex layout for icons */
padding: 0;
margin: 0;
height: auto;
}
.ui.ui.dropdown > .icon.icon:hover {
opacity: 1;
}
.ui.ui.button.dropdown > .icon.icon,
.ui.ui.selection.dropdown > .icon.icon {
position: absolute; /* selection dropdown uses absolute layout for icons */
top: 50%;
transform: translateY(-50%);
}
.ui.ui.dropdown > .dropdown.icon {
right: 0.5em;
}
.ui.ui.dropdown > .remove.icon {
right: 2em;
}
.btn,
.ui.ui.button,
.ui.ui.dropdown,
.flex-text-inline,
.flex-text-inline > a,
.flex-text-inline > span {
.flex-text-inline {
display: inline-flex;
align-items: center;
gap: var(--gap-inline);
@@ -1087,24 +860,13 @@ table th[data-sortt-desc] .svg {
min-width: 0; /* make ellipsis work */
}
.ui.multiple.selection.dropdown {
flex-wrap: wrap;
}
.ui.ui.dropdown.selection {
min-width: 14em; /* match the default min width */
}
.ui.dropdown .ui.label .svg {
vertical-align: middle;
}
.ui.ui.labeled.button {
gap: 0;
align-items: stretch;
}
.ui.list.flex-items-block > .item,
.ui.vertical.menu.flex-items-block > .item,
.ui.form .field > label.flex-text-block, /* override fomantic "block" style */
.flex-items-block > .item,
.flex-text-block {
@@ -1114,44 +876,11 @@ table th[data-sortt-desc] .svg {
min-width: 0;
}
.ui.dropdown > .ui.button,
.flex-text-block > .ui.button,
.flex-text-inline > .ui.button {
margin: 0; /* fomantic buttons have default margin, when we use them in a flex container with gap, we do not need these margins */
}
/* to override Fomantic's default display: block for ".menu .item", and use a slightly larger gap for menu item content
the "!important" is necessary to override Fomantic UI menu item styles, meanwhile we should keep the "hidden" items still hidden */
.ui.dropdown .menu.flex-items-menu > .item:not(.hidden, .filtered, .tw-hidden) {
display: flex !important;
align-items: center;
gap: var(--gap-block);
min-width: 0;
}
.ui.dropdown .menu.flex-items-menu > .item img,
.ui.dropdown .menu.flex-items-menu > .item svg {
margin: 0; /* use gap, but not margin */
}
.ui.dropdown.ellipsis-text-items {
/* reset y padding and use the line-height below instead, to avoid the "overflow: hidden" clips the larger image in the "text" element */
padding-top: 0;
padding-bottom: 0;
}
.ui.dropdown.ellipsis-text-items > .text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 2.71; /* matches fomantic dropdown's default min-height */
}
.ui.dropdown.ellipsis-text-items .menu > .item {
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
}
.svg.octicon-file-directory-fill,
.svg.octicon-file-directory-open-fill,
.svg.octicon-file-submodule {
@@ -1,46 +0,0 @@
/* LineTableTD */
.chroma .lntd {
vertical-align: top;
padding: 0;
margin: 0;
border: 0;
}
/* LineTable */
.chroma .lntable {
border-spacing: 0;
padding: 0;
margin: 0;
border: 0;
width: auto;
overflow: auto;
display: block;
}
/* LineHighlight */
.chroma .hl {
display: block;
width: 100%;
}
/* LineNumbersTable */
.chroma .lnt {
margin-right: 0.4em;
padding: 0 0.4em;
}
/* LineNumbers */
.chroma .ln {
margin-right: 0.4em;
padding: 0 0.4em;
}
/* GenericStrong */
.chroma .gs {
font-weight: var(--font-weight-semibold);
}
/* GenericUnderline */
.chroma .gl {
text-decoration: underline;
}
@@ -1,76 +0,0 @@
/* https://github.com/alecthomas/chroma/blob/6428fb4e65f3c1493491571c8a6a8f1add1da822/types.go#L208 */
.chroma .bp { color: #fabd2f; } /* NameBuiltinPseudo */
.chroma .c { color: #777e94; } /* Comment */
.chroma .c1 { color: #777e94; } /* CommentSingle */
.chroma .ch { color: #777e94; } /* CommentHashbang */
.chroma .cm { color: #777e94; } /* CommentMultiline */
.chroma .cp { color: #8ec07c; } /* CommentPreproc */
.chroma .cpf { color: #649bc4; } /* CommentPreprocFile */
.chroma .cs { color: #9075cd; } /* CommentSpecial */
.chroma .dl { color: #649bc4; } /* LiteralStringDelimiter */
.chroma .fm {} /* NameFunctionMagic */
.chroma .g {} /* Generic */
.chroma .gd { color: #ffffff; background-color: #5f3737; } /* GenericDeleted */
.chroma .ge { color: #ddee30; } /* GenericEmph */
.chroma .gh { color: #ffaa10; } /* GenericHeading */
.chroma .gi { color: #ffffff; background-color: #3a523a; } /* GenericInserted */
.chroma .gl {} /* GenericUnderline */
.chroma .go { color: #777e94; } /* GenericOutput */
.chroma .gp { color: #ebdbb2; } /* GenericPrompt */
.chroma .gr { color: #ff4433; } /* GenericError */
.chroma .gs { color: #ebdbb2; } /* GenericStrong */
.chroma .gt { color: #ff7540; } /* GenericTraceback */
.chroma .gu { color: #b8bb26; } /* GenericSubheading */
.chroma .il { color: #649bc4; } /* LiteralNumberIntegerLong */
.chroma .k { color: #ff7540; } /* Keyword */
.chroma .kc { color: #649bc4; } /* KeywordConstant */
.chroma .kd { color: #ff7540; } /* KeywordDeclaration */
.chroma .kn { color: #ffaa10; } /* KeywordNamespace */
.chroma .kp { color: #5f8700; } /* KeywordPseudo */
.chroma .kr { color: #ff7540; } /* KeywordReserved */
.chroma .kt { color: #ff7b72; } /* KeywordType */
.chroma .l {} /* Literal */
.chroma .ld {} /* LiteralDate */
.chroma .m { color: #649bc4; } /* LiteralNumber */
.chroma .mb { color: #649bc4; } /* LiteralNumberBin */
.chroma .mf { color: #649bc4; } /* LiteralNumberFloat */
.chroma .mh { color: #649bc4; } /* LiteralNumberHex */
.chroma .mi { color: #649bc4; } /* LiteralNumberInteger */
.chroma .mo { color: #649bc4; } /* LiteralNumberOct */
.chroma .n { color: #c9d1d9; } /* Name */
.chroma .na { color: #fabd2f; } /* NameAttribute */
.chroma .nb { color: #fabd2f; } /* NameBuiltin */
.chroma .nc { color: #ffaa10; } /* NameClass */
.chroma .nd { color: #8ec07c; } /* NameDecorator */
.chroma .ne { color: #ff7540; } /* NameException */
.chroma .nf { color: #fabd2f; } /* NameFunction */
.chroma .ni { color: #fabd2f; } /* NameEntity */
.chroma .nl { color: #ff7540; } /* NameLabel */
.chroma .nn { color: #c9d1d9; } /* NameNamespace */
.chroma .no { color: #649bc4; } /* NameConstant */
.chroma .nt { color: #ff7540; } /* NameTag */
.chroma .nv { color: #ebdbb2; } /* NameVariable */
.chroma .nx { color: #b6bac5; } /* NameOther */
.chroma .o { color: #ff7540; } /* Operator */
.chroma .ow { color: #5f8700; } /* OperatorWord */
.chroma .p { color: #d2d4db; } /* Punctuation */
.chroma .py {} /* NameProperty */
.chroma .s { color: #b8bb26; } /* LiteralString */
.chroma .s1 { color: #b8bb26; } /* LiteralStringSingle */
.chroma .s2 { color: #b8bb26; } /* LiteralStringDouble */
.chroma .sa { color: #ffaa10; } /* LiteralStringAffix */
.chroma .sb { color: #b8bb26; } /* LiteralStringBacktick */
.chroma .sc { color: #ffaa10; } /* LiteralStringChar */
.chroma .sd { color: #b8bb26; } /* LiteralStringDoc */
.chroma .se { color: #ff8540; } /* LiteralStringEscape */
.chroma .sh { color: #b8bb26; } /* LiteralStringHeredoc */
.chroma .si { color: #ffaa10; } /* LiteralStringInterpol */
.chroma .sr { color: #9075cd; } /* LiteralStringRegex */
.chroma .ss { color: #ff8540; } /* LiteralStringSymbol */
.chroma .sx { color: #ffaa10; } /* LiteralStringOther */
.chroma .vc { color: #649bee; } /* NameVariableClass */
.chroma .vg { color: #649bee; } /* NameVariableGlobal */
.chroma .vi { color: #649bee; } /* NameVariableInstance */
.chroma .vm {} /* NameVariableMagic */
.chroma .w { color: #7f8699; } /* TextWhitespace */
.chroma .err {/* not styled because Chroma uses it on too many things like JSX */} /* Error */
@@ -1,76 +0,0 @@
/* https://github.com/alecthomas/chroma/blob/6428fb4e65f3c1493491571c8a6a8f1add1da822/types.go#L208 */
.chroma .bp { color: #999999; } /* NameBuiltinPseudo */
.chroma .c { color: #6a737d; } /* Comment */
.chroma .c1 { color: #6a737d; } /* CommentSingle */
.chroma .ch { color: #6a737d; } /* CommentHashbang */
.chroma .cm { color: #999988; } /* CommentMultiline */
.chroma .cp { color: #109295; } /* CommentPreproc */
.chroma .cpf { color: #4c4dbc; } /* CommentPreprocFile */
.chroma .cs { color: #999999; } /* CommentSpecial */
.chroma .dl { color: #106303; } /* LiteralStringDelimiter */
.chroma .fm {} /* NameFunctionMagic */
.chroma .g {} /* Generic */
.chroma .gd { color: #000000; background-color: #ffdddd; } /* GenericDeleted */
.chroma .ge { color: #000000; } /* GenericEmph */
.chroma .gh { color: #999999; } /* GenericHeading */
.chroma .gi { color: #000000; background-color: #ddffdd; } /* GenericInserted */
.chroma .gl {} /* GenericUnderline */
.chroma .go { color: #888888; } /* GenericOutput */
.chroma .gp { color: #555555; } /* GenericPrompt */
.chroma .gr { color: #aa0000; } /* GenericError */
.chroma .gs {} /* GenericStrong */
.chroma .gt { color: #aa0000; } /* GenericTraceback */
.chroma .gu { color: #aaaaaa; } /* GenericSubheading */
.chroma .il { color: #009999; } /* LiteralNumberIntegerLong */
.chroma .k { color: #d73a49; } /* Keyword */
.chroma .kc { color: #d73a49; } /* KeywordConstant */
.chroma .kd { color: #d73a49; } /* KeywordDeclaration */
.chroma .kn { color: #d73a49; } /* KeywordNamespace */
.chroma .kp { color: #d73a49; } /* KeywordPseudo */
.chroma .kr { color: #d73a49; } /* KeywordReserved */
.chroma .kt { color: #445588; } /* KeywordType */
.chroma .l {} /* Literal */
.chroma .ld {} /* LiteralDate */
.chroma .m { color: #009999; } /* LiteralNumber */
.chroma .mb { color: #009999; } /* LiteralNumberBin */
.chroma .mf { color: #009999; } /* LiteralNumberFloat */
.chroma .mh { color: #009999; } /* LiteralNumberHex */
.chroma .mi { color: #009999; } /* LiteralNumberInteger */
.chroma .mo { color: #009999; } /* LiteralNumberOct */
.chroma .n {} /* Name */
.chroma .na { color: #d73a49; } /* NameAttribute */
.chroma .nb { color: #005cc5; } /* NameBuiltin */
.chroma .nc { color: #445588; } /* NameClass */
.chroma .nd { color: #3c5d5d; } /* NameDecorator */
.chroma .ne { color: #990000; } /* NameException */
.chroma .nf { color: #005cc5; } /* NameFunction */
.chroma .ni { color: #6f42c1; } /* NameEntity */
.chroma .nl { color: #990000; } /* NameLabel */
.chroma .nn { color: #555555; } /* NameNamespace */
.chroma .no { color: #008080; } /* NameConstant */
.chroma .nt { color: #22863a; } /* NameTag */
.chroma .nv { color: #008080; } /* NameVariable */
.chroma .nx { color: #24292e; } /* NameOther */
.chroma .o { color: #d73a49; } /* Operator */
.chroma .ow { color: #d73a49; } /* OperatorWord */
.chroma .p {} /* Punctuation */
.chroma .py {} /* NameProperty */
.chroma .s { color: #106303; } /* LiteralString */
.chroma .s1 { color: #106303; } /* LiteralStringSingle */
.chroma .s2 { color: #106303; } /* LiteralStringDouble */
.chroma .sa { color: #cc7a00; } /* LiteralStringAffix */
.chroma .sb { color: #106303; } /* LiteralStringBacktick */
.chroma .sc { color: #cc7a00; } /* LiteralStringChar */
.chroma .sd { color: #106303; } /* LiteralStringDoc */
.chroma .se { color: #994400; } /* LiteralStringEscape */
.chroma .sh { color: #106303; } /* LiteralStringHeredoc */
.chroma .si { color: #cc7a00; } /* LiteralStringInterpol */
.chroma .sr { color: #4c4dbc; } /* LiteralStringRegex */
.chroma .ss { color: #994400; } /* LiteralStringSymbol */
.chroma .sx { color: #106303; } /* LiteralStringOther */
.chroma .vc { color: #008080; } /* NameVariableClass */
.chroma .vg { color: #008080; } /* NameVariableGlobal */
.chroma .vi { color: #008080; } /* NameVariableInstance */
.chroma .vm {} /* NameVariableMagic */
.chroma .w { color: #bbbbbb; } /* TextWhitespace */
.chroma .err {/* not styled because Chroma uses it on too many things like JSX */} /* Error */
@@ -1,49 +0,0 @@
.ui .field:not(:last-child) .EasyMDEContainer .editor-statusbar {
margin-bottom: -1em; /* when there is a statusbar, the "margin-bottom: 1em" of the "field" is not needed, because the statusbar is likely a blank line */
}
.EasyMDEContainer .CodeMirror {
color: var(--color-input-text);
background-color: var(--color-input-background);
border-color: var(--color-secondary);
font: 14px var(--fonts-monospace);
}
.EasyMDEContainer .CodeMirror.cm-s-default {
border-radius: var(--border-radius);
padding: 0 !important;
}
.EasyMDEContainer .CodeMirror.CodeMirror-fullscreen.CodeMirror-focused {
border-right: 1px solid var(--color-primary) !important;
}
.CodeMirror-cursor {
border-color: var(--color-caret) !important;
}
.CodeMirror .cm-comment {
background: inherit !important;
}
.CodeMirror .CodeMirror-code {
font: 14px var(--fonts-monospace);
}
.CodeMirror-selected {
background: var(--color-primary-light-1) !important;
color: var(--color-white) !important;
}
.CodeMirror-placeholder {
color: var(--color-placeholder-text) !important;
opacity: 1 !important;
}
.CodeMirror-focused {
border-color: var(--color-primary) !important;
}
.CodeMirror :focus {
outline: none;
}
@@ -1,106 +0,0 @@
.CodeMirror.cm-s-default .cm-property,
.CodeMirror.cm-s-paper .cm-property {
color: #a0cc75;
}
.CodeMirror.cm-s-default .cm-header,
.CodeMirror.cm-s-paper .cm-header {
color: #9daccc;
}
.CodeMirror.cm-s-default .cm-quote,
.CodeMirror.cm-s-paper .cm-quote {
color: #009900;
}
.CodeMirror.cm-s-default .cm-keyword,
.CodeMirror.cm-s-paper .cm-keyword {
color: #cc8a61;
}
.CodeMirror.cm-s-default .cm-atom,
.CodeMirror.cm-s-paper .cm-atom {
color: #ef5e77;
}
.CodeMirror.cm-s-default .cm-number,
.CodeMirror.cm-s-paper .cm-number {
color: #ff5656;
}
.CodeMirror.cm-s-default .cm-def,
.CodeMirror.cm-s-paper .cm-def {
color: #e4e4e4;
}
.CodeMirror.cm-s-default .cm-variable-2,
.CodeMirror.cm-s-paper .cm-variable-2 {
color: #00bdbf;
}
.CodeMirror.cm-s-default .cm-variable-3,
.CodeMirror.cm-s-paper .cm-variable-3 {
color: #008855;
}
.CodeMirror.cm-s-default .cm-comment,
.CodeMirror.cm-s-paper .cm-comment {
color: #8e9ab3;
}
.CodeMirror.cm-s-default .cm-string,
.CodeMirror.cm-s-paper .cm-string {
color: #a77272;
}
.CodeMirror.cm-s-default .cm-string-2,
.CodeMirror.cm-s-paper .cm-string-2 {
color: #ff5500;
}
.CodeMirror.cm-s-default .cm-meta,
.CodeMirror.cm-s-paper .cm-meta,
.CodeMirror.cm-s-default .cm-qualifier,
.CodeMirror.cm-s-paper .cm-qualifier {
color: #ffb176;
}
.CodeMirror.cm-s-default .cm-builtin,
.CodeMirror.cm-s-paper .cm-builtin {
color: #b7c951;
}
.CodeMirror.cm-s-default .cm-bracket,
.CodeMirror.cm-s-paper .cm-bracket {
color: #999977;
}
.CodeMirror.cm-s-default .cm-tag,
.CodeMirror.cm-s-paper .cm-tag {
color: #f1d273;
}
.CodeMirror.cm-s-default .cm-attribute,
.CodeMirror.cm-s-paper .cm-attribute {
color: #bfcc70;
}
.CodeMirror.cm-s-default .cm-hr,
.CodeMirror.cm-s-paper .cm-hr {
color: #999999;
}
.CodeMirror.cm-s-default .cm-url,
.CodeMirror.cm-s-paper .cm-url {
color: #c5cfd0;
}
.CodeMirror.cm-s-default .cm-link,
.CodeMirror.cm-s-paper .cm-link {
color: #d8c792;
}
.CodeMirror.cm-s-default .cm-error,
.CodeMirror.cm-s-paper .cm-error {
color: #dbdbeb;
}
@@ -1 +0,0 @@
/* Placeholder, there is no light theme, it uses CM defaults */
@@ -22,11 +22,6 @@
justify-content: space-between;
}
.dashboard.feeds .filter.menu .item .text.truncate,
.dashboard.issues .filter.menu .item .text.truncate {
width: 75%;
}
/* Sort */
.dashboard.feeds .filter.menu .jump.item,
.dashboard.issues .filter.menu .jump.item {
@@ -0,0 +1,465 @@
/* Inlined styles from easymde.min.css (includes EasyMDE and CodeMirror base) */
.EasyMDEContainer {
display: block;
}
/* CodeMirror base layout (from codemirror.css) */
.EasyMDEContainer .CodeMirror {
position: relative;
overflow: hidden;
box-sizing: border-box;
height: auto;
border: 1px solid var(--color-input-border);
border-bottom-left-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
padding: 10px;
font: 14px var(--fonts-monospace);
z-index: 0;
overflow-wrap: break-word;
color: var(--color-input-text);
background-color: var(--color-input-background);
direction: ltr;
}
.EasyMDEContainer .CodeMirror.cm-s-default {
border-radius: var(--border-radius);
padding: 0;
}
.EasyMDEContainer .CodeMirror-lines {
padding: 4px 0;
cursor: text;
min-height: 1px;
}
.EasyMDEContainer .CodeMirror pre.CodeMirror-line,
.EasyMDEContainer .CodeMirror pre.CodeMirror-line-like {
padding: 0 4px;
border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
overflow-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
font-variant-ligatures: contextual;
}
.EasyMDEContainer .CodeMirror-wrap pre.CodeMirror-line,
.EasyMDEContainer .CodeMirror-wrap pre.CodeMirror-line-like {
overflow-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.EasyMDEContainer .CodeMirror-scroll {
overflow: scroll !important; /* things will break if this is overridden */
margin-bottom: -50px;
margin-right: -50px;
padding-bottom: 50px;
height: 100%;
outline: none;
position: relative;
z-index: 0;
cursor: text;
}
.EasyMDEContainer .CodeMirror-sizer {
position: relative;
border-right: 50px solid transparent;
}
.EasyMDEContainer .CodeMirror-vscrollbar,
.EasyMDEContainer .CodeMirror-hscrollbar,
.EasyMDEContainer .CodeMirror-scrollbar-filler,
.EasyMDEContainer .CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
outline: none;
}
.EasyMDEContainer .CodeMirror-vscrollbar {
right: 0;
top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.EasyMDEContainer .CodeMirror-hscrollbar {
bottom: 0;
left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.EasyMDEContainer .CodeMirror-scrollbar-filler {
right: 0;
bottom: 0;
}
/* Cursor */
.EasyMDEContainer .CodeMirror-cursor {
position: absolute;
pointer-events: none;
border-left: 1px solid var(--color-caret);
border-right: none;
width: 0;
}
.EasyMDEContainer div.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 3;
}
.EasyMDEContainer div.CodeMirror-dragcursors {
visibility: visible;
}
.EasyMDEContainer .CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
/* Selection */
.EasyMDEContainer .CodeMirror-selected {
background: var(--color-primary-light-1);
}
.EasyMDEContainer .CodeMirror-focused .CodeMirror-selected {
background: var(--color-primary-light-1);
}
.EasyMDEContainer .CodeMirror-line::selection,
.EasyMDEContainer .CodeMirror-line > span::selection,
.EasyMDEContainer .CodeMirror-line > span > span::selection {
background: var(--color-primary-light-1);
}
/* Misc */
.EasyMDEContainer .cm-tab {
display: inline-block;
text-decoration: inherit;
}
.EasyMDEContainer .CodeMirror-rtl pre {
direction: rtl;
}
.EasyMDEContainer .CodeMirror-code {
font: 14px var(--fonts-monospace);
outline: none;
}
.EasyMDEContainer .CodeMirror-scroll,
.EasyMDEContainer .CodeMirror-sizer {
box-sizing: content-box;
}
.EasyMDEContainer .CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.EasyMDEContainer .CodeMirror-measure pre {
position: static;
}
.EasyMDEContainer .CodeMirror-composing {
border-bottom: 2px solid;
}
.EasyMDEContainer span.CodeMirror-selectedtext {
background: none;
}
@media print {
.EasyMDEContainer .CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* Default theme overrides */
.EasyMDEContainer .cm-header,
.EasyMDEContainer .cm-strong {
font-weight: var(--font-weight-bold);
}
.EasyMDEContainer .cm-em {
font-style: italic;
}
.EasyMDEContainer .cm-link {
text-decoration: underline;
}
.EasyMDEContainer .cm-strikethrough {
text-decoration: line-through;
}
.EasyMDEContainer .cm-comment {
background: inherit;
}
/* Placeholder */
.EasyMDEContainer .CodeMirror-placeholder {
color: var(--color-placeholder-text);
opacity: 1;
}
/* Focus */
.EasyMDEContainer .CodeMirror-focused {
border-color: var(--color-primary);
}
.EasyMDEContainer .CodeMirror :focus {
outline: none;
}
/* Fullscreen */
.EasyMDEContainer .CodeMirror-fullscreen {
background: var(--color-body);
position: fixed;
inset: 50px 0 0;
height: auto;
z-index: 8;
border-right: none;
border-bottom-right-radius: 0;
}
.EasyMDEContainer .CodeMirror-fullscreen.CodeMirror-focused {
border-right: 1px solid var(--color-primary);
}
/* Statusbar */
.ui .field:not(:last-child) .EasyMDEContainer .editor-statusbar {
margin-bottom: -1em; /* when there is a statusbar, the "margin-bottom: 1em" of the "field" is not needed, because the statusbar is likely a blank line */
}
/* Toolbar */
.EasyMDEContainer .editor-toolbar {
position: relative;
user-select: none;
padding: 9px 10px;
border-top: 1px solid var(--color-input-border);
border-left: 1px solid var(--color-input-border);
border-right: 1px solid var(--color-input-border);
border-top-left-radius: var(--border-radius);
border-top-right-radius: var(--border-radius);
}
.EasyMDEContainer .editor-toolbar.fullscreen {
width: 100%;
height: 50px;
padding-top: 10px;
padding-bottom: 10px;
box-sizing: border-box;
background: var(--color-body);
border: 0;
position: fixed;
top: 0;
left: 0;
opacity: 1;
z-index: 9;
}
.EasyMDEContainer .editor-toolbar button {
background: transparent;
display: inline-block;
text-align: center;
text-decoration: none;
height: 30px;
margin: 0;
padding: 0 6px;
border: none;
border-radius: 3px;
cursor: pointer;
font-weight: var(--font-weight-bold);
min-width: 30px;
white-space: nowrap;
color: var(--color-text-light);
}
.EasyMDEContainer .editor-toolbar button:not(:hover) {
background-color: transparent;
}
.EasyMDEContainer .editor-toolbar button:hover {
background: var(--color-hover);
}
.EasyMDEContainer .editor-toolbar button.active {
background: var(--color-active);
}
.EasyMDEContainer .editor-toolbar i.separator {
display: inline-block;
width: 0;
border-left: none;
border-right: 1px solid var(--color-input-border);
color: transparent;
text-indent: -10px;
margin: 0 6px;
}
.EasyMDEContainer .editor-toolbar button::after {
font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
font-size: 65%;
vertical-align: text-bottom;
position: relative;
top: 2px;
}
.EasyMDEContainer .editor-toolbar button.heading-1::after {
content: "1";
}
.EasyMDEContainer .editor-toolbar button.heading-2::after {
content: "2";
}
.EasyMDEContainer .editor-toolbar button.heading-3::after {
content: "3";
}
.EasyMDEContainer .editor-toolbar button.heading-bigger::after {
content: "\25B2";
}
.EasyMDEContainer .editor-toolbar button.heading-smaller::after {
content: "\25BC";
}
.EasyMDEContainer .editor-toolbar.disabled-for-preview button:not(.no-disable) {
opacity: 0.6;
pointer-events: none;
}
/* hide preview button, we have the preview tab for this */
.EasyMDEContainer .editor-toolbar:not(.fullscreen) .preview {
display: none;
}
/* hide revert button in fullscreen, it breaks the page */
.EasyMDEContainer .editor-toolbar.fullscreen .revert-to-textarea {
display: none;
}
@media only screen and (max-width: 700px) {
.EasyMDEContainer .editor-toolbar i.no-mobile {
display: none;
}
}
/* Statusbar */
.EasyMDEContainer .editor-statusbar {
padding: 8px 10px;
font-size: 12px;
color: var(--color-text-light);
text-align: right;
}
.EasyMDEContainer .editor-statusbar span {
display: inline-block;
min-width: 4em;
margin-left: 1em;
}
.EasyMDEContainer .editor-statusbar .lines::before {
content: "lines: ";
}
.EasyMDEContainer .editor-statusbar .words::before {
content: "words: ";
}
.EasyMDEContainer .editor-statusbar .characters::before {
content: "characters: ";
}
/* Preview */
.EasyMDEContainer .editor-preview-full {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 7;
overflow: auto;
display: none;
box-sizing: border-box;
}
.EasyMDEContainer .editor-preview-side {
position: fixed;
bottom: 0;
width: 50%;
top: 50px;
right: 0;
z-index: 9;
overflow: auto;
display: none;
box-sizing: border-box;
border: 1px solid var(--color-secondary);
overflow-wrap: break-word;
}
.EasyMDEContainer .editor-preview-active-side {
display: block;
}
.EasyMDEContainer .editor-preview-active {
display: block;
}
.EasyMDEContainer .editor-preview {
padding: 10px;
background-color: var(--color-body);
}
.EasyMDEContainer .editor-preview > p {
margin-top: 0;
}
.EasyMDEContainer .editor-preview pre {
background: var(--color-markup-code-block);
margin-bottom: 10px;
}
.EasyMDEContainer .editor-preview table td,
.EasyMDEContainer .editor-preview table th {
border: 1px solid var(--color-secondary);
padding: 5px;
}
.EasyMDEContainer .CodeMirror .cm-keyword { color: var(--color-syntax-keyword); }
.EasyMDEContainer .CodeMirror .cm-atom { color: var(--color-syntax-keyword); }
.EasyMDEContainer .CodeMirror .cm-number { color: var(--color-syntax-number); }
.EasyMDEContainer .CodeMirror .cm-def { color: var(--color-syntax-name); }
.EasyMDEContainer .CodeMirror .cm-variable-2 { color: var(--color-syntax-name); }
.EasyMDEContainer .CodeMirror .cm-variable-3 { color: var(--color-syntax-type); }
.EasyMDEContainer .CodeMirror .cm-property { color: var(--color-syntax-name); }
.EasyMDEContainer .CodeMirror .cm-comment { color: var(--color-syntax-comment); }
.EasyMDEContainer .CodeMirror .cm-string { color: var(--color-syntax-string); }
.EasyMDEContainer .CodeMirror .cm-string-2 { color: var(--color-syntax-regexp); }
.EasyMDEContainer .CodeMirror .cm-meta { color: var(--color-syntax-control); }
.EasyMDEContainer .CodeMirror .cm-qualifier { color: var(--color-syntax-control); }
.EasyMDEContainer .CodeMirror .cm-builtin { color: var(--color-syntax-keyword); }
.EasyMDEContainer .CodeMirror .cm-bracket { color: var(--color-syntax-operator); }
.EasyMDEContainer .CodeMirror .cm-tag { color: var(--color-syntax-type); }
.EasyMDEContainer .CodeMirror .cm-attribute { color: var(--color-syntax-name); }
.EasyMDEContainer .CodeMirror .cm-header { color: var(--color-syntax-type); }
.EasyMDEContainer .CodeMirror .cm-quote { color: var(--color-syntax-comment); }
.EasyMDEContainer .CodeMirror .cm-hr { color: var(--color-syntax-operator); }
.EasyMDEContainer .CodeMirror .cm-url { color: var(--color-syntax-link); }
.EasyMDEContainer .CodeMirror .cm-link { color: var(--color-syntax-link); }
.EasyMDEContainer .CodeMirror .cm-error { color: var(--color-syntax-invalid); }
@@ -1,76 +0,0 @@
.repository.file.editor .tab[data-tab="write"] {
padding: 0 !important;
}
.repository.file.editor .tab[data-tab="write"] .editor-toolbar {
border: 0 !important;
}
.repository.file.editor .tab[data-tab="write"] .CodeMirror {
border-left: 0;
border-right: 0;
border-bottom: 0;
}
.repo-editor-header {
display: flex;
margin: 1rem 0;
padding: 3px 0;
}
.editor-toolbar {
border-color: var(--color-secondary);
}
.editor-toolbar.fullscreen {
background: var(--color-body);
}
.editor-toolbar button {
border: none !important;
color: var(--color-text-light);
}
.editor-toolbar button:not(:hover) {
background-color: transparent !important;
}
.editor-toolbar i.separator {
border-left: none;
border-right-color: var(--color-secondary);
}
.editor-toolbar button:hover {
background: var(--color-hover);
}
.editor-toolbar button.active {
background: var(--color-active);
}
/* hide preview button, we have the preview tab for this */
.editor-toolbar:not(.fullscreen) .preview {
display: none;
}
/* hide revert button in fullscreen, it breaks the page */
.editor-toolbar.fullscreen .revert-to-textarea {
display: none;
}
.editor-preview {
background-color: var(--color-body);
}
.editor-preview-side {
border-color: var(--color-secondary);
}
.editor-statusbar {
color: var(--color-text-light);
}
.editor-loading {
padding: 1rem;
text-align: center;
}
@@ -0,0 +1,33 @@
.m-captcha-style {
width: 100%;
height: 5em;
vertical-align: middle;
display: inline-block;
}
@media (min-width: 768px) {
.g-recaptcha-style,
.h-captcha-style {
margin: 0 auto !important;
width: 304px;
padding-left: 30px;
}
.g-recaptcha-style iframe,
.h-captcha-style iframe {
border-radius: var(--border-radius) !important;
width: 302px !important;
height: 76px !important;
}
.m-captcha-style {
max-width: 450px;
}
}
@media (max-height: 575px) {
#rc-imageselect, /* google recaptcha */
.g-recaptcha-style,
.h-captcha-style {
transform: scale(0.77);
transform-origin: 0 0;
}
}
@@ -1,42 +0,0 @@
.monaco-editor-container,
.editor-loading.is-loading {
width: 100%;
min-height: 200px;
height: 90vh;
}
.edit.githook .monaco-editor-container {
border: 1px solid var(--color-secondary);
height: 70vh;
}
/* overwrite conflicting styles from fomantic */
.monaco-editor-container .inputarea {
min-height: 0 !important;
margin: 0 !important;
padding: 0 !important;
resize: none !important;
border: none !important;
color: transparent !important;
background-color: transparent !important;
}
.monaco-editor,
.monaco-editor .overflow-guard {
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
/* fomantic styles destroy this element only visible on IOS, restore it */
.monaco-editor .iPadShowKeyboard {
border: none !important;
width: 58px !important;
min-width: 0 !important;
height: 36px !important;
min-height: 0 !important;
margin: 0 !important;
padding: 0 !important;
position: absolute !important;
resize: none !important;
overflow: hidden !important;
border-radius: var(--border-radius-medium) !important;
}
@@ -46,10 +46,6 @@
height: 20px;
}
#git-graph-container li .author {
color: var(--color-text-light);
}
#git-graph-container li .time {
color: var(--color-text-light-3);
font-size: 80%;
-312
View File
@@ -1,312 +0,0 @@
.ui .form .autofill-dummy {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
z-index: -10000;
}
.ui .form .sub.field {
margin-left: 25px;
}
.ui.form .fields.error .field textarea,
.ui.form .fields.error .field select,
.ui.form .fields.error .field input:not([type]),
.ui.form .fields.error .field input[type="date"],
.ui.form .fields.error .field input[type="datetime-local"],
.ui.form .fields.error .field input[type="email"],
.ui.form .fields.error .field input[type="number"],
.ui.form .fields.error .field input[type="password"],
.ui.form .fields.error .field input[type="search"],
.ui.form .fields.error .field input[type="tel"],
.ui.form .fields.error .field input[type="time"],
.ui.form .fields.error .field input[type="text"],
.ui.form .fields.error .field input[type="file"],
.ui.form .fields.error .field input[type="url"],
.ui.form .fields.error .field .ui.dropdown,
.ui.form .fields.error .field .ui.dropdown .item,
.ui.form .field.error .ui.dropdown,
.ui.form .field.error .ui.dropdown .text,
.ui.form .field.error .ui.dropdown .item,
.ui.form .field.error textarea,
.ui.form .field.error select,
.ui.form .field.error input:not([type]),
.ui.form .field.error input[type="date"],
.ui.form .field.error input[type="datetime-local"],
.ui.form .field.error input[type="email"],
.ui.form .field.error input[type="number"],
.ui.form .field.error input[type="password"],
.ui.form .field.error input[type="search"],
.ui.form .field.error input[type="tel"],
.ui.form .field.error input[type="time"],
.ui.form .field.error input[type="text"],
.ui.form .field.error input[type="file"],
.ui.form .field.error input[type="url"],
.ui.form .field.error select:focus,
.ui.form .field.error input:not([type]):focus,
.ui.form .field.error input[type="date"]:focus,
.ui.form .field.error input[type="datetime-local"]:focus,
.ui.form .field.error input[type="email"]:focus,
.ui.form .field.error input[type="number"]:focus,
.ui.form .field.error input[type="password"]:focus,
.ui.form .field.error input[type="search"]:focus,
.ui.form .field.error input[type="tel"]:focus,
.ui.form .field.error input[type="time"]:focus,
.ui.form .field.error input[type="text"]:focus,
.ui.form .field.error input[type="file"]:focus,
.ui.form .field.error input[type="url"]:focus {
background-color: var(--color-error-bg);
border-color: var(--color-error-border);
color: var(--color-error-text);
}
.ui.form .fields.error .field .ui.dropdown,
.ui.form .field.error .ui.dropdown,
.ui.form .fields.error .field .ui.dropdown:hover,
.ui.form .field.error .ui.dropdown:hover {
border-color: var(--color-error-border) !important;
}
.ui.form .fields.error .field .ui.dropdown .menu .item:hover,
.ui.form .field.error .ui.dropdown .menu .item:hover {
background-color: var(--color-error-bg-hover);
}
.ui.form .fields.error .field .ui.dropdown .menu .active.item,
.ui.form .field.error .ui.dropdown .menu .active.item {
background-color: var(--color-error-bg-active) !important;
}
.ui.form .fields.error .dropdown .menu,
.ui.form .field.error .dropdown .menu {
border-color: var(--color-error-border) !important;
}
input:-webkit-autofill,
input:-webkit-autofill:focus,
input:-webkit-autofill:hover,
input:-webkit-autofill:active,
.ui.form .field.field input:-webkit-autofill,
.ui.form .field.field input:-webkit-autofill:focus,
.ui.form .field.field input:-webkit-autofill:hover,
.ui.form .field.field input:-webkit-autofill:active {
-webkit-background-clip: text;
-webkit-text-fill-color: var(--color-text);
box-shadow: 0 0 0 100px var(--color-primary-light-6) inset !important;
border-color: var(--color-primary-light-4) !important;
}
.ui.form .field.muted {
opacity: var(--opacity-disabled);
}
.ui.form textarea:not([rows]) {
height: var(--min-height-textarea); /* override fomantic default 12em */
min-height: var(--min-height-textarea); /* override fomantic default 8em */
}
.ui.input textarea,
.ui.form textarea,
.ui.form input:not([type]),
.ui.form input[type="date"],
.ui.form input[type="datetime-local"],
.ui.form input[type="email"],
.ui.form input[type="number"],
.ui.form input[type="password"],
.ui.form input[type="search"],
.ui.form input[type="tel"],
.ui.form input[type="time"],
.ui.form input[type="text"],
.ui.form input[type="file"],
.ui.form input[type="url"] {
transition: none;
}
input,
textarea,
.ui.input > input,
.ui.form input:not([type]),
.ui.form select,
.ui.form textarea,
.ui.form input[type="date"],
.ui.form input[type="datetime-local"],
.ui.form input[type="email"],
.ui.form input[type="file"],
.ui.form input[type="number"],
.ui.form input[type="password"],
.ui.form input[type="search"],
.ui.form input[type="tel"],
.ui.form input[type="text"],
.ui.form input[type="time"],
.ui.form input[type="url"],
.ui.selection.dropdown {
background: var(--color-input-background);
border-color: var(--color-input-border);
color: var(--color-input-text);
}
input:hover,
textarea:hover,
.ui.input input:hover,
.ui.form input:not([type]):hover,
.ui.form select:hover,
.ui.form textarea:hover,
.ui.form input[type="date"]:hover,
.ui.form input[type="datetime-local"]:hover,
.ui.form input[type="email"]:hover,
.ui.form input[type="file"]:hover,
.ui.form input[type="number"]:hover,
.ui.form input[type="password"]:hover,
.ui.form input[type="search"]:hover,
.ui.form input[type="tel"]:hover,
.ui.form input[type="text"]:hover,
.ui.form input[type="time"]:hover,
.ui.form input[type="url"]:hover,
.ui.selection.dropdown:hover {
background: var(--color-input-background);
border-color: var(--color-input-border-hover);
color: var(--color-input-text);
}
input:focus,
textarea:focus,
.ui.input input:focus,
.ui.form input:not([type]):focus,
.ui.form select:focus,
.ui.form textarea:focus,
.ui.form input[type="date"]:focus,
.ui.form input[type="datetime-local"]:focus,
.ui.form input[type="email"]:focus,
.ui.form input[type="file"]:focus,
.ui.form input[type="number"]:focus,
.ui.form input[type="password"]:focus,
.ui.form input[type="search"]:focus,
.ui.form input[type="tel"]:focus,
.ui.form input[type="text"]:focus,
.ui.form input[type="time"]:focus,
.ui.form input[type="url"]:focus,
.ui.selection.dropdown:focus {
background: var(--color-input-background);
border-color: var(--color-primary);
color: var(--color-input-text);
}
.ui.form .field > label,
.ui.form .inline.fields > label,
.ui.form .inline.fields .field > label,
.ui.form .inline.fields .field > p,
.ui.form .inline.field > label,
.ui.form .inline.field > p {
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 label.required::after {
color: var(--color-red);
}
.ui.input {
color: var(--color-input-text);
}
.ui.form .field > .selection.dropdown {
min-width: 14em; /* matches the default min width */
}
.form .help {
color: var(--color-secondary-dark-5);
margin-top: 0.25em;
padding-bottom: 0.6em;
display: inline-block;
text-wrap: balance;
}
.form .help code {
color: var(--color-text-light-1);
}
.form .help pre.command-block {
white-space: pre-wrap;
overflow-wrap: anywhere;
margin: 0.25em 0 0.25em 1em;
}
.m-captcha-style {
width: 100%;
height: 5em;
vertical-align: middle;
display: inline-block;
}
@media (min-width: 768px) {
.g-recaptcha-style,
.h-captcha-style {
margin: 0 auto !important;
width: 304px;
padding-left: 30px;
}
.g-recaptcha-style iframe,
.h-captcha-style iframe {
border-radius: var(--border-radius) !important;
width: 302px !important;
height: 76px !important;
}
.m-captcha-style {
max-width: 450px;
}
}
@media (max-height: 575px) {
#rc-imageselect, /* google recaptcha */
.g-recaptcha-style,
.h-captcha-style {
transform: scale(0.77);
transform-origin: 0 0;
}
}
.ui.form.left-right-form .inline.field > label {
text-align: right;
width: 250px;
margin-right: 10px;
}
.ui.form.left-right-form .inline.field > .help {
display: block;
margin-left: calc(250px + 15px);
}
.ui.form.left-right-form .inline.field input:not([type="checkbox"], [type="radio"]),
.ui.form.left-right-form .inline.field .ui.dropdown,
.ui.form.left-right-form .inline.field textarea {
width: 50%;
}
.ui.form.left-right-form .inline.field .ui.dropdown input.search {
width: 100%;
}
.ui.form.left-right-form .inline.field .inline-right {
display: inline-flex;
flex-direction: column;
gap: 0.5em;
}
@media (max-width: 767.98px) {
.ui.form.left-right-form .inline.field > label {
width: 100%;
margin: 0;
text-align: left;
}
.ui.form.left-right-form .inline.field > .help {
margin: 0;
}
.ui.form.left-right-form .inline.field input:not([type="checkbox"], [type="radio"]),
.ui.form.left-right-form .inline.field .ui.dropdown,
.ui.form.left-right-form .inline.field textarea {
width: 100%;
}
}
+23 -4
View File
@@ -56,7 +56,7 @@
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 0.25em;
gap: 0.5em;
}
.page-footer .right-links {
@@ -65,15 +65,34 @@
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 1em;
}
.page-footer .right-links > a {
border-left: 1px solid var(--color-secondary-dark-1);
padding-left: 8px;
margin-left: 5px;
padding-left: 1em;
}
.page-footer .ui.dropdown .menu.language-menu {
/* the theme item is also used for the menu's "default text" display */
.page-footer .ui.dropdown .theme-menu-item {
display: flex;
align-items: center;
gap: 0.5em;
}
/* Fomantic UI dropdown "remote items by API" can't change parent "item" element,
so we use "theme-menu-item" in the "item" and add tooltip to the inner one.
Then the inner one needs to get padding and parent "item" padding needs to be removed */
.page-footer .menu.theme-menu > .item {
padding: 0 !important;
}
.page-footer .menu.theme-menu > .item > .theme-menu-item {
padding: 11px 16px;
}
.page-footer .ui.dropdown .menu.language-menu,
.page-footer .ui.dropdown .menu.theme-menu {
max-height: min(500px, calc(100vh - 60px));
overflow-y: auto;
margin-bottom: 10px;
+9 -6
View File
@@ -18,7 +18,11 @@
@import "./modules/checkbox.css";
@import "./modules/dimmer.css";
@import "./modules/modal.css";
@import "./modules/search.css";
@import "./modules/tab.css";
@import "./modules/form.css";
@import "./modules/dropdown.css";
@import "./modules/shortcut.css";
@import "./modules/tippy.css";
@import "./modules/breadcrumb.css";
@@ -27,6 +31,9 @@
@import "./modules/toast.css";
@import "./modules/svg.css";
@import "./modules/flexcontainer.css";
@import "./modules/codeeditor.css";
@import "./modules/chroma.css";
@import "./modules/charescape.css";
@import "./shared/flex-list.css";
@import "./shared/milestone.css";
@@ -37,24 +44,21 @@
@import "./features/gitgraph.css";
@import "./features/heatmap.css";
@import "./features/imagediff.css";
@import "./features/codeeditor.css";
@import "./features/projects.css";
@import "./features/expander.css";
@import "./features/cropper.css";
@import "./features/console.css";
@import "./features/captcha.css";
@import "./markup/content.css";
@import "./markup/codecopy.css";
@import "./markup/codeblock.css";
@import "./markup/codepreview.css";
@import "./markup/asciicast.css";
@import "./chroma/base.css";
@import "./codemirror/base.css";
@import "./font_i18n.css";
@import "./base.css";
@import "./home.css";
@import "./install.css";
@import "./form.css";
@import "./repo.css";
@import "./repo/release-tag.css";
@@ -72,7 +76,6 @@
@import "./repo/commit-sign.css";
@import "./repo/packages.css";
@import "./editor/fileeditor.css";
@import "./editor/combomarkdowneditor.css";
@import "./org.css";
@@ -3,6 +3,8 @@
height: auto;
}
.ap-terminal {
/* Related: https://github.com/asciinema/asciinema-player/blob/develop/src/components/Terminal.js : <div class="ap-term" ...>
Old PR: Fix UI regression of asciinema player https://github.com/go-gitea/gitea/pull/26159 */
.ap-term {
overflow: hidden !important;
}
@@ -0,0 +1,10 @@
.markup .ui.button.code-copy {
top: 8px;
right: 6px;
margin: 0;
}
.markup .mermaid-block .view-controller {
right: 6px;
bottom: 5px;
}
@@ -1,30 +0,0 @@
.markup .code-copy {
position: absolute;
top: 8px;
right: 6px;
padding: 9px;
visibility: hidden;
animation: fadeout 0.2s both;
}
/* adjustments for comment content having only 14px font size */
.repository.view.issue .comment-list .comment .markup .code-copy {
right: 5px;
padding: 8px;
}
/* can not use regular transparent button colors for hover and active states because
we need opaque colors here as code can appear behind the button */
.markup .code-copy:hover {
background: var(--color-secondary) !important;
}
.markup .code-copy:active {
background: var(--color-secondary-dark-1) !important;
}
.markup .code-block-container:hover .code-copy,
.markup .code-block:hover .code-copy {
visibility: visible;
animation: fadein 0.2s both;
}
@@ -24,8 +24,8 @@
.markup .anchor {
float: left;
padding-right: 4px;
margin-left: -20px;
padding-inline-end: 4px;
margin-inline-start: -20px;
color: inherit;
}
@@ -151,31 +151,30 @@ In markup content, we always use bottom margin for all elements */
.markup ul,
.markup ol {
padding-left: 2em;
}
.markup ul.no-list,
.markup ol.no-list {
padding: 0;
list-style-type: none;
padding-inline-start: 2em;
}
.markup .task-list-item {
list-style-type: none;
}
.markup .task-list-item > ul {
margin-top: 4px;
}
.markup .task-list-item p + ul {
margin-top: 16px;
}
.markup .task-list-item input[type="checkbox"] {
margin: 0 .3em .25em -1.4em;
margin-bottom: 0.25em;
margin-inline: -1.4em 0.6em;
vertical-align: middle;
padding: 0;
}
.markup .task-list-item input[type="checkbox"] + p {
margin-left: -0.2em;
margin-inline-start: -0.2em;
display: inline;
}
@@ -188,48 +187,12 @@ In markup content, we always use bottom margin for all elements */
}
.markup input[type="checkbox"] {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
position: relative;
border: 1px solid var(--color-secondary);
border-radius: var(--border-radius);
background: var(--color-input-background);
height: 14px;
width: 14px;
margin-inline-end: .25em;
margin-bottom: .25em;
cursor: default;
opacity: 1 !important; /* override fomantic on edit preview */
pointer-events: auto !important; /* override fomantic on edit preview */
vertical-align: middle !important; /* override fomantic on edit preview */
-webkit-print-color-adjust: exact;
color-adjust: exact;
}
.markup input[type="checkbox"]:not([disabled]):hover,
.markup input[type="checkbox"]:not([disabled]):active {
border-color: var(--color-primary);
}
.markup input[type="checkbox"]::after {
position: absolute;
inset: 0;
pointer-events: none;
background: var(--color-text);
mask-size: cover;
-webkit-mask-size: cover;
}
.markup input[type="checkbox"]:checked::after {
content: "";
mask-image: var(--checkbox-mask-checked);
-webkit-mask-image: var(--checkbox-mask-checked);
-webkit-print-color-adjust: exact;
color-adjust: exact;
}
.markup input[type="checkbox"]:indeterminate::after {
content: "";
mask-image: var(--checkbox-mask-indeterminate);
-webkit-mask-image: var(--checkbox-mask-indeterminate);
}
.markup ul ul,
@@ -271,7 +234,7 @@ In markup content, we always use bottom margin for all elements */
}
.markup blockquote {
margin-left: 0;
margin-inline-start: 0;
padding: 0 15px;
color: var(--color-text-light-2);
border-left: 0.25em solid var(--color-secondary);
@@ -316,6 +279,18 @@ In markup content, we always use bottom margin for all elements */
box-sizing: initial;
}
/* GitHub-style dark/light image switching.
These fragments are kept in the rendered HTML as part of the `src` attribute.
We decide which to show via `data-gitea-theme-dark` on `<html>`, which is
mirrored from `--is-dark-theme` in JS (so it also works with auto/custom themes).
*/
html[data-gitea-theme-dark="true"] .markup a[href*="#gh-light-mode-only"],
html[data-gitea-theme-dark="true"] .markup img[src*="#gh-light-mode-only"],
html[data-gitea-theme-dark="false"] .markup a[href*="#gh-dark-mode-only"],
html[data-gitea-theme-dark="false"] .markup img[src*="#gh-dark-mode-only"] {
display: none;
}
.file-view.markup {
padding: 1em 2em;
font-size: 16px;
@@ -340,12 +315,12 @@ In markup content, we always use bottom margin for all elements */
.markup img[align="right"],
.markup video[align="right"] {
padding-left: 20px;
padding-inline-start: 20px;
}
.markup img[align="left"],
.markup video[align="left"] {
padding-right: 28px;
padding-inline-end: 28px;
}
.markup span.frame {
@@ -376,69 +351,6 @@ In markup content, we always use bottom margin for all elements */
color: var(--color-text);
}
.markup span.align-center {
display: block;
overflow: hidden;
clear: both;
}
.markup span.align-center > span {
display: block;
margin: 13px auto 0;
overflow: hidden;
text-align: center;
}
.markup span.align-center span img,
.markup span.align-center span video {
margin: 0 auto;
text-align: center;
}
.markup span.align-right {
display: block;
overflow: hidden;
clear: both;
}
.markup span.align-right > span {
display: block;
margin: 13px 0 0;
overflow: hidden;
text-align: right;
}
.markup span.align-right span img,
.markup span.align-right span video {
margin: 0;
text-align: right;
}
.markup span.float-left {
display: block;
float: left;
margin-right: 13px;
overflow: hidden;
}
.markup span.float-left span {
margin: 13px 0 0;
}
.markup span.float-right {
display: block;
float: right;
margin-left: 13px;
overflow: hidden;
}
.markup span.float-right > span {
display: block;
margin: 13px auto 0;
overflow: hidden;
text-align: right;
}
.markup code,
.markup tt {
padding: 0.2em 0.4em;
@@ -524,13 +436,13 @@ In markup content, we always use bottom margin for all elements */
background-color: var(--color-markup-code-inline);
border: 1px solid var(--color-secondary);
border-radius: var(--border-radius);
box-shadow: inset 0 -1px 0 var(--color-secondary);
box-shadow: var(--box-shadow-kbd);
}
.markup .ui.list .list,
.markup ol.ui.list ol,
.markup ul.ui.list ul {
padding-left: 2em;
padding-inline-start: 2em;
}
.markup details.frontmatter-content summary {
@@ -546,8 +458,11 @@ In markup content, we always use bottom margin for all elements */
}
.external-render-iframe {
display: block; /* removes the inline baseline gap below the iframe */
width: 100%;
height: max(300px, 80vh);
border: none;
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
.markup-content-iframe {
@@ -601,3 +516,40 @@ In markup content, we always use bottom margin for all elements */
.file-view.markup.orgmode li.indeterminate > p {
display: inline-block;
}
/* auto-hide-control is a control element or a container for control elements,
it floats over the code-block and only shows when the code-block is hovered. */
.markup .auto-hide-control {
position: absolute;
z-index: 1;
visibility: hidden; /* prevent from click events even opacity=0 */
opacity: 0;
transition: var(--transition-hover-fade);
}
/* all rendered code-block elements are in their container,
the manually written code-block elements on "packages" pages don't have the container */
.markup .code-block-container:hover .auto-hide-control,
.markup .code-block:hover .auto-hide-control {
visibility: visible;
opacity: 1;
}
@media (hover: none) {
.markup .auto-hide-control {
visibility: visible;
opacity: 1;
}
}
/* can not use regular transparent button colors for hover and active states
because we need opaque colors here as code can appear behind the button */
.markup .auto-hide-control.ui.button:hover,
.markup .auto-hide-control .ui.button:hover {
background: var(--color-secondary) !important;
}
.markup .auto-hide-control.ui.button:active,
.markup .auto-hide-control .ui.button:active {
background: var(--color-secondary-dark-1) !important;
}
@@ -28,7 +28,7 @@
aspect-ratio: 1;
transform: translate(-50%, -50%);
animation: isloadingspin 1000ms infinite linear;
border-width: 4px;
border-width: 3px;
border-style: solid;
border-color: var(--color-secondary) var(--color-secondary) var(--color-secondary-dark-8) var(--color-secondary-dark-8);
border-radius: var(--border-radius-full);
@@ -82,15 +82,6 @@ code.language-math.is-loading::after {
}
}
@keyframes fadeout {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
/* 1p5 means 1-point-5. it can't use "pulse" here, otherwise the animation is not right (maybe due to some conflicts */
@keyframes pulse-1p5 {
0% {
@@ -1,6 +1,7 @@
.breadcrumb {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 3px;
overflow-wrap: anywhere;
}
@@ -1,6 +1,8 @@
.ui.button {
cursor: pointer;
display: inline-flex;
align-items: center;
min-width: 0;
outline: none;
font-family: var(--fonts-regular);
margin: 0 0.25em 0 0;
@@ -100,11 +102,13 @@
/* reference sizes (not exactly at the moment): normal: padding-x=21, height=38 ; compact: padding-x=15, height=32 */
.ui.button { /* stylelint-disable-line no-duplicate-selectors */
gap: var(--gap-block);
min-height: 38px;
padding: 0.57em /* around 8px */ 1.43em /* around 20px */;
}
.ui.compact.buttons .button,
.ui.compact.button {
gap: var(--gap-inline);
padding: 0.42em /* around 8px */ 1.07em /* around 15px */;
min-height: 32px;
}
@@ -118,6 +122,7 @@
.ui.mini.buttons .dropdown .menu > .item,
.ui.mini.buttons .button,
.ui.ui.ui.ui.mini.button {
gap: var(--gap-inline);
font-size: 11px;
min-height: 30px;
}
@@ -130,6 +135,7 @@
.ui.tiny.buttons .dropdown .menu > .item,
.ui.tiny.buttons .button,
.ui.ui.ui.ui.tiny.button {
gap: var(--gap-inline);
font-size: 12px;
min-height: 32px;
}
@@ -208,7 +214,6 @@
.ui.fluid.button {
width: 100%;
display: block;
}
.ui.primary.button,
@@ -0,0 +1,55 @@
/*
Show the escaped and hide the real char:
<span class="broken-code-point" data-escaped="␡"><span class="char">{real-char}</span></span>
Only show the real-char:
<span class="broken-code-point">{real-char}</span>
*/
.broken-code-point:not([data-escaped]) {
border-radius: 4px;
padding: 0 2px;
border: 1px var(--color-yellow) solid;
}
.broken-code-point[data-escaped] {
position: relative;
}
.broken-code-point[data-escaped]::before {
visibility: visible;
content: attr(data-escaped);
border-radius: 2px;
padding: 0 1px;
border: 1px var(--color-yellow) solid;
}
.broken-code-point[data-escaped] .char {
/* keep the original character selectable/copyable while showing the escaped label via ::before */
position: absolute;
left: 0;
opacity: 0;
pointer-events: none;
}
/*
Show the escaped and hide the real-char:
<span class="unicode-escaped">
<span class="escaped-code-point" data-escaped="[U+1F600]"><span class="char">{real-char}</span></span>
</span>
Hide the escaped and show the real-char:
<span>
<span class="escaped-code-point" data-escaped="[U+1F600]"><span class="char">{real-char}</span></span>
</span>
*/
.unicode-escaped .escaped-code-point[data-escaped]::before {
visibility: visible;
content: attr(data-escaped);
color: var(--color-red);
}
.unicode-escaped .escaped-code-point .char {
display: none;
}
.unicode-escaped .ambiguous-code-point {
border: 1px var(--color-yellow) solid;
}
@@ -1,10 +1,75 @@
/* 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"] {
appearance: none;
width: var(--checkbox-size);
height: var(--checkbox-size);
border: 1px solid var(--color-input-border);
border-radius: 50%;
background: var(--color-input-background);
}
input[type="radio"]:checked {
background: var(--color-white);
border: 4px solid var(--color-primary);
}
input[type="checkbox"] {
appearance: none;
display: inline-grid;
place-content: center;
width: var(--checkbox-size);
height: var(--checkbox-size);
border: 1px solid var(--color-input-border);
border-radius: 3px;
background: var(--color-input-background);
overflow: hidden;
print-color-adjust: exact;
}
input[type="checkbox"]::before {
content: "";
background: var(--color-white);
width: var(--checkbox-size);
height: var(--checkbox-size);
clip-path: inset(var(--checkbox-size) 0 0 0);
mask-image: var(--checkbox-mask-checked);
-webkit-mask-image: var(--checkbox-mask-checked);
mask-size: 75%;
-webkit-mask-size: 75%;
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-position: center;
-webkit-mask-position: center;
}
input[type="checkbox"]:checked,
input[type="checkbox"]:indeterminate {
background: var(--color-primary);
border-color: var(--color-primary);
}
input[type="checkbox"]:checked::before {
clip-path: inset(0);
}
input[type="checkbox"]:disabled:checked,
input[type="checkbox"]:disabled:indeterminate {
background: var(--color-secondary-dark-4);
border-color: var(--color-secondary-dark-4);
}
input[type="radio"]:disabled:checked {
border-color: var(--color-secondary-dark-4);
}
input[type="checkbox"]:indeterminate::before {
clip-path: inset(0);
mask-image: var(--checkbox-mask-indeterminate);
-webkit-mask-image: var(--checkbox-mask-indeterminate);
mask-size: 75%;
-webkit-mask-size: 75%;
}
.ui.checkbox {
@@ -26,17 +91,9 @@ input[type="radio"] {
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,
@@ -0,0 +1,79 @@
/* https://github.com/alecthomas/chroma/blob/6428fb4e65f3c1493491571c8a6a8f1add1da822/types.go#L208 */
/* structural */
.chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
.chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block; }
.chroma .hl { display: block; width: 100%; }
.chroma .lnt { margin-right: 0.4em; padding: 0 0.4em; }
.chroma .ln { margin-right: 0.4em; padding: 0 0.4em; }
/* tokens */
.chroma .bp { color: var(--color-syntax-name-pseudo); } /* NameBuiltinPseudo */
.chroma .c { color: var(--color-syntax-comment); } /* Comment */
.chroma .c1 { color: var(--color-syntax-comment); } /* CommentSingle */
.chroma .ch { color: var(--color-syntax-comment); } /* CommentHashbang */
.chroma .cm { color: var(--color-syntax-comment); } /* CommentMultiline */
.chroma .cp { color: var(--color-syntax-preproc); } /* CommentPreproc */
.chroma .cpf { color: var(--color-syntax-preproc-file); } /* CommentPreprocFile */
.chroma .cs { color: var(--color-syntax-comment-special); } /* CommentSpecial */
.chroma .dl { color: var(--color-syntax-string); } /* LiteralStringDelimiter */
.chroma .gd { color: var(--color-syntax-diff-fg); background-color: var(--color-syntax-deleted-bg); } /* GenericDeleted */
.chroma .ge { color: var(--color-syntax-emph); } /* GenericEmph */
.chroma .gh { color: var(--color-syntax-heading); } /* GenericHeading */
.chroma .gi { color: var(--color-syntax-diff-fg); background-color: var(--color-syntax-inserted-bg); } /* GenericInserted */
.chroma .gl { text-decoration: underline; } /* GenericUnderline */
.chroma .go { color: var(--color-syntax-output); } /* GenericOutput */
.chroma .gp { color: var(--color-syntax-prompt); } /* GenericPrompt */
.chroma .gr { color: var(--color-syntax-invalid); } /* GenericError */
.chroma .gs { color: var(--color-syntax-strong); font-weight: var(--font-weight-semibold); } /* GenericStrong */
.chroma .gt { color: var(--color-syntax-traceback); } /* GenericTraceback */
.chroma .gu { color: var(--color-syntax-subheading); } /* GenericSubheading */
.chroma .il { color: var(--color-syntax-number); } /* LiteralNumberIntegerLong */
.chroma .k { color: var(--color-syntax-keyword); } /* Keyword */
.chroma .kc { color: var(--color-syntax-bool); } /* KeywordConstant */
.chroma .kd { color: var(--color-syntax-keyword); } /* KeywordDeclaration */
.chroma .kn { color: var(--color-syntax-control); } /* KeywordNamespace */
.chroma .kp { color: var(--color-syntax-keyword); } /* KeywordPseudo */
.chroma .kr { color: var(--color-syntax-keyword); } /* KeywordReserved */
.chroma .kt { color: var(--color-syntax-type); } /* KeywordType */
.chroma .m { color: var(--color-syntax-number); } /* LiteralNumber */
.chroma .mb { color: var(--color-syntax-number); } /* LiteralNumberBin */
.chroma .mf { color: var(--color-syntax-number); } /* LiteralNumberFloat */
.chroma .mh { color: var(--color-syntax-number); } /* LiteralNumberHex */
.chroma .mi { color: var(--color-syntax-number); } /* LiteralNumberInteger */
.chroma .mo { color: var(--color-syntax-number); } /* LiteralNumberOct */
.chroma .n { color: var(--color-syntax-text); } /* Name */
.chroma .na { color: var(--color-syntax-attribute); } /* NameAttribute */
.chroma .nb { color: var(--color-syntax-name); } /* NameBuiltin */
.chroma .nc { color: var(--color-syntax-type); } /* NameClass */
.chroma .nd { color: var(--color-syntax-decorator); } /* NameDecorator */
.chroma .ne { color: var(--color-syntax-keyword); } /* NameException */
.chroma .nf { color: var(--color-syntax-name); } /* NameFunction */
.chroma .ni { color: var(--color-syntax-entity); } /* NameEntity */
.chroma .nl { color: var(--color-syntax-keyword); } /* NameLabel */
.chroma .nn { color: var(--color-syntax-namespace); } /* NameNamespace */
.chroma .no { color: var(--color-syntax-variable); } /* NameConstant */
.chroma .nt { color: var(--color-syntax-tag); } /* NameTag */
.chroma .nv { color: var(--color-syntax-variable); } /* NameVariable */
.chroma .nx { color: var(--color-syntax-text-alt); } /* NameOther */
.chroma .o { color: var(--color-syntax-operator); } /* Operator */
.chroma .ow { color: var(--color-syntax-operator); } /* OperatorWord */
.chroma .p { color: var(--color-syntax-punctuation); } /* Punctuation */
.chroma .s { color: var(--color-syntax-string); } /* LiteralString */
.chroma .s1 { color: var(--color-syntax-string); } /* LiteralStringSingle */
.chroma .s2 { color: var(--color-syntax-string); } /* LiteralStringDouble */
.chroma .sa { color: var(--color-syntax-string-special); } /* LiteralStringAffix */
.chroma .sb { color: var(--color-syntax-string); } /* LiteralStringBacktick */
.chroma .sc { color: var(--color-syntax-string-special); } /* LiteralStringChar */
.chroma .sd { color: var(--color-syntax-string); } /* LiteralStringDoc */
.chroma .se { color: var(--color-syntax-escape); } /* LiteralStringEscape */
.chroma .sh { color: var(--color-syntax-string); } /* LiteralStringHeredoc */
.chroma .si { color: var(--color-syntax-string-special); } /* LiteralStringInterpol */
.chroma .sr { color: var(--color-syntax-regexp); } /* LiteralStringRegex */
.chroma .ss { color: var(--color-syntax-escape); } /* LiteralStringSymbol */
.chroma .sx { color: var(--color-syntax-string); } /* LiteralStringOther */
.chroma .vc { color: var(--color-syntax-variable); } /* NameVariableClass */
.chroma .vg { color: var(--color-syntax-variable); } /* NameVariableGlobal */
.chroma .vi { color: var(--color-syntax-variable); } /* NameVariableInstance */
.chroma .w { color: var(--color-syntax-whitespace); } /* TextWhitespace */
.chroma .err {/* not styled because Chroma uses it on too many things like JSX */} /* Error */
@@ -0,0 +1,569 @@
.code-editor-options {
flex-wrap: wrap;
}
.editor-loading {
padding: 1rem;
text-align: center;
}
.editor-loading.is-loading {
width: 100%;
min-height: 200px;
height: 90vh;
}
.edit.githook .code-editor-container {
border: 1px solid var(--color-secondary);
}
/* editor layout */
.code-editor-container .cm-editor {
color: var(--color-text);
background-color: var(--color-code-bg);
font-family: var(--fonts-monospace);
font-size: 12px;
max-height: 90vh;
}
.code-editor-container .cm-editor,
.code-editor-container .cm-editor .cm-scroller {
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
.code-editor-container .cm-content,
.code-editor-container .cm-gutter {
min-height: 200px;
}
.code-editor-container .cm-scroller {
overflow: auto;
line-height: var(--line-height-code);
}
.code-editor-container .cm-content * {
caret-color: inherit;
}
.code-editor-container .cm-content {
padding: 0;
}
.code-editor-container .cm-cursor,
.code-editor-container .cm-dropCursor {
border-left-color: var(--color-caret);
}
.code-editor-container .cm-editor.cm-focused {
outline: none;
}
.code-editor-container .cm-editor.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground,
.code-editor-container .cm-selectionBackground {
background-color: var(--color-primary-alpha-30);
}
.code-editor-container .cm-panels {
background-color: var(--color-body);
color: var(--color-text);
border-color: var(--color-secondary);
font-family: var(--fonts-regular);
font-size: 13px;
}
.code-editor-container .cm-panel.cm-search {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px;
position: relative;
padding-right: 24px;
}
.code-editor-container .cm-panel.cm-search br {
display: none;
}
.code-editor-container .cm-panel.cm-search::after {
content: "";
flex-basis: 100%;
order: 1;
}
.code-editor-container .cm-panel.cm-search .cm-textfield {
width: 200px;
}
.code-editor-container .cm-panel.cm-search br + input.cm-textfield,
.code-editor-container .cm-panel.cm-search br + input.cm-textfield ~ * {
order: 2;
}
.code-editor-container .cm-panel.cm-search input,
.code-editor-container .cm-panel.cm-search button,
.code-editor-container .cm-panel.cm-search label {
margin: 0;
}
.code-editor-container .cm-panel.cm-search .cm-button + label,
.code-editor-container .cm-panel.cm-search label + label {
margin-left: 4px;
}
.code-editor-container .cm-panel.cm-search label {
display: inline-flex;
align-items: center;
gap: 4px;
}
.code-editor-container .cm-panel.cm-search input[type="checkbox"] {
cursor: pointer;
}
.code-editor-container .cm-editor .cm-panel button[name="close"] {
font-size: 0;
width: 16px;
height: 16px;
background-color: currentcolor;
mask-image: var(--octicon-x);
mask-size: cover;
-webkit-mask-image: var(--octicon-x);
-webkit-mask-size: cover;
border-radius: var(--border-radius);
}
.code-editor-container .cm-panel.cm-search button[name="close"] {
position: absolute;
right: 8px;
top: 8px;
}
.code-editor-container .cm-editor .cm-panel button[name="close"]:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
.code-editor-container .cm-activeLine,
.code-editor-container .cm-activeLineGutter {
background-color: var(--color-editor-line-highlight);
}
.code-editor-container .cm-gutters {
background-color: var(--color-code-bg);
color: var(--color-secondary-dark-6);
border-right: none;
}
.code-editor-container .cm-gutters .cm-lineNumbers .cm-gutterElement {
min-width: 30px;
display: flex;
align-items: center;
justify-content: flex-end;
}
.code-editor-container .cm-foldGutter .cm-gutterElement {
display: flex;
align-items: center;
justify-content: center;
padding: 0 2px;
overflow: hidden;
}
.code-editor-container .cm-foldGutter .cm-gutterElement svg {
color: var(--color-text-light-2);
cursor: pointer;
visibility: hidden;
}
.code-editor-container .cm-gutters:hover .cm-foldGutter .cm-gutterElement svg {
visibility: visible;
}
.code-editor-container .cm-foldGutter .cm-gutterElement:hover svg {
color: var(--color-text);
}
.code-editor-container .cm-gutters .cm-lineNumbers .cm-activeLineGutter {
color: var(--color-text-light);
}
.code-editor-container .cm-editor .cm-line ::selection,
.code-editor-container .cm-editor .cm-line::selection {
color: currentcolor;
background-color: var(--color-editor-selection);
}
.code-editor-container .cm-foldPlaceholder {
display: inline-flex;
align-items: center;
background: none;
border: none;
color: var(--color-text-light-2);
cursor: pointer;
vertical-align: middle;
padding: 0 1px;
margin: 0 2px;
}
.code-editor-container .cm-foldPlaceholder:hover {
color: var(--color-text);
}
.code-editor-container .cm-searchMatch {
background-color: var(--color-highlight-bg);
outline: 1px solid var(--color-highlight-fg);
}
.code-editor-container .cm-searchMatch-selected {
background-color: var(--color-primary-alpha-30);
}
.code-editor-container .cm-selectionMatch {
background-color: var(--color-highlight-bg);
}
.code-editor-container .cm-tooltip {
background-color: var(--color-body);
color: var(--color-text);
border-color: var(--color-secondary);
}
.code-editor-container .cm-tooltip.cm-tooltip-autocomplete > ul > li {
display: flex;
align-items: center;
padding: 4px;
}
.code-editor-container .cm-completionIcon {
display: flex;
align-items: center;
font-size: 16px;
padding-right: 4px;
color: var(--color-text-light);
opacity: 1;
}
.code-editor-container .cm-tooltip-autocomplete ul li[aria-selected] {
background-color: var(--color-primary-alpha-30);
color: var(--color-text);
}
.code-editor-container .cm-completionMatchedText {
text-decoration: none;
color: var(--color-primary-dark-1);
font-weight: var(--font-weight-semibold);
}
.code-editor-container .cm-placeholder {
color: var(--color-placeholder-text);
}
.code-editor-container .cm-button {
background-image: none;
background-color: var(--color-button);
color: var(--color-text);
border: 1px solid var(--color-secondary);
border-radius: var(--border-radius);
height: 28px;
padding: 0 10px;
cursor: pointer;
font-size: 12px;
}
.code-editor-container .cm-button:hover {
background-color: var(--color-hover);
}
.code-editor-container .cm-textfield {
background-color: var(--color-input-background);
color: var(--color-input-text);
border: 1px solid var(--color-input-border);
border-radius: var(--border-radius);
height: 28px;
padding: 0 6px;
}
.code-editor-container .cm-textfield:focus {
border-color: var(--color-primary);
}
.code-editor-container .cm-specialChar {
color: var(--color-syntax-invalid);
}
.code-editor-container .cm-trailingSpace {
background-color: var(--color-error-bg);
}
.code-editor-container .cm-activeLine .cm-trailingSpace {
background-color: transparent;
}
.code-editor-container.cm-mod-held .cm-url {
text-decoration: underline dotted var(--color-syntax-link);
cursor: pointer;
}
.code-editor-container .cm-editor.cm-focused .cm-matchingBracket {
background-color: var(--color-syntax-matching-bracket-bg);
}
.code-editor-container .cm-editor.cm-focused .cm-nonmatchingBracket {
background-color: var(--color-syntax-nonmatching-bracket-bg);
}
.code-editor-container .cm-panels-top {
border-bottom-color: var(--color-secondary);
}
.code-editor-container .cm-panels-bottom {
border-top-color: var(--color-secondary);
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
.code-editor-container .cm-snippetField {
background-color: var(--color-primary-alpha-10);
}
.code-editor-container .cm-snippetFieldPosition {
border-left-color: var(--color-text-light-3);
}
.code-editor-container .cm-tooltip.cm-tooltip-autocomplete > ul > completion-section {
border-bottom-color: var(--color-secondary);
}
.code-editor-container .cm-tooltip-autocomplete-disabled ul li[aria-selected] {
background-color: var(--color-secondary);
}
/* command palette */
.code-editor-container {
position: relative;
min-height: 90vh;
}
.cm-command-palette {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
z-index: 301;
width: 400px;
max-width: min(calc(100% - 16px), 90vw);
background-color: var(--color-body);
border: 1px solid var(--color-secondary);
border-radius: 0 0 var(--border-radius) var(--border-radius);
box-shadow: 0 4px 12px var(--color-shadow);
font-family: var(--fonts-regular);
font-size: 13px;
display: flex;
flex-direction: column;
gap: 4px;
padding: 4px;
}
.cm-command-palette-input {
display: block;
width: 100%;
box-sizing: border-box;
padding: 4px 6px !important;
border: 1px solid var(--color-secondary);
border-radius: var(--border-radius);
background-color: var(--color-input-background);
color: var(--color-input-text);
font-size: 13px;
outline: none;
}
.cm-command-palette-input:focus {
border-color: var(--color-primary);
}
.cm-command-palette-list {
position: relative;
max-height: calc(8 * 24px);
overflow-y: auto;
}
.cm-command-palette-item {
display: flex;
align-items: center;
justify-content: space-between;
height: 24px;
padding: 0 6px;
border-radius: 2px;
cursor: pointer;
}
.cm-command-palette-item[aria-selected="true"] {
background-color: var(--color-primary-alpha-30);
}
.cm-command-palette-empty {
color: var(--color-text-light);
text-align: center;
}
.cm-command-palette-label {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.cm-command-palette-label mark {
background: none;
color: var(--color-primary-dark-1);
font-weight: var(--font-weight-semibold);
}
.cm-command-palette-keys {
flex-shrink: 0;
display: flex;
align-items: center;
gap: 2px;
margin-left: 8px;
}
.code-editor-container .cm-gutter-lint {
width: 14px;
}
.code-editor-container .cm-gutter-lint .cm-gutterElement {
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
.code-editor-container .cm-lint-marker-error {
content: "";
width: 8px;
height: 8px;
border-radius: 50%;
background-color: var(--color-red);
}
.code-editor-container .cm-lint-marker-warning {
content: "";
width: 8px;
height: 8px;
border-radius: 50%;
background-color: var(--color-yellow);
}
.code-editor-container .cm-lintRange-error {
background-image: none;
text-decoration: wavy underline var(--color-red);
text-underline-offset: 3px;
}
.code-editor-container .cm-lintRange-warning {
background-image: none;
text-decoration: wavy underline var(--color-yellow);
text-underline-offset: 3px;
}
.code-editor-container .cm-lintPoint-error::after {
border-bottom-color: var(--color-red);
}
.code-editor-container .cm-lintPoint-warning::after {
border-bottom-color: var(--color-yellow);
}
.code-editor-container .cm-tooltip-lint {
background-color: var(--color-body);
color: var(--color-text);
border-color: var(--color-secondary);
}
.code-editor-container .cm-panel.cm-panel-lint button[name="close"] {
top: 4px;
right: 8px;
}
.code-editor-container .cm-panel.cm-panel-lint ul {
max-height: 120px;
outline: none;
}
.code-editor-container .cm-panel.cm-panel-lint [aria-selected] {
background-color: var(--color-primary-alpha-30) !important;
}
.code-editor-container .cm-diagnostic.cm-diagnostic-error {
border-left-color: var(--color-red);
}
.code-editor-container .cm-diagnostic.cm-diagnostic-warning {
border-left-color: var(--color-yellow);
}
/* syntax highlighting classes from @lezer/highlight classHighlighter */
.code-editor-container .tok-keyword,
.code-editor-container .tok-atom { color: var(--color-syntax-keyword); }
.code-editor-container .tok-bool { color: var(--color-syntax-bool); }
.code-editor-container .tok-variableName { color: var(--color-syntax-variable); }
.code-editor-container .tok-variableName2 { color: var(--color-syntax-keyword); }
.code-editor-container .tok-propertyName { color: var(--color-syntax-property); }
.code-editor-container .tok-typeName,
.code-editor-container .tok-className { color: var(--color-syntax-type); }
.code-editor-container .tok-namespace { color: var(--color-syntax-namespace); }
.code-editor-container .tok-macroName { color: var(--color-syntax-name); }
.code-editor-container .tok-labelName { color: var(--color-syntax-name); }
.code-editor-container .tok-number { color: var(--color-syntax-number); }
.code-editor-container .tok-string { color: var(--color-syntax-string); }
.code-editor-container .tok-string2 { color: var(--color-syntax-regexp); }
.code-editor-container .tok-operator { color: var(--color-syntax-operator); }
.code-editor-container .tok-punctuation { color: var(--color-syntax-punctuation); }
.code-editor-container .tok-comment { color: var(--color-syntax-comment); }
.code-editor-container .tok-meta { color: var(--color-syntax-preproc); }
.code-editor-container .tok-invalid { color: var(--color-syntax-invalid); }
.code-editor-container .tok-link { color: var(--color-syntax-link); }
.code-editor-container .tok-heading { color: var(--color-syntax-heading); }
.code-editor-container .tok-emphasis { color: var(--color-syntax-emph); font-style: italic; }
.code-editor-container .tok-strong { font-weight: var(--font-weight-bold); }
.code-editor-container .tok-inserted { color: var(--color-syntax-string); }
.code-editor-container .tok-deleted { color: var(--color-syntax-invalid); }
/* language-specific overrides */
.code-editor-container[data-language="json"] .tok-propertyName,
.code-editor-container[data-language="json5"] .tok-propertyName,
.code-editor-container[data-language="yaml"] .tok-propertyName { color: var(--color-syntax-tag); }
.code-editor-container[data-language="css"] .tok-propertyName { color: var(--color-syntax-name); }
.code-editor-container[data-language="html"] .tok-propertyName,
.code-editor-container[data-language="xml"] .tok-propertyName { color: var(--color-syntax-attribute); }
/* context menu — uses tippy "menu" theme for base styling */
.cm-context-menu {
min-width: 200px;
font-size: 13px;
user-select: none;
}
.cm-context-menu .item {
height: 24px !important;
padding: 0 12px !important;
cursor: pointer;
}
.cm-context-menu .item.disabled {
color: var(--color-text-light-3);
cursor: default;
pointer-events: none;
}
.cm-context-menu-keys {
display: flex;
align-items: center;
gap: 2px;
margin-left: auto;
padding-left: 30px;
}
.cm-context-menu-separator {
border-top: 1px solid var(--color-secondary);
margin: 4px 0;
}
@@ -56,15 +56,6 @@
min-width: 0;
}
.ui.comments .comment .author {
font-size: 1em;
font-weight: var(--font-weight-medium);
}
.ui.comments .comment a.author {
cursor: pointer;
}
.ui.comments .comment .metadata {
display: inline-block;
margin-left: 0.5em;
@@ -14,3 +14,21 @@
.ui.container.medium-width {
width: 800px;
}
.ui.message.web-banner-container {
position: relative;
margin: 0;
border-radius: 0;
}
.ui.message.web-banner-container > .web-banner-content {
width: 1280px;
max-width: calc(100% - calc(2 * var(--page-margin-x)));
margin: auto;
}
.ui.message.web-banner-container > button.dismiss-banner {
position: absolute;
right: 20px;
top: 15px;
}
@@ -36,8 +36,3 @@ h4.divider {
.divider.divider-text::after {
margin-left: .75em;
}
.ui.dropdown .menu > .divider {
border-top: 1px solid var(--color-secondary);
margin: 4px 0;
}
@@ -0,0 +1,954 @@
/* These are the remnants of the fomantic dropdown module */
.ui.dropdown {
cursor: pointer;
position: relative;
display: inline-block;
outline: none;
text-align: left;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.ui.dropdown .menu {
cursor: auto;
position: absolute;
display: none;
outline: none;
top: 100%;
min-width: max-content;
margin: 0;
padding: 0;
background: var(--color-menu);
font-size: 1em;
text-align: left;
box-shadow: 0 6px 18px var(--color-shadow);
border: 1px solid var(--color-secondary);
border-radius: 0.28571429rem;
z-index: 11;
left: 0;
}
.ui.dropdown .menu > * {
white-space: nowrap;
}
.ui.dropdown > input:not(.search):first-child,
.ui.dropdown > select {
display: none !important;
}
.ui.dropdown > .dropdown.icon {
line-height: 1;
height: 1em;
width: auto;
backface-visibility: hidden;
text-align: center;
}
.ui.dropdown:not(.labeled) > .dropdown.icon {
position: relative;
width: auto;
font-size: 0.85714286em;
margin: 0 0 0 1em;
}
.ui.dropdown > .text {
display: inline-block;
}
.ui.dropdown .menu > .item {
position: relative;
cursor: pointer;
display: block;
border: none;
height: auto;
min-height: 2.57142857rem;
text-align: left;
border-top: none;
line-height: var(--line-height-default);
font-size: 1rem;
color: var(--color-text);
padding: 0.78571429rem 1.14285714rem !important;
text-transform: none;
font-weight: var(--font-weight-normal);
box-shadow: none;
-webkit-touch-callout: none;
}
.ui.dropdown .menu > .item:first-child {
border-top-width: 0;
}
.ui.dropdown .menu > .header {
margin: 1rem 0 0.75rem;
padding: 0 1.14285714rem;
font-weight: var(--font-weight-medium);
text-transform: none;
}
.ui.dropdown .menu > .header:not(.ui) {
color: var(--color-text);
font-size: 0.95em;
}
.ui.dropdown .menu > .divider {
border-top: 1px solid var(--color-secondary);
height: 0;
margin: 4px 0;
}
.ui.dropdown.dropdown .menu > .input {
width: auto;
display: flex;
margin: 1.14285714rem 0.78571429rem;
min-width: 10rem;
}
.ui.dropdown .menu > .header + .input {
margin-top: 0;
}
.ui.dropdown .menu > .input:not(.transparent) input {
padding: 0.5em 1em;
}
.ui.dropdown .menu > .input:not(.transparent) .button,
.ui.dropdown .menu > .input:not(.transparent) i.icon,
.ui.dropdown .menu > .input:not(.transparent) .label {
padding-top: 0.5em;
padding-bottom: 0.5em;
}
.ui.dropdown > .text > .description,
.ui.dropdown .menu > .item > .description {
float: right;
margin: 0 0 0 1em;
color: var(--color-text-light-2);
}
.ui.dropdown .menu > .message {
padding: 0.78571429rem 1.14285714rem;
font-weight: var(--font-weight-normal);
}
.ui.dropdown .menu > .message:not(.ui) {
color: var(--color-text-light-2);
}
/* Remove Menu Item Divider */
.ui.dropdown .ui.menu > .item::before,
.ui.menu .ui.dropdown .menu > .item::before {
display: none;
}
/* Prevent Menu Item Border */
.ui.menu .ui.dropdown .menu .active.item {
border-left: none;
}
/* Automatically float dropdown menu right on last menu item */
.ui.menu .right.menu .dropdown:last-child > .menu:not(.left),
.ui.menu .right.dropdown.item > .menu:not(.left),
.ui.buttons > .ui.dropdown:last-child > .menu:not(.left) {
left: auto;
right: 0;
}
.ui.button.dropdown .menu {
min-width: 100%;
}
select.ui.dropdown {
height: 38px;
padding: 0.5em;
border: 1px solid var(--color-input-border);
visibility: visible;
}
.ui.selection.dropdown {
cursor: pointer;
overflow-wrap: break-word;
line-height: 1em;
white-space: normal;
outline: 0;
transform: rotateZ(0deg);
min-width: 14em;
min-height: 2.71428571em;
background: var(--color-input-background);
display: inline-block;
padding: 0.78571429em 3.2em 0.78571429em 1em;
color: var(--color-input-text);
box-shadow: none;
border: 1px solid var(--color-input-border);
border-radius: 0.28571429rem;
}
.ui.selection.dropdown.visible,
.ui.selection.dropdown.active {
z-index: 10;
}
.ui.selection.dropdown > .search.icon,
.ui.selection.dropdown > .delete.icon,
.ui.selection.dropdown > .dropdown.icon {
cursor: pointer;
position: absolute;
width: auto;
height: auto;
line-height: 1.21428571em;
top: 0.78571429em;
right: 1em;
z-index: 3;
margin: -0.78571429em;
padding: 0.91666667em;
opacity: 0.8;
}
.ui.selection.dropdown .menu {
overflow-x: hidden;
overflow-y: auto;
backface-visibility: hidden;
-webkit-overflow-scrolling: touch;
border-top-width: 0 !important;
outline: none;
margin: 0 -1px;
min-width: calc(100% + 2px);
width: calc(100% + 2px);
border-radius: 0 0 0.28571429rem 0.28571429rem;
box-shadow: 0 6px 18px var(--color-shadow);
}
.ui.selection.dropdown .menu::after,
.ui.selection.dropdown .menu::before {
display: none;
}
.ui.selection.dropdown .menu > .message {
padding: 0.78571429rem 1.14285714rem;
}
@media only screen and (max-width: 767.98px) {
.ui.selection.dropdown .menu {
max-height: 8.01428571rem;
}
}
@media only screen and (min-width: 768px) {
.ui.selection.dropdown .menu {
max-height: 10.68571429rem;
}
}
@media only screen and (min-width: 992px) {
.ui.selection.dropdown .menu {
max-height: 16.02857143rem;
}
}
@media only screen and (min-width: 1920px) {
.ui.selection.dropdown .menu {
max-height: 21.37142857rem;
}
}
.ui.selection.dropdown .menu > .item {
border-top: 1px solid var(--color-secondary);
padding: 0.78571429rem 1.14285714rem !important;
white-space: normal;
overflow-wrap: normal;
}
.ui.selection.dropdown .menu .item:first-of-type {
border-radius: 0;
}
.ui.selection.dropdown .menu > .hidden.addition.item {
display: none;
}
.ui.selection.dropdown:hover {
box-shadow: none;
}
.ui.selection.active.dropdown {
border-color: var(--color-primary);
box-shadow: 0 6px 18px var(--color-shadow);
border-bottom-left-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.ui.selection.active.dropdown .menu {
border-color: var(--color-primary);
box-shadow: 0 6px 18px var(--color-shadow);
}
.ui.selection.dropdown:focus {
border-color: var(--color-primary);
box-shadow: none;
}
.ui.selection.dropdown:focus .menu {
border-color: var(--color-primary);
box-shadow: 0 6px 18px var(--color-shadow);
}
.ui.selection.visible.dropdown > .text:not(.default) {
font-weight: var(--font-weight-normal);
color: var(--color-text);
}
.ui.selection.active.dropdown:hover {
border-color: var(--color-primary);
box-shadow: 0 6px 18px var(--color-shadow);
}
.ui.selection.active.dropdown:hover .menu {
border-color: var(--color-primary);
box-shadow: 0 6px 18px var(--color-shadow);
}
.ui.active.selection.dropdown > .dropdown.icon,
.ui.visible.selection.dropdown > .dropdown.icon {
z-index: 3;
}
.ui.active.empty.selection.dropdown {
border-radius: 0.28571429rem !important;
box-shadow: none !important;
}
.ui.active.empty.selection.dropdown .menu {
border: none !important;
box-shadow: none !important;
}
.ui.search.dropdown > input.search {
background: none transparent !important;
border: none !important;
box-shadow: none !important;
cursor: text;
top: 0;
left: 1px;
width: 100%;
outline: none;
-webkit-tap-highlight-color: transparent;
padding: inherit;
position: absolute;
z-index: 2;
}
.ui.search.dropdown > .text {
cursor: text;
position: relative;
left: 1px;
z-index: auto;
}
.ui.search.selection.dropdown > input.search {
line-height: 1.21428571em;
padding: 0.67857143em 3.2em 0.67857143em 1em;
}
.ui.search.selection.dropdown > span.sizer {
line-height: 1.21428571em;
padding: 0.67857143em 3.2em 0.67857143em 1em;
display: none;
white-space: pre;
}
.ui.search.dropdown.active > input.search,
.ui.search.dropdown.visible > input.search {
cursor: auto;
}
.ui.search.dropdown.active > .text,
.ui.search.dropdown.visible > .text {
pointer-events: none;
}
.ui.active.search.dropdown input.search:focus + .text i.icon {
opacity: var(--opacity-disabled);
}
.ui.active.search.dropdown input.search:focus + .text {
color: var(--color-placeholder-text) !important;
}
.ui.search.dropdown .menu {
overflow-x: hidden;
overflow-y: auto;
backface-visibility: hidden;
-webkit-overflow-scrolling: touch;
}
@media only screen and (max-width: 767.98px) {
.ui.search.dropdown .menu {
max-height: 8.01428571rem;
}
}
@media only screen and (min-width: 768px) {
.ui.search.dropdown .menu {
max-height: 10.68571429rem;
}
}
@media only screen and (min-width: 992px) {
.ui.search.dropdown .menu {
max-height: 16.02857143rem;
}
}
@media only screen and (min-width: 1920px) {
.ui.search.dropdown .menu {
max-height: 21.37142857rem;
}
}
.ui.dropdown > .remove.icon {
cursor: pointer;
font-size: 0.85714286em;
margin: -0.78571429em;
padding: 0.91666667em;
right: 3em;
top: 0.78571429em;
position: absolute;
opacity: 0.6;
z-index: 3;
}
.ui.clearable.dropdown .text,
.ui.clearable.dropdown a:last-of-type {
margin-right: 1.5em;
}
.ui.dropdown select.noselection ~ .remove.icon,
.ui.dropdown input[value=""] ~ .remove.icon,
.ui.dropdown input:not([value]) ~ .remove.icon,
.ui.dropdown.loading > .remove.icon {
display: none;
}
.ui.ui.multiple.dropdown {
padding: 0.22619048em 3.2em 0.22619048em 0.35714286em;
}
.ui.multiple.dropdown .menu {
cursor: auto;
}
.ui.multiple.dropdown > .label {
display: inline-block;
white-space: normal;
font-size: 1em;
padding: 0.35714286em 0.78571429em;
margin: 0.14285714rem 0.28571429rem 0.14285714rem 0;
box-shadow: 0 0 0 1px var(--color-secondary) inset;
}
/* Text */
.ui.multiple.dropdown > .text {
position: static;
padding: 0;
max-width: 100%;
margin: 0.45238095em 0 0.45238095em 0.64285714em;
line-height: 1.21428571em;
}
.ui.multiple.dropdown > .text.default {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ui.multiple.dropdown > .label ~ input.search {
margin-left: 0.14285714em !important;
}
.ui.multiple.dropdown > .label ~ .text {
display: none;
}
.ui.multiple.search.dropdown,
.ui.multiple.search.dropdown > input.search {
cursor: text;
}
.ui.multiple.search.dropdown > .text {
display: inline-block;
position: absolute;
top: 0;
left: 0;
padding: inherit;
margin: 0.45238095em 0 0.45238095em 0.64285714em;
line-height: 1.21428571em;
}
.ui.multiple.search.dropdown > .label ~ .text {
display: none;
}
.ui.multiple.search.dropdown > input.search {
position: static;
padding: 0;
max-width: 100%;
margin: 0.45238095em 0 0.45238095em 0.64285714em;
width: 2.2em;
line-height: 1.21428571em;
}
.ui.dropdown .menu .active.item {
background: var(--color-active);
font-weight: var(--font-weight-normal);
color: var(--color-text);
box-shadow: none;
z-index: 12;
border-radius: 0;
}
.ui.dropdown .menu > .item:hover {
background: var(--color-hover);
color: var(--color-text);
z-index: 13;
}
.ui.dropdown .menu > .item:active {
color: var(--color-text);
background: var(--color-active);
}
.ui.dropdown:not(.button) > .default.text,
.ui.default.dropdown:not(.button) > .text {
color: var(--color-placeholder-text);
}
.ui.dropdown:not(.button) > input:focus ~ .default.text,
.ui.default.dropdown:not(.button) > input:focus ~ .text {
color: var(--color-placeholder-text);
}
.ui.loading.dropdown > i.icon {
height: 1em !important;
}
.ui.loading.selection.dropdown > i.icon {
padding: 1.5em 1.28571429em !important;
}
.ui.loading.dropdown > i.icon::before {
position: absolute;
content: "";
top: 50%;
left: 50%;
margin: -0.64285714em 0 0 -0.64285714em;
width: 1.28571429em;
height: 1.28571429em;
border-radius: 500rem;
border: 0.2em solid var(--color-secondary);
}
.ui.loading.dropdown > i.icon::after {
position: absolute;
content: "";
top: 50%;
left: 50%;
box-shadow: 0 0 0 1px transparent;
margin: -0.64285714em 0 0 -0.64285714em;
width: 1.28571429em;
height: 1.28571429em;
animation: loader 0.6s infinite linear;
border: 0.2em solid var(--color-text-light-2);
border-radius: 500rem;
}
.ui.dropdown .loading.menu {
display: block;
visibility: hidden;
z-index: -1;
}
.ui.dropdown > .loading.menu {
left: 0 !important;
right: auto !important;
}
.ui.dropdown > .menu .loading.menu {
left: 100% !important;
right: auto !important;
}
.ui.dropdown.selected,
.ui.dropdown .menu .selected.item {
color: var(--color-text);
background: var(--color-hover);
}
.ui.dropdown > .filtered.text {
visibility: hidden;
}
.ui.dropdown .filtered.item {
display: none !important;
}
.ui.disabled.dropdown,
.ui.dropdown .menu > .disabled.item {
cursor: default;
pointer-events: none;
opacity: var(--opacity-disabled);
}
.ui.dropdown > .left.menu {
left: auto !important;
right: 0 !important;
}
.ui.upward.dropdown > .menu {
top: auto;
bottom: 100%;
box-shadow: 0 6px 18px var(--color-shadow);
border-radius: 0.28571429rem 0.28571429rem 0 0;
}
.ui.dropdown .upward.menu {
top: auto !important;
bottom: 0 !important;
}
.ui.upward.selection.dropdown .menu {
border-top-width: 1px !important;
border-bottom-width: 0 !important;
box-shadow: 0 6px 18px var(--color-shadow);
}
.ui.upward.selection.dropdown:hover {
box-shadow: 0 0 2px 0 var(--color-shadow);
}
.ui.active.upward.selection.dropdown {
border-radius: 0 0 0.28571429rem 0.28571429rem !important;
}
.ui.upward.selection.dropdown.visible {
box-shadow: 0 0 3px 0 var(--color-shadow);
border-radius: 0 0 0.28571429rem 0.28571429rem !important;
}
.ui.upward.active.selection.dropdown:hover {
box-shadow: 0 0 3px 0 var(--color-shadow);
}
.ui.upward.active.selection.dropdown:hover .menu {
box-shadow: 0 6px 18px var(--color-shadow);
}
.ui.scrolling.dropdown .menu,
.ui.dropdown .scrolling.menu {
overflow-x: hidden;
overflow-y: auto;
}
.ui.scrolling.dropdown .menu {
overflow-x: hidden;
overflow-y: auto;
backface-visibility: hidden;
-webkit-overflow-scrolling: touch;
min-width: 100% !important;
width: auto !important;
}
.ui.dropdown .scrolling.menu {
position: static;
overflow-y: auto;
border: none;
box-shadow: none !important;
margin: 0 !important;
min-width: 100% !important;
width: auto !important;
border-top: 1px solid var(--color-secondary);
border-color: var(--color-secondary);
border-radius: 0 0 var(--border-radius) var(--border-radius) !important;
}
.ui.scrolling.dropdown .menu .item.item.item,
.ui.dropdown .scrolling.menu > .item.item.item {
border-top: none;
}
.ui.scrolling.dropdown .menu .item:first-child,
.ui.dropdown .scrolling.menu .item:first-child {
border-top: none;
}
.ui.dropdown > .animating.menu .scrolling.menu,
.ui.dropdown > .visible.menu .scrolling.menu {
display: block;
}
@media only screen and (max-width: 767.98px) {
.ui.scrolling.dropdown .menu,
.ui.dropdown .scrolling.menu {
max-height: 10.28571429rem;
}
}
@media only screen and (min-width: 768px) {
.ui.scrolling.dropdown .menu,
.ui.dropdown .scrolling.menu {
max-height: 15.42857143rem;
}
}
@media only screen and (min-width: 992px) {
.ui.scrolling.dropdown .menu,
.ui.dropdown .scrolling.menu {
max-height: 20.57142857rem;
}
}
@media only screen and (min-width: 1920px) {
.ui.scrolling.dropdown .menu,
.ui.dropdown .scrolling.menu {
max-height: 20.57142857rem;
}
}
.ui.fluid.dropdown {
display: block;
width: 100% !important;
min-width: 0;
}
.ui.fluid.dropdown > .dropdown.icon {
float: right;
}
.ui.floating.dropdown .menu {
left: 0;
right: auto;
box-shadow: 0 6px 18px var(--color-shadow) !important;
border-radius: 0.28571429rem !important;
}
.ui.floating.dropdown > .menu {
border-radius: 0.28571429rem !important;
}
.ui:not(.upward).floating.dropdown > .menu {
margin-top: 0.5em;
}
.ui.upward.floating.dropdown > .menu {
margin-bottom: 0.5em;
}
.ui.pointing.dropdown > .menu {
top: 100%;
margin-top: 0.78571429rem;
border-radius: 0.28571429rem;
}
.ui.pointing.dropdown > .menu:not(.hidden)::after {
display: block;
position: absolute;
pointer-events: none;
content: "";
visibility: visible;
transform: rotate(45deg);
width: 0.5em;
height: 0.5em;
box-shadow: -1px -1px 0 0 var(--color-secondary);
background: var(--color-menu);
z-index: 2;
top: -0.25em;
left: 50%;
margin: 0 0 0 -0.25em;
}
.ui.top.right.pointing.dropdown > .menu {
inset: 100% 0 auto auto;
margin: 1em 0 0;
}
.ui.top.pointing.dropdown > .left.menu::after,
.ui.top.right.pointing.dropdown > .menu::after {
top: -0.25em;
left: auto !important;
right: 1em !important;
margin: 0;
transform: rotate(45deg);
}
.ui.dropdown,
.ui.dropdown .menu > .item {
font-size: 1rem;
}
.ui.mini.dropdown,
.ui.mini.dropdown .menu > .item {
font-size: 0.78571429rem;
}
.ui.tiny.dropdown,
.ui.tiny.dropdown .menu > .item {
font-size: 0.85714286rem;
}
.ui.small.dropdown,
.ui.small.dropdown .menu > .item {
font-size: 0.92857143rem;
}
/* This rule must come AFTER .ui.selection.dropdown because both have
specificity (0,3,0) and source order determines the winner.
In the original codebase this was in base.css which loaded after fomantic. */
.ui.dropdown:not(.button) {
line-height: var(--line-height-default);
}
/* Icons / Flags / Labels / Image */
.ui.dropdown > .text > img,
.ui.dropdown > .text > .image,
.ui.dropdown .menu > .item > .image,
.ui.dropdown .menu > .item > img {
margin-left: 0;
float: none;
margin-right: 0.78571429rem;
}
.ui.dropdown .menu > .item > svg {
margin-right: 0.78rem;
}
/* extend fomantic style '.ui.dropdown > .text > img' to include svg.img */
.ui.dropdown > .text > .img {
margin-left: 0;
float: none;
margin-right: 0.78571429rem;
}
.ui.dropdown > .text > img,
.ui.dropdown > .text > .image:not(.icon),
.ui.dropdown .menu > .item > .image:not(.icon),
.ui.dropdown .menu > .item > img {
display: inline-block;
vertical-align: top;
width: auto;
margin-top: -0.25rem;
margin-bottom: -0.25rem;
max-height: 2em;
}
.ui.dropdown .menu .item {
border-radius: 0;
}
.ui.dropdown .menu .item:first-of-type {
border-radius: var(--border-radius) var(--border-radius) 0 0;
}
.ui.dropdown .menu .item:last-of-type {
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
/* Gitea uses SVG images instead of Fomantic builtin "<i>" font icons, so we need to reset the icon styles */
.ui.ui.dropdown > .icon.icon {
position: initial;
padding: 0;
margin: 0;
height: auto;
}
.ui.ui.dropdown > .icon.icon:hover {
opacity: 1;
}
.ui.ui.button.dropdown > .icon.icon,
.ui.ui.selection.dropdown > .icon.icon {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
.ui.ui.dropdown > .dropdown.icon {
right: 0.5em;
}
.ui.ui.dropdown > .remove.icon {
right: 2em;
}
.ui.dropdown.mini.button,
.ui.dropdown.tiny.button {
padding-right: 20px;
}
.ui.dropdown.button {
padding-right: 22px;
}
.ui.multiple.selection.dropdown {
flex-wrap: wrap;
}
.ui.ui.dropdown.selection {
min-width: 14em;
}
.ui.dropdown .ui.label .svg {
vertical-align: middle;
}
.ui.dropdown > .ui.button {
margin: 0;
}
/* popover box shadow for menu dropdown */
.ui.menu .dropdown.item .menu {
box-shadow: 0 6px 18px var(--color-shadow);
}
.ui.dropdown .menu.context-user-switch .scrolling.menu {
border-radius: 0 !important;
box-shadow: none !important;
border-bottom: 1px solid var(--color-secondary);
max-width: 80vw;
}
.scrolling.menu .item.selected {
font-weight: var(--font-weight-semibold) !important;
}
/* to override Fomantic's default display: block for ".menu .item", and use a slightly larger gap for menu item content
the "!important" is necessary to override Fomantic UI menu item styles, meanwhile we should keep the "hidden" items still hidden */
.ui.dropdown .menu.flex-items-menu > .item:not(.hidden, .filtered, .tw-hidden) {
display: flex !important;
align-items: center;
gap: var(--gap-block);
min-width: 0;
}
.ui.dropdown .menu.flex-items-menu > .item img,
.ui.dropdown .menu.flex-items-menu > .item svg {
margin: 0;
}
.ui.dropdown.ellipsis-text-items {
/* reset y padding and use the line-height below instead, to avoid the "overflow: hidden" clips the larger image in the "text" element */
padding-top: 0;
padding-bottom: 0;
}
.ui.dropdown.ellipsis-text-items > .text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 2.71;
}
.ui.dropdown.ellipsis-text-items .menu > .item {
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
}
@@ -0,0 +1,483 @@
.ui.form {
position: relative;
max-width: 100%;
}
.ui.form > p {
margin: 1em 0;
}
.ui.form .field {
clear: both;
margin: 0 0 1em;
}
.ui.form .fields .fields,
.ui.form .field:last-child,
.ui.form .fields:last-child .field {
margin-bottom: 0;
}
.ui.form .fields .field {
clear: both;
margin: 0;
}
.ui.form .field > label {
display: block;
margin: 0 0 0.28571429rem;
color: var(--color-text);
font-size: 0.92857143em;
font-weight: var(--font-weight-medium);
text-transform: none;
}
.ui.form textarea,
.ui.form input:not([type="checkbox"], [type="radio"]) {
width: 100%;
vertical-align: top;
}
.ui.form ::-webkit-datetime-edit,
.ui.form ::-webkit-inner-spin-button {
height: 1.21428571em;
}
.ui.form input:not([type="checkbox"], [type="radio"]),
.ui.search > .prompt {
font-family: var(--fonts-regular);
margin: 0;
outline: none;
line-height: 1.21428571;
padding: 0.67857143em 1em;
font-size: 1em;
color: var(--color-text);
border-radius: 0.28571429rem;
}
.ui.input textarea,
.ui.form textarea {
margin: 0;
padding: 0.78571429em 1em;
outline: none;
color: var(--color-text);
border-radius: 0.28571429rem;
font-size: 1em;
font-family: var(--fonts-regular);
line-height: 1.2857;
resize: vertical;
}
.ui.form textarea:not([rows]) {
height: var(--min-height-textarea);
min-height: var(--min-height-textarea);
max-height: 24em;
}
input:not([type="checkbox"], [type="radio"]),
textarea,
.ui.input > input,
.ui.form input:not([type="checkbox"], [type="radio"]),
.ui.form select,
.ui.form textarea,
.ui.selection.dropdown,
.ui.search > .prompt {
background: var(--color-input-background);
border: 1px solid var(--color-input-border);
color: var(--color-input-text);
}
input:not([type="checkbox"], [type="radio"]):focus,
textarea:focus,
.ui.input input:focus,
.ui.form input:not([type="checkbox"], [type="radio"]):focus,
.ui.form select:focus,
.ui.form textarea:focus,
.ui.search > .prompt:focus {
background: var(--color-input-background);
border-color: var(--color-primary);
color: var(--color-input-text);
}
.ui.form input:not([type="checkbox"], [type="radio"])[readonly],
.ui.form textarea[readonly],
.ui.form select[readonly],
.ui.form .ui.selection.dropdown[readonly] {
background: var(--color-secondary-bg);
}
.ui.input {
color: var(--color-input-text);
}
.ui.form textarea,
.ui.form input[type="checkbox"] {
vertical-align: top;
}
.ui.form .fields:not(.grouped):not(.inline) .field:not(:only-child) label + .ui.ui.checkbox {
margin-top: 0.7em;
}
.ui.form .fields:not(.grouped):not(.inline) .field:not(:only-child) .ui.checkbox {
margin-top: 2.41428571em;
}
.ui.form .fields:not(.grouped):not(.inline) .field:not(:only-child) .ui.toggle.checkbox {
margin-top: 2.21428571em;
}
.ui.form .fields:not(.grouped):not(.inline) .field:not(:only-child) .ui.slider.checkbox {
margin-top: 2.61428571em;
}
.ui.ui.form .field .fields .field:not(:only-child) .ui.checkbox {
margin-top: 0.6em;
}
.ui.ui.form .field .fields .field:not(:only-child) .ui.toggle.checkbox {
margin-top: 0.5em;
}
.ui.ui.form .field .fields .field:not(:only-child) .ui.slider.checkbox {
margin-top: 0.7em;
}
.ui.form .field > .selection.dropdown {
min-width: 14em; /* matches the default min width */
width: 100%;
}
.ui.form .field > .selection.dropdown > .dropdown.icon {
float: right;
}
.ui.form .inline.fields .field > .selection.dropdown,
.ui.form .inline.field > .selection.dropdown {
width: auto;
}
.ui.form .inline.fields .field > .selection.dropdown > .dropdown.icon,
.ui.form .inline.field > .selection.dropdown > .dropdown.icon {
float: none;
}
.ui.form .field .ui.input,
.ui.form .fields .field .ui.input {
width: 100%;
}
.ui.form .inline.fields .field .ui.input,
.ui.form .inline.field .ui.input {
width: auto;
vertical-align: middle;
}
.ui.form .fields .field .ui.input input,
.ui.form .field .ui.input input {
width: auto;
}
.ui.form .error.message,
.ui.form .error.message:empty {
display: none;
}
.ui.form .message:first-child {
margin-top: 0;
}
.ui.form .ui.action.input:not([class*="left action"]) input:focus {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.ui.form .ui[class*="left action"].input input {
border-bottom-left-radius: 0;
border-top-left-radius: 0;
}
.ui.form.error .error.message:not(:empty) {
display: block;
}
.ui.form .fields.error .error.message:not(:empty),
.ui.form .field.error .error.message:not(:empty) {
display: block;
}
.ui.form .fields.error .field textarea,
.ui.form .fields.error .field select,
.ui.form .fields.error .field input,
.ui.form .field.error textarea,
.ui.form .field.error select,
.ui.form .field.error input {
background-color: var(--color-error-bg);
border-color: var(--color-error-border);
color: var(--color-input-text);
}
.ui.form .field.error textarea:focus,
.ui.form .field.error select:focus,
.ui.form .field.error input:focus {
background-color: var(--color-error-bg);
border-color: var(--color-error-border);
color: var(--color-input-text);
}
.ui.form .field.error select {
appearance: menulist-button;
}
.ui.form .fields.error .field .ui.dropdown,
.ui.form .field.error .ui.dropdown {
border-color: var(--color-error-border);
}
.ui.form .fields.error .field .ui.dropdown:hover,
.ui.form .field.error .ui.dropdown:hover {
border-color: var(--color-error-border);
}
.ui.form .fields.error .field .ui.dropdown .menu .item:hover,
.ui.form .field.error .ui.dropdown .menu .item:hover {
background-color: var(--color-error-bg-hover);
}
.ui.form .fields.error .field .ui.dropdown .menu .active.item,
.ui.form .field.error .ui.dropdown .menu .active.item {
background-color: var(--color-error-bg-active) !important;
}
.ui.form .disabled.fields .field,
.ui.form .disabled.field,
.ui.form .field :disabled {
pointer-events: none;
opacity: var(--opacity-disabled);
}
.ui.form .field.disabled > label,
.ui.form .fields.disabled > label {
opacity: var(--opacity-disabled);
}
.ui.form .field.disabled :disabled {
opacity: 1;
}
.ui.form .required.fields:not(.grouped) > .field > label::after,
.ui.form .required.fields.grouped > label::after,
.ui.form .required.field > label::after,
.ui.form .required.fields:not(.grouped) > .field > .checkbox::after,
.ui.form .required.field > .checkbox::after,
.ui.form label.required::after {
margin: -0.2em 0 0 0.2em;
content: "*";
color: var(--color-red);
}
.ui.form .required.fields:not(.grouped) > .field > label::after,
.ui.form .required.fields.grouped > label::after,
.ui.form .required.field > label::after,
.ui.form label.required::after {
display: inline-block;
vertical-align: top;
}
.ui.form .required.fields:not(.grouped) > .field > .checkbox::after,
.ui.form .required.field > .checkbox::after {
position: absolute;
top: 0;
left: 100%;
}
.ui.form .grouped.fields {
display: block;
margin: 0 0 1em;
}
.ui.form .grouped.fields:last-child {
margin-bottom: 0;
}
.ui.form .grouped.fields > label {
margin: 0 0 0.28571429rem;
color: var(--color-text);
font-size: 0.92857143em;
font-weight: var(--font-weight-medium);
text-transform: none;
}
.ui.form .grouped.fields .field,
.ui.form .grouped.inline.fields .field {
display: block;
margin: 0.5em 0;
padding: 0;
}
.ui.form .grouped.inline.fields .ui.checkbox {
margin-bottom: 0.4em;
}
.ui.form .fields {
display: flex;
flex-direction: row;
margin: 0 -0.5em 1em;
}
.ui.form .fields > .field {
flex: 0 1 auto;
padding-left: 0.5em;
padding-right: 0.5em;
}
.ui.form .fields > .field:first-child {
border-left: none;
}
@media only screen and (max-width: 767.98px) {
.ui.form .fields {
flex-wrap: wrap;
margin-bottom: 0;
}
.ui.form .fields > .fields,
.ui.form .fields > .field {
width: 100%;
margin: 0 0 1em;
}
}
.ui.form .inline.fields {
margin: 0 0 1em;
align-items: center;
}
.ui.form .inline.fields .field {
margin: 0;
padding: 0 1em 0 0;
}
.ui.form .inline.fields > label,
.ui.form .inline.fields .field > label,
.ui.form .inline.fields .field > p,
.ui.form .inline.field > label,
.ui.form .inline.field > p {
display: inline-block;
width: auto;
margin-top: 0;
margin-bottom: 0;
vertical-align: baseline;
font-size: 0.92857143em;
font-weight: var(--font-weight-medium);
color: var(--color-text);
text-transform: none;
}
.ui.form .inline.fields > label {
margin: 0.035714em 1em 0 0;
}
.ui.form .inline.fields .field > input,
.ui.form .inline.fields .field > select,
.ui.form .inline.field > input,
.ui.form .inline.field > select {
display: inline-block;
width: auto;
margin-top: 0;
margin-bottom: 0;
vertical-align: middle;
font-size: 1em;
}
.ui.form .inline.fields .field > :first-child,
.ui.form .inline.field > :first-child {
margin: 0 0.85714286em 0 0;
}
.ui.form .inline.fields .field > :only-child,
.ui.form .inline.field > :only-child {
margin: 0;
}
.ui.form,
.ui.form .field .dropdown,
.ui.form .field .dropdown .menu > .item {
font-size: 1rem;
}
/* previously from web_src/css/form.css */
.ui .form .autofill-dummy {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
z-index: -10000;
}
.ui .form .sub.field {
margin-left: 25px;
}
.ui.form .fields.error .field .ui.dropdown,
.ui.form .field.error .ui.dropdown,
.ui.form .fields.error .field .ui.dropdown:hover,
.ui.form .field.error .ui.dropdown:hover {
border-color: var(--color-error-border) !important;
}
.ui.form .fields.error .dropdown .menu,
.ui.form .field.error .dropdown .menu {
border-color: var(--color-error-border) !important;
}
input:-webkit-autofill,
input:-webkit-autofill:focus,
input:-webkit-autofill:hover,
input:-webkit-autofill:active {
-webkit-background-clip: text;
-webkit-text-fill-color: var(--color-text);
box-shadow: 0 0 0 100px var(--color-primary-light-6) inset !important;
border-color: var(--color-primary-light-4) !important;
}
.ui.form .field.muted {
opacity: var(--opacity-disabled);
}
.form .help {
color: var(--color-secondary-dark-5);
margin-top: 0.25em;
padding-bottom: 0.6em;
display: inline-block;
text-wrap: balance;
}
.form .help code {
color: var(--color-text-light-1);
}
.form .help pre.command-block {
white-space: pre-wrap;
overflow-wrap: anywhere;
margin: 0.25em 0 0.25em 1em;
}
.ui.form.left-right-form .inline.field > label {
text-align: right;
width: 250px;
margin-right: 10px;
}
.ui.form.left-right-form .inline.field > .help {
display: block;
margin-left: calc(250px + 15px);
}
.ui.form.left-right-form .inline.field input:not([type="checkbox"], [type="radio"]),
.ui.form.left-right-form .inline.field .ui.dropdown,
.ui.form.left-right-form .inline.field textarea {
width: 50%;
}
.ui.form.left-right-form .inline.field .ui.dropdown input.search {
width: 100%;
}
.ui.form.left-right-form .inline.field .inline-right {
display: inline-flex;
flex-direction: column;
gap: 0.5em;
}
@media (max-width: 767.98px) {
.ui.form.left-right-form .inline.field > label {
width: 100%;
margin: 0;
text-align: left;
}
.ui.form.left-right-form .inline.field > .help {
margin: 0;
}
.ui.form.left-right-form .inline.field input:not([type="checkbox"], [type="radio"]),
.ui.form.left-right-form .inline.field .ui.dropdown,
.ui.form.left-right-form .inline.field textarea {
width: 100%;
}
}
@@ -38,7 +38,7 @@
.ui.input.error > input {
background: var(--color-error-bg);
border-color: var(--color-error-border);
color: var(--color-error-text);
color: var(--color-input-text);
}
.ui.icon.input > i.icon {
@@ -124,7 +124,8 @@
margin: 0;
}
.ui.action.input:not([class*="left action"]) > input {
.ui.action.input:not([class*="left action"]) > input,
.ui.action.input:not([class*="left action"]) > .ui.input > input {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right-color: transparent;
@@ -292,6 +292,7 @@ If the labels-list itself needs some layouts, use extra classes or "tw" helpers.
}
.labels-list a {
display: inline-flex;
max-width: 100%; /* for ellipsis */
}
@@ -25,15 +25,6 @@
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;
@@ -41,9 +41,9 @@
margin-bottom: 1em;
}
.ui.info.message .header,
.ui.blue.message .header {
color: var(--color-blue);
.ui.message .header {
color: inherit;
font-weight: var(--font-weight-semibold);
}
.ui.info.message,
@@ -51,31 +51,17 @@
.ui.blue.message,
.ui.attached.blue.message {
background: var(--color-info-bg);
color: var(--color-info-text);
border-color: var(--color-info-border);
}
.ui.success.message .header,
.ui.positive.message .header,
.ui.green.message .header {
color: var(--color-green);
}
.ui.success.message,
.ui.attached.success.message,
.ui.positive.message,
.ui.attached.positive.message {
background: var(--color-success-bg);
color: var(--color-success-text);
border-color: var(--color-success-border);
}
.ui.error.message .header,
.ui.negative.message .header,
.ui.red.message .header {
color: var(--color-red);
}
.ui.error.message,
.ui.attached.error.message,
.ui.red.message,
@@ -83,21 +69,14 @@
.ui.negative.message,
.ui.attached.negative.message {
background: var(--color-error-bg);
color: var(--color-error-text);
border-color: var(--color-error-border);
}
.ui.warning.message .header,
.ui.yellow.message .header {
color: var(--color-yellow);
}
.ui.warning.message,
.ui.attached.warning.message,
.ui.yellow.message,
.ui.attached.yellow.message {
background: var(--color-warning-bg);
color: var(--color-warning-text);
border-color: var(--color-warning-border);
}
@@ -1,3 +1,290 @@
/* These are the remnants of the fomantic modal module */
.ui.modal {
position: absolute;
display: none;
z-index: var(--z-index-modal);
text-align: left;
background: var(--color-body);
border: none;
box-shadow: 1px 3px 3px 0 var(--color-shadow), 1px 3px 15px 2px var(--color-shadow);
transform-origin: 50% 25%;
flex: 0 0 auto;
border-radius: var(--border-radius);
user-select: text;
will-change: top, left, margin, transform, opacity;
}
.ui.modal > :first-child:not(.icon) {
border-top-left-radius: var(--border-radius);
border-top-right-radius: var(--border-radius);
}
.ui.modal > :last-child {
border-bottom-left-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
}
.ui.modal > .close {
cursor: pointer;
position: absolute;
top: -2.5rem;
right: -2.5rem;
z-index: 1;
opacity: 0.8;
font-size: 1.25em;
color: var(--color-white);
width: 2.25rem;
height: 2.25rem;
padding: 0.625rem 0 0;
}
.ui.modal > .close:hover {
opacity: 1;
}
.ui.modal > .header {
display: block;
font-family: var(--fonts-regular);
background: var(--color-body);
margin: 0;
padding: 1.25rem 1.5rem;
box-shadow: none;
color: var(--color-text-dark);
border-bottom: 1px solid var(--color-secondary);
border-top-left-radius: var(--border-radius);
border-top-right-radius: var(--border-radius);
}
.ui.modal > .header:not(.ui) {
font-size: 1.42857143rem;
line-height: 1.28571429em;
font-weight: var(--font-weight-medium);
}
.ui.modal > .header .svg {
vertical-align: middle;
display: inline-block;
}
.ui.modal > .content,
.ui.modal form > .content {
display: block;
width: 100%;
font-size: 1em;
line-height: 1.4;
padding: 1.5em;
background: var(--color-body);
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
.ui.modal > .actions,
.ui.modal .content + .actions,
.ui.modal .content + form > .actions {
background: var(--color-secondary-bg);
padding: 1rem;
border-top: 1px solid var(--color-secondary);
text-align: right;
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
.ui.modal .actions > .button {
margin-left: 0.75em;
}
@media only screen and (max-width: 767.98px) {
.ui.modal {
width: 95%;
}
}
@media only screen and (min-width: 768px) {
.ui.modal {
width: 88%;
}
}
@media only screen and (min-width: 992px) {
.ui.modal {
width: 850px;
}
}
@media only screen and (min-width: 1200px) {
.ui.modal {
width: 900px;
}
}
@media only screen and (min-width: 1920px) {
.ui.modal {
width: 950px;
}
}
@media only screen and (max-width: 991.98px) {
.ui.modal > .header {
padding-right: 2.25rem;
}
.ui.modal > .close {
top: 1.0535rem;
right: 1rem;
color: var(--color-text);
}
}
@media only screen and (max-width: 767.98px) {
.ui.modal > .header {
padding: 0.75rem 1rem !important;
padding-right: 2.25rem !important;
}
.ui.modal > .content {
display: block;
padding: 1rem !important;
}
.ui.modal > .close {
top: 0.5rem !important;
right: 0.5rem !important;
}
.ui.modal > .actions {
padding: 1rem 1rem 0 !important;
}
.ui.modal .actions > .buttons,
.ui.modal .actions > .button {
margin-bottom: 1rem;
}
}
.ui.active.modal {
display: block;
}
.scrolling.dimmable > .dimmer {
justify-content: flex-start;
position: fixed;
}
.modals.dimmer .ui.scrolling.modal {
margin: 2rem auto;
}
.ui.modal > .close.inside + .header {
padding-right: 2.25rem;
}
.ui.modal > .close.inside {
top: 1.0535rem;
right: 1rem;
color: inherit;
}
.ui.modal > .close.icon[height="16"] {
top: 0.7em;
color: var(--color-text-dark);
}
.ui.mini.modal > .header:not(.ui) {
font-size: 1.3em;
}
@media only screen and (max-width: 767.98px) {
.ui.mini.modal {
width: 95%;
}
}
@media only screen and (min-width: 768px) {
.ui.mini.modal {
width: 35.2%;
}
}
@media only screen and (min-width: 992px) {
.ui.mini.modal {
width: 340px;
}
}
@media only screen and (min-width: 1200px) {
.ui.mini.modal {
width: 360px;
}
}
@media only screen and (min-width: 1920px) {
.ui.mini.modal {
width: 380px;
}
}
.ui.tiny.modal > .header:not(.ui) {
font-size: 1.3em;
}
@media only screen and (max-width: 767.98px) {
.ui.tiny.modal {
width: 95%;
}
}
@media only screen and (min-width: 768px) {
.ui.tiny.modal {
width: 52.8%;
}
}
@media only screen and (min-width: 992px) {
.ui.tiny.modal {
width: 510px;
}
}
@media only screen and (min-width: 1200px) {
.ui.tiny.modal {
width: 540px;
}
}
@media only screen and (min-width: 1920px) {
.ui.tiny.modal {
width: 570px;
}
}
.ui.small.modal > .header:not(.ui) {
font-size: 1.3em;
}
@media only screen and (max-width: 767.98px) {
.ui.small.modal {
width: 95%;
}
}
@media only screen and (min-width: 768px) {
.ui.small.modal {
width: 70.4%;
}
}
@media only screen and (min-width: 992px) {
.ui.small.modal {
width: 680px;
}
}
@media only screen and (min-width: 1200px) {
.ui.small.modal {
width: 720px;
}
}
@media only screen and (min-width: 1920px) {
.ui.small.modal {
width: 760px;
}
}
.ui.modal.g-modal-confirm {
max-width: min(800px, 90vw);
width: fit-content;
@@ -10,70 +297,12 @@
top: 1.2em;
}
.ui.modal > .close.inside {
color: inherit;
}
.ui.modal > .close.icon[height="16"] {
top: 0.7em; /* fomantic uses absolute layout, so if we have special icon size, it needs this trick to align vertically */
color: var(--color-text-dark);
}
.ui.modal > .header {
/* can't use display:flex, because some headers have space-separated elements, eg: delete branch modal */
color: var(--color-text-dark);
background: var(--color-body);
border-color: var(--color-secondary);
border-top-left-radius: var(--border-radius);
border-top-right-radius: var(--border-radius);
vertical-align: middle;
}
.ui.modal > .header .svg {
vertical-align: middle;
display: inline-block;
}
.ui.modal {
background: var(--color-body);
box-shadow: 1px 3px 3px 0 var(--color-shadow), 1px 3px 15px 2px var(--color-shadow);
}
/* Gitea sometimes use a form in a modal dialog, then the "positive" button could submit the form directly
Fomantic UI only supports the layout: <div .modal><div .content/><div .actions/></div>
However, Gitea uses the following layouts:
* <div .modal><div .content><div .actions/></div></div>
* <div .modal><form><div .content/><div .actions/></form></div>
* <div .modal><div .content><form><div .actions/></form></div></div>
* <div .modal><div .content></div><form><div .actions/></form></div>
* ...
These inconsistent layouts should be refactored to simple ones.
*/
.ui.modal > .content,
.ui.modal form > .content {
padding: 1.5em;
background: var(--color-body);
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
.ui.modal > .actions,
.ui.modal .content + .actions,
.ui.modal .content + form > .actions {
background: var(--color-secondary-bg);
border-color: var(--color-secondary);
padding: 1rem;
text-align: right;
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
.ui.modal .content > form > .actions,
.ui.modal .content > .actions {
padding-top: 1em; /* if the "actions" is in the "content", some paddings are already added by the "content" */
padding-top: 1em;
text-align: right;
}
/* positive/negative action buttons */
.ui.modal .actions > .ui.button {
display: inline-flex;
align-items: center;
@@ -7,6 +7,11 @@
padding: 0 10px;
}
/* When notification message is present after navbar, hide border to avoid double border */
#navbar:has(+ .ui.message) {
border-bottom: none;
}
#navbar .navbar-left,
#navbar .navbar-right {
display: flex;
@@ -63,6 +68,7 @@
}
#navbar .navbar-mobile-right {
display: flex;
align-items: center;
margin: 0 0 0 auto;
width: auto;
}
@@ -92,7 +98,6 @@
}
#navbar.navbar-menu-open .navbar-left .navbar-mobile-right {
justify-content: flex-end;
width: 50%;
min-height: 49px;
}
#navbar #mobile-stopwatch-icon,
@@ -102,12 +107,14 @@
}
#navbar a.item:hover .notification_count,
#navbar a.item:hover .header-stopwatch-dot {
#navbar a.item:hover .header-stopwatch-dot,
#navbar .item.active .navbar-admin-badge {
border-color: var(--color-nav-hover-bg);
}
#navbar a.item .notification_count,
#navbar a.item .header-stopwatch-dot {
#navbar a.item .header-stopwatch-dot,
#navbar .item .navbar-admin-badge {
color: var(--color-nav-bg);
padding: 0 3.75px;
font-size: 12px;
@@ -127,6 +134,19 @@
z-index: 1; /* prevent menu button background from overlaying icon */
user-select: none;
white-space: nowrap;
overflow: visible;
}
.navbar-avatar {
position: relative;
display: inline-flex;
}
#navbar .item .navbar-admin-badge {
position: absolute;
bottom: calc(100% - 29px);
left: calc(100% - 18px);
padding: 1.5px;
}
.secondary-nav {
@@ -0,0 +1,86 @@
/* These are the remnants of the fomantic search module */
.ui.search {
position: relative;
}
.ui.search > .results {
display: none;
position: absolute;
top: 100%;
left: 0;
white-space: normal;
text-align: left;
background: var(--color-body);
margin-top: 0.5em;
width: 18em;
border-radius: 0.28571429rem;
box-shadow: 0 2px 4px 0 var(--color-shadow), 0 2px 10px 0 var(--color-shadow);
border: 1px solid var(--color-secondary);
z-index: 998;
transform-origin: center top;
overflow-wrap: anywhere;
}
.ui.search > .results > :first-child {
border-top-left-radius: 0.28571429rem;
border-top-right-radius: 0.28571429rem;
}
.ui.search > .results > :last-child {
border-bottom-left-radius: 0.28571429rem;
border-bottom-right-radius: 0.28571429rem;
}
.ui.search > .results .result {
cursor: pointer;
display: flex;
align-items: center;
overflow: hidden;
padding: 0.85714286em 1.14285714em;
color: var(--color-text);
line-height: 1.33;
border-bottom: 1px solid var(--color-secondary);
background: var(--color-body);
}
.ui.search > .results .result:last-child {
border-bottom: none;
}
.ui.search > .results .result .image {
overflow: hidden;
border-radius: 0.25em;
}
.ui.search > .results .result .image img {
display: block;
height: 100%;
}
.ui.search > .results .result .title {
margin: -0.14285714em 0 0;
font-family: var(--fonts-regular);
font-weight: var(--font-weight-medium);
color: var(--color-text-dark);
}
.ui.search > .results .result .description {
font-size: 0.92857143em;
color: var(--color-text-light-2);
}
.ui.search > .results .result:hover,
.ui.search > .results .result.active {
background: var(--color-hover);
}
.ui.fluid.search .results {
width: 100%;
}
@media only screen and (max-width: 767.98px) {
.ui.search .results {
max-width: calc(100vw - 2rem);
}
}
@@ -0,0 +1,26 @@
.global-shortcut-wrapper {
position: relative;
}
kbd {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 18px;
padding: 2px 4px;
font-size: 11px;
line-height: 1;
color: var(--color-text-light-2);
background-color: var(--color-box-body);
border: 1px solid var(--color-secondary);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow-kbd);
}
.global-shortcut-wrapper > kbd {
position: absolute;
right: 6px;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
}
@@ -196,11 +196,6 @@
margin-bottom: 0;
}
.ui.striped.table > tr:nth-child(2n),
.ui.striped.table > tbody > tr:nth-child(2n) {
background: var(--color-light);
}
.ui.table[class*="single line"],
.ui.table [class*="single line"] {
white-space: nowrap;
@@ -291,37 +286,10 @@
.ui.basic.table > tr > td {
background: transparent;
}
.ui.basic.striped.table > tbody > tr:nth-child(2n) {
background: var(--color-light);
}
.ui.basic.striped.selectable.table > tbody > tr:nth-child(2n):hover {
background: var(--color-hover);
}
.ui[class*="very basic"].table {
border: none;
}
.ui[class*="very basic"].table:not(.striped) > tr > th:first-child,
.ui[class*="very basic"].table:not(.striped) > thead > tr > th:first-child,
.ui[class*="very basic"].table:not(.striped) > tbody > tr > th:first-child,
.ui[class*="very basic"].table:not(.striped) > tfoot > tr > th:first-child,
.ui[class*="very basic"].table:not(.striped) > tr > td:first-child,
.ui[class*="very basic"].table:not(.striped) > tbody > tr > td:first-child,
.ui[class*="very basic"].table:not(.striped) > tfoot > tr > td:first-child {
padding-left: 0;
}
.ui[class*="very basic"].table:not(.striped) > tr > th:last-child,
.ui[class*="very basic"].table:not(.striped) > thead > tr > th:last-child,
.ui[class*="very basic"].table:not(.striped) > tbody > tr > th:last-child,
.ui[class*="very basic"].table:not(.striped) > tfoot > tr > th:last-child,
.ui[class*="very basic"].table:not(.striped) > tr > td:last-child,
.ui[class*="very basic"].table:not(.striped) > tbody > tr > td:last-child,
.ui[class*="very basic"].table:not(.striped) > tfoot > tr > td:last-child {
padding-right: 0;
}
.ui[class*="very basic"].table:not(.striped) > thead > tr:first-child > th {
padding-top: 0;
}
.ui.celled.table > tr > th,
.ui.celled.table > thead > tr > th,
+115 -97
View File
@@ -8,26 +8,6 @@
min-width: 40% !important;
}
.repository .unicode-escaped .escaped-code-point[data-escaped]::before {
visibility: visible;
content: attr(data-escaped);
font-family: var(--fonts-monospace);
color: var(--color-red);
}
.repository .unicode-escaped .escaped-code-point .char {
display: none;
}
.repository .broken-code-point {
font-family: var(--fonts-monospace);
color: var(--color-blue);
}
.repository .unicode-escaped .ambiguous-code-point {
border: 1px var(--color-yellow) solid;
}
.issue-content {
display: flex;
align-items: flex-start;
@@ -95,8 +75,9 @@
}
}
/* make all issue filter dropdown menus popup leftward, to avoid go out the viewport (right side) */
.repository .filter.menu .ui.dropdown .menu {
/* make all issue filter dropdown (user dashboard, repo issue list) menus popup leftward, to avoid go out the viewport (right side) */
.list-header-filters .ui.dropdown .menu,
.issue-list-toolbar .ui.dropdown .menu {
max-height: 500px;
max-width: 300px;
overflow-x: hidden;
@@ -105,7 +86,8 @@
}
/* the label-filter is the first dropdown, it shouldn't be shown leftward, otherwise it may go out the viewport (left side) */
.repository .filter.menu .ui.dropdown.label-filter .menu {
.list-header-filters .ui.dropdown.label-filter .menu,
.issue-list-toolbar .ui.dropdown.label-filter .menu {
min-width: max-content;
right: unset;
left: 0;
@@ -150,83 +132,89 @@ td .commit-summary {
}
}
.repository.file.list .non-diff-file-content .header .icon {
.non-diff-file-content .header .icon {
font-size: 1em;
}
.repository.file.list .non-diff-file-content .header .small.icon {
.non-diff-file-content .header .small.icon {
font-size: 0.75em;
}
.repository.file.list .non-diff-file-content .header .tiny.icon {
.non-diff-file-content .header .tiny.icon {
font-size: 0.5em;
}
.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon {
.non-diff-file-content .header .file-actions .btn-octicon {
line-height: var(--line-height-default);
padding: 8px;
vertical-align: middle;
color: var(--color-text);
}
.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon:hover {
.non-diff-file-content .header .file-actions .btn-octicon:hover {
color: var(--color-primary);
}
.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon-danger:hover {
.non-diff-file-content .header .file-actions .btn-octicon-danger:hover {
color: var(--color-red);
}
.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon.disabled {
.non-diff-file-content .header .file-actions .btn-octicon.disabled {
color: inherit;
opacity: var(--opacity-disabled);
cursor: default;
}
.repository.file.list .non-diff-file-content .plain-text {
.non-diff-file-content .plain-text {
padding: 1em 2em;
}
.repository.file.list .non-diff-file-content .plain-text pre {
.non-diff-file-content .plain-text pre {
overflow-wrap: anywhere;
white-space: pre-wrap;
}
.repository.file.list .non-diff-file-content .csv {
.non-diff-file-content .csv {
overflow-x: auto;
padding: 0 !important;
}
.repository.file.list .non-diff-file-content pre {
.non-diff-file-content pre {
overflow: auto;
}
.repository.file.list .non-diff-file-content .asciicast {
.non-diff-file-content .asciicast {
padding: 0 !important;
}
.repo-editor-menu {
min-height: auto !important;
}
.repo-editor-header {
/* it should match ".repo-button-row" so the tree toggle button stays aligned */
margin: 8px 0;
display: flex;
align-items: center;
gap: 8px;
width: 100%;
}
.repo-editor-header input {
vertical-align: middle !important;
width: auto !important;
padding: 7px 8px !important;
height: 30px !important;
padding: 5px 8px !important;
margin-right: 5px !important;
}
.repository.file.editor .tabular.menu .svg {
margin-right: 5px;
}
.repository.file.editor .commit-form-wrapper {
padding-left: 48px;
padding-left: 58px;
}
.repository.file.editor .commit-form-wrapper .commit-avatar {
float: left;
margin-left: -48px;
margin-left: -58px;
}
.repository.file.editor .commit-form-wrapper .commit-form {
@@ -300,10 +288,6 @@ td .commit-summary {
min-width: 100px;
}
.repository.view.issue .instruct-toggle {
display: inline-block;
}
/* issue title & meta & edit */
.issue-title-header {
width: 100%;
@@ -462,7 +446,7 @@ td .commit-summary {
}
.repository.view.issue .comment-list .timeline-avatar-offset {
margin-top: 48px;
top: 51px;
}
.repository.view.issue .comment-list .timeline-item {
@@ -500,9 +484,8 @@ td .commit-summary {
border-radius: var(--border-radius-full);
display: flex;
flex-shrink: 0;
float: left;
margin-left: -33px;
margin-right: 8px;
margin-right: 4px;
color: var(--color-text);
align-items: center;
justify-content: center;
@@ -519,10 +502,6 @@ td .commit-summary {
padding: 3px;
}
.repository.view.issue .comment-list .timeline-item .badge .svg.octicon-comment {
margin-top: 2px;
}
.repository.view.issue .comment-list .timeline-item.comment > .content {
margin-left: -16px;
}
@@ -531,7 +510,6 @@ td .commit-summary {
/* TODO: this "line-height" is not ideal (actually it is abused), many layouts depend on this magic value,
for example: alignment of the header arrow and the avatar, view PR commit list left icon layout, dismiss review with reason, etc */
line-height: 32px;
vertical-align: middle;
color: var(--color-text-light);
min-width: 0;
}
@@ -544,23 +522,12 @@ td .commit-summary {
color: inherit;
}
.repository.view.issue .comment-list .timeline-item .avatar-with-link + .comment-text-line {
margin-left: 0.25em;
}
.repository.view.issue .comment-list .timeline-item.commits-list {
padding-left: 15px;
padding-top: 0;
}
.repository.view.issue .comment-list .timeline-item.event > .commit-status-link {
float: right;
margin-right: 8px;
margin-top: 4px;
}
.repository.view.issue .comment-list .timeline-item .comment-text-label {
vertical-align: middle;
border: 1px solid var(--color-light-border);
height: 26px;
margin: 4px 0; /* because this label is beside the comment line, which has "line-height: 34px" */
@@ -674,21 +641,35 @@ td .commit-summary {
.repository.view.issue .comment-list .event {
padding-left: 15px;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px;
}
.repository.view.issue .comment-list .event .detail {
margin-top: 4px;
.repository.view.issue .comment-list .event.code-comments-list {
display: block;
}
.repository.view.issue .comment-list .event > .badge {
align-self: flex-start;
}
.repository.view.issue .comment-list .event > .avatar-with-link {
align-self: flex-start;
height: 32px;
display: inline-flex;
align-items: center;
}
.repository.view.issue .comment-list .event > .comment-text-line {
flex: 1;
min-width: 0;
}
.repository.view.issue .comment-list .event > .detail {
margin-left: 15px;
}
.repository.view.issue .comment-list .event .detail .text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.repository.view.issue .comment-list .event .segments {
box-shadow: none;
width: 100%;
}
.repository.view.issue .ui.depending .item.is-closed .issue-dependency-title {
@@ -771,10 +752,6 @@ td .commit-summary {
width: 200px;
}
.repository #commits-table.ui.basic.striped.table tbody tr:nth-child(2n) {
background-color: var(--color-light) !important;
}
.repository .data-table {
width: 100%;
}
@@ -845,7 +822,7 @@ td .commit-summary {
width: 1%;
min-width: 50px;
font-family: monospace;
line-height: 20px;
line-height: var(--line-height-code);
color: var(--color-text-light-1);
white-space: nowrap;
vertical-align: top;
@@ -1292,8 +1269,7 @@ td .commit-summary {
filter: drop-shadow(-4px 0 0 var(--color-primary-alpha-30)) !important;
}
.code-comment:target,
.diff-file-box:target {
.code-comment:target {
border-color: var(--color-primary) !important;
border-radius: var(--border-radius) !important;
box-shadow: 0 0 0 3px var(--color-primary-alpha-30) !important;
@@ -1409,12 +1385,25 @@ td .commit-summary {
flex-grow: 1;
}
.repo-button-row .ui.button {
.repo-button-row .ui.button,
.repo-view-container .ui.button.repo-view-file-tree-toggle {
flex-shrink: 0;
margin: 0;
min-height: 30px;
}
.repo-view-container .ui.button.repo-view-file-tree-toggle {
padding: 0 6px;
}
.repo-button-row .repo-file-search-container .ui.input {
height: 30px;
}
.repo-button-row .ui.dropdown > .menu {
margin-top: 4px;
}
tbody.commit-list {
vertical-align: baseline;
}
@@ -1471,11 +1460,6 @@ tbody.commit-list {
}
}
.commit-list .commit-status-link {
display: inline-block;
vertical-align: middle;
}
.commit-body {
margin: 0.25em 0;
white-space: pre-wrap;
@@ -1483,6 +1467,12 @@ tbody.commit-list {
line-height: initial;
}
.commit-body a.commit code,
.commit-summary a.commit code {
/* these links are generated by the render: <a class="commit" href="...">...</a> */
background: inherit;
}
.git-notes.top {
text-align: left;
}
@@ -1661,18 +1651,27 @@ tbody.commit-list {
margin-top: 1em;
}
.diff-file-box:target {
border-color: var(--color-primary) !important;
border-radius: var(--border-radius) !important;
box-shadow: 0 0 0 3px var(--color-primary-alpha-30) !important;
}
.diff-file-header {
padding: 5px 8px !important;
box-shadow: 0 -1px 0 1px var(--color-body); /* prevent borders being visible behind top corners when sticky and scrolled */
font-weight: var(--font-weight-normal);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 0.5em;
/* prevent borders from being visible behind top corners when sticky and scrolled,
this "shadow" is used to use body's color to cover the scrolled-up left and right borders at corners */
box-shadow: 0 -1px 0 1px var(--color-body);
}
.diff-file-header .file {
min-width: 0;
.diff-file-box:target .diff-file-header {
box-shadow: unset; /* when targeted, still use the parent's box-shadow, remove the patched above */
}
.diff-file-header .file-link {
@@ -1695,6 +1694,7 @@ tbody.commit-list {
.diff-file-header {
flex-direction: column;
align-items: stretch;
gap: 0;
}
}
@@ -1723,13 +1723,13 @@ tbody.commit-list {
.diff-stats-bar {
display: inline-block;
background-color: var(--color-red);
background-color: var(--color-diff-removed-fg); /* the background is used as "text foreground color" */
height: 12px;
width: 44px;
}
.diff-stats-bar .diff-stats-add-bar {
background-color: var(--color-green);
background-color: var(--color-diff-added-fg);
height: 100%;
}
@@ -1739,13 +1739,23 @@ tbody.commit-list {
}
.removed-code {
color: var(--color-text);
background: var(--color-diff-removed-word-bg);
}
.removed-code span {
color: inherit !important;
}
.added-code {
color: var(--color-text);
background: var(--color-diff-added-word-bg);
}
.added-code span {
color: inherit !important;
}
.code-diff-unified .del-code,
.code-diff-unified .del-code td,
.code-diff-split .del-code .lines-num-old,
@@ -1942,7 +1952,15 @@ tbody.commit-list {
padding-right: 0.5em; /* To match the alignment with the "required" label */
}
.search-fullname {
.username-display {
max-width: 100%; /* the inner part might have "gt-ellipsis" */
min-width: 0; /* if it is the top flex container, "max-width" works; but if it is inside another flex container, the parent needs to handle the x-axis and here also needs "min-width" */
display: inline-flex;
gap: var(--gap-inline);
align-items: center;
}
.username-display > .username-fullname {
color: var(--color-text-light-2);
}
@@ -7,6 +7,10 @@
flex-shrink: 0;
}
.ui.label.commit-sign-badge > * {
display: flex;
}
.ui.label.commit-id-short {
font-family: var(--fonts-monospace);
height: 24px;
@@ -54,7 +54,9 @@
gap: var(--page-spacing);
}
.repo-view-container .repo-view-file-tree-container {
.repo-view-file-tree-container {
display: flex;
flex-direction: column;
flex: 0 0 15%;
min-width: 0;
max-height: 100vh;
@@ -65,6 +67,12 @@
overflow-y: hidden;
}
@media (max-width: 767.98px) {
.repo-view-file-tree-container {
display: none;
}
}
.repo-view-content {
flex: 1;
min-width: 0;
@@ -95,46 +95,3 @@
font-size: 18px;
font-weight: var(--font-weight-normal);
}
.repository.new.release .target {
min-width: 500px;
}
.repository.new.release .target #tag-name {
margin-top: -4px;
}
.repository.new.release .target .at {
margin-left: -5px;
margin-right: 5px;
}
.repository.new.release .target .selection.dropdown {
padding-top: 10px;
padding-bottom: 10px;
}
.repository.new.release .prerelease.field {
margin-bottom: 0;
}
@media (max-width: 438px) {
.repository.new.release .field button,
.repository.new.release .field input {
width: 100%;
}
}
@media (max-width: 767.98px) {
.repository.new.release .field button {
margin-bottom: 1em;
}
}
.repository.new.release .field .attachment_edit {
max-width: 48em;
}
.repository.new.release .markup {
min-height: 240px;
}
@@ -1,7 +1,3 @@
.repository.wiki .wiki-pages-list tr:hover {
background-color: var(--color-hover);
}
.repository.wiki .wiki-pages-list .wiki-git-entry {
margin-left: 10px;
display: none;
@@ -50,7 +46,7 @@
}
.repository.wiki .wiki-content-toc ul ul {
border-left: 1px var(--color-secondary);
border-left: 1px var(--color-secondary);
border-left-style: dashed;
}
+32 -6
View File
@@ -15,11 +15,20 @@
transform: scale(1.1);
}
.lines-escape .toggle-escape-button {
margin: -1px 2px 0;
}
.lines-escape .toggle-escape-button::before {
visibility: visible;
content: "⚠️";
font-family: var(--fonts-emoji);
color: var(--color-red);
content: "";
display: inline-flex;
width: 14px;
height: 14px;
background-color: var(--color-yellow); /* TODO: maybe it needs a new kind of color, there is no suitable "warning" color in the current palette */
mask-image: var(--octicon-alert-fill);
-webkit-mask-image: var(--octicon-alert-fill);
mask-size: contain;
-webkit-mask-size: contain;
}
.repository .diff-file-box .code-diff td.lines-escape {
@@ -115,6 +124,23 @@
color: var(--color-text);
}
.code-expander-buttons {
position: relative;
}
.code-expander-buttons .code-comment-more {
position: absolute;
line-height: 12px;
padding: 2px 4px;
font-size: 10px;
background-color: var(--color-primary);
color: var(--color-primary-contrast);
border-radius: 8px !important;
left: -12px;
top: 6px;
text-align: center;
}
.code-expander-button {
border: none;
color: var(--color-text-light);
@@ -127,8 +153,8 @@
flex: 1;
}
/* expand direction 3 is both ways with two buttons */
.code-expander-buttons[data-expand-direction="3"] .code-expander-button {
/* expand direction "updown" is both ways with two buttons */
.code-expander-buttons[data-expand-direction="updown"] .code-expander-button {
height: 18px;
}
@@ -1 +0,0 @@
/* dummy */
@@ -1,42 +0,0 @@
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
background: #fff;
}
.swagger-back-link {
color: #1f69c0;
text-decoration: none;
position: absolute;
top: 1rem;
right: 1.5rem;
display: flex;
align-items: center;
}
.swagger-back-link:hover {
text-decoration: underline;
}
.swagger-back-link svg {
color: inherit;
fill: currentcolor;
margin-right: 0.5rem;
}
@media (prefers-color-scheme: dark) {
body {
background: #1e1e1e;
}
.swagger-ui, .swagger-back-link {
filter: invert(88%) hue-rotate(180deg);
}
.swagger-ui .microlight {
filter: invert(100%) hue-rotate(180deg);
}
}
@@ -0,0 +1,13 @@
@import "../../node_modules/swagger-ui-dist/swagger-ui.css";
html,
html body,
html .swagger-ui,
html .swagger-ui .scheme-container {
background: var(--gitea-iframe-bgcolor, var(--color-box-body)) !important;
}
/* swagger's bug: the selector was incorrectly written in "thead": "html.dark-mode .swagger-ui .opblock.opblock-get thead tr td" */
html.dark-mode .swagger-ui table.headers td {
color: var(--color-text) !important;
}
@@ -0,0 +1,29 @@
@import "swagger-render.css";
body {
margin: 0;
}
.swagger-back-link {
color: var(--color-primary);
text-decoration: none;
position: absolute;
top: 1rem;
right: 1.5rem;
display: flex;
align-items: center;
}
.swagger-back-link:hover {
text-decoration: underline;
}
.swagger-back-link svg {
color: inherit;
fill: currentcolor;
margin-right: 0.5rem;
}
.swagger-spec-content {
display: none;
}
@@ -2,5 +2,7 @@
@import "./theme-gitea-dark-protanopia-deuteranopia.css" (prefers-color-scheme: dark);
gitea-theme-meta-info {
--theme-display-name: "Auto (Red/Green Colorblind-friendly)";
--theme-display-name: "Auto";
--theme-colorblind-type: "red-green";
--theme-color-scheme: "auto";
}
@@ -0,0 +1,8 @@
@import "./theme-gitea-light-tritanopia.css" (prefers-color-scheme: light);
@import "./theme-gitea-dark-tritanopia.css" (prefers-color-scheme: dark);
gitea-theme-meta-info {
--theme-display-name: "Auto";
--theme-colorblind-type: "blue-yellow";
--theme-color-scheme: "auto";
}
@@ -3,4 +3,5 @@
gitea-theme-meta-info {
--theme-display-name: "Auto";
--theme-color-scheme: "auto";
}
@@ -1,16 +1,19 @@
@import "./theme-gitea-dark.css";
gitea-theme-meta-info {
--theme-display-name: "Dark (Red/Green Colorblind-friendly)";
--theme-display-name: "Dark";
--theme-colorblind-type: "red-green";
--theme-color-scheme: "dark";
}
/* red/green colorblind-friendly colors */
/* from GitHub: --diffBlob-addition-*, --diffBlob-deletion-*, etc */
:root {
--color-diff-added-linenum-bg: #1979fd46;
--color-diff-added-row-bg: #1979fd20;
--color-diff-added-word-bg: #1979fd66;
--color-diff-removed-linenum-bg: #c8622146;
--color-diff-removed-row-bg: #c8622120;
--color-diff-removed-word-bg: #c8622166;
--color-diff-added-fg: #58a6ff;
--color-diff-added-linenum-bg: #243d5d;
--color-diff-added-row-bg: #132339;
--color-diff-added-word-bg: #214d87;
--color-diff-removed-fg: #f0883e;
--color-diff-removed-linenum-bg: #5b361c;
--color-diff-removed-row-bg: #3c2419;
--color-diff-removed-word-bg: #824e1f;
}
@@ -0,0 +1,15 @@
@import "./theme-gitea-dark.css";
gitea-theme-meta-info {
--theme-display-name: "Dark";
--theme-colorblind-type: "blue-yellow";
--theme-color-scheme: "dark";
}
/* blue/yellow colorblind-friendly colors */
:root {
--color-diff-added-fg: #58a6ff;
--color-diff-added-linenum-bg: #243d5d;
--color-diff-added-row-bg: #132339;
--color-diff-added-word-bg: #214d87;
}
@@ -1,8 +1,6 @@
@import "../chroma/dark.css";
@import "../codemirror/dark.css";
gitea-theme-meta-info {
--theme-display-name: "Dark";
--theme-color-scheme: "dark";
}
:root {
@@ -34,45 +32,46 @@ gitea-theme-meta-info {
--color-primary-alpha-90: #4183c4e1;
--color-primary-hover: var(--color-primary-light-1);
--color-primary-active: var(--color-primary-light-2);
--color-secondary: #3b444c;
--color-secondary-dark-1: #414b54;
--color-secondary-dark-2: #49545f;
--color-secondary-dark-3: #576471;
--color-secondary-dark-4: #677685;
--color-secondary-dark-5: #758594;
--color-secondary-dark-6: #8392a0;
--color-secondary-dark-7: #929eab;
--color-secondary-dark-8: #a2acb7;
--color-secondary-dark-9: #a9b3bd;
--color-secondary-dark-10: #b7bfc7;
--color-secondary-dark-11: #c5cbd2;
--color-secondary-dark-12: #cfd4da;
--color-secondary-dark-13: #d2d7dc;
--color-secondary-light-1: #313940;
--color-secondary-light-2: #292f35;
--color-secondary-light-3: #1d2226;
--color-secondary-light-4: #171b1e;
--color-secondary-alpha-10: #3b444c19;
--color-secondary-alpha-20: #3b444c33;
--color-secondary-alpha-30: #3b444c4b;
--color-secondary-alpha-40: #3b444c66;
--color-secondary-alpha-50: #3b444c80;
--color-secondary-alpha-60: #3b444c99;
--color-secondary-alpha-70: #3b444cb3;
--color-secondary-alpha-80: #3b444ccc;
--color-secondary-alpha-90: #3b444ce1;
--color-secondary: #3f4248;
--color-secondary-dark-1: #46494f;
--color-secondary-dark-2: #4f5259;
--color-secondary-dark-3: #5e626a;
--color-secondary-dark-4: #6f747d;
--color-secondary-dark-5: #7d828c;
--color-secondary-dark-6: #8b8f98;
--color-secondary-dark-7: #999da4;
--color-secondary-dark-8: #a8abb1;
--color-secondary-dark-9: #aeb1b8;
--color-secondary-dark-10: #bbbec3;
--color-secondary-dark-11: #c8cacf;
--color-secondary-dark-12: #d2d4d7;
--color-secondary-dark-13: #d5d6d9;
--color-secondary-light-1: #35373c;
--color-secondary-light-2: #2c2e32;
--color-secondary-light-3: #1f2124;
--color-secondary-light-4: #191a1c;
--color-secondary-alpha-10: #3f424819;
--color-secondary-alpha-20: #3f424833;
--color-secondary-alpha-30: #3f42484b;
--color-secondary-alpha-40: #3f424866;
--color-secondary-alpha-50: #3f424880;
--color-secondary-alpha-60: #3f424899;
--color-secondary-alpha-70: #3f4248b3;
--color-secondary-alpha-80: #3f4248cc;
--color-secondary-alpha-90: #3f4248e1;
--color-secondary-button: var(--color-secondary-dark-4);
--color-secondary-hover: var(--color-secondary-dark-3);
--color-secondary-active: var(--color-secondary-dark-2);
/* console colors - used for actions console and console files */
--color-console-fg: #f7f8f9;
--color-console-fg-subtle: #bdc4cc;
--color-console-bg: #171b1e;
--color-console-border: #2e353b;
--color-console-hover-bg: #272d33;
--color-console-active-bg: #2e353b;
--color-console-menu-bg: #262b31;
--color-console-menu-border: #414b55;
--color-console-fg: #f8f8f8;
--color-console-fg-subtle: #c1c3c8;
--color-console-bg: #191a1c;
--color-console-border: #313338;
--color-console-hover-bg: #2a2c30;
--color-console-active-bg: #313338;
--color-console-menu-bg: #292b2e;
--color-console-menu-border: #46494f;
--color-console-link: #969aa1;
/* named colors */
--color-red: #cc4848;
--color-orange: #cc580c;
@@ -85,7 +84,7 @@ gitea-theme-meta-info {
--color-purple: #b259d0;
--color-pink: #d22e8b;
--color-brown: #a47252;
--color-black: #1d2328;
--color-black: #202225;
/* light variants - produced via Sass scale-color(color, $lightness: +10%) */
--color-red-light: #d15a5a;
--color-orange-light: #f6a066;
@@ -98,7 +97,7 @@ gitea-theme-meta-info {
--color-purple-light: #ba6ad5;
--color-pink-light: #d74397;
--color-brown-light: #b08061;
--color-black-light: #424851;
--color-black-light: #45484e;
/* dark 1 variants - produced via Sass scale-color(color, $lightness: -10%) */
--color-red-dark-1: #c23636;
--color-orange-dark-1: #f38236;
@@ -111,7 +110,7 @@ gitea-theme-meta-info {
--color-purple-dark-1: #a742c9;
--color-pink-dark-1: #be297d;
--color-brown-dark-1: #94674a;
--color-black-dark-1: #292e38;
--color-black-dark-1: #2e3033;
/* dark 2 variants - produced via Sass scale-color(color, $lightness: -20%) */
--color-red-dark-2: #ad3030;
--color-orange-dark-2: #f16e17;
@@ -124,9 +123,9 @@ gitea-theme-meta-info {
--color-purple-dark-2: #9834b9;
--color-pink-dark-2: #a9246f;
--color-brown-dark-2: #835b42;
--color-black-dark-2: #272930;
--color-black-dark-2: #292b2e;
/* ansi colors used for actions console and console files */
--color-ansi-black: #1e2327;
--color-ansi-black: #202225;
--color-ansi-red: #cc4848;
--color-ansi-green: #87ab63;
--color-ansi-yellow: #cc9903;
@@ -134,7 +133,7 @@ gitea-theme-meta-info {
--color-ansi-magenta: #d22e8b;
--color-ansi-cyan: #00918a;
--color-ansi-white: var(--color-console-fg-subtle);
--color-ansi-bright-black: #424851;
--color-ansi-bright-black: #45484e;
--color-ansi-bright-red: #d15a5a;
--color-ansi-bright-green: #93b373;
--color-ansi-bright-yellow: #eaaf03;
@@ -143,35 +142,40 @@ gitea-theme-meta-info {
--color-ansi-bright-cyan: #00b6ad;
--color-ansi-bright-white: var(--color-console-fg);
/* other colors */
--color-grey: #384149;
--color-grey-light: #818f9e;
--color-grey: #3d3f44;
--color-grey-light: #898d96;
--color-gold: #b1983b;
--color-white: #ffffff;
--color-diff-added-fg: #87ab63;
--color-diff-added-linenum-bg: #274227;
--color-diff-added-row-bg: #203224;
--color-diff-added-row-border: #314a37;
--color-diff-added-word-bg: #3c653c;
--color-diff-moved-row-bg: #818044;
--color-diff-moved-row-border: #bcca6f;
--color-diff-removed-fg: #cc4848;
--color-diff-removed-linenum-bg: #482121;
--color-diff-removed-row-bg: #301e1e;
--color-diff-removed-row-border: #634343;
--color-diff-removed-word-bg: #6f3333;
--color-diff-inactive: #22282d;
--color-error-border: #a04141;
--color-error-bg: #522;
--color-error-bg-active: #744;
--color-error-bg-hover: #633;
--color-error-text: #f9cbcb;
--color-success-border: #458a57;
--color-success-bg: #284034;
--color-success-text: #6cc664;
--color-warning-border: #bb9d00;
--color-warning-bg: #3a3a30;
--color-warning-text: #fbbd08;
--color-info-border: #306090;
--color-info-bg: #26354c;
--color-info-text: #38a8e8;
--color-diff-inactive: #25272a;
--color-error-border: #763232;
--color-error-bg: #322226;
--color-error-bg-active: #49262a;
--color-error-bg-hover: #3c2427;
--color-error-text: #f85149;
--color-success-border: #225633;
--color-success-bg: #1c3329;
--color-success-text: #3fb950;
--color-warning-border: #5f481a;
--color-warning-bg: #342e1f;
--color-warning-text: #d29922;
--color-info-border: #254a7e;
--color-info-bg: #1b283a;
--color-info-text: #2f81f7;
--color-priority-border: #4a268d;
--color-priority-bg: #251c39;
--color-priority-text: #a371f7;
--color-red-badge: #db2828;
--color-red-badge-bg: #db28281a;
--color-red-badge-hover-bg: #db28284d;
@@ -187,62 +191,106 @@ gitea-theme-meta-info {
--color-git: #f05133;
--color-logo: #609926;
/* target-based colors */
--color-body: #1b1f23;
--color-box-header: #1a1d1f;
--color-box-body: #14171a;
--color-box-body-highlight: #1e2226;
--color-text-dark: #f7f8f9;
--color-text: #d0d5da;
--color-text-light: #bcc3cb;
--color-text-light-1: #a5afb9;
--color-text-light-2: #8f9ba8;
--color-text-light-3: #788797;
--color-body: #1e1f20;
--color-box-header: #1b1c1e;
--color-box-body: #161718;
--color-box-body-highlight: #202124;
--color-text-dark: #f8f8f8;
--color-text: #d2d4d8;
--color-text-light: #c0c2c7;
--color-text-light-1: #aaadb4;
--color-text-light-2: #969aa1;
--color-text-light-3: #80858f;
--color-footer: var(--color-nav-bg);
--color-timeline: #343c44;
--color-timeline: #383b40;
--color-input-text: var(--color-text-dark);
--color-input-background: #171a1e;
--color-input-toggle-background: #2e353c;
--color-input-border: var(--color-secondary);
--color-input-border-hover: var(--color-secondary-dark-1);
--color-light: #00001728;
--color-light-mimic-enabled: rgba(0, 0, 0, calc(40 / 255 * 222 / 255 / var(--opacity-disabled)));
--color-light-border: #e8f3ff28;
--color-hover: #e8f3ff19;
--color-hover-opaque: #21252a; /* TODO: color-mix(in srgb, var(--color-body), var(--color-hover)); */
--color-active: #e8f3ff24;
--color-menu: #171a1e;
--color-card: #171a1e;
--color-markup-table-row: #e8f3ff0f;
--color-markup-code-block: #e8f3ff12;
--color-markup-code-inline: #e8f3ff28;
--color-button: #171a1e;
--color-code-bg: #14171a;
--color-shadow: #00001758;
--color-shadow-opaque: #000017;
--color-secondary-bg: #2a3137;
--color-expand-button: #2f363d;
--color-input-background: #191a1c;
--color-input-toggle-background: #323438;
--color-input-border: var(--color-secondary-dark-1);
--color-light: #0b0b0c28;
--color-light-border: #f3f3f428;
--color-hover: #f3f3f419;
--color-hover-opaque: #232528; /* TODO: color-mix(in srgb, var(--color-body), var(--color-hover)); */
--color-active: #f3f3f424;
--color-menu: #191a1c;
--color-card: #191a1c;
--color-markup-table-row: #f3f3f40f;
--color-markup-code-block: #f3f3f412;
--color-markup-code-inline: #f3f3f428;
--color-button: #191a1c;
--color-code-bg: #161718;
--color-shadow: #0b0b0c58;
--color-shadow-opaque: #0b0b0c;
--color-secondary-bg: #2e3033;
--color-expand-button: #333539;
--color-placeholder-text: var(--color-text-light-3);
--color-editor-line-highlight: var(--color-primary-light-5);
--color-editor-line-highlight: var(--color-secondary-alpha-40);
--color-editor-selection: var(--color-primary-alpha-50);
--color-project-column-bg: var(--color-secondary-light-2);
--color-caret: var(--color-text); /* should ideally be --color-text-dark, see #15651 */
--color-reaction-bg: #e8f3ff12;
--color-reaction-bg: #f3f3f412;
--color-reaction-hover-bg: var(--color-primary-light-4);
--color-reaction-active-bg: var(--color-primary-light-5);
--color-tooltip-text: #f9fafb;
--color-tooltip-bg: #000b17f0;
--color-nav-bg: #16191d;
--color-tooltip-text: #fafafa;
--color-tooltip-bg: #0b0b0cf0;
--color-nav-bg: #18191b;
--color-nav-hover-bg: var(--color-secondary-light-1);
--color-nav-text: var(--color-text);
--color-secondary-nav-bg: #181c20;
--color-secondary-nav-bg: #1a1b1e;
--color-label-text: var(--color-text);
--color-label-bg: #7282924b;
--color-label-hover-bg: #728292a0;
--color-label-active-bg: #728292ff;
--color-label-bg: #7a7f8a4b;
--color-label-hover-bg: #7a7f8aa0;
--color-label-active-bg: #7a7f8aff;
--color-accent: var(--color-primary-light-1);
--color-small-accent: var(--color-primary-light-5);
--color-highlight-fg: #87651e;
--color-highlight-bg: #352c1c;
--color-highlight-bg: #443a27;
--color-overlay-backdrop: #080808c0;
--color-danger: var(--color-red);
--color-transparency-grid-light: #2a2a2a;
--color-transparency-grid-dark: #1a1a1a;
--color-workflow-edge-hover: #666a73;
--color-syntax-keyword: #ff8854;
--color-syntax-bool: #25bbc9;
--color-syntax-control: #dd9e17;
--color-syntax-name: #c7a618;
--color-syntax-type: #eb8cb3;
--color-syntax-number: #63b2dd;
--color-syntax-operator: #ff8854;
--color-syntax-regexp: #b89de4;
--color-syntax-string: #95b62a;
--color-syntax-comment: #8898b0;
--color-syntax-invalid: #ff8686;
--color-syntax-link: var(--color-primary);
--color-syntax-tag: #ff8854;
--color-syntax-attribute: #c792ff;
--color-syntax-property: #55afff;
--color-syntax-variable: #e29b33;
--color-syntax-string-special: #dd9e17;
--color-syntax-escape: #c7a618;
--color-syntax-entity: #c792ff;
--color-syntax-preproc: #4cbe7a;
--color-syntax-preproc-file: #63b2dd;
--color-syntax-decorator: #4cbe7a;
--color-syntax-namespace: #c9d1d9;
--color-syntax-name-pseudo: #c792ff;
--color-syntax-comment-special: #b89de4;
--color-syntax-text: #c9d1d9;
--color-syntax-text-alt: #b9bcc7;
--color-syntax-punctuation: #d2d4db;
--color-syntax-whitespace: #7f8699;
--color-syntax-diff-fg: #ffffff;
--color-syntax-deleted-bg: #5f3737;
--color-syntax-inserted-bg: #3a523a;
--color-syntax-emph: #d1a242;
--color-syntax-strong: #e29b33;
--color-syntax-heading: #dd9e17;
--color-syntax-subheading: #95b62a;
--color-syntax-output: #8898b0;
--color-syntax-prompt: #e29b33;
--color-syntax-traceback: #ff8686;
--color-syntax-matching-bracket-bg: #00918a48;
--color-syntax-nonmatching-bracket-bg: #cc484848;
accent-color: var(--color-accent);
color-scheme: dark;
}
@@ -1,16 +1,19 @@
@import "./theme-gitea-light.css";
gitea-theme-meta-info {
--theme-display-name: "Light (Red/Green Colorblind-friendly)";
--theme-display-name: "Light";
--theme-colorblind-type: "red-green";
--theme-color-scheme: "light";
}
/* red/green colorblind-friendly colors */
/* from GitHub: --diffBlob-addition-*, --diffBlob-deletion-*, etc */
:root {
--color-diff-added-linenum-bg: #54aeff4d;
--color-diff-added-row-bg: #ddf4ff80;
--color-diff-added-word-bg: #54aeff66;
--color-diff-removed-linenum-bg: #ffb77c4d;
--color-diff-removed-row-bg: #fff1e580;
--color-diff-removed-word-bg: #ffb77c80;
--color-diff-added-fg: #2185d0;
--color-diff-added-linenum-bg: #b6e3ff;
--color-diff-added-row-bg: #ddf4ff;
--color-diff-added-word-bg: #b6e3ff;
--color-diff-removed-fg: #fc6500;
--color-diff-removed-linenum-bg: #ffd8b5;
--color-diff-removed-row-bg: #fff1e5;
--color-diff-removed-word-bg: #ffd8b5;
}
@@ -0,0 +1,15 @@
@import "./theme-gitea-light.css";
gitea-theme-meta-info {
--theme-display-name: "Light";
--theme-colorblind-type: "blue-yellow";
--theme-color-scheme: "light";
}
/* blue/yellow colorblind-friendly colors */
:root {
--color-diff-added-fg: #2185d0;
--color-diff-added-linenum-bg: #b6e3ff;
--color-diff-added-row-bg: #ddf4ff;
--color-diff-added-word-bg: #b6e3ff;
}
@@ -1,8 +1,6 @@
@import "../chroma/light.css";
@import "../codemirror/light.css";
gitea-theme-meta-info {
--theme-display-name: "Light";
--theme-color-scheme: "light";
}
:root {
@@ -65,14 +63,15 @@ gitea-theme-meta-info {
--color-secondary-hover: var(--color-secondary-dark-5);
--color-secondary-active: var(--color-secondary-dark-6);
/* console colors - used for actions console and console files */
--color-console-fg: #f7f8f9;
--color-console-fg-subtle: #bdc4cc;
--color-console-bg: #171b1e;
--color-console-border: #2e353b;
--color-console-hover-bg: #272d33;
--color-console-active-bg: #2e353b;
--color-console-menu-bg: #262b31;
--color-console-menu-border: #414b55;
--color-console-fg: #0d1117;
--color-console-fg-subtle: #40474d;
--color-console-bg: #ffffff;
--color-console-border: #d0d7de;
--color-console-hover-bg: #f1f3f5;
--color-console-active-bg: #d0d7de;
--color-console-menu-bg: #f8f9fb;
--color-console-menu-border: #d0d7de;
--color-console-link: #5c656d;
/* named colors */
--color-red: #db2828;
--color-orange: #f2711c;
@@ -147,31 +146,36 @@ gitea-theme-meta-info {
--color-grey-light: #7c838a;
--color-gold: #a1882b;
--color-white: #ffffff;
--color-diff-added-fg: #21ba45;
--color-diff-added-linenum-bg: #d1f8d9;
--color-diff-added-row-bg: #e6ffed;
--color-diff-added-row-border: #e6ffed;
--color-diff-added-word-bg: #acf2bd;
--color-diff-moved-row-bg: #f1f8d1;
--color-diff-moved-row-border: #d0e27f;
--color-diff-removed-fg: #db2828;
--color-diff-removed-linenum-bg: #ffcecb;
--color-diff-removed-row-bg: #ffeef0;
--color-diff-removed-row-border: #f1c0c0;
--color-diff-removed-word-bg: #fdb8c0;
--color-diff-inactive: #f0f2f4;
--color-error-border: #e0b4b4;
--color-error-bg: #fff6f6;
--color-error-bg-active: #fbb;
--color-error-bg-hover: #fdd;
--color-error-text: #9f3a38;
--color-success-border: #a3c293;
--color-success-bg: #fcfff5;
--color-success-text: #2c662d;
--color-warning-border: #c9ba9b;
--color-warning-bg: #fffaf3;
--color-warning-text: #573a08;
--color-info-border: #a9d5de;
--color-info-bg: #f8ffff;
--color-info-text: #276f86;
--color-error-border: #ff818266;
--color-error-bg: #ffebe9;
--color-error-bg-active: #ffcecb;
--color-error-bg-hover: #ffdcd7;
--color-error-text: #d1242f;
--color-success-border: #4ac26b66;
--color-success-bg: #dafbe1;
--color-success-text: #1a7f37;
--color-warning-border: #d4a72c66;
--color-warning-bg: #fff8c5;
--color-warning-text: #9a6700;
--color-info-border: #54aeff66;
--color-info-bg: #ddf4ff;
--color-info-text: #0969da;
--color-priority-border: #b9a1ff66;
--color-priority-bg: #f3e7ff;
--color-priority-text: #8250df;
--color-red-badge: #db2828;
--color-red-badge-bg: #db28281a;
--color-red-badge-hover-bg: #db28284d;
@@ -202,10 +206,8 @@ gitea-theme-meta-info {
--color-input-text: var(--color-text-dark);
--color-input-background: #fff;
--color-input-toggle-background: #d0d7de;
--color-input-border: var(--color-secondary);
--color-input-border-hover: var(--color-secondary-dark-1);
--color-input-border: var(--color-secondary-dark-1);
--color-light: #00001706;
--color-light-mimic-enabled: rgba(0, 0, 0, calc(6 / 255 * 222 / 255 / var(--opacity-disabled)));
--color-light-border: #0000171d;
--color-hover: #00001708;
--color-hover-opaque: #f1f3f5; /* TODO: color-mix(in srgb, var(--color-body), var(--color-hover)); */
@@ -222,7 +224,8 @@ gitea-theme-meta-info {
--color-secondary-bg: #f2f5f8;
--color-expand-button: #cfe8fa;
--color-placeholder-text: var(--color-text-light-3);
--color-editor-line-highlight: var(--color-primary-light-6);
--color-editor-line-highlight: var(--color-secondary-alpha-30);
--color-editor-selection: var(--color-primary-alpha-30);
--color-project-column-bg: var(--color-secondary-light-4);
--color-caret: var(--color-text-dark);
--color-reaction-bg: #0000170a;
@@ -241,8 +244,53 @@ gitea-theme-meta-info {
--color-accent: var(--color-primary-light-1);
--color-small-accent: var(--color-primary-light-6);
--color-highlight-fg: #eed200;
--color-highlight-bg: #fffbdd;
--color-highlight-bg: #f5efc5;
--color-overlay-backdrop: #080808c0;
--color-danger: var(--color-red);
--color-transparency-grid-light: #fafafa;
--color-transparency-grid-dark: #e2e2e2;
--color-workflow-edge-hover: #b1b7bd;
--color-syntax-keyword: #a73a00;
--color-syntax-bool: #076872;
--color-syntax-control: #7d5700;
--color-syntax-name: #785900;
--color-syntax-type: #ae2368;
--color-syntax-number: #105ead;
--color-syntax-operator: #a73a00;
--color-syntax-regexp: #773dc5;
--color-syntax-string: #456800;
--color-syntax-comment: #506070;
--color-syntax-invalid: #c00000;
--color-syntax-link: var(--color-primary);
--color-syntax-tag: #a73a00;
--color-syntax-attribute: #6f41c5;
--color-syntax-property: #2060a0;
--color-syntax-variable: #944a00;
--color-syntax-string-special: #7d5700;
--color-syntax-escape: #785900;
--color-syntax-entity: #6f41c5;
--color-syntax-preproc: #2d6a4b;
--color-syntax-preproc-file: #105ead;
--color-syntax-decorator: #2d6a4b;
--color-syntax-namespace: #555555;
--color-syntax-name-pseudo: #6f41c5;
--color-syntax-comment-special: #773dc5;
--color-syntax-text: inherit;
--color-syntax-text-alt: #47525b;
--color-syntax-punctuation: inherit;
--color-syntax-whitespace: #bbbbbb;
--color-syntax-diff-fg: #000000;
--color-syntax-deleted-bg: #ffdddd;
--color-syntax-inserted-bg: #ddffdd;
--color-syntax-emph: #8b5000;
--color-syntax-strong: inherit;
--color-syntax-heading: #7d5700;
--color-syntax-subheading: #456800;
--color-syntax-output: #506070;
--color-syntax-prompt: #944a00;
--color-syntax-traceback: #c00000;
--color-syntax-matching-bracket-bg: #00b5ad38;
--color-syntax-nonmatching-bracket-bg: #db282838;
accent-color: var(--color-accent);
color-scheme: light;
}
+19 -3
View File
@@ -92,15 +92,31 @@
}
.user-badges {
display: grid;
grid-template-columns: repeat(auto-fill, 64px);
gap: 2px;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin: 0;
min-width: 0;
}
.user-badge-item {
display: inline-flex;
flex: 0 0 auto;
min-width: max-content;
}
.user-badges img {
object-fit: contain;
}
.user-badge-chip {
max-width: none !important;
overflow: visible !important;
text-overflow: clip !important;
white-space: nowrap;
min-width: max-content;
}
#readme_profile {
padding: 1em 2em;
border-radius: var(--border-radius);
@@ -1144,8 +1144,8 @@ $.api.settings = {
},
regExp : {
required : /\{\$*[A-z0-9]+\}/g,
optional : /\{\/\$*[A-z0-9]+\}/g,
required : /\{\$*[_A-Za-z0-9]+\}/g, // GITEA-PATCH: use "_A-Za-z" instead of "A-z" for variable name matching
optional : /\{\/\$*[_A-Za-z0-9]+\}/g, // GITEA-PATCH: use "_A-Za-z" instead of "A-z" for variable name matching
},
className: {
File diff suppressed because it is too large Load Diff
@@ -66,7 +66,7 @@ $.fn.dropdown = function(parameters) {
moduleNamespace = 'module-' + namespace,
$module = $(this),
$context = $(settings.context),
$context = (typeof settings.context === 'string') ? $(document).find(settings.context) : $(settings.context), // GITEA-PATCH: use "jQuery.find(selector)" instead of "jQuery(selector)"
$text = $module.find(selector.text),
$search = $module.find(selector.search),
$sizer = $module.find(selector.sizer),
@@ -407,6 +407,9 @@ $.fn.dropdown = function(parameters) {
.html( templates.dropdown(selectValues, fields, settings.preserveHTML, settings.className) )
.insertBefore($input)
;
$module.attr('data-tooltip-content', $input.attr('data-tooltip-content') ?? null); // GITEA-PATCH: convert "select" to "dropdown" with attrs
if($input.hasClass(className.multiple) && $input.prop('multiple') === false) {
module.error(error.missingMultiple);
$input.prop('multiple', true);
@@ -4155,7 +4158,7 @@ $.fn.dropdown.settings.templates = {
html = '',
escape = $.fn.dropdown.settings.templates.escape
;
html += '<i class="dropdown icon"></i>';
html += '<i class="dropdown icon"><svg viewBox="0 0 16 16" class="svg octicon-triangle-down" aria-hidden="true" width="14" height="14"><path d="m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427"></path></svg></i>';
if(placeholder) {
html += '<div class="default text">' + escape(placeholder,preserveHTML) + '</div>';
}
File diff suppressed because it is too large Load Diff
@@ -1,698 +0,0 @@
/*!
* # Fomantic-UI - Modal
* http://github.com/fomantic/Fomantic-UI/
*
*
* Released under the MIT license
* http://opensource.org/licenses/MIT
*
*/
/*******************************
Modal
*******************************/
.ui.modal {
position: absolute;
display: none;
z-index: 1001;
text-align: left;
background: #FFFFFF;
border: none;
box-shadow: 1px 3px 3px 0 rgba(0, 0, 0, 0.2), 1px 3px 15px 2px rgba(0, 0, 0, 0.2);
transform-origin: 50% 25%;
flex: 0 0 auto;
border-radius: 0.28571429rem;
-webkit-user-select: text;
-moz-user-select: text;
user-select: text;
will-change: top, left, margin, transform, opacity;
}
.ui.modal > :first-child:not(.icon):not(.dimmer),
.ui.modal > i.icon:first-child + *,
.ui.modal > .dimmer:first-child + *:not(.icon),
.ui.modal > .dimmer:first-child + i.icon + * {
border-top-left-radius: 0.28571429rem;
border-top-right-radius: 0.28571429rem;
}
.ui.modal > :last-child {
border-bottom-left-radius: 0.28571429rem;
border-bottom-right-radius: 0.28571429rem;
}
.ui.modal > .ui.dimmer {
border-radius: inherit;
}
/*******************************
Content
*******************************/
/*--------------
Close
---------------*/
.ui.modal > .close {
cursor: pointer;
position: absolute;
top: -2.5rem;
right: -2.5rem;
z-index: 1;
opacity: 0.8;
font-size: 1.25em;
color: #FFFFFF;
width: 2.25rem;
height: 2.25rem;
padding: 0.625rem 0 0 0;
}
.ui.modal > .close:hover {
opacity: 1;
}
/*--------------
Header
---------------*/
.ui.modal > .header {
display: block;
font-family: var(--fonts-regular);
background: #FFFFFF;
margin: 0;
padding: 1.25rem 1.5rem;
box-shadow: none;
color: rgba(0, 0, 0, 0.85);
border-bottom: 1px solid rgba(34, 36, 38, 0.15);
}
.ui.modal > .header:not(.ui) {
font-size: 1.42857143rem;
line-height: 1.28571429em;
font-weight: 500;
}
/*--------------
Content
---------------*/
.ui.modal > .content {
display: block;
width: 100%;
font-size: 1em;
line-height: 1.4;
padding: 1.5rem;
background: #FFFFFF;
}
.ui.modal > .image.content {
display: flex;
flex-direction: row;
}
/* Image */
.ui.modal > .content > .image {
display: block;
flex: 0 1 auto;
width: '';
align-self: start;
max-width: 100%;
}
.ui.modal > [class*="stretched"] {
align-self: stretch;
}
/* Description */
.ui.modal > .content > .description {
display: block;
flex: 1 0 auto;
min-width: 0;
align-self: start;
}
.ui.modal > .content > i.icon + .description,
.ui.modal > .content > .image + .description {
flex: 0 1 auto;
min-width: '';
width: auto;
padding-left: 2em;
}
/*rtl:ignore*/
.ui.modal > .content > .image > i.icon {
margin: 0;
opacity: 1;
width: auto;
line-height: 1;
font-size: 8rem;
}
/*--------------
Actions
---------------*/
.ui.modal > .actions {
background: #F9FAFB;
padding: 1rem 1rem;
border-top: 1px solid rgba(34, 36, 38, 0.15);
text-align: right;
}
.ui.modal .actions > .button:not(.fluid) {
margin-left: 0.75em;
}
.ui.basic.modal > .actions {
border-top: none;
}
/*-------------------
Responsive
--------------------*/
/* Modal Width */
@media only screen and (max-width: 767.98px) {
.ui.modal:not(.fullscreen) {
width: 95%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 768px) {
.ui.modal:not(.fullscreen) {
width: 88%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 992px) {
.ui.modal:not(.fullscreen) {
width: 850px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1200px) {
.ui.modal:not(.fullscreen) {
width: 900px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1920px) {
.ui.modal:not(.fullscreen) {
width: 950px;
margin: 0 0 0 0;
}
}
/* Tablet and Mobile */
@media only screen and (max-width: 991.98px) {
.ui.modal > .header {
padding-right: 2.25rem;
}
.ui.modal > .close {
top: 1.0535rem;
right: 1rem;
color: rgba(0, 0, 0, 0.87);
}
}
/* Mobile */
@media only screen and (max-width: 767.98px) {
.ui.modal > .header {
padding: 0.75rem 1rem !important;
padding-right: 2.25rem !important;
}
.ui.overlay.fullscreen.modal > .content.content.content {
min-height: calc(100vh - 8.1rem);
}
.ui.overlay.fullscreen.modal > .scrolling.content.content.content {
max-height: calc(100vh - 8.1rem);
}
.ui.modal > .content {
display: block;
padding: 1rem !important;
}
.ui.modal > .close {
top: 0.5rem !important;
right: 0.5rem !important;
}
/*rtl:ignore*/
.ui.modal .image.content {
flex-direction: column;
}
.ui.modal > .content > .image {
display: block;
max-width: 100%;
margin: 0 auto !important;
text-align: center;
padding: 0 0 1rem !important;
}
.ui.modal > .content > .image > i.icon {
font-size: 5rem;
text-align: center;
}
/*rtl:ignore*/
.ui.modal > .content > .description {
display: block;
width: 100% !important;
margin: 0 !important;
padding: 1rem 0 !important;
box-shadow: none;
}
/* Let Buttons Stack */
.ui.modal > .actions {
padding: 1rem 1rem 0rem !important;
}
.ui.modal .actions > .buttons,
.ui.modal .actions > .button {
margin-bottom: 1rem;
}
}
/*--------------
Coupling
---------------*/
.ui.inverted.dimmer > .ui.modal {
box-shadow: 1px 3px 10px 2px rgba(0, 0, 0, 0.2);
}
/*******************************
Types
*******************************/
.ui.basic.modal {
background-color: transparent;
border: none;
border-radius: 0;
box-shadow: none !important;
color: #FFFFFF;
}
.ui.basic.modal > .header,
.ui.basic.modal > .content,
.ui.basic.modal > .actions {
background-color: transparent;
}
.ui.basic.modal > .header {
color: #FFFFFF;
border-bottom: none;
}
.ui.basic.modal > .close {
top: 1rem;
right: 1.5rem;
color: #FFFFFF;
}
.ui.inverted.dimmer > .basic.modal {
color: rgba(0, 0, 0, 0.87);
}
.ui.inverted.dimmer > .ui.basic.modal > .header {
color: rgba(0, 0, 0, 0.85);
}
/* Resort to margin positioning if legacy */
.ui.legacy.legacy.modal,
.ui.legacy.legacy.page.dimmer > .ui.modal {
left: 50% !important;
}
.ui.legacy.legacy.modal:not(.aligned),
.ui.legacy.legacy.page.dimmer > .ui.modal:not(.aligned) {
top: 50%;
}
.ui.legacy.legacy.page.dimmer > .ui.scrolling.modal:not(.aligned),
.ui.page.dimmer > .ui.scrolling.legacy.legacy.modal:not(.aligned),
.ui.top.aligned.legacy.legacy.page.dimmer > .ui.modal:not(.aligned),
.ui.top.aligned.dimmer > .ui.legacy.legacy.modal:not(.aligned) {
top: auto;
}
.ui.legacy.overlay.fullscreen.modal {
margin-top: -2rem !important;
}
/*******************************
States
*******************************/
.ui.loading.modal {
display: block;
visibility: hidden;
z-index: -1;
}
.ui.active.modal {
display: block;
}
/*******************************
Variations
*******************************/
/*--------------
Aligned
---------------*/
.modals.dimmer .ui.top.aligned.modal {
top: 5vh;
}
.modals.dimmer .ui.bottom.aligned.modal {
bottom: 5vh;
}
@media only screen and (max-width: 767.98px) {
.modals.dimmer .ui.top.aligned.modal {
top: 1rem;
}
.modals.dimmer .ui.bottom.aligned.modal {
bottom: 1rem;
}
}
/*--------------
Scrolling
---------------*/
/* Scrolling Dimmer */
.scrolling.dimmable.dimmed {
overflow: hidden;
}
.scrolling.dimmable > .dimmer {
justify-content: flex-start;
position: fixed;
}
.scrolling.dimmable.dimmed > .dimmer {
overflow: auto;
-webkit-overflow-scrolling: touch;
}
.modals.dimmer .ui.scrolling.modal:not(.fullscreen) {
margin: 2rem auto;
}
/* Fix for Firefox, Edge, IE11 */
.modals.dimmer .ui.scrolling.modal:not([class*="overlay fullscreen"])::after {
content: '\00A0';
position: absolute;
height: 2rem;
}
/* Undetached Scrolling */
.scrolling.undetached.dimmable.dimmed {
overflow: auto;
-webkit-overflow-scrolling: touch;
}
.scrolling.undetached.dimmable.dimmed > .dimmer {
overflow: hidden;
}
.scrolling.undetached.dimmable .ui.scrolling.modal:not(.fullscreen) {
position: absolute;
left: 50%;
}
/* Scrolling Content */
.ui.modal > .scrolling.content {
max-height: calc(80vh - 10rem);
overflow: auto;
}
.ui.overlay.fullscreen.modal > .content {
min-height: calc(100vh - 9.1rem);
}
.ui.overlay.fullscreen.modal > .scrolling.content {
max-height: calc(100vh - 9.1rem);
}
/*--------------
Full Screen
---------------*/
.ui.fullscreen.modal {
width: 95%;
left: 2.5%;
margin: 1em auto;
}
.ui.overlay.fullscreen.modal {
width: 100%;
left: 0;
margin: 0 auto;
top: 0;
border-radius: 0;
}
.ui.modal > .close.inside + .header,
.ui.fullscreen.modal > .header {
padding-right: 2.25rem;
}
.ui.modal > .close.inside,
.ui.fullscreen.modal > .close {
top: 1.0535rem;
right: 1rem;
color: rgba(0, 0, 0, 0.87);
}
.ui.basic.fullscreen.modal > .close {
color: #FFFFFF;
}
/*--------------
Size
---------------*/
.ui.modal {
font-size: 1rem;
}
.ui.mini.modal > .header:not(.ui) {
font-size: 1.3em;
}
@media only screen and (max-width: 767.98px) {
.ui.mini.modal {
width: 95%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 768px) {
.ui.mini.modal {
width: 35.2%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 992px) {
.ui.mini.modal {
width: 340px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1200px) {
.ui.mini.modal {
width: 360px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1920px) {
.ui.mini.modal {
width: 380px;
margin: 0 0 0 0;
}
}
.ui.tiny.modal > .header:not(.ui) {
font-size: 1.3em;
}
@media only screen and (max-width: 767.98px) {
.ui.tiny.modal {
width: 95%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 768px) {
.ui.tiny.modal {
width: 52.8%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 992px) {
.ui.tiny.modal {
width: 510px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1200px) {
.ui.tiny.modal {
width: 540px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1920px) {
.ui.tiny.modal {
width: 570px;
margin: 0 0 0 0;
}
}
.ui.small.modal > .header:not(.ui) {
font-size: 1.3em;
}
@media only screen and (max-width: 767.98px) {
.ui.small.modal {
width: 95%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 768px) {
.ui.small.modal {
width: 70.4%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 992px) {
.ui.small.modal {
width: 680px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1200px) {
.ui.small.modal {
width: 720px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1920px) {
.ui.small.modal {
width: 760px;
margin: 0 0 0 0;
}
}
.ui.large.modal > .header:not(.ui) {
font-size: 1.6em;
}
@media only screen and (max-width: 767.98px) {
.ui.large.modal {
width: 95%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 768px) {
.ui.large.modal {
width: 88%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 992px) {
.ui.large.modal {
width: 1020px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1200px) {
.ui.large.modal {
width: 1080px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1920px) {
.ui.large.modal {
width: 1140px;
margin: 0 0 0 0;
}
}
.ui.big.modal > .header:not(.ui) {
font-size: 1.6em;
}
@media only screen and (max-width: 767.98px) {
.ui.big.modal {
width: 95%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 768px) {
.ui.big.modal {
width: 88%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 992px) {
.ui.big.modal {
width: 1190px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1200px) {
.ui.big.modal {
width: 1260px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1920px) {
.ui.big.modal {
width: 1330px;
margin: 0 0 0 0;
}
}
.ui.huge.modal > .header:not(.ui) {
font-size: 1.6em;
}
@media only screen and (max-width: 767.98px) {
.ui.huge.modal {
width: 95%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 768px) {
.ui.huge.modal {
width: 88%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 992px) {
.ui.huge.modal {
width: 1360px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1200px) {
.ui.huge.modal {
width: 1440px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1920px) {
.ui.huge.modal {
width: 1520px;
margin: 0 0 0 0;
}
}
.ui.massive.modal > .header:not(.ui) {
font-size: 1.8em;
}
@media only screen and (max-width: 767.98px) {
.ui.massive.modal {
width: 95%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 768px) {
.ui.massive.modal {
width: 88%;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 992px) {
.ui.massive.modal {
width: 1530px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1200px) {
.ui.massive.modal {
width: 1620px;
margin: 0 0 0 0;
}
}
@media only screen and (min-width: 1920px) {
.ui.massive.modal {
width: 1710px;
margin: 0 0 0 0;
}
}
/*******************************
Theme Overrides
*******************************/
/*******************************
Site Overrides
*******************************/
@@ -64,7 +64,7 @@ $.fn.modal = function(parameters) {
moduleNamespace = 'module-' + namespace,
$module = $(this),
$context = $(settings.context),
$context = (typeof settings.context === 'string') ? $(document).find(settings.context) : $(settings.context), // GITEA-PATCH: use "jQuery.find(selector)" instead of "jQuery(selector)"
$close = $module.find(selector.close),
$allModals,
@@ -1,520 +0,0 @@
/*!
* # Fomantic-UI - Search
* http://github.com/fomantic/Fomantic-UI/
*
*
* Released under the MIT license
* http://opensource.org/licenses/MIT
*
*/
/*******************************
Search
*******************************/
.ui.search {
position: relative;
}
.ui.search > .prompt {
margin: 0;
outline: none;
-webkit-appearance: none;
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
text-shadow: none;
font-style: normal;
font-weight: normal;
line-height: 1.21428571em;
padding: 0.67857143em 1em;
font-size: 1em;
background: #FFFFFF;
border: 1px solid rgba(34, 36, 38, 0.15);
color: rgba(0, 0, 0, 0.87);
box-shadow: 0 0 0 0 transparent inset;
transition: background-color 0.1s ease, color 0.1s ease, box-shadow 0.1s ease, border-color 0.1s ease;
}
.ui.search .prompt {
border-radius: 500rem;
}
/*--------------
Icon
---------------*/
.ui.search .prompt ~ .search.icon {
cursor: pointer;
}
/*--------------
Results
---------------*/
.ui.search > .results {
display: none;
position: absolute;
top: 100%;
left: 0;
transform-origin: center top;
white-space: normal;
text-align: left;
text-transform: none;
background: #FFFFFF;
margin-top: 0.5em;
width: 18em;
border-radius: 0.28571429rem;
box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15);
border: 1px solid #D4D4D5;
z-index: 998;
}
.ui.search > .results > :first-child {
border-radius: 0.28571429rem 0.28571429rem 0 0;
}
.ui.search > .results > :last-child {
border-radius: 0 0 0.28571429rem 0.28571429rem;
}
/*--------------
Result
---------------*/
.ui.search > .results .result {
cursor: pointer;
display: block;
overflow: hidden;
font-size: 1em;
padding: 0.85714286em 1.14285714em;
color: rgba(0, 0, 0, 0.87);
line-height: 1.33;
border-bottom: 1px solid rgba(34, 36, 38, 0.1);
}
.ui.search > .results .result:last-child {
border-bottom: none !important;
}
/* Image */
.ui.search > .results .result .image {
float: right;
overflow: hidden;
background: none;
width: 5em;
height: 3em;
border-radius: 0.25em;
}
.ui.search > .results .result .image img {
display: block;
width: auto;
height: 100%;
}
/*--------------
Info
---------------*/
.ui.search > .results .result .image + .content {
margin: 0 6em 0 0;
}
.ui.search > .results .result .title {
margin: -0.14285714em 0 0;
font-family: var(--fonts-regular);
font-weight: 500;
font-size: 1em;
color: rgba(0, 0, 0, 0.85);
}
.ui.search > .results .result .description {
margin-top: 0;
font-size: 0.92857143em;
color: rgba(0, 0, 0, 0.4);
}
.ui.search > .results .result .price {
float: right;
color: #21BA45;
}
/*--------------
Message
---------------*/
.ui.search > .results > .message {
padding: 1em 1em;
}
.ui.search > .results > .message .header {
font-family: var(--fonts-regular);
font-size: 1rem;
font-weight: 500;
color: rgba(0, 0, 0, 0.87);
}
.ui.search > .results > .message .description {
margin-top: 0.25rem;
font-size: 1em;
color: rgba(0, 0, 0, 0.87);
}
/* View All Results */
.ui.search > .results > .action {
display: block;
border-top: none;
background: #F3F4F5;
padding: 0.92857143em 1em;
color: rgba(0, 0, 0, 0.87);
font-weight: 500;
text-align: center;
}
/*******************************
States
*******************************/
/*--------------------
Focus
---------------------*/
.ui.search > .prompt:focus {
border-color: rgba(34, 36, 38, 0.35);
background: #FFFFFF;
color: rgba(0, 0, 0, 0.95);
}
/*--------------------
Loading
---------------------*/
.ui.loading.search .input > i.icon:before {
position: absolute;
content: '';
top: 50%;
left: 50%;
margin: -0.64285714em 0 0 -0.64285714em;
width: 1.28571429em;
height: 1.28571429em;
border-radius: 500rem;
border: 0.2em solid rgba(0, 0, 0, 0.1);
}
.ui.loading.search .input > i.icon:after {
position: absolute;
content: '';
top: 50%;
left: 50%;
margin: -0.64285714em 0 0 -0.64285714em;
width: 1.28571429em;
height: 1.28571429em;
animation: loader 0.6s infinite linear;
border: 0.2em solid #767676;
border-radius: 500rem;
box-shadow: 0 0 0 1px transparent;
}
/*--------------
Hover
---------------*/
.ui.search > .results .result:hover,
.ui.category.search > .results .category .result:hover {
background: #F9FAFB;
}
.ui.search .action:hover:not(div) {
background: #E0E0E0;
}
/*--------------
Active
---------------*/
.ui.category.search > .results .category.active {
background: #F3F4F5;
}
.ui.category.search > .results .category.active > .name {
color: rgba(0, 0, 0, 0.87);
}
.ui.search > .results .result.active,
.ui.category.search > .results .category .result.active {
position: relative;
border-left-color: rgba(34, 36, 38, 0.1);
background: #F3F4F5;
box-shadow: none;
}
.ui.search > .results .result.active .title {
color: rgba(0, 0, 0, 0.85);
}
.ui.search > .results .result.active .description {
color: rgba(0, 0, 0, 0.85);
}
/*--------------------
Disabled
----------------------*/
/* Disabled */
.ui.disabled.search {
cursor: default;
pointer-events: none;
opacity: var(--opacity-disabled);
}
/*******************************
Types
*******************************/
/*--------------
Selection
---------------*/
.ui.search.selection .prompt {
border-radius: 0.28571429rem;
}
/* Remove input */
.ui.search.selection > .icon.input > .remove.icon {
pointer-events: none;
position: absolute;
left: auto;
opacity: 0;
color: '';
top: 0;
right: 0;
transition: color 0.1s ease, opacity 0.1s ease;
}
.ui.search.selection > .icon.input > .active.remove.icon {
cursor: pointer;
opacity: 0.8;
pointer-events: auto;
}
.ui.search.selection > .icon.input:not([class*="left icon"]) > .icon ~ .remove.icon {
right: 1.85714em;
}
.ui.search.selection > .icon.input > .remove.icon:hover {
opacity: 1;
color: #DB2828;
}
/*--------------
Category
---------------*/
.ui.category.search .results {
width: 28em;
}
.ui.category.search .results.animating,
.ui.category.search .results.visible {
display: table;
}
/* Category */
.ui.category.search > .results .category {
display: table-row;
background: #F3F4F5;
box-shadow: none;
transition: background 0.1s ease, border-color 0.1s ease;
}
/* Last Category */
.ui.category.search > .results .category:last-child {
border-bottom: none;
}
/* First / Last */
.ui.category.search > .results .category:first-child .name + .result {
border-radius: 0 0.28571429rem 0 0;
}
.ui.category.search > .results .category:last-child .result:last-child {
border-radius: 0 0 0.28571429rem 0;
}
/* Category Result Name */
.ui.category.search > .results .category > .name {
display: table-cell;
text-overflow: ellipsis;
width: 100px;
white-space: nowrap;
background: transparent;
font-family: var(--fonts-regular);
font-size: 1em;
padding: 0.4em 1em;
font-weight: 500;
color: rgba(0, 0, 0, 0.4);
border-bottom: 1px solid rgba(34, 36, 38, 0.1);
}
/* Category Result */
.ui.category.search > .results .category .results {
display: table-cell;
background: #FFFFFF;
border-left: 1px solid rgba(34, 36, 38, 0.15);
border-bottom: 1px solid rgba(34, 36, 38, 0.1);
}
.ui.category.search > .results .category .result {
border-bottom: 1px solid rgba(34, 36, 38, 0.1);
transition: background 0.1s ease, border-color 0.1s ease;
padding: 0.85714286em 1.14285714em;
}
/*******************************
Variations
*******************************/
/*-------------------
Scrolling
--------------------*/
.ui.scrolling.search > .results,
.ui.search.long > .results,
.ui.search.short > .results {
overflow-x: hidden;
overflow-y: auto;
backface-visibility: hidden;
-webkit-overflow-scrolling: touch;
}
@media only screen and (max-width: 767.98px) {
.ui.scrolling.search > .results {
max-height: 12.17714286em;
}
}
@media only screen and (min-width: 768px) {
.ui.scrolling.search > .results {
max-height: 18.26571429em;
}
}
@media only screen and (min-width: 992px) {
.ui.scrolling.search > .results {
max-height: 24.35428571em;
}
}
@media only screen and (min-width: 1920px) {
.ui.scrolling.search > .results {
max-height: 36.53142857em;
}
}
@media only screen and (max-width: 767.98px) {
.ui.search.short > .results {
max-height: 12.17714286em;
}
.ui.search[class*="very short"] > .results {
max-height: 9.13285714em;
}
.ui.search.long > .results {
max-height: 24.35428571em;
}
.ui.search[class*="very long"] > .results {
max-height: 36.53142857em;
}
}
@media only screen and (min-width: 768px) {
.ui.search.short > .results {
max-height: 18.26571429em;
}
.ui.search[class*="very short"] > .results {
max-height: 13.69928571em;
}
.ui.search.long > .results {
max-height: 36.53142857em;
}
.ui.search[class*="very long"] > .results {
max-height: 54.79714286em;
}
}
@media only screen and (min-width: 992px) {
.ui.search.short > .results {
max-height: 24.35428571em;
}
.ui.search[class*="very short"] > .results {
max-height: 18.26571429em;
}
.ui.search.long > .results {
max-height: 48.70857143em;
}
.ui.search[class*="very long"] > .results {
max-height: 73.06285714em;
}
}
@media only screen and (min-width: 1920px) {
.ui.search.short > .results {
max-height: 36.53142857em;
}
.ui.search[class*="very short"] > .results {
max-height: 27.39857143em;
}
.ui.search.long > .results {
max-height: 73.06285714em;
}
.ui.search[class*="very long"] > .results {
max-height: 109.59428571em;
}
}
/*-------------------
Left / Right
--------------------*/
.ui[class*="left aligned"].search > .results {
right: auto;
left: 0;
}
.ui[class*="right aligned"].search > .results {
right: 0;
left: auto;
}
/*--------------
Fluid
---------------*/
.ui.fluid.search .results {
width: 100%;
}
/*--------------
Sizes
---------------*/
.ui.search {
font-size: 1em;
}
.ui.mini.search {
font-size: 0.78571429em;
}
.ui.tiny.search {
font-size: 0.85714286em;
}
.ui.small.search {
font-size: 0.92857143em;
}
.ui.large.search {
font-size: 1.14285714em;
}
.ui.big.search {
font-size: 1.28571429em;
}
.ui.huge.search {
font-size: 1.42857143em;
}
.ui.massive.search {
font-size: 1.71428571em;
}
/*--------------
Mobile
---------------*/
@media only screen and (max-width: 767.98px) {
.ui.search .results {
max-width: calc(100vw - 2rem);
}
}
/*******************************
Theme Overrides
*******************************/
/*******************************
Site Overrides
*******************************/
@@ -1,4 +0,0 @@
@import "./components/dropdown.css";
@import "./components/form.css";
@import "./components/modal.css";
@import "./components/search.css";
@@ -22,10 +22,6 @@
"admin": false,
"components": [
"api",
"dropdown",
"form",
"modal",
"search",
"tab"
]
}
@@ -1,12 +0,0 @@
import {showGlobalErrorMessage} from './bootstrap.ts';
test('showGlobalErrorMessage', () => {
document.body.innerHTML = '<div class="page-content"></div>';
showGlobalErrorMessage('test msg 1');
showGlobalErrorMessage('test msg 2');
showGlobalErrorMessage('test msg 1'); // duplicated
expect(document.body.innerHTML).toContain('>test msg 1 (2)<');
expect(document.body.innerHTML).toContain('>test msg 2<');
expect(document.querySelectorAll('.js-global-error').length).toEqual(2);
});
+7 -77
View File
@@ -1,92 +1,22 @@
// DO NOT IMPORT window.config HERE!
// to make sure the error handler always works, we should never import `window.config`, because
// some user's custom template breaks it.
import type {Intent} from './types.ts';
import {html} from './utils/html.ts';
import {showGlobalErrorMessage, processWindowErrorEvent} from './modules/errors.ts';
// This sets up the URL prefix used in webpack's chunk loading.
// This file must be imported before any lazy-loading is being attempted.
__webpack_public_path__ = `${window.config?.assetUrlPrefix ?? '/assets'}/`;
function shouldIgnoreError(err: Error) {
const ignorePatterns = [
'/assets/js/monaco.', // https://github.com/go-gitea/gitea/issues/30861 , https://github.com/microsoft/monaco-editor/issues/4496
];
for (const pattern of ignorePatterns) {
if (err.stack?.includes(pattern)) return true;
}
return false;
}
export function showGlobalErrorMessage(msg: string, msgType: Intent = 'error') {
const msgContainer = document.querySelector('.page-content') ?? document.body;
if (!msgContainer) {
alert(`${msgType}: ${msg}`);
return;
}
const msgCompact = msg.replace(/\W/g, '').trim(); // compact the message to a data attribute to avoid too many duplicated messages
let msgDiv = msgContainer.querySelector<HTMLDivElement>(`.js-global-error[data-global-error-msg-compact="${msgCompact}"]`);
if (!msgDiv) {
const el = document.createElement('div');
el.innerHTML = html`<div class="ui container js-global-error tw-my-[--page-spacing]"><div class="ui ${msgType} message tw-text-center tw-whitespace-pre-line"></div></div>`;
msgDiv = el.childNodes[0] as HTMLDivElement;
}
// merge duplicated messages into "the message (count)" format
const msgCount = Number(msgDiv.getAttribute(`data-global-error-msg-count`)) + 1;
msgDiv.setAttribute(`data-global-error-msg-compact`, msgCompact);
msgDiv.setAttribute(`data-global-error-msg-count`, msgCount.toString());
msgDiv.querySelector('.ui.message').textContent = msg + (msgCount > 1 ? ` (${msgCount})` : '');
msgContainer.prepend(msgDiv);
}
function processWindowErrorEvent({error, reason, message, type, filename, lineno, colno}: ErrorEvent & PromiseRejectionEvent) {
const err = error ?? reason;
const assetBaseUrl = String(new URL(__webpack_public_path__, window.location.origin));
const {runModeIsProd} = window.config ?? {};
// `error` and `reason` are not guaranteed to be errors. If the value is falsy, it is likely a
// non-critical event from the browser. We log them but don't show them to users. Examples:
// - https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver#observation_errors
// - https://github.com/mozilla-mobile/firefox-ios/issues/10817
// - https://github.com/go-gitea/gitea/issues/20240
if (!err) {
if (message) console.error(new Error(message));
if (runModeIsProd) return;
}
if (err instanceof Error) {
// If the error stack trace does not include the base URL of our script assets, it likely came
// from a browser extension or inline script. Do not show such errors in production.
if (!err.stack?.includes(assetBaseUrl) && runModeIsProd) return;
// Ignore some known errors that are unable to fix
if (shouldIgnoreError(err)) return;
}
let msg = err?.message ?? message;
if (lineno) msg += ` (${filename} @ ${lineno}:${colno})`;
const dot = msg.endsWith('.') ? '' : '.';
const renderedType = type === 'unhandledrejection' ? 'promise rejection' : type;
showGlobalErrorMessage(`JavaScript ${renderedType}: ${msg}${dot} Open browser console to see more details.`);
}
function initGlobalErrorHandler() {
if (window._globalHandlerErrors?._inited) {
showGlobalErrorMessage(`The global error handler has been initialized, do not initialize it again`);
return;
}
// A module should not be imported twice, otherwise there will be bugs when a module has its internal states.
// A real example is "generateElemId" in "utils/dom.ts", if it is imported twice in different module scopes,
// It will generate duplicate IDs (ps: don't try to use "random" to fix, it is just a real example to show the importance of "do not import a module twice")
if (!window._globalHandlerErrors?._inited) {
if (!window.config) {
showGlobalErrorMessage(`Gitea JavaScript code couldn't run correctly, please check your custom templates`);
}
// we added an event handler for window error at the very beginning of <script> of page head the
// handler calls `_globalHandlerErrors.push` (array method) to record all errors occur before
// this init then in this init, we can collect all error events and show them.
for (const e of window._globalHandlerErrors || []) {
for (const e of (window._globalHandlerErrors as Iterable<ErrorEvent & PromiseRejectionEvent>) || []) {
processWindowErrorEvent(e);
}
// then, change _globalHandlerErrors to an object with push method, to process further error
// events directly
// @ts-expect-error -- this should be refactored to not use a fake array
window._globalHandlerErrors = {_inited: true, push: (e: ErrorEvent & PromiseRejectionEvent) => processWindowErrorEvent(e)};
window._globalHandlerErrors = {_inited: true, push: (e: ErrorEvent & PromiseRejectionEvent) => processWindowErrorEvent(e)} as any;
}
initGlobalErrorHandler();
@@ -0,0 +1,721 @@
<script setup lang="ts">
import {nextTick, onBeforeUnmount, onMounted, ref, toRefs, watch} from 'vue';
import {SvgIcon} from '../svg.ts';
import ActionRunStatus from './ActionRunStatus.vue';
import {addDelegatedEventListener, createElementFromAttrs, toggleElem} from '../utils/dom.ts';
import {formatDatetime} from '../utils/time.ts';
import {POST} from '../modules/fetch.ts';
import type {IntervalId} from '../types.ts';
import {toggleFullScreen} from '../utils.ts';
import {localUserSettings} from '../modules/user-settings.ts';
import type {ActionsArtifact, ActionsRun, ActionsRunStatus} from '../modules/gitea-actions.ts';
import {
type ActionRunViewStore,
createLogLineMessage,
type LogLine,
type LogLineCommand,
parseLogLineCommand
} from './ActionRunView.ts';
function isLogElementInViewport(el: Element, {extraViewPortHeight}={extraViewPortHeight: 0}): boolean {
const rect = el.getBoundingClientRect();
// only check whether bottom is in viewport, because the log element can be a log group which is usually tall
return 0 <= rect.bottom && rect.bottom <= window.innerHeight + extraViewPortHeight;
}
type Step = {
summary: string,
duration: string,
status: ActionsRunStatus,
}
type JobStepState = {
cursor: string|null,
expanded: boolean,
manuallyCollapsed: boolean, // whether the user manually collapsed the step, used to avoid auto-expanding it again
}
type StepContainerElement = HTMLElement & {
// To remember the last active logs container, for example: a batch of logs only starts a group but doesn't end it,
// then the following batches of logs should still use the same group (active logs container).
// maybe it can be refactored to decouple from the HTML element in the future.
_stepLogsActiveContainer?: HTMLElement;
}
type LocaleStorageOptions = {
autoScroll: boolean;
expandRunning: boolean;
actionsLogShowSeconds: boolean;
actionsLogShowTimestamps: boolean;
};
type CurrentJob = {
title: string;
detail: string;
steps: Array<Step>;
};
type JobData = {
artifacts: Array<ActionsArtifact>;
state: {
run: ActionsRun;
currentJob: CurrentJob;
},
logs: {
stepsLog?: Array<{
step: number;
cursor: string | null;
started: number;
lines: LogLine[];
}>;
},
};
defineOptions({
name: 'ActionRunJobView',
});
const props = defineProps<{
store: ActionRunViewStore,
runId: number;
jobId: number;
actionsUrl: string;
locale: Record<string, any>;
}>();
const store = props.store;
const {currentRun: run} = toRefs(store.viewData);
const defaultViewOptions: LocaleStorageOptions = {
autoScroll: true,
expandRunning: false,
actionsLogShowSeconds: false,
actionsLogShowTimestamps: false,
};
const savedViewOptions = localUserSettings.getJsonObject('actions-view-options', defaultViewOptions);
const {autoScroll, expandRunning, actionsLogShowSeconds, actionsLogShowTimestamps} = savedViewOptions;
// internal state
let loadingAbortController: AbortController | null = null;
let intervalID: IntervalId | null = null;
const currentJobStepsStates = ref<Array<JobStepState>>([]);
const menuVisible = ref(false);
const isFullScreen = ref(false);
const timeVisible = ref<Record<string, boolean>>({
'log-time-stamp': actionsLogShowTimestamps,
'log-time-seconds': actionsLogShowSeconds,
});
const optionAlwaysAutoScroll = ref(autoScroll);
const optionAlwaysExpandRunning = ref(expandRunning);
const currentJob = ref<CurrentJob>({
title: '',
detail: '',
steps: [] as Array<Step>,
});
const stepsContainer = ref<HTMLElement | null>(null);
const jobStepLogs = ref<Array<StepContainerElement | undefined>>([]);
watch(optionAlwaysAutoScroll, () => {
saveLocaleStorageOptions();
});
watch(optionAlwaysExpandRunning, () => {
saveLocaleStorageOptions();
});
onMounted(async () => {
// load job data and then auto-reload periodically
// need to await first loadJob so this.currentJobStepsStates is initialized and can be used in hashChangeListener
await loadJob();
// auto-scroll to the bottom of the log group when it is opened
// "toggle" event doesn't bubble, so we need to use 'click' event delegation to handle it
addDelegatedEventListener(elStepsContainer(), 'click', 'summary.job-log-group-summary', (el, _) => {
if (!optionAlwaysAutoScroll.value) return;
const elJobLogGroup = el.closest('details.job-log-group') as HTMLDetailsElement;
setTimeout(() => {
if (elJobLogGroup.open && !isLogElementInViewport(elJobLogGroup)) {
elJobLogGroup.scrollIntoView({behavior: 'smooth', block: 'end'});
}
}, 0);
});
intervalID = setInterval(() => void loadJob(), 1000);
document.body.addEventListener('click', closeDropdown);
void hashChangeListener();
window.addEventListener('hashchange', hashChangeListener);
});
onBeforeUnmount(() => {
document.body.removeEventListener('click', closeDropdown);
window.removeEventListener('hashchange', hashChangeListener);
// clear the interval timer when the component is unmounted
// even our page is rendered once, not spa style
if (intervalID) {
clearInterval(intervalID);
intervalID = null;
}
});
function saveLocaleStorageOptions() {
const opts: LocaleStorageOptions = {
autoScroll: optionAlwaysAutoScroll.value,
expandRunning: optionAlwaysExpandRunning.value,
actionsLogShowSeconds: timeVisible.value['log-time-seconds'],
actionsLogShowTimestamps: timeVisible.value['log-time-stamp'],
};
localUserSettings.setJsonObject('actions-view-options', opts);
}
// get the job step logs container ('.job-step-logs')
function getJobStepLogsContainer(stepIndex: number): StepContainerElement {
return jobStepLogs.value[stepIndex] as StepContainerElement;
}
// get the active logs container element, either the `job-step-logs` or the `job-log-list` in the `job-log-group`
function getActiveLogsContainer(stepIndex: number): StepContainerElement {
const el = getJobStepLogsContainer(stepIndex);
return el._stepLogsActiveContainer ?? el;
}
// begin a log group
function beginLogGroup(stepIndex: number, startTime: number, line: LogLine, cmd: LogLineCommand) {
const el = getJobStepLogsContainer(stepIndex);
// Using "summary + details" is the best way to create a log group because it has built-in support for "toggle" and "accessibility".
// And it makes users can use "Ctrl+F" to search the logs without opening all log groups.
const elJobLogGroupSummary = createElementFromAttrs('summary', {class: 'job-log-group-summary'},
createLogLine(stepIndex, startTime, line, cmd),
);
const elJobLogList = createElementFromAttrs('div', {class: 'job-log-list'});
const elJobLogGroup = createElementFromAttrs('details', {class: 'job-log-group'},
elJobLogGroupSummary,
elJobLogList,
);
el.append(elJobLogGroup);
el._stepLogsActiveContainer = elJobLogList;
}
// end a log group
function endLogGroup(stepIndex: number) {
const el = getJobStepLogsContainer(stepIndex);
el._stepLogsActiveContainer = undefined;
}
// show/hide the step logs for a step
function toggleStepLogs(idx: number) {
currentJobStepsStates.value[idx].expanded = !currentJobStepsStates.value[idx].expanded;
if (currentJobStepsStates.value[idx].expanded) {
void loadJobForce(); // try to load the data immediately instead of waiting for next timer interval
} else if (currentJob.value.steps[idx].status === 'running') {
currentJobStepsStates.value[idx].manuallyCollapsed = true;
}
}
function createLogLine(stepIndex: number, startTime: number, line: LogLine, cmd: LogLineCommand | null) {
const lineNum = createElementFromAttrs('a', {class: 'line-num muted', href: `#jobstep-${stepIndex}-${line.index}`},
String(line.index),
);
const logTimeStamp = createElementFromAttrs('span', {class: 'log-time-stamp'},
formatDatetime(new Date(line.timestamp * 1000)), // for "Show timestamps"
);
const logMsg = createLogLineMessage(line, cmd);
const seconds = Math.floor(line.timestamp - startTime);
const logTimeSeconds = createElementFromAttrs('span', {class: 'log-time-seconds'},
`${seconds}s`, // for "Show seconds"
);
toggleElem(logTimeStamp, timeVisible.value['log-time-stamp']);
toggleElem(logTimeSeconds, timeVisible.value['log-time-seconds']);
const lineClass = cmd?.name ? `job-log-line log-line-${cmd.name}` : 'job-log-line';
return createElementFromAttrs('div', {id: `jobstep-${stepIndex}-${line.index}`, class: lineClass},
lineNum, logTimeStamp, logMsg, logTimeSeconds,
);
}
function shouldAutoScroll(stepIndex: number): boolean {
if (!optionAlwaysAutoScroll.value) return false;
const el = getJobStepLogsContainer(stepIndex);
// if the logs container is empty, then auto-scroll if the step is expanded
if (!el.lastChild) return currentJobStepsStates.value[stepIndex].expanded;
// use extraViewPortHeight to tolerate some extra "virtual view port" height (for example: the last line is partially visible)
return isLogElementInViewport(el.lastChild as Element, {extraViewPortHeight: 5});
}
function appendLogs(stepIndex: number, startTime: number, logLines: LogLine[]) {
for (const line of logLines) {
const cmd = parseLogLineCommand(line);
switch (cmd?.name) {
case 'hidden':
continue;
case 'group':
beginLogGroup(stepIndex, startTime, line, cmd);
continue;
case 'endgroup':
endLogGroup(stepIndex);
continue;
}
// the active logs container may change during the loop, for example: entering and leaving a group
const el = getActiveLogsContainer(stepIndex);
el.append(createLogLine(stepIndex, startTime, line, cmd));
}
}
async function fetchJobData(abortController: AbortController): Promise<JobData> {
const logCursors = currentJobStepsStates.value.map((it, idx) => {
// cursor is used to indicate the last position of the logs
// it's only used by backend, frontend just reads it and passes it back, it can be any type.
// for example: make cursor=null means the first time to fetch logs, cursor=eof means no more logs, etc
return {step: idx, cursor: it.cursor, expanded: it.expanded};
});
const url = `${props.actionsUrl}/runs/${props.runId}/jobs/${props.jobId}`;
const resp = await POST(url, {
signal: abortController.signal,
data: {logCursors},
});
return await resp.json();
}
async function loadJobForce() {
loadingAbortController?.abort();
loadingAbortController = null;
await loadJob();
}
async function loadJob() {
if (loadingAbortController) return;
const abortController = new AbortController();
loadingAbortController = abortController;
try {
const runJobResp = await fetchJobData(abortController);
if (loadingAbortController !== abortController) return;
// FIXME: this logic is quite hacky and dirty, it should be refactored in a better way in the future
// Use consistent "store" operations to load/update the view data
store.viewData.runArtifacts = runJobResp.artifacts || [];
store.viewData.currentRun = runJobResp.state.run;
currentJob.value = runJobResp.state.currentJob;
const jobLogs = runJobResp.logs.stepsLog ?? [];
// sync the currentJobStepsStates to store the job step states
for (let i = 0; i < currentJob.value.steps.length; i++) {
const autoExpand = optionAlwaysExpandRunning.value && currentJob.value.steps[i].status === 'running';
if (!currentJobStepsStates.value[i]) {
// initial states for job steps
currentJobStepsStates.value[i] = {cursor: null, expanded: autoExpand, manuallyCollapsed: false};
} else {
// if the step is not manually collapsed by user, then auto-expand it if option is enabled
if (autoExpand && !currentJobStepsStates.value[i].manuallyCollapsed) {
currentJobStepsStates.value[i].expanded = true;
}
}
}
await nextTick();
// find the step indexes that need to auto-scroll
const autoScrollStepIndexes = new Map<number, boolean>();
for (const stepLogs of jobLogs) {
if (autoScrollStepIndexes.has(stepLogs.step)) continue;
autoScrollStepIndexes.set(stepLogs.step, shouldAutoScroll(stepLogs.step));
}
// append logs to the UI
for (const stepLogs of jobLogs) {
// save the cursor, it will be passed to backend next time
currentJobStepsStates.value[stepLogs.step].cursor = stepLogs.cursor;
appendLogs(stepLogs.step, stepLogs.started, stepLogs.lines);
}
// auto-scroll to the last log line of the last step
let autoScrollJobStepElement: StepContainerElement | undefined;
for (let stepIndex = 0; stepIndex < currentJob.value.steps.length; stepIndex++) {
if (!autoScrollStepIndexes.get(stepIndex)) continue;
autoScrollJobStepElement = getJobStepLogsContainer(stepIndex);
}
const lastLogElem = autoScrollJobStepElement?.lastElementChild;
if (lastLogElem && !isLogElementInViewport(lastLogElem)) {
lastLogElem.scrollIntoView({behavior: 'smooth', block: 'end'});
}
// clear the interval timer if the job is done
if (run.value.done && intervalID) {
clearInterval(intervalID);
intervalID = null;
}
} catch (e) {
// avoid network error while unloading page, and ignore "abort" error
if (e instanceof TypeError || abortController.signal.aborted) return;
throw e;
} finally {
if (loadingAbortController === abortController) loadingAbortController = null;
}
}
function isDone(status: ActionsRunStatus) {
return ['success', 'skipped', 'failure', 'cancelled'].includes(status);
}
function isExpandable(status: ActionsRunStatus) {
return ['success', 'running', 'failure', 'cancelled'].includes(status);
}
function closeDropdown() {
if (menuVisible.value) menuVisible.value = false;
}
function elStepsContainer(): HTMLElement {
return stepsContainer.value as HTMLElement;
}
function toggleTimeDisplay(type: 'seconds' | 'stamp') {
timeVisible.value[`log-time-${type}`] = !timeVisible.value[`log-time-${type}`];
for (const el of elStepsContainer().querySelectorAll(`.log-time-${type}`)) {
toggleElem(el, timeVisible.value[`log-time-${type}`]);
}
saveLocaleStorageOptions();
}
function toggleFullScreenMode() {
isFullScreen.value = !isFullScreen.value;
toggleFullScreen(document.querySelector('.action-view-right')!, isFullScreen.value, '.action-view-body');
}
async function hashChangeListener() {
const selectedLogStep = window.location.hash;
if (!selectedLogStep) return;
const [_, step, _line] = selectedLogStep.split('-');
const stepNum = Number(step);
if (!currentJobStepsStates.value[stepNum]) return;
if (!currentJobStepsStates.value[stepNum].expanded && currentJobStepsStates.value[stepNum].cursor === null) {
currentJobStepsStates.value[stepNum].expanded = true;
// need to await for load job if the step log is loaded for the first time
// so logline can be selected by querySelector
await loadJob();
}
await nextTick();
const logLine = elStepsContainer().querySelector(selectedLogStep);
if (!logLine) return;
logLine.querySelector<HTMLAnchorElement>('.line-num')!.click();
}
</script>
<template>
<div class="job-info-header">
<div class="job-info-header-left gt-ellipsis">
<h3 class="job-info-header-title gt-ellipsis">
{{ currentJob.title }}
</h3>
<p class="job-info-header-detail">
{{ currentJob.detail }}
</p>
</div>
<div class="job-info-header-right">
<div class="ui top right pointing dropdown custom jump item" @click.stop="menuVisible = !menuVisible" @keyup.enter="menuVisible = !menuVisible">
<button class="ui button tw-px-3">
<SvgIcon name="octicon-gear" :size="18"/>
</button>
<div class="menu transition action-job-menu" :class="{visible: menuVisible}" v-if="menuVisible" v-cloak>
<a class="item" @click="toggleTimeDisplay('seconds')">
<i class="icon"><SvgIcon :name="timeVisible['log-time-seconds'] ? 'octicon-check' : 'gitea-empty-checkbox'"/></i>
{{ locale.showLogSeconds }}
</a>
<a class="item" @click="toggleTimeDisplay('stamp')">
<i class="icon"><SvgIcon :name="timeVisible['log-time-stamp'] ? 'octicon-check' : 'gitea-empty-checkbox'"/></i>
{{ locale.showTimeStamps }}
</a>
<a class="item" @click="toggleFullScreenMode()">
<i class="icon"><SvgIcon :name="isFullScreen ? 'octicon-check' : 'gitea-empty-checkbox'"/></i>
{{ locale.showFullScreen }}
</a>
<div class="divider"/>
<a class="item" @click="optionAlwaysAutoScroll = !optionAlwaysAutoScroll">
<i class="icon"><SvgIcon :name="optionAlwaysAutoScroll ? 'octicon-check' : 'gitea-empty-checkbox'"/></i>
{{ locale.logsAlwaysAutoScroll }}
</a>
<a class="item" @click="optionAlwaysExpandRunning = !optionAlwaysExpandRunning">
<i class="icon"><SvgIcon :name="optionAlwaysExpandRunning ? 'octicon-check' : 'gitea-empty-checkbox'"/></i>
{{ locale.logsAlwaysExpandRunning }}
</a>
<div class="divider"/>
<a :class="['item', !currentJob.steps.length ? 'disabled' : '']" :href="run.link + '/jobs/' + jobId + '/logs'" download>
<i class="icon"><SvgIcon name="octicon-download"/></i>
{{ locale.downloadLogs }}
</a>
</div>
</div>
</div>
</div>
<!-- always create the node because we have our own event listeners on it, don't use "v-if" -->
<div class="job-step-container" ref="stepsContainer" v-show="currentJob.steps.length">
<div class="job-step-section" v-for="(jobStep, stepIdx) in currentJob.steps" :key="stepIdx">
<div
class="job-step-summary"
@click.stop="isExpandable(jobStep.status) && toggleStepLogs(stepIdx)"
:class="[currentJobStepsStates[stepIdx].expanded ? 'selected' : '', isExpandable(jobStep.status) && 'step-expandable']"
>
<!-- If the job is done and the job step log is loaded for the first time, show the loading icon
currentJobStepsStates[i].cursor === null means the log is loaded for the first time
-->
<SvgIcon
v-if="isDone(run.status) && currentJobStepsStates[stepIdx].expanded && currentJobStepsStates[stepIdx].cursor === null"
name="gitea-running"
class="tw-mr-2 rotate-clockwise"
/>
<SvgIcon
v-else
:name="currentJobStepsStates[stepIdx].expanded ? 'octicon-chevron-down' : 'octicon-chevron-right'"
:class="['tw-mr-2', !isExpandable(jobStep.status) && 'tw-invisible']"
/>
<ActionRunStatus :status="jobStep.status" class="tw-mr-2"/>
<span class="step-summary-msg gt-ellipsis">{{ jobStep.summary }}</span>
<span class="step-summary-duration">{{ jobStep.duration }}</span>
</div>
<!-- the log elements could be a lot, do not use v-if to destroy/reconstruct the DOM,
use native DOM elements for "log line" to improve performance, Vue is not suitable for managing so many reactive elements. -->
<div class="job-step-logs" :ref="(el) => jobStepLogs[stepIdx] = el as StepContainerElement" v-show="currentJobStepsStates[stepIdx].expanded"/>
</div>
</div>
</template>
<style scoped>
/* begin fomantic dropdown menu overrides */
.action-view-right .ui.dropdown .menu {
background: var(--color-console-menu-bg);
border-color: var(--color-console-menu-border);
}
.action-view-right .ui.dropdown .menu > .item {
color: var(--color-console-fg);
}
.action-view-right .ui.dropdown .menu > .item:hover {
color: var(--color-console-fg);
background: var(--color-console-hover-bg);
}
.action-view-right .ui.dropdown .menu > .item:active {
color: var(--color-console-fg);
background: var(--color-console-active-bg);
}
.action-view-right .ui.dropdown .menu > .divider {
border-top-color: var(--color-console-menu-border);
}
.action-view-right .ui.pointing.dropdown > .menu:not(.hidden)::after {
background: var(--color-console-menu-bg);
box-shadow: -1px -1px 0 0 var(--color-console-menu-border);
}
/* end fomantic dropdown menu overrides */
.job-info-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 12px;
position: sticky;
top: 0;
height: 60px;
z-index: 1; /* above .job-step-container */
background: var(--color-console-bg);
border-radius: 3px;
}
.job-info-header:has(+ .job-step-container) {
border-radius: var(--border-radius) var(--border-radius) 0 0;
}
.job-info-header .job-info-header-title {
color: var(--color-console-fg);
font-size: 16px;
margin: 0;
}
.job-info-header .job-info-header-detail {
color: var(--color-console-fg-subtle);
font-size: 12px;
}
.job-info-header-left {
flex: 1;
}
.job-step-container {
max-height: 100%;
border-radius: 0 0 var(--border-radius) var(--border-radius);
border-top: 1px solid var(--color-console-border);
z-index: 0;
}
.job-step-container .job-step-summary {
padding: 5px 10px;
display: flex;
align-items: center;
border-radius: var(--border-radius);
}
.job-step-container .job-step-summary.step-expandable {
cursor: pointer;
}
.job-step-container .job-step-summary.step-expandable:hover {
color: var(--color-console-fg);
background: var(--color-console-hover-bg);
}
.job-step-container .job-step-summary .step-summary-msg {
flex: 1;
}
.job-step-container .job-step-summary .step-summary-duration {
margin-left: 16px;
}
.job-step-container .job-step-summary.selected {
color: var(--color-console-fg);
background-color: var(--color-console-active-bg);
position: sticky;
top: 60px;
/* workaround ansi_up issue related to faintStyle generating a CSS stacking context via `opacity`
inline style which caused such elements to render above the .job-step-summary header. */
z-index: 1;
}
</style>
<style> /* eslint-disable-line vue-scoped-css/enforce-style-type */
/* some elements are not managed by vue, so we need to use global style */
.job-step-section {
margin: 10px;
}
.job-step-section .job-step-logs {
font-family: var(--fonts-monospace);
margin: 8px 0;
font-size: 12px;
}
.job-step-section .job-step-logs .job-log-line {
display: flex;
}
.job-log-line:hover,
.job-log-line:target {
background-color: var(--color-console-hover-bg);
}
.job-log-line:target {
scroll-margin-top: 95px;
}
/* class names 'log-time-seconds' and 'log-time-stamp' are used in the method toggleTimeDisplay */
.job-log-line .line-num, .log-time-seconds {
width: 48px;
color: var(--color-text-light-3);
text-align: right;
user-select: none;
}
.job-log-line:target > .line-num {
color: var(--color-primary);
text-decoration: underline;
}
.log-time-seconds {
padding-right: 2px;
}
.job-log-line .log-time,
.log-time-stamp {
color: var(--color-text-light-3);
margin-left: 10px;
white-space: nowrap;
}
.job-step-logs .job-log-line .log-msg {
flex: 1;
white-space: break-spaces;
margin-left: 10px;
overflow-wrap: anywhere;
}
.job-step-logs .log-msg a {
color: var(--color-console-link) !important;
text-decoration: underline;
}
.job-step-logs .job-log-line .log-cmd-command {
color: var(--color-ansi-blue);
}
.job-step-logs .log-msg-label {
font-weight: var(--font-weight-semibold);
}
.job-step-logs .log-line-error {
background: var(--color-error-bg);
}
.job-step-logs .log-line-warning {
background: var(--color-warning-bg);
}
.job-step-logs .log-line-notice {
background: var(--color-info-bg);
}
.job-step-logs .log-line-debug {
background: var(--color-secondary-alpha-30);
}
.job-step-logs .log-cmd-error > .log-msg-label {
color: var(--color-error-text);
}
.job-step-logs .log-cmd-warning > .log-msg-label {
color: var(--color-warning-text);
}
.job-step-logs .log-cmd-notice > .log-msg-label {
color: var(--color-info-text);
}
.job-step-logs .log-cmd-debug > .log-msg-label {
color: var(--color-violet);
}
/* selectors here are intentionally exact to only match fullscreen */
.full.height > .action-view-right {
width: 100%;
height: 100%;
padding: 0;
border-radius: 0;
}
.full.height > .action-view-right > .job-info-header {
border-radius: 0;
}
.full.height > .action-view-right > .job-step-container {
height: calc(100% - 60px);
border-radius: 0;
}
.job-log-group .job-log-list .job-log-line .log-msg {
margin-left: 2em;
}
.job-log-group-summary {
position: relative;
}
.job-log-group-summary > .job-log-line {
position: absolute;
inset: 0;
z-index: -1; /* to avoid hiding the triangle of the "details" element */
overflow: hidden;
}
</style>
@@ -19,12 +19,12 @@ withDefaults(defineProps<{
<template>
<span :data-tooltip-content="localeStatus ?? status" v-if="status">
<SvgIcon name="octicon-check-circle-fill" class="text green" :size="size" :class="className" v-if="status === 'success'"/>
<SvgIcon name="octicon-skip" class="text grey" :size="size" :class="className" v-else-if="status === 'skipped'"/>
<SvgIcon name="octicon-stop" class="text grey" :size="size" :class="className" v-else-if="status === 'cancelled'"/>
<SvgIcon name="octicon-circle" class="text grey" :size="size" :class="className" v-else-if="status === 'waiting'"/>
<SvgIcon name="octicon-blocked" class="text yellow" :size="size" :class="className" v-else-if="status === 'blocked'"/>
<SvgIcon name="gitea-running" class="text yellow" :size="size" :class="'rotate-clockwise ' + className" v-else-if="status === 'running'"/>
<SvgIcon name="octicon-x-circle-fill" class="text red" :size="size" v-else/><!-- failure, unknown -->
<SvgIcon name="octicon-check-circle-fill" class="tw-text-green" :size="size" :class="className" v-if="status === 'success'"/>
<SvgIcon name="octicon-skip" class="tw-text-text-light" :size="size" :class="className" v-else-if="status === 'skipped'"/>
<SvgIcon name="octicon-stop" class="tw-text-text-light" :size="size" :class="className" v-else-if="status === 'cancelled'"/>
<SvgIcon name="octicon-circle" class="tw-text-text-light" :size="size" :class="className" v-else-if="status === 'waiting'"/>
<SvgIcon name="octicon-blocked" class="tw-text-yellow" :size="size" :class="className" v-else-if="status === 'blocked'"/>
<SvgIcon name="gitea-running" class="tw-text-yellow" :size="size" :class="'rotate-clockwise ' + className" v-else-if="status === 'running'"/>
<SvgIcon name="octicon-x-circle-fill" class="tw-text-red" :size="size" v-else/><!-- failure, unknown -->
</span>
</template>
@@ -0,0 +1,70 @@
<script setup lang="ts">
import ActionRunStatus from './ActionRunStatus.vue';
import WorkflowGraph from './WorkflowGraph.vue';
import type {ActionRunViewStore} from "./ActionRunView.ts";
import {computed, onBeforeUnmount, onMounted, toRefs} from "vue";
defineOptions({
name: 'ActionRunSummaryView',
});
const props = defineProps<{
store: ActionRunViewStore;
locale: Record<string, any>;
}>();
const {currentRun: run} = toRefs(props.store.viewData);
const runTriggeredAtIso = computed(() => {
const t = props.store.viewData.currentRun.triggeredAt;
return t ? new Date(t * 1000).toISOString() : '';
});
onMounted(async () => {
await props.store.startPollingCurrentRun();
});
onBeforeUnmount(() => {
props.store.stopPollingCurrentRun();
});
</script>
<template>
<div class="action-run-summary-view">
<div class="action-run-summary-block">
<div class="flex-text-block">
{{ locale.triggeredVia.replace('%s', run.triggerEvent) }} <relative-time :datetime="runTriggeredAtIso" prefix=""/>
</div>
<div class="flex-text-block">
<ActionRunStatus :locale-status="locale.status[run.status]" :status="run.status" :size="16"/>
<span>{{ locale.status[run.status] }}</span> <span>{{ locale.totalDuration }} {{ run.duration || '' }}</span>
</div>
</div>
<WorkflowGraph
v-if="run.jobs.length > 0"
:store="store"
:jobs="run.jobs"
:run-link="run.link"
:workflow-id="run.workflowID"
/>
</div>
</template>
<style scoped>
.action-run-summary-view {
flex: 1;
display: flex;
flex-direction: column;
color: var(--color-text-light-1);
}
.action-run-summary-block {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 6px;
padding: 12px;
border-bottom: 1px solid var(--color-secondary);
border-radius: var(--border-radius) var(--border-radius) 0 0;
background: var(--color-box-header);
}
</style>
@@ -0,0 +1,32 @@
import {createLogLineMessage, parseLogLineCommand} from './ActionRunView.ts';
test('LogLineMessage', () => {
const cases = {
'normal message': '<span class="log-msg">normal message</span>',
'##[group] foo': '<span class="log-msg log-cmd-group"> foo</span>',
'::group::foo': '<span class="log-msg log-cmd-group">foo</span>',
'##[endgroup]': '<span class="log-msg log-cmd-endgroup"></span>',
'::endgroup::': '<span class="log-msg log-cmd-endgroup"></span>',
'##[error] foo': '<span class="log-msg log-cmd-error"><span class="log-msg-label">Error:</span><span> foo</span></span>',
'##[warning] foo': '<span class="log-msg log-cmd-warning"><span class="log-msg-label">Warning:</span><span> foo</span></span>',
'##[notice] foo': '<span class="log-msg log-cmd-notice"><span class="log-msg-label">Notice:</span><span> foo</span></span>',
'##[debug] foo': '<span class="log-msg log-cmd-debug"><span class="log-msg-label">Debug:</span><span> foo</span></span>',
'::error::foo': '<span class="log-msg log-cmd-error"><span class="log-msg-label">Error:</span><span> foo</span></span>',
'::warning file=test.js,line=1::foo': '<span class="log-msg log-cmd-warning"><span class="log-msg-label">Warning:</span><span> foo</span></span>',
'::notice::foo': '<span class="log-msg log-cmd-notice"><span class="log-msg-label">Notice:</span><span> foo</span></span>',
'::debug::foo': '<span class="log-msg log-cmd-debug"><span class="log-msg-label">Debug:</span><span> foo</span></span>',
'[command] foo': '<span class="log-msg log-cmd-command"> foo</span>',
// hidden is special, it is actually skipped before creating
'##[add-matcher]foo': '<span class="log-msg log-cmd-hidden">foo</span>',
'::add-matcher::foo': '<span class="log-msg log-cmd-hidden">foo</span>',
'::remove-matcher foo::': '<span class="log-msg log-cmd-hidden"> foo::</span>', // not correctly parsed, but we don't need it
};
for (const [input, html] of Object.entries(cases)) {
const line = {index: 0, timestamp: 0, message: input};
const cmd = parseLogLineCommand(line);
const el = createLogLineMessage(line, cmd);
expect(el.outerHTML).toBe(html);
}
});
@@ -0,0 +1,181 @@
import {createElementFromAttrs} from '../utils/dom.ts';
import {renderAnsi} from '../render/ansi.ts';
import {reactive} from 'vue';
import type {ActionsArtifact, ActionsJob, ActionsRun, ActionsRunStatus} from '../modules/gitea-actions.ts';
import type {IntervalId} from '../types.ts';
import {POST} from '../modules/fetch.ts';
// How GitHub Actions logs work:
// * Workflow command outputs log commands like "::group::the-title", "::add-matcher::...."
// * Workflow runner parses and processes the commands to "##[group]", apply "matchers", hide secrets, etc.
// * The reported logs are the processed logs.
// HOWEVER: Gitea runner does not completely process those commands. Many works are done by the frontend at the moment.
const LogLinePrefixCommandMap: Record<string, LogLineCommandName> = {
'::group::': 'group',
'##[group]': 'group',
'::endgroup::': 'endgroup',
'##[endgroup]': 'endgroup',
'##[error]': 'error',
'##[warning]': 'warning',
'##[notice]': 'notice',
'##[debug]': 'debug',
'[command]': 'command',
// https://github.com/actions/toolkit/blob/master/docs/commands.md
// https://github.com/actions/runner/blob/main/docs/adrs/0276-problem-matchers.md#registration
'::add-matcher::': 'hidden',
'##[add-matcher]': 'hidden',
'::remove-matcher': 'hidden', // it has arguments
};
// Pattern for ::cmd:: and ::cmd args:: format (args are stripped for display)
const LogLineCmdPattern = /^::(error|warning|notice|debug)(?:\s[^:]*)?::/;
export type LogLine = {
index: number;
timestamp: number;
message: string;
};
export type LogLineCommandName = 'group' | 'endgroup' | 'command' | 'error' | 'warning' | 'notice' | 'debug' | 'hidden';
export type LogLineCommand = {
name: LogLineCommandName,
prefix: string,
};
export function parseLogLineCommand(line: LogLine): LogLineCommand | null {
// TODO: in the future it can be refactored to be a general parser that can parse arguments, drop the "prefix match"
for (const prefix of Object.keys(LogLinePrefixCommandMap)) {
if (line.message.startsWith(prefix)) {
return {name: LogLinePrefixCommandMap[prefix], prefix};
}
}
// Handle ::cmd:: and ::cmd args:: format (runner may pass these through raw)
const match = LogLineCmdPattern.exec(line.message);
if (match) {
return {name: match[1] as LogLineCommandName, prefix: match[0]};
}
return null;
}
const LogLineLabelMap: Partial<Record<LogLineCommandName, string>> = {
'error': 'Error',
'warning': 'Warning',
'notice': 'Notice',
'debug': 'Debug',
};
export function createLogLineMessage(line: LogLine, cmd: LogLineCommand | null) {
const logMsgAttrs = {class: 'log-msg'};
if (cmd?.name) logMsgAttrs.class += ` log-cmd-${cmd.name}`; // make it easier to add styles to some commands like "error"
// TODO: for some commands (::group::), the "prefix removal" works well, for some commands with "arguments" (::remove-matcher ...::),
// it needs to do further processing in the future (fortunately, at the moment we don't need to handle these commands)
const msgContent = cmd ? line.message.substring(cmd.prefix.length) : line.message;
const logMsg = createElementFromAttrs('span', logMsgAttrs);
const label = cmd ? LogLineLabelMap[cmd.name] : null;
if (label) {
logMsg.append(createElementFromAttrs('span', {class: 'log-msg-label'}, `${label}:`));
const msgSpan = document.createElement('span');
msgSpan.innerHTML = ` ${renderAnsi(msgContent.trimStart())}`;
logMsg.append(msgSpan);
} else {
logMsg.innerHTML = renderAnsi(msgContent);
}
return logMsg;
}
export function createEmptyActionsRun(): ActionsRun {
return {
repoId: 0,
link: '',
title: '',
titleHTML: '',
status: '' as ActionsRunStatus, // do not show the status before initialized, otherwise it would show an incorrect "error" icon
canCancel: false,
canApprove: false,
canRerun: false,
canRerunFailed: false,
canDeleteArtifact: false,
done: false,
workflowID: '',
workflowLink: '',
isSchedule: false,
duration: '',
triggeredAt: 0,
triggerEvent: '',
jobs: [] as Array<ActionsJob>,
commit: {
localeCommit: '',
localePushedBy: '',
shortSHA: '',
link: '',
pusher: {
displayName: '',
link: '',
},
branch: {
name: '',
link: '',
isDeleted: false,
},
},
};
}
export function createActionRunViewStore(actionsUrl: string, runId: number) {
let loadingAbortController: AbortController | null = null;
let intervalID: IntervalId | null = null;
const viewData = reactive({
currentRun: createEmptyActionsRun(),
runArtifacts: [] as Array<ActionsArtifact>,
});
const loadCurrentRun = async () => {
if (loadingAbortController) return;
const abortController = new AbortController();
loadingAbortController = abortController;
try {
const url = `${actionsUrl}/runs/${runId}`;
const resp = await POST(url, {signal: abortController.signal, data: {}});
const runResp = await resp.json();
if (loadingAbortController !== abortController) return;
viewData.runArtifacts = runResp.artifacts || [];
viewData.currentRun = runResp.state.run;
// clear the interval timer if the job is done
if (viewData.currentRun.done && intervalID) {
clearInterval(intervalID);
intervalID = null;
}
} catch (e) {
// avoid network error while unloading page, and ignore "abort" error
if (e instanceof TypeError || abortController.signal.aborted) return;
throw e;
} finally {
if (loadingAbortController === abortController) loadingAbortController = null;
}
};
return reactive({
viewData,
async startPollingCurrentRun() {
await loadCurrentRun();
intervalID = setInterval(() => loadCurrentRun(), 1000);
},
async forceReloadCurrentRun() {
loadingAbortController?.abort();
loadingAbortController = null;
await loadCurrentRun();
},
stopPollingCurrentRun() {
if (!intervalID) return;
clearInterval(intervalID);
intervalID = null;
},
});
}
export type ActionRunViewStore = ReturnType<typeof createActionRunViewStore>;
@@ -5,7 +5,7 @@ import {onMounted, shallowRef} from 'vue';
import type {Value as HeatmapValue, Locale as HeatmapLocale} from '@silverwind/vue3-calendar-heatmap';
defineProps<{
values?: HeatmapValue[];
values: HeatmapValue[];
locale: {
textTotalContributions: string;
heatMapLocale: Partial<HeatmapLocale>;
@@ -28,7 +28,7 @@ const endDate = shallowRef(new Date());
onMounted(() => {
// work around issue with first legend color being rendered twice and legend cut off
const legend = document.querySelector<HTMLElement>('.vch__external-legend-wrapper');
const legend = document.querySelector<HTMLElement>('.vch__external-legend-wrapper')!;
legend.setAttribute('viewBox', '12 0 80 10');
legend.style.marginRight = '-12px';
});
@@ -1,8 +1,9 @@
<script lang="ts" setup>
import {SvgIcon} from '../svg.ts';
import {GET} from '../modules/fetch.ts';
import {getIssueColor, getIssueIcon} from '../features/issue.ts';
import {getIssueColorClass, getIssueIcon} from '../features/issue.ts';
import {computed, onMounted, shallowRef} from 'vue';
import type {Issue} from '../types.ts';
const props = defineProps<{
repoLink: string,
@@ -10,22 +11,24 @@ const props = defineProps<{
}>();
const loading = shallowRef(false);
const issue = shallowRef(null);
const issue = shallowRef<Issue | null>(null);
const renderedLabels = shallowRef('');
const errorMessage = shallowRef(null);
const errorMessage = shallowRef('');
const createdAt = computed(() => {
if (!issue?.value) return '';
return new Date(issue.value.created_at).toLocaleDateString(undefined, {year: 'numeric', month: 'short', day: 'numeric'});
});
const body = computed(() => {
if (!issue?.value) return '';
const body = issue.value.body.replace(/\n+/g, ' ');
return body.length > 85 ? `${body.substring(0, 85)}` : body;
});
onMounted(async () => {
loading.value = true;
errorMessage.value = null;
errorMessage.value = '';
try {
const resp = await GET(props.loadIssueInfoUrl);
if (!resp.ok) {
@@ -50,7 +53,7 @@ onMounted(async () => {
on {{ createdAt }}
</div>
<div class="flex-text-block">
<svg-icon :name="getIssueIcon(issue)" :class="['text', getIssueColor(issue)]"/>
<svg-icon :name="getIssueIcon(issue)" :class="getIssueColorClass(issue)"/>
<span class="issue-title tw-font-semibold tw-break-anywhere">
{{ issue.title }}
<span class="index">#{{ issue.number }}</span>
@@ -3,26 +3,42 @@ import {nextTick, defineComponent} from 'vue';
import {SvgIcon} from '../svg.ts';
import {GET} from '../modules/fetch.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts';
import type {SvgName} from '../svg.ts';
const {appSubUrl, assetUrlPrefix, pageData} = window.config;
type DashboardRepo = {
id: number,
link: string,
full_name: string,
archived: boolean,
fork: boolean,
mirror: boolean,
template: boolean,
private: boolean,
internal: boolean,
latest_commit_status_state?: CommitStatus,
latest_commit_status_state_link?: string,
locale_latest_commit_status_state?: string,
};
type CommitStatus = 'pending' | 'success' | 'error' | 'failure' | 'warning' | 'skipped';
type CommitStatusMap = {
[status in CommitStatus]: {
name: string,
name: SvgName,
color: string,
};
};
// make sure this matches templates/repo/commit_status.tmpl
const commitStatus: CommitStatusMap = {
pending: {name: 'octicon-dot-fill', color: 'yellow'},
success: {name: 'octicon-check', color: 'green'},
error: {name: 'gitea-exclamation', color: 'red'},
failure: {name: 'octicon-x', color: 'red'},
warning: {name: 'gitea-exclamation', color: 'yellow'},
skipped: {name: 'octicon-skip', color: 'grey'},
pending: {name: 'octicon-dot-fill', color: 'tw-text-yellow'},
success: {name: 'octicon-check', color: 'tw-text-green'},
error: {name: 'gitea-exclamation', color: 'tw-text-red'},
failure: {name: 'octicon-x', color: 'tw-text-red'},
warning: {name: 'gitea-exclamation', color: 'tw-text-yellow'},
skipped: {name: 'octicon-skip', color: 'tw-text-text-light'},
};
export default defineComponent({
@@ -38,8 +54,8 @@ export default defineComponent({
return {
tab,
repos: [],
reposTotalCount: null,
repos: [] as DashboardRepo[],
reposTotalCount: null as number | null,
reposFilter,
archivedFilter,
privateFilter,
@@ -48,7 +64,7 @@ export default defineComponent({
searchQuery,
isLoading: false,
staticPrefix: assetUrlPrefix,
counts: {},
counts: {} as Record<string, number>,
repoTypes: {
all: {
searchMode: '',
@@ -65,16 +81,49 @@ export default defineComponent({
collaborative: {
searchMode: 'collaborative',
},
},
textArchivedFilterTitles: {},
textPrivateFilterTitles: {},
organizations: [],
} as Record<string, {searchMode: string}>,
textArchivedFilterTitles: {} as Record<string, string>,
textPrivateFilterTitles: {} as Record<string, string>,
organizations: [] as Array<{name: string, full_name: string, num_repos: number, org_visibility: string}>,
isOrganization: true,
canCreateOrganization: false,
organizationsTotalCount: 0,
organizationId: 0,
searchLimit: 0,
uid: 0,
teamId: 0,
isMirrorsEnabled: false,
isStarsEnabled: false,
canCreateMigrations: false,
textNoOrg: '',
textNoRepo: '',
textRepository: '',
textOrganization: '',
textMyRepos: '',
textNewRepo: '',
textSearchRepos: '',
textFilter: '',
textShowArchived: '',
textShowPrivate: '',
textShowBothArchivedUnarchived: '',
textShowOnlyUnarchived: '',
textShowOnlyArchived: '',
textShowBothPrivatePublic: '',
textShowOnlyPublic: '',
textShowOnlyPrivate: '',
textAll: '',
textSources: '',
textForks: '',
textMirrors: '',
textCollaborative: '',
textFirstPage: '',
textPreviousPage: '',
textNextPage: '',
textLastPage: '',
textMyOrgs: '',
textNewOrg: '',
textOrgVisibilityLimited: '',
textOrgVisibilityPrivate: '',
subUrl: appSubUrl,
...pageData.dashboardRepoList,
activeIndex: -1, // don't select anything at load, first cursor down will select
@@ -110,9 +159,9 @@ export default defineComponent({
},
mounted() {
const el = document.querySelector('#dashboard-repo-list');
const el = document.querySelector('#dashboard-repo-list')!;
this.changeReposFilter(this.reposFilter);
fomanticQuery(el.querySelector('.ui.dropdown')).dropdown();
fomanticQuery(el.querySelector('.ui.dropdown')!).dropdown();
this.textArchivedFilterTitles = {
'archived': this.textShowOnlyArchived,
@@ -250,7 +299,7 @@ export default defineComponent({
nextTick(() => {
// MDN: If there's no focused element, this is the Document.body or Document.documentElement.
if ((document.activeElement === document.body || document.activeElement === document.documentElement)) {
this.$refs.search.focus({preventScroll: true});
(this.$refs.search as HTMLInputElement).focus({preventScroll: true});
}
});
}
@@ -283,7 +332,7 @@ export default defineComponent({
}
},
repoIcon(repo: any) {
repoIcon(repo: DashboardRepo) {
if (repo.fork) {
return 'octicon-repo-forked';
} else if (repo.mirror) {
@@ -307,6 +356,7 @@ export default defineComponent({
},
async reposFilterKeyControl(e: KeyboardEvent) {
if (e.isComposing) return;
switch (e.key) {
case 'Enter':
document.querySelector<HTMLAnchorElement>('.repo-owner-name-list li.active a')?.click();
@@ -429,14 +479,14 @@ export default defineComponent({
<li class="tw-flex tw-items-center tw-py-2" v-for="(repo, index) in repos" :class="{'active': index === activeIndex}" :key="repo.id">
<a class="repo-list-link muted" :href="repo.link">
<svg-icon :name="repoIcon(repo)" :size="16" class="repo-list-icon"/>
<div class="text truncate">{{ repo.full_name }}</div>
<div class="tw-inline-block tw-truncate">{{ repo.full_name }}</div>
<div v-if="repo.archived">
<svg-icon name="octicon-archive" :size="16"/>
</div>
</a>
<a class="tw-flex tw-items-center" v-if="repo.latest_commit_status_state" :href="repo.latest_commit_status_state_link || null" :data-tooltip-content="repo.locale_latest_commit_status_state">
<a class="tw-flex tw-items-center" v-if="repo.latest_commit_status_state" :href="repo.latest_commit_status_state_link || undefined" :data-tooltip-content="repo.locale_latest_commit_status_state">
<!-- the commit status icon logic is taken from templates/repo/commit_status.tmpl -->
<svg-icon :name="statusIcon(repo.latest_commit_status_state)" :class="'tw-ml-2 commit-status icon text ' + statusColor(repo.latest_commit_status_state)" :size="16"/>
<svg-icon :name="statusIcon(repo.latest_commit_status_state)" :class="'tw-ml-2 commit-status icon ' + statusColor(repo.latest_commit_status_state)" :size="16"/>
</a>
</li>
</ul>
@@ -493,14 +543,14 @@ export default defineComponent({
<li class="tw-flex tw-items-center tw-py-2" v-for="org in organizations" :key="org.name">
<a class="repo-list-link muted" :href="subUrl + '/' + encodeURIComponent(org.name)">
<svg-icon name="octicon-organization" :size="16" class="repo-list-icon"/>
<div class="text truncate">{{ org.full_name ? `${org.full_name} (${org.name})` : org.name }}</div>
<div class="tw-inline-block tw-truncate">{{ org.full_name ? `${org.full_name} (${org.name})` : org.name }}</div>
<div><!-- div to prevent underline of label on hover -->
<span class="ui tiny basic label" v-if="org.org_visibility !== 'public'">
{{ org.org_visibility === 'limited' ? textOrgVisibilityLimited: textOrgVisibilityPrivate }}
</span>
</div>
</a>
<div class="text light grey tw-flex tw-items-center tw-ml-2">
<div class="tw-text-grey-light tw-flex tw-items-center tw-ml-2">
{{ org.num_repos }}
<svg-icon name="octicon-repo" :size="16" class="tw-ml-1 tw-mt-0.5"/>
</div>
@@ -23,7 +23,7 @@ type CommitListResult = {
export default defineComponent({
components: {SvgIcon},
data: () => {
const el = document.querySelector('#diff-commit-select');
const el = document.querySelector('#diff-commit-select')!;
return {
menuVisible: false,
isLoading: false,
@@ -35,7 +35,7 @@ export default defineComponent({
mergeBase: el.getAttribute('data-merge-base'),
commits: [] as Array<Commit>,
hoverActivated: false,
lastReviewCommitSha: '',
lastReviewCommitSha: '' as string | null,
uniqueIdMenu: generateElemId('diff-commit-selector-menu-'),
uniqueIdShowAll: generateElemId('diff-commit-selector-show-all-'),
};
@@ -165,7 +165,7 @@ export default defineComponent({
},
/** Called when user clicks on since last review */
changesSinceLastReviewClick() {
window.location.assign(`${this.issueLink}/files/${this.lastReviewCommitSha}..${this.commits.at(-1).id}${this.queryParams}`);
window.location.assign(`${this.issueLink}/files/${this.lastReviewCommitSha}..${this.commits.at(-1)!.id}${this.queryParams}`);
},
/** Clicking on a single commit opens this specific commit */
commitClicked(commitId: string, newWindow = false) {
@@ -193,7 +193,7 @@ export default defineComponent({
// find all selected commits and generate a link
const firstSelected = this.commits.findIndex((x) => x.selected);
const lastSelected = this.commits.findLastIndex((x) => x.selected);
let beforeCommitID: string;
let beforeCommitID: string | null = null;
if (firstSelected === 0) {
beforeCommitID = this.mergeBase;
} else {
@@ -204,7 +204,7 @@ export default defineComponent({
if (firstSelected === lastSelected) {
// if the start and end are the same, we show this single commit
window.location.assign(`${this.issueLink}/commits/${afterCommitID}${this.queryParams}`);
} else if (beforeCommitID === this.mergeBase && afterCommitID === this.commits.at(-1).id) {
} else if (beforeCommitID === this.mergeBase && afterCommitID === this.commits.at(-1)!.id) {
// if the first commit is selected and the last commit is selected, we show all commits
window.location.assign(`${this.issueLink}/files${this.queryParams}`);
} else {
@@ -236,7 +236,7 @@ export default defineComponent({
<div class="gt-ellipsis">
{{ locale.show_all_commits }}
</div>
<div class="gt-ellipsis text light-2 tw-mb-0">
<div class="gt-ellipsis tw-text-text-light-2 tw-mb-0">
{{ locale.stats_num_commits }}
</div>
</div>
@@ -251,11 +251,11 @@ export default defineComponent({
<div class="gt-ellipsis">
{{ locale.show_changes_since_your_last_review }}
</div>
<div class="gt-ellipsis text light-2">
<div class="gt-ellipsis tw-text-text-light-2">
{{ commitsSinceLastReview }} commits
</div>
</div>
<span v-if="!isLoading" class="info text light-2">{{ locale.select_commit_hold_shift_for_range }}</span>
<span v-if="!isLoading" class="info tw-text-text-light-2">{{ locale.select_commit_hold_shift_for_range }}</span>
<template v-for="(commit, idx) in commits" :key="commit.id">
<div
class="item" role="menuitem"
@@ -273,7 +273,7 @@ export default defineComponent({
<div class="gt-ellipsis commit-list-summary">
{{ commit.summary }}
</div>
<div class="gt-ellipsis text light-2">
<div class="gt-ellipsis tw-text-text-light-2">
{{ commit.committer_or_author_name }}
<span class="text right">
<!-- TODO: make this respect the PreferredTimestampTense setting -->
@@ -4,6 +4,7 @@ import {toggleElem} from '../utils/dom.ts';
import {diffTreeStore} from '../modules/diff-file.ts';
import {setFileFolding} from '../features/file-fold.ts';
import {onMounted, onUnmounted} from 'vue';
import {localUserSettings} from '../modules/user-settings.ts';
const LOCAL_STORAGE_KEY = 'diff_file_tree_visible';
@@ -11,15 +12,15 @@ const store = diffTreeStore();
onMounted(() => {
// Default to true if unset
store.fileTreeIsVisible = localStorage.getItem(LOCAL_STORAGE_KEY) !== 'false';
document.querySelector('.diff-toggle-file-tree-button').addEventListener('click', toggleVisibility);
store.fileTreeIsVisible = localUserSettings.getBoolean(LOCAL_STORAGE_KEY, true);
document.querySelector('.diff-toggle-file-tree-button')!.addEventListener('click', toggleVisibility);
hashChangeListener();
window.addEventListener('hashchange', hashChangeListener);
});
onUnmounted(() => {
document.querySelector('.diff-toggle-file-tree-button').removeEventListener('click', toggleVisibility);
document.querySelector('.diff-toggle-file-tree-button')!.removeEventListener('click', toggleVisibility);
window.removeEventListener('hashchange', hashChangeListener);
});
@@ -33,7 +34,7 @@ function expandSelectedFile() {
if (store.selectedItem) {
const box = document.querySelector(store.selectedItem);
const folded = box?.getAttribute('data-folded') === 'true';
if (folded) setFileFolding(box, box.querySelector('.fold-file'), false);
if (folded) setFileFolding(box, box.querySelector('.fold-file')!, false);
}
}
@@ -43,15 +44,15 @@ function toggleVisibility() {
function updateVisibility(visible: boolean) {
store.fileTreeIsVisible = visible;
localStorage.setItem(LOCAL_STORAGE_KEY, store.fileTreeIsVisible.toString());
localUserSettings.setBoolean(LOCAL_STORAGE_KEY, store.fileTreeIsVisible);
updateState(store.fileTreeIsVisible);
}
function updateState(visible: boolean) {
const btn = document.querySelector('.diff-toggle-file-tree-button');
const btn = document.querySelector('.diff-toggle-file-tree-button')!;
const [toShow, toHide] = btn.querySelectorAll('.icon');
const tree = document.querySelector('#diff-file-tree');
const newTooltip = btn.getAttribute(visible ? 'data-hide-text' : 'data-show-text');
const tree = document.querySelector('#diff-file-tree')!;
const newTooltip = btn.getAttribute(visible ? 'data-hide-text' : 'data-show-text')!;
btn.setAttribute('data-tooltip-content', newTooltip);
toggleElem(tree, visible);
toggleElem(toShow, !visible);
@@ -12,13 +12,13 @@ const collapsed = shallowRef(props.item.IsViewed);
function getIconForDiffStatus(pType: DiffStatus) {
const diffTypes: Record<DiffStatus, { name: SvgName, classes: Array<string> }> = {
'': {name: 'octicon-blocked', classes: ['text', 'red']}, // unknown case
'added': {name: 'octicon-diff-added', classes: ['text', 'green']},
'modified': {name: 'octicon-diff-modified', classes: ['text', 'yellow']},
'deleted': {name: 'octicon-diff-removed', classes: ['text', 'red']},
'renamed': {name: 'octicon-diff-renamed', classes: ['text', 'teal']},
'copied': {name: 'octicon-diff-renamed', classes: ['text', 'green']},
'typechange': {name: 'octicon-diff-modified', classes: ['text', 'green']}, // there is no octicon for copied, so renamed should be ok
'': {name: 'octicon-blocked', classes: ['tw-text-red']}, // unknown case
'added': {name: 'octicon-diff-added', classes: ['tw-text-green']},
'modified': {name: 'octicon-diff-modified', classes: ['tw-text-yellow']},
'deleted': {name: 'octicon-diff-removed', classes: ['tw-text-red']},
'renamed': {name: 'octicon-diff-renamed', classes: ['tw-text-teal']},
'copied': {name: 'octicon-diff-renamed', classes: ['tw-text-green']},
'typechange': {name: 'octicon-diff-modified', classes: ['tw-text-green']}, // there is no octicon for copied, so renamed should be ok
};
return diffTypes[pType] ?? diffTypes[''];
}
@@ -3,9 +3,9 @@ import {computed, onMounted, onUnmounted, shallowRef, watch} from 'vue';
import {SvgIcon} from '../svg.ts';
import {toggleElem} from '../utils/dom.ts';
const {csrfToken, pageData} = window.config;
const {pageData} = window.config;
const mergeForm = pageData.pullRequestMergeForm;
const mergeForm = pageData.pullRequestMergeForm!;
const mergeTitleFieldValue = shallowRef('');
const mergeMessageFieldValue = shallowRef('');
@@ -95,7 +95,6 @@ function clearMergeMessage() {
<!-- another similar form is in pull.tmpl (manual merge)-->
<form class="ui form form-fetch-action" v-if="showActionForm" :action="mergeForm.baseLink+'/merge'" method="post">
<input type="hidden" name="_csrf" :value="csrfToken">
<input type="hidden" name="head_commit_id" v-model="mergeForm.pullHeadCommitID">
<input type="hidden" name="merge_when_checks_succeed" v-model="autoMergeWhenSucceed">
<input type="hidden" name="force_merge" v-model="forceMerge">
@@ -177,7 +176,6 @@ function clearMergeMessage() {
<!-- the cancel auto merge button -->
<form v-if="mergeForm.hasPendingPullRequestMerge" :action="mergeForm.baseLink+'/cancel_auto_merge'" method="post" class="tw-ml-4">
<input type="hidden" name="_csrf" :value="csrfToken">
<button class="ui button">
{{ mergeForm.textAutoMergeCancelSchedule }}
</button>
@@ -232,7 +230,7 @@ function clearMergeMessage() {
bottom: -1px;
position: absolute;
align-items: center;
color: var(--color-info-text);
color: var(--color-text);
background-color: var(--color-info-bg);
border: 1px solid var(--color-info-border);
border-left: none;
@@ -240,7 +238,7 @@ function clearMergeMessage() {
}
.auto-merge-small:hover {
color: var(--color-info-text);
color: var(--color-text);
background-color: var(--color-info-bg);
border: 1px solid var(--color-info-border);
}
File diff suppressed because it is too large Load Diff
@@ -1,7 +1,6 @@
<script lang="ts" setup>
// @ts-expect-error - module exports no types
import {VueBarGraph} from 'vue-bar-graph';
import {computed, onMounted, shallowRef, useTemplateRef} from 'vue';
import {computed, onMounted, shallowRef, useTemplateRef, type ShallowRef} from 'vue';
const colors = shallowRef({
barColor: 'green',
@@ -41,8 +40,8 @@ const graphWidth = computed(() => {
return activityTopAuthors.length * 40;
});
const styleElement = useTemplateRef('styleElement');
const altStyleElement = useTemplateRef('altStyleElement');
const styleElement = useTemplateRef('styleElement') as Readonly<ShallowRef<HTMLDivElement>>;
const altStyleElement = useTemplateRef('altStyleElement') as Readonly<ShallowRef<HTMLDivElement>>;
onMounted(() => {
const refStyle = window.getComputedStyle(styleElement.value);
@@ -20,12 +20,14 @@ type TabLoadingStates = Record<SelectedTab, '' | 'loading' | 'done'>
export default defineComponent({
components: {SvgIcon},
props: {
elRoot: HTMLElement,
elRoot: {
type: HTMLElement,
required: true,
},
},
data() {
const shouldShowTabBranches = this.elRoot.getAttribute('data-show-tab-branches') === 'true';
return {
csrfToken: window.config.csrfToken,
allItems: [] as ListItem[],
selectedTab: (shouldShowTabBranches ? 'branches' : 'tags') as SelectedTab,
searchTerm: '',
@@ -33,28 +35,28 @@ export default defineComponent({
activeItemIndex: 0,
tabLoadingStates: {} as TabLoadingStates,
textReleaseCompare: this.elRoot.getAttribute('data-text-release-compare'),
textBranches: this.elRoot.getAttribute('data-text-branches'),
textTags: this.elRoot.getAttribute('data-text-tags'),
textFilterBranch: this.elRoot.getAttribute('data-text-filter-branch'),
textFilterTag: this.elRoot.getAttribute('data-text-filter-tag'),
textDefaultBranchLabel: this.elRoot.getAttribute('data-text-default-branch-label'),
textCreateTag: this.elRoot.getAttribute('data-text-create-tag'),
textCreateBranch: this.elRoot.getAttribute('data-text-create-branch'),
textCreateRefFrom: this.elRoot.getAttribute('data-text-create-ref-from'),
textNoResults: this.elRoot.getAttribute('data-text-no-results'),
textViewAllBranches: this.elRoot.getAttribute('data-text-view-all-branches'),
textViewAllTags: this.elRoot.getAttribute('data-text-view-all-tags'),
textReleaseCompare: this.elRoot.getAttribute('data-text-release-compare')!,
textBranches: this.elRoot.getAttribute('data-text-branches')!,
textTags: this.elRoot.getAttribute('data-text-tags')!,
textFilterBranch: this.elRoot.getAttribute('data-text-filter-branch')!,
textFilterTag: this.elRoot.getAttribute('data-text-filter-tag')!,
textDefaultBranchLabel: this.elRoot.getAttribute('data-text-default-branch-label')!,
textCreateTag: this.elRoot.getAttribute('data-text-create-tag')!,
textCreateBranch: this.elRoot.getAttribute('data-text-create-branch')!,
textCreateRefFrom: this.elRoot.getAttribute('data-text-create-ref-from')!,
textNoResults: this.elRoot.getAttribute('data-text-no-results')!,
textViewAllBranches: this.elRoot.getAttribute('data-text-view-all-branches')!,
textViewAllTags: this.elRoot.getAttribute('data-text-view-all-tags')!,
currentRepoDefaultBranch: this.elRoot.getAttribute('data-current-repo-default-branch'),
currentRepoLink: this.elRoot.getAttribute('data-current-repo-link'),
currentTreePath: this.elRoot.getAttribute('data-current-tree-path'),
currentRepoDefaultBranch: this.elRoot.getAttribute('data-current-repo-default-branch')!,
currentRepoLink: this.elRoot.getAttribute('data-current-repo-link')!,
currentTreePath: this.elRoot.getAttribute('data-current-tree-path')!,
currentRefType: this.elRoot.getAttribute('data-current-ref-type') as GitRefType,
currentRefShortName: this.elRoot.getAttribute('data-current-ref-short-name'),
currentRefShortName: this.elRoot.getAttribute('data-current-ref-short-name')!,
refLinkTemplate: this.elRoot.getAttribute('data-ref-link-template'),
refFormActionTemplate: this.elRoot.getAttribute('data-ref-form-action-template'),
dropdownFixedText: this.elRoot.getAttribute('data-dropdown-fixed-text'),
refLinkTemplate: this.elRoot.getAttribute('data-ref-link-template')!,
refFormActionTemplate: this.elRoot.getAttribute('data-ref-form-action-template')!,
dropdownFixedText: this.elRoot.getAttribute('data-dropdown-fixed-text')!,
showTabBranches: shouldShowTabBranches,
showTabTags: this.elRoot.getAttribute('data-show-tab-tags') === 'true',
allowCreateNewRef: this.elRoot.getAttribute('data-allow-create-new-ref') === 'true',
@@ -92,7 +94,7 @@ export default defineComponent({
}).length;
},
createNewRefFormActionUrl() {
return `${this.currentRepoLink}/branches/_new/${this.currentRefType}/${pathEscapeSegments(this.currentRefShortName)}`;
return `${this.currentRepoLink}/branches/_new/${this.currentRefType}/${pathEscapeSegments(this.currentRefShortName!)}`;
},
},
watch: {
@@ -153,11 +155,11 @@ export default defineComponent({
return -1;
},
getActiveItem() {
const el = this.$refs[`listItem${this.activeItemIndex}`];
// @ts-expect-error - el is unknown type
return (el && el.length) ? el[0] : null;
const el = this.$refs[`listItem${this.activeItemIndex}`] as Array<HTMLDivElement>;
return el?.length ? el[0] : null;
},
keydown(e: KeyboardEvent) {
if (e.isComposing) return;
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
e.preventDefault();
@@ -172,7 +174,7 @@ export default defineComponent({
return;
}
this.activeItemIndex = nextIndex;
this.getActiveItem().scrollIntoView({block: 'nearest'});
this.getActiveItem()!.scrollIntoView({block: 'nearest'});
} else if (e.key === 'Enter') {
e.preventDefault();
this.getActiveItem()?.click();
@@ -265,11 +267,10 @@ export default defineComponent({
<svg-icon name="octicon-git-branch" class="tw-mr-1"/>
<span v-text="textCreateBranch.replace('%s', searchTerm)"/>
</div>
<div class="text small">
<div class="tw-text-xs">
{{ textCreateRefFrom.replace('%s', currentRefShortName) }}
</div>
<form ref="createNewRefForm" method="post" :action="createNewRefFormActionUrl">
<input type="hidden" name="_csrf" :value="csrfToken">
<input type="hidden" name="new_branch_name" :value="searchTerm">
<input type="hidden" name="create_tag" :value="String(selectedTab === 'tags')">
<input type="hidden" name="current_path" :value="currentTreePath">
@@ -49,7 +49,7 @@ defineProps<{
const isLoading = shallowRef(false);
const errorText = shallowRef('');
const repoLink = pageData.repoLink;
const repoLink = pageData.repoLink!;
const data = shallowRef<DayData[]>([]);
onMounted(() => {
@@ -153,7 +153,7 @@ const options: ChartOptions<'line'> = {
<SvgIcon name="gitea-running" class="tw-mr-2 rotate-clockwise"/>
{{ locale.loadingInfo }}
</div>
<div v-else class="text red">
<div v-else class="tw-text-red">
<SvgIcon name="octicon-x-circle-fill"/>
{{ errorText }}
</div>
@@ -41,6 +41,15 @@ const customEventListener: Plugin = {
},
};
type LineOptions = ChartOptions<'line'> & {
plugins?: {
customEventListener?: {
chartType: string;
instance: unknown;
};
};
}
Chart.defaults.color = chartJsColors.text;
Chart.defaults.borderColor = chartJsColors.border;
@@ -174,7 +183,7 @@ export default defineComponent({
user.max_contribution_type = 0;
const filteredWeeks = user.weeks.filter((week: Record<string, number>) => {
const oneWeek = 7 * 24 * 60 * 60 * 1000;
if (week.week >= this.xAxisMin - oneWeek && week.week <= this.xAxisMax + oneWeek) {
if (week.week >= this.xAxisMin! - oneWeek && week.week <= this.xAxisMax! + oneWeek) {
user.total_commits += week.commits;
user.total_additions += week.additions;
user.total_deletions += week.deletions;
@@ -238,8 +247,8 @@ export default defineComponent({
},
updateOtherCharts({chart}: {chart: Chart}, reset: boolean = false) {
const minVal = Number(chart.options.scales.x.min);
const maxVal = Number(chart.options.scales.x.max);
const minVal = Number(chart.options.scales?.x?.min);
const maxVal = Number(chart.options.scales?.x?.max);
if (reset) {
this.xAxisMin = this.xAxisStart;
this.xAxisMax = this.xAxisEnd;
@@ -251,7 +260,7 @@ export default defineComponent({
}
},
getOptions(type: string): ChartOptions<'line'> {
getOptions(type: string): LineOptions {
return {
responsive: true,
maintainAspectRatio: false,
@@ -264,7 +273,6 @@ export default defineComponent({
position: 'top',
align: 'center',
},
// @ts-expect-error: bug in chart.js types
customEventListener: {
chartType: type,
instance: this,
@@ -302,8 +310,8 @@ export default defineComponent({
},
scales: {
x: {
min: this.xAxisMin,
max: this.xAxisMax,
min: this.xAxisMin ?? undefined,
max: this.xAxisMax ?? undefined,
type: 'time',
grid: {
display: false,
@@ -334,7 +342,7 @@ export default defineComponent({
<div class="ui header tw-flex tw-items-center tw-justify-between">
<div>
<relative-time
v-if="xAxisMin > 0"
v-if="xAxisMin && xAxisMin > 0"
format="datetime"
year="numeric"
month="short"
@@ -346,7 +354,7 @@ export default defineComponent({
</relative-time>
{{ isLoading ? locale.loadingTitle : errorText ? locale.loadingTitleFailed: "-" }}
<relative-time
v-if="xAxisMax > 0"
v-if="xAxisMax && xAxisMax > 0"
format="datetime"
year="numeric"
month="short"
@@ -384,7 +392,7 @@ export default defineComponent({
<SvgIcon name="gitea-running" class="tw-mr-2 rotate-clockwise"/>
{{ locale.loadingInfo }}
</div>
<div v-else class="text red">
<div v-else class="tw-text-red">
<SvgIcon name="octicon-x-circle-fill"/>
{{ errorText }}
</div>
@@ -416,8 +424,8 @@ export default defineComponent({
{{ contributor.total_commits.toLocaleString() }} {{ locale.contributionType.commits }}
</a>
</strong>
<strong v-if="contributor.total_additions" class="text green">{{ contributor.total_additions.toLocaleString() }}++ </strong>
<strong v-if="contributor.total_deletions" class="text red">
<strong v-if="contributor.total_additions" class="tw-text-green">{{ contributor.total_additions.toLocaleString() }}++ </strong>
<strong v-if="contributor.total_deletions" class="tw-text-red">
{{ contributor.total_deletions.toLocaleString() }}--</strong>
</p>
</div>
@@ -0,0 +1,233 @@
<script lang="ts" setup>
import {ref, computed, watch, nextTick, useTemplateRef, onMounted, onUnmounted, type ShallowRef} from 'vue';
import {generateElemId} from '../utils/dom.ts';
import {GET} from '../modules/fetch.ts';
import {filterRepoFilesWeighted} from '../features/repo-findfile.ts';
import {pathEscapeSegments} from '../utils/url.ts';
import {SvgIcon} from '../svg.ts';
import {throttle} from 'throttle-debounce';
const props = defineProps({
repoLink: { type: String, required: true },
currentRefNameSubURL: { type: String, required: true },
treeListUrl: { type: String, required: true },
noResultsText: { type: String, required: true },
placeholder: { type: String, required: true },
});
const refElemInput = useTemplateRef('searchInput') as Readonly<ShallowRef<HTMLInputElement>>;
const refElemPopup = useTemplateRef('searchPopup') as Readonly<ShallowRef<HTMLDivElement>>;
const searchQuery = ref('');
const allFiles = ref<string[]>([]);
const selectedIndex = ref(0);
const isLoadingFileList = ref(false);
const hasLoadedFileList = ref(false);
const showPopup = computed(() => searchQuery.value.length > 0);
const filteredFiles = computed(() => {
if (!searchQuery.value) return [];
return filterRepoFilesWeighted(allFiles.value, searchQuery.value);
});
const applySearchQuery = throttle(300, () => {
searchQuery.value = refElemInput.value.value;
selectedIndex.value = 0;
});
const handleSearchInput = () => {
loadFileListForSearch();
applySearchQuery();
};
const handleKeyDown = (e: KeyboardEvent) => {
if (e.isComposing) return;
if (e.key === 'Escape') {
clearSearch();
nextTick(() => refElemInput.value.blur());
return;
}
if (!searchQuery.value || filteredFiles.value.length === 0) return;
const handleSelectedItem = (idx: number) => {
e.preventDefault();
selectedIndex.value = idx;
const el = refElemPopup.value.querySelector(`.file-search-results > :nth-child(${idx+1} of .item)`);
el?.scrollIntoView({ block: 'nearest', behavior: 'instant' });
};
if (e.key === 'ArrowDown') {
handleSelectedItem(Math.min(selectedIndex.value + 1, filteredFiles.value.length - 1));
} else if (e.key === 'ArrowUp') {
handleSelectedItem(Math.max(selectedIndex.value - 1, 0))
} else if (e.key === 'Enter') {
e.preventDefault();
const selectedFile = filteredFiles.value[selectedIndex.value];
if (selectedFile) {
handleSearchResultClick(selectedFile.matchResult.join(''));
}
}
};
const clearSearch = () => {
searchQuery.value = '';
refElemInput.value.value = '';
};
const handleClickOutside = (e: MouseEvent) => {
if (!searchQuery.value) return;
const target = e.target as HTMLElement;
const clickInside = refElemInput.value.contains(target) || refElemPopup.value.contains(target);
if (!clickInside) clearSearch();
};
const loadFileListForSearch = async () => {
if (hasLoadedFileList.value || isLoadingFileList.value) return;
isLoadingFileList.value = true;
try {
const response = await GET(props.treeListUrl);
allFiles.value = await response.json();
hasLoadedFileList.value = true;
} finally {
isLoadingFileList.value = false;
}
};
function handleSearchResultClick(filePath: string) {
clearSearch();
window.location.href = `${props.repoLink}/src/${pathEscapeSegments(props.currentRefNameSubURL)}/${pathEscapeSegments(filePath)}`;
}
const updatePosition = () => {
if (!showPopup.value) return;
const rectInput = refElemInput.value.getBoundingClientRect();
const rectPopup = refElemPopup.value.getBoundingClientRect();
const docElem = document.documentElement;
const style = refElemPopup.value.style;
style.top = `${docElem.scrollTop + rectInput.bottom + 4}px`;
if (rectInput.x + rectPopup.width < docElem.clientWidth) {
// enough space to align left with the input
style.left = `${docElem.scrollLeft + rectInput.x}px`;
} else {
// no enough space, align right from the viewport right edge minus page margin
const leftPos = docElem.scrollLeft + docElem.getBoundingClientRect().width - rectPopup.width;
style.left = `calc(${leftPos}px - var(--page-margin-x))`;
}
};
onMounted(() => {
const searchPopupId = generateElemId('file-search-popup-');
refElemPopup.value.setAttribute('id', searchPopupId);
refElemInput.value.setAttribute('aria-controls', searchPopupId);
document.addEventListener('click', handleClickOutside);
window.addEventListener('resize', updatePosition);
});
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside);
window.removeEventListener('resize', updatePosition);
});
// Position search results below the input
watch([searchQuery, filteredFiles], async () => {
if (searchQuery.value) {
await nextTick();
updatePosition();
}
});
</script>
<template>
<div>
<div class="ui small input global-shortcut-wrapper">
<input
ref="searchInput" :placeholder="placeholder" autocomplete="off"
role="combobox" aria-autocomplete="list" :aria-expanded="searchQuery ? 'true' : 'false'"
@input="handleSearchInput" @keydown="handleKeyDown"
>
<kbd data-global-init="onGlobalShortcut" data-shortcut-keys="t">T</kbd>
</div>
<Teleport to="body">
<div v-show="showPopup" ref="searchPopup" class="file-search-popup">
<!-- always create the popup by v-show above to avoid null ref, only create the popup content if the popup should be displayed to save memory -->
<template v-if="showPopup">
<div v-if="filteredFiles.length" role="listbox" class="file-search-results flex-items-block">
<div
v-for="(result, idx) in filteredFiles" :key="result.matchResult.join('')"
:class="['item', { 'selected': idx === selectedIndex }]"
role="option" :aria-selected="idx === selectedIndex" @click="handleSearchResultClick(result.matchResult.join(''))"
@mouseenter="selectedIndex = idx" :title="result.matchResult.join('')"
>
<SvgIcon name="octicon-file" class="file-icon"/>
<span class="full-path">
<span v-for="(part, index) in result.matchResult" :key="index">{{ part }}</span>
</span>
</div>
</div>
<div v-else-if="isLoadingFileList">
<div class="is-loading"/>
</div>
<div v-else class="tw-p-4">
{{ props.noResultsText }}
</div>
</template>
</div>
</Teleport>
</div>
</template>
<style scoped>
.file-search-popup {
position: absolute;
background: var(--color-box-body);
border: 1px solid var(--color-secondary);
border-radius: var(--border-radius);
width: max-content;
max-height: min(calc(100vw - 20px), 300px);
max-width: min(calc(100vw - 40px), 600px);
overflow-y: auto;
}
.file-search-popup .is-loading {
width: 200px;
height: 200px;
}
.file-search-results .item {
align-items: flex-start;
padding: 0.5rem 0.75rem;
cursor: pointer;
border-bottom: 1px solid var(--color-secondary);
}
.file-search-results .item:last-child {
border-bottom: none;
}
.file-search-results .item:hover,
.file-search-results .item.selected {
background-color: var(--color-hover);
}
.file-search-results .item .file-icon {
flex-shrink: 0;
margin-top: 0.125rem;
}
.file-search-results .item .full-path {
flex: 1;
overflow-wrap: anywhere;
}
.file-search-results .item .full-path :nth-child(even) {
color: var(--color-red);
font-weight: var(--font-weight-semibold);
}
</style>
@@ -8,6 +8,7 @@ import {
TimeScale,
type ChartOptions,
type ChartData,
type ChartDataset,
} from 'chart.js';
import {GET} from '../modules/fetch.ts';
import {Bar} from 'vue-chartjs';
@@ -45,7 +46,7 @@ defineProps<{
const isLoading = shallowRef(false);
const errorText = shallowRef('');
const repoLink = pageData.repoLink;
const repoLink = pageData.repoLink!;
const data = ref<DayData[]>([]);
onMounted(() => {
@@ -83,13 +84,12 @@ function toGraphData(data: DayData[]): ChartData<'bar'> {
return {
datasets: [
{
// @ts-expect-error -- bar chart expects one-dimensional data, but apparently x/y still works
data: data.map((i) => ({x: i.week, y: i.commits})),
label: 'Commits',
backgroundColor: chartJsColors['commits'],
borderWidth: 0,
tension: 0.3,
},
} as unknown as ChartDataset<'bar'>,
],
};
}
@@ -131,7 +131,7 @@ const options: ChartOptions<'bar'> = {
<SvgIcon name="gitea-running" class="tw-mr-2 rotate-clockwise"/>
{{ locale.loadingInfo }}
</div>
<div v-else class="text red">
<div v-else class="tw-text-red">
<SvgIcon name="octicon-x-circle-fill"/>
{{ errorText }}
</div>
@@ -1,9 +1,9 @@
<script lang="ts" setup>
import ViewFileTreeItem from './ViewFileTreeItem.vue';
import {onMounted, useTemplateRef} from 'vue';
import {onMounted, useTemplateRef, type ShallowRef} from 'vue';
import {createViewFileTreeStore} from './ViewFileTreeStore.ts';
const elRoot = useTemplateRef('elRoot');
const elRoot = useTemplateRef('elRoot') as Readonly<ShallowRef<HTMLDivElement>>;;
const props = defineProps({
repoLink: {type: String, required: true},
@@ -24,7 +24,7 @@ onMounted(async () => {
<template>
<div class="view-file-tree-items" ref="elRoot">
<ViewFileTreeItem v-for="item in store.rootFiles" :key="item.name" :item="item" :store="store"/>
<ViewFileTreeItem v-for="item in store.rootFiles" :key="item.entryName" :item="item" :store="store"/>
</div>
</template>
@@ -1,21 +1,12 @@
<script lang="ts" setup>
import {SvgIcon} from '../svg.ts';
import {isPlainClick} from '../utils/dom.ts';
import {shouldTriggerAreYouSure} from '../vendor/jquery.are-you-sure.ts';
import {shallowRef} from 'vue';
import {type createViewFileTreeStore} from './ViewFileTreeStore.ts';
type Item = {
entryName: string;
entryMode: 'blob' | 'exec' | 'tree' | 'commit' | 'symlink' | 'unknown';
entryIcon: string;
entryIconOpen: string;
fullPath: string;
submoduleUrl?: string;
children?: Item[];
};
import type {createViewFileTreeStore, FileTreeItem} from './ViewFileTreeStore.ts';
const props = defineProps<{
item: Item,
item: FileTreeItem,
store: ReturnType<typeof createViewFileTreeStore>
}>();
@@ -37,9 +28,10 @@ const doLoadChildren = async () => {
};
const onItemClick = (e: MouseEvent) => {
// only handle the click event with page partial reloading if the user didn't press any special key
// let browsers handle special keys like "Ctrl+Click"
if (!isPlainClick(e)) return;
// only handle the click event with partial page reloading if both
// - the user didn't press any special key like "Ctrl+Click" (which may have custom browser behavior)
// - the editor/commit form isn't dirty (a full page reload shows a confirmation dialog if the form contains unsaved changes)
if (!isPlainClick(e) || shouldTriggerAreYouSure()) return;
e.preventDefault();
if (props.item.entryMode === 'tree') doLoadChildren();
store.navigateTreeView(props.item.fullPath);
@@ -4,12 +4,25 @@ import {pathEscapeSegments} from '../utils/url.ts';
import {createElementFromHTML} from '../utils/dom.ts';
import {html} from '../utils/html.ts';
export type FileTreeItem = {
entryName: string;
entryMode: 'blob' | 'exec' | 'tree' | 'commit' | 'symlink' | 'unknown';
entryIcon: string;
entryIconOpen: string;
fullPath: string;
submoduleUrl?: string;
children?: Array<FileTreeItem>;
};
export function createViewFileTreeStore(props: {repoLink: string, treePath: string, currentRefNameSubURL: string}) {
const store = reactive({
rootFiles: [],
rootFiles: [] as Array<FileTreeItem>,
selectedItem: props.treePath,
async loadChildren(treePath: string, subPath: string = '') {
// there is no git ref if no commits were made yet (an empty repo)
if (!props.currentRefNameSubURL) return null;
const response = await GET(`${props.repoLink}/tree-view/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}?sub_path=${encodeURIComponent(subPath)}`);
const json = await response.json();
const poolSvgs = [];
@@ -28,7 +41,7 @@ export function createViewFileTreeStore(props: {repoLink: string, treePath: stri
const u = new URL(url, window.origin);
u.searchParams.set('only_content', 'true');
const response = await GET(u.href);
const elViewContent = document.querySelector('.repo-view-content');
const elViewContent = document.querySelector('.repo-view-content')!;
elViewContent.innerHTML = await response.text();
const elViewContentData = elViewContent.querySelector('.repo-view-content-data');
if (!elViewContentData) return; // if error occurs, there is no such element
@@ -39,7 +52,7 @@ export function createViewFileTreeStore(props: {repoLink: string, treePath: stri
async navigateTreeView(treePath: string) {
const url = store.buildTreePathWebUrl(treePath);
window.history.pushState({treePath, url}, null, url);
window.history.pushState({treePath, url}, '', url);
store.selectedItem = treePath;
await store.loadViewContent(url);
},
@@ -0,0 +1,802 @@
<script setup lang="ts">
import {computed, onMounted, onUnmounted, ref, watch} from 'vue';
import {SvgIcon} from '../svg.ts';
import ActionRunStatus from './ActionRunStatus.vue';
import {localUserSettings} from '../modules/user-settings.ts';
import {isPlainClick} from '../utils/dom.ts';
import {debounce} from 'throttle-debounce';
import type {ActionsJob, ActionsRunStatus} from '../modules/gitea-actions.ts';
import type {ActionRunViewStore} from './ActionRunView.ts';
interface JobNode {
id: number;
name: string;
status: ActionsRunStatus;
duration: string;
x: number;
y: number;
level: number;
}
interface Edge {
fromId: number;
toId: number;
key: string;
}
interface RoutedEdge extends Edge {
path: string;
fromNode: JobNode;
toNode: JobNode;
}
interface StoredState {
scale: number;
translateX: number;
translateY: number;
timestamp: number;
}
const props = defineProps<{
store: ActionRunViewStore;
jobs: ActionsJob[];
runLink: string;
workflowId: string;
}>()
const settingKeyStates = 'actions-graph-states';
const maxStoredStates = 10;
const scale = ref(1);
const translateX = ref(0);
const translateY = ref(0);
const isDragging = ref(false);
const lastMousePos = ref({x: 0, y: 0});
const graphContainer = ref<HTMLElement | null>(null);
const hoveredJobId = ref<number | null>(null);
const stateKey = () => `${props.store.viewData.currentRun.repoId}-${props.workflowId}`;
const loadSavedState = () => {
const allStates = localUserSettings.getJsonObject<Record<string, StoredState>>(settingKeyStates, {});
const saved = allStates[stateKey()];
if (!saved) return;
scale.value = clampScale(saved.scale ?? scale.value);
translateX.value = saved.translateX ?? translateX.value;
translateY.value = saved.translateY ?? translateY.value;
};
const saveState = () => {
const allStates = localUserSettings.getJsonObject<Record<string, StoredState>>(settingKeyStates, {});
allStates[stateKey()] = {
scale: scale.value,
translateX: translateX.value,
translateY: translateY.value,
timestamp: Date.now(),
};
const sortedStates = Object.entries(allStates)
.sort(([, a], [, b]) => b.timestamp - a.timestamp)
.slice(0, maxStoredStates);
localUserSettings.setJsonObject(settingKeyStates, Object.fromEntries(sortedStates));
};
const nodeWidth = computed(() => {
const maxNameLength = Math.max(...props.jobs.map(j => j.name.length));
return Math.min(Math.max(140, maxNameLength * 8), 180);
});
const horizontalSpacing = computed(() => nodeWidth.value + 84);
const graphWidth = computed(() => {
if (jobsWithLayout.value.length === 0) return 800;
const maxX = Math.max(...jobsWithLayout.value.map(j => j.x + nodeWidth.value));
return maxX + margin * 2;
});
const graphHeight = computed(() => {
if (jobsWithLayout.value.length === 0) return 400;
const maxY = Math.max(...jobsWithLayout.value.map(j => j.y + nodeHeight));
return maxY + margin * 2;
});
const jobsWithLayout = computed<JobNode[]>(() => {
try {
const levels = computeJobLevels(props.jobs);
const currentHorizontalSpacing = horizontalSpacing.value;
const jobsByLevel: ActionsJob[][] = [];
let maxJobsPerLevel = 0;
props.jobs.forEach(job => {
const level = levels.get(job.name) || levels.get(job.jobId) || 0;
if (!jobsByLevel[level]) {
jobsByLevel[level] = [];
}
jobsByLevel[level].push(job);
if (jobsByLevel[level].length > maxJobsPerLevel) {
maxJobsPerLevel = jobsByLevel[level].length;
}
});
const result: JobNode[] = [];
jobsByLevel.forEach((levelJobs, levelIndex) => {
if (!levelJobs || levelJobs.length === 0) {
return;
}
const startY = margin;
levelJobs.forEach((job, jobIndex) => {
result.push({
id: job.id,
name: job.name,
status: job.status,
duration: job.duration,
x: margin + levelIndex * currentHorizontalSpacing,
y: startY + jobIndex * verticalSpacing,
level: levelIndex,
});
});
});
return result;
} catch (error) {
return props.jobs.map((job, index) => ({
id: job.id,
name: job.name,
status: job.status,
duration: job.duration,
x: margin + index * horizontalSpacing.value,
y: margin,
level: 0,
}));
}
});
function buildDirectNeedsMap(jobs: ActionsJob[]): Map<string, string[]> {
const directNeedsByJobId = new Map<string, string[]>();
const dependentsByJobId = new Map<string, Set<string>>();
for (const job of jobs) {
const needs = job.needs || [];
directNeedsByJobId.set(job.jobId, needs);
for (const need of needs) {
if (!dependentsByJobId.has(need)) {
dependentsByJobId.set(need, new Set());
}
dependentsByJobId.get(need)!.add(job.jobId);
}
}
const reachabilityCache = new Map<string, boolean>();
function canReach(fromJobId: string, toJobId: string): boolean {
const cacheKey = `${fromJobId}->${toJobId}`;
if (reachabilityCache.has(cacheKey)) {
return reachabilityCache.get(cacheKey)!;
}
const visited = new Set<string>();
const stack = [...(dependentsByJobId.get(fromJobId) || [])];
while (stack.length > 0) {
const current = stack.pop()!;
if (current === toJobId) {
reachabilityCache.set(cacheKey, true);
return true;
}
if (visited.has(current)) continue;
visited.add(current);
stack.push(...(dependentsByJobId.get(current) || []));
}
reachabilityCache.set(cacheKey, false);
return false;
}
const reducedNeedsByJobId = new Map<string, string[]>();
for (const [jobId, needs] of directNeedsByJobId.entries()) {
reducedNeedsByJobId.set(jobId, needs.filter((need) => {
return !needs.some((otherNeed) => otherNeed !== need && canReach(need, otherNeed));
}));
}
return reducedNeedsByJobId;
}
const directNeedsByJobId = computed(() => buildDirectNeedsMap(props.jobs));
const edges = computed<Edge[]>(() => {
const edgesList: Edge[] = [];
const jobsByJobId = new Map<string, ActionsJob[]>();
for (const job of props.jobs) {
if (!jobsByJobId.has(job.jobId)) {
jobsByJobId.set(job.jobId, []);
}
jobsByJobId.get(job.jobId)!.push(job);
}
for (const job of props.jobs) {
for (const need of directNeedsByJobId.value.get(job.jobId) || []) {
const upstreamJobs = jobsByJobId.get(need) || [];
for (const upstreamJob of upstreamJobs) {
edgesList.push({
fromId: upstreamJob.id,
toId: job.id,
key: `${upstreamJob.id}-${job.id}`,
});
}
}
}
return edgesList;
});
function buildRoundedConnectorPath(startX: number, startY: number, endX: number, endY: number, turnX: number): string {
const deltaY = endY - startY;
if (Math.abs(deltaY) < 1) {
return `M ${startX} ${startY} H ${endX}`;
}
const direction = deltaY > 0 ? 1 : -1;
const elbowSize = Math.max(8, Math.min(24, Math.abs(deltaY) / 2, Math.abs(endX - startX) / 2));
const controlOffset = elbowSize / 2;
const clampedTurnX = Math.min(Math.max(turnX, startX + elbowSize), endX - elbowSize);
return [
`M ${startX} ${startY}`,
`H ${clampedTurnX - elbowSize}`,
`C ${clampedTurnX - controlOffset} ${startY} ${clampedTurnX} ${startY + direction * controlOffset} ${clampedTurnX} ${startY + direction * elbowSize}`,
`V ${endY - direction * elbowSize}`,
`C ${clampedTurnX} ${endY - direction * controlOffset} ${clampedTurnX + controlOffset} ${endY} ${clampedTurnX + elbowSize} ${endY}`,
`H ${endX}`,
].join(' ');
}
const routedEdges = computed<RoutedEdge[]>(() => {
const nodesById = new Map(jobsWithLayout.value.map((job) => [job.id, job]));
const outgoingEdges = new Map<number, Edge[]>();
const incomingEdges = new Map<number, Edge[]>();
for (const edge of edges.value) {
if (!outgoingEdges.has(edge.fromId)) {
outgoingEdges.set(edge.fromId, []);
}
outgoingEdges.get(edge.fromId)!.push(edge);
if (!incomingEdges.has(edge.toId)) {
incomingEdges.set(edge.toId, []);
}
incomingEdges.get(edge.toId)!.push(edge);
}
for (const sourceEdges of outgoingEdges.values()) {
sourceEdges.sort((a, b) => {
const targetA = nodesById.get(a.toId);
const targetB = nodesById.get(b.toId);
if (!targetA || !targetB) return 0;
return targetA.y - targetB.y || a.toId - b.toId;
});
}
const edgePaths: RoutedEdge[] = [];
for (const edge of edges.value) {
const fromNode = nodesById.get(edge.fromId);
const toNode = nodesById.get(edge.toId);
if (!fromNode || !toNode) continue;
const startX = fromNode.x + nodeWidth.value;
const startY = fromNode.y + nodeHeight / 2;
const endX = toNode.x;
const endY = toNode.y + nodeHeight / 2;
const sourceEdges = outgoingEdges.get(edge.fromId) || [];
const targetEdges = incomingEdges.get(edge.toId) || [];
const horizontalGap = endX - startX;
const turnOffset = Math.min(28, Math.max(16, horizontalGap * 0.14));
const sourceTurnX = startX + turnOffset;
const targetTurnX = endX - turnOffset;
let turnX = startX + horizontalGap / 2;
if (sourceEdges.length > 1) {
turnX = sourceTurnX;
} else if (targetEdges.length > 1) {
turnX = targetTurnX;
}
const path = buildRoundedConnectorPath(startX, startY, endX, endY, turnX);
edgePaths.push({
...edge,
path,
fromNode,
toNode,
});
}
return edgePaths;
});
const graphMetrics = computed(() => {
const successCount = jobsWithLayout.value.filter(job => job.status === 'success').length;
const levels = new Map<number, number>();
jobsWithLayout.value.forEach(job => {
const count = levels.get(job.level) || 0;
levels.set(job.level, count + 1);
})
const parallelism = Math.max(...Array.from(levels.values()), 0);
return {
successRate: `${((successCount / jobsWithLayout.value.length) * 100).toFixed(0)}%`,
parallelism,
};
})
const nodeHeight = 48;
const verticalSpacing = 88;
const margin = 40;
const minScale = 0.3;
const maxScale = 1;
function clampScale(nextScale: number): number {
return Math.min(Math.max(Math.round(nextScale * 100) / 100, minScale), maxScale);
}
const canZoomIn = computed(() => scale.value < maxScale);
function zoomTo(nextScale: number) {
scale.value = clampScale(nextScale);
}
function zoomIn() {
zoomTo(scale.value * 1.2);
}
function zoomOut() {
zoomTo(scale.value / 1.2);
}
function resetView() {
scale.value = 1;
translateX.value = 0;
translateY.value = 0;
}
function handleMouseDown(e: MouseEvent) {
if (!isPlainClick(e)) return;
// don't start drag on interactive/text elements inside the SVG
const target = e.target as Element;
const interactive = target.closest('div, p, a, span, button, input, text, .job-node-group');
if (interactive?.closest('svg')) return;
e.preventDefault();
isDragging.value = true;
lastMousePos.value = {x: e.clientX, y: e.clientY};
graphContainer.value!.style.cursor = 'grabbing';
}
function handleMouseMoveOnDocument(event: MouseEvent) {
if (!isDragging.value) return;
const dx = event.clientX - lastMousePos.value.x;
const dy = event.clientY - lastMousePos.value.y;
translateX.value += dx;
translateY.value += dy;
lastMousePos.value = {x: event.clientX, y: event.clientY};
}
function handleMouseUpOnDocument() {
if (!isDragging.value) return;
isDragging.value = false;
graphContainer.value!.style.cursor = 'grab';
}
function handleWheel(event: WheelEvent) {
// Without a modifier, let the wheel scroll the page
if (!event.ctrlKey && !event.metaKey) {
return;
}
event.preventDefault();
const zoomFactor = Math.exp(-event.deltaY * 0.0015);
zoomTo(scale.value * zoomFactor);
}
onMounted(() => {
loadSavedState();
watch([translateX, translateY, scale], debounce(500, saveState));
watch([scale], debounce(100, saveState));
document.addEventListener('mousemove', handleMouseMoveOnDocument);
document.addEventListener('mouseup', handleMouseUpOnDocument);
});
onUnmounted(() => {
document.removeEventListener('mousemove', handleMouseMoveOnDocument);
document.removeEventListener('mouseup', handleMouseUpOnDocument);
});
function handleNodeMouseEnter(job: JobNode) {
hoveredJobId.value = job.id;
}
function handleNodeMouseLeave() {
hoveredJobId.value = null;
}
function isEdgeHighlighted(edge: RoutedEdge): boolean {
if (!hoveredJobId.value) {
return false;
}
return edge.fromId === hoveredJobId.value || edge.toId === hoveredJobId.value;
}
const nodesWithIncomingEdge = computed(() => {
const set = new Set<number>();
for (const edge of routedEdges.value) set.add(edge.toId);
return set;
});
const nodesWithOutgoingEdge = computed(() => {
const set = new Set<number>();
for (const edge of routedEdges.value) set.add(edge.fromId);
return set;
});
function computeJobLevels(jobs: ActionsJob[]): Map<string, number> {
const jobMap = new Map<string, ActionsJob>()
jobs.forEach(job => {
jobMap.set(job.name, job);
if (job.jobId) jobMap.set(job.jobId, job);
});
const levels = new Map<string, number>();
const visited = new Set<string>();
const recursionStack = new Set<string>();
const MAX_DEPTH = 100;
function dfs(jobNameOrId: string, depth: number = 0): number {
if (depth > MAX_DEPTH) {
console.error(`Max recursion depth (${MAX_DEPTH}) reached for: ${jobNameOrId}`);
return 0;
}
if (recursionStack.has(jobNameOrId)) {
console.error(`Cycle detected involving: ${jobNameOrId}`);
return 0;
}
if (visited.has(jobNameOrId)) {
return levels.get(jobNameOrId) || 0;
}
recursionStack.add(jobNameOrId);
visited.add(jobNameOrId);
const job = jobMap.get(jobNameOrId);
if (!job) {
recursionStack.delete(jobNameOrId);
return 0;
}
if (!job.needs?.length) {
levels.set(job.jobId, 0);
recursionStack.delete(jobNameOrId);
return 0;
}
let maxLevel = -1;
for (const need of job.needs) {
const needJob = jobMap.get(need);
if (!needJob) continue;
const needLevel = dfs(need, depth + 1);
maxLevel = Math.max(maxLevel, needLevel);
}
const level = maxLevel + 1
levels.set(job.name, level);
if (job.jobId && job.jobId !== job.name) {
levels.set(job.jobId, level);
}
recursionStack.delete(jobNameOrId);
return level;
}
jobs.forEach(job => {
if (!visited.has(job.name) && !visited.has(job.jobId)) {
dfs(job.name);
}
})
return levels;
}
function onNodeClick(job: JobNode, event: MouseEvent) {
const link = `${props.runLink}/jobs/${job.id}`;
if (event.ctrlKey || event.metaKey) {
window.open(link, '_blank');
return;
}
window.location.href = link;
}
</script>
<template>
<div class="workflow-graph" v-if="jobs.length > 0">
<div class="graph-header">
<h4 class="graph-title">Workflow Dependencies</h4>
<div class="graph-stats">
{{ jobs.length }} jobs {{ edges.length }} dependencies
<span v-if="graphMetrics">
<span class="graph-metrics">{{ graphMetrics.successRate }} success</span>
</span>
</div>
<div class="flex-text-block">
<button
type="button"
@click="zoomIn"
class="ui compact tiny icon button"
:disabled="!canZoomIn"
:title="canZoomIn ? 'Zoom in (Ctrl/Cmd + scroll on graph)' : 'Already at 100% zoom'"
>
<SvgIcon name="octicon-zoom-in" :size="12"/>
</button>
<button type="button" @click="resetView" class="ui compact tiny icon button" title="Reset view">
<SvgIcon name="octicon-sync" :size="12"/>
</button>
<button type="button" @click="zoomOut" class="ui compact tiny icon button" title="Zoom out (Ctrl/Cmd + scroll on graph)">
<SvgIcon name="octicon-zoom-out" :size="12"/>
</button>
</div>
</div>
<div
class="graph-container"
ref="graphContainer"
@mousedown="handleMouseDown"
@wheel="handleWheel"
:class="{dragging: isDragging}"
>
<svg
:width="graphWidth"
:height="graphHeight"
class="graph-svg"
:style="{
transform: `translate(${translateX}px, ${translateY}px) scale(${scale})`,
transformOrigin: '0 0',
}"
>
<path
v-for="edge in routedEdges"
:key="edge.key"
:d="edge.path"
fill="none"
stroke="var(--color-secondary-alpha-50)"
stroke-width="1.75"
:class="['node-edge', { 'highlighted-edge': isEdgeHighlighted(edge) }]"
/>
<g
v-for="job in jobsWithLayout"
:key="job.id"
class="job-node-group"
@click="onNodeClick(job, $event)"
@mouseenter="handleNodeMouseEnter(job)"
@mouseleave="handleNodeMouseLeave"
>
<title>{{ job.name }}</title>
<rect
:x="job.x"
:y="job.y"
:width="nodeWidth"
:height="nodeHeight"
rx="10"
fill="var(--color-button)"
stroke="var(--color-light-border)"
stroke-width="1.25"
class="job-rect"
/>
<circle
v-if="nodesWithIncomingEdge.has(job.id)"
:cx="job.x"
:cy="job.y + nodeHeight / 2"
r="6"
class="node-port"
/>
<circle
v-if="nodesWithOutgoingEdge.has(job.id)"
:cx="job.x + nodeWidth"
:cy="job.y + nodeHeight / 2"
r="6"
class="node-port"
/>
<foreignObject
:x="job.x + 10"
:y="job.y + 14"
width="20"
height="20"
class="job-status-fg-obj"
>
<div class="job-status-icon-wrap">
<ActionRunStatus :status="job.status"/>
</div>
</foreignObject>
<foreignObject
:x="job.x + 36"
:y="job.y"
:width="nodeWidth - 40"
:height="nodeHeight"
>
<div class="job-text-wrap">
<span class="job-name">{{ job.name }}</span>
<span
v-if="job.duration || job.status === 'success' || job.status === 'failure'"
class="job-duration"
>{{ job.duration }}</span>
</div>
</foreignObject>
</g>
</svg>
</div>
</div>
</template>
<style scoped>
.workflow-graph {
flex: 1;
display: flex;
flex-direction: column;
}
.graph-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 14px;
background: var(--color-box-header);
border-bottom: 1px solid var(--color-secondary);
gap: var(--gap-block);
flex-wrap: wrap;
}
.graph-title {
margin: 0;
color: var(--color-text);
font-size: 16px;
font-weight: var(--font-weight-semibold);
flex: 1;
min-width: 200px;
}
.graph-stats {
display: flex;
align-items: baseline;
column-gap: 8px;
color: var(--color-text-light-1);
font-size: 13px;
white-space: nowrap;
}
.graph-metrics {
color: var(--color-primary);
font-weight: var(--font-weight-medium);
}
.graph-container {
flex: 1;
overflow: hidden;
padding: 12px 16px 20px;
border-radius: 0 0 var(--border-radius) var(--border-radius);
cursor: grab;
position: relative;
background: var(--color-box-body);
}
.graph-container.dragging {
cursor: grabbing;
}
.graph-svg {
display: block;
will-change: transform;
}
.graph-svg path {
transition: all 0.2s ease;
stroke-linecap: round;
stroke-linejoin: round;
}
.highlighted-edge {
stroke-width: 2.25 !important;
stroke: var(--color-workflow-edge-hover) !important;
}
.job-node-group {
cursor: pointer;
transition: all 0.2s ease;
}
.job-node-group:hover .job-rect {
/* due to SVG rendering limitation, only one of fill and drop-shadow can work */
fill: var(--color-hover);
/* filter: drop-shadow(0 1px 3px var(--color-shadow-opaque)); */
}
.job-text-wrap {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 4px;
padding-right: 8px;
}
.job-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 11px;
font-weight: var(--font-weight-semibold);
color: var(--color-text);
user-select: none;
pointer-events: none;
}
.job-duration {
font-size: 11px;
color: var(--color-text-light-2);
white-space: nowrap;
flex-shrink: 0;
user-select: none;
pointer-events: none;
}
.job-status-fg-obj,
.job-status-icon-wrap {
pointer-events: none;
}
.job-status-icon-wrap {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.node-port {
fill: var(--color-box-body);
stroke: var(--color-light-border);
stroke-width: 1.5;
pointer-events: none;
}
.node-edge {
transition: stroke-width 0.2s ease, opacity 0.2s ease;
}
</style>
@@ -1,6 +1,6 @@
class Source {
url: string;
eventSource: EventSource;
eventSource: EventSource | null;
listening: Record<string, boolean>;
clients: Array<MessagePort>;
@@ -47,7 +47,7 @@ class Source {
listen(eventType: string) {
if (this.listening[eventType]) return;
this.listening[eventType] = true;
this.eventSource.addEventListener(eventType, (event) => {
this.eventSource?.addEventListener(eventType, (event) => {
this.notifyClients({
type: eventType,
data: event.data,
@@ -64,18 +64,17 @@ class Source {
status(port: MessagePort) {
port.postMessage({
type: 'status',
message: `url: ${this.url} readyState: ${this.eventSource.readyState}`,
message: `url: ${this.url} readyState: ${this.eventSource?.readyState}`,
});
}
}
const sourcesByUrl: Map<string, Source | null> = new Map();
const sourcesByPort: Map<MessagePort, Source | null> = new Map();
const sourcesByUrl = new Map<string, Source | null>();
const sourcesByPort = new Map<MessagePort, Source | null>();
// @ts-expect-error: typescript bug?
self.addEventListener('connect', (e: MessageEvent) => {
(self as unknown as SharedWorkerGlobalScope).addEventListener('connect', (e: MessageEvent) => {
for (const port of e.ports) {
port.addEventListener('message', (event) => {
port.addEventListener('message', (event: MessageEvent) => {
if (!self.EventSource) {
// some browsers (like PaleMoon, Firefox<53) don't support EventSource in SharedWorkerGlobalScope.
// this event handler needs EventSource when doing "new Source(url)", so just post a message back to the caller,
@@ -85,14 +84,14 @@ self.addEventListener('connect', (e: MessageEvent) => {
}
if (event.data.type === 'start') {
const url = event.data.url;
if (sourcesByUrl.get(url)) {
let source = sourcesByUrl.get(url);
if (source) {
// we have a Source registered to this url
const source = sourcesByUrl.get(url);
source.register(port);
sourcesByPort.set(port, source);
return;
}
let source = sourcesByPort.get(port);
source = sourcesByPort.get(port);
if (source) {
if (source.eventSource && source.url === url) return;
@@ -111,11 +110,10 @@ self.addEventListener('connect', (e: MessageEvent) => {
sourcesByUrl.set(url, source);
sourcesByPort.set(port, source);
} else if (event.data.type === 'listen') {
const source = sourcesByPort.get(port);
const source = sourcesByPort.get(port)!;
source.listen(event.data.eventType);
} else if (event.data.type === 'close') {
const source = sourcesByPort.get(port);
if (!source) return;
const count = source.deregister(port);
@@ -0,0 +1,67 @@
import type {FrontendRenderFunc, FrontendRenderOptions} from './render/plugin.ts';
type LazyLoadFunc = () => Promise<{frontendRender: FrontendRenderFunc}>;
// It must use a wrapper function to avoid the "import" statement being treated
// as static import and cause the all plugins being loaded together,
// We only need to load the plugins we need.
const frontendPlugins: Record<string, LazyLoadFunc> = {
'viewer-3d': () => import('./render/plugins/frontend-viewer-3d.ts'),
'openapi-swagger': () => import('./render/plugins/frontend-openapi-swagger.ts'),
};
class Options implements FrontendRenderOptions {
container: HTMLElement;
treePath: string;
rawEncoding: string;
rawString: string;
cachedBytes: Uint8Array<ArrayBuffer> | null = null;
cachedString: string | null = null;
constructor(container: HTMLElement, treePath: string, rawEncoding: string, rawString: string) {
this.container = container;
this.treePath = treePath;
this.rawEncoding = rawEncoding;
this.rawString = rawString;
}
decodeBase64(): Uint8Array<ArrayBuffer> {
return Uint8Array.from(atob(this.rawString), (c) => c.charCodeAt(0));
}
contentBytes(): Uint8Array<ArrayBuffer> {
if (this.cachedBytes === null) {
this.cachedBytes = this.rawEncoding === 'base64' ? this.decodeBase64() : new TextEncoder().encode(this.rawString);
}
return this.cachedBytes;
}
contentString(): string {
if (this.cachedString === null) {
this.cachedString = this.rawEncoding === 'base64' ? new TextDecoder('utf-8').decode(this.decodeBase64()) : this.rawString;
}
return this.cachedString;
}
}
async function initFrontendExternalRender() {
const viewerContainer = document.querySelector<HTMLElement>('#frontend-render-viewer')!;
const renderNames = viewerContainer.getAttribute('data-frontend-renders')!.split(' ');
const fileTreePath = viewerContainer.getAttribute('data-file-tree-path')!;
const fileDataElem = document.querySelector<HTMLTextAreaElement>('#frontend-render-data')!;
fileDataElem.remove();
const fileDataContent = fileDataElem.value;
const fileDataEncoding = fileDataElem.getAttribute('data-content-encoding')!;
const opts = new Options(viewerContainer, fileTreePath, fileDataEncoding, fileDataContent);
let found = false;
for (const name of renderNames) {
if (!(name in frontendPlugins)) continue;
const plugin = await frontendPlugins[name]();
found = true;
if (await plugin.frontendRender(opts)) break;
}
if (!found) {
viewerContainer.textContent = 'No frontend render plugin found for this file, but backend declares that there must be one, there must be a bug';
}
}
initFrontendExternalRender();

Some files were not shown because too many files have changed in this diff Show More