bring remote assets to local
This commit is contained in:
@ -1,406 +1,406 @@
|
||||
|
||||
* {
|
||||
min-width: 0px;
|
||||
min-height: 0px;
|
||||
min-width: 0px;
|
||||
min-height: 0px;
|
||||
}
|
||||
|
||||
.darkmuted {
|
||||
/* Background and foreground */
|
||||
--b0: hsl(215, 25%, 8%);
|
||||
--b1: hsl(215, 25%, 12%);
|
||||
--b2: hsl(215, 25%, 16%);
|
||||
--b3: hsl(215, 25%, 20%);
|
||||
--b4: hsl(215, 25%, 30%);
|
||||
--b5: hsl(215, 25%, 40%);
|
||||
--f4: hsl(215, 25%, 55%);
|
||||
--f3: hsl(215, 25%, 70%);
|
||||
--f2: hsl(215, 25%, 85%);
|
||||
--f1: white;
|
||||
/* Background and foreground */
|
||||
--b0: hsl(215, 25%, 8%);
|
||||
--b1: hsl(215, 25%, 12%);
|
||||
--b2: hsl(215, 25%, 16%);
|
||||
--b3: hsl(215, 25%, 20%);
|
||||
--b4: hsl(215, 25%, 30%);
|
||||
--b5: hsl(215, 25%, 40%);
|
||||
--f4: hsl(215, 25%, 55%);
|
||||
--f3: hsl(215, 25%, 70%);
|
||||
--f2: hsl(215, 25%, 85%);
|
||||
--f1: white;
|
||||
}
|
||||
.btn, .textbox {
|
||||
border-radius: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.btn.iconOnly .icon {
|
||||
margin: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
.btn:focus-visible {
|
||||
outline: 2px solid var(--f2);
|
||||
outline: 2px solid var(--f2);
|
||||
}
|
||||
.textbox,
|
||||
.textbox.textarea > textarea {
|
||||
padding: 0px 12px;
|
||||
padding-top: 2px;
|
||||
padding: 0px 12px;
|
||||
padding-top: 2px;
|
||||
}
|
||||
.textbox.textarea {
|
||||
padding: 8px 0px;
|
||||
padding: 8px 0px;
|
||||
}
|
||||
.popup {
|
||||
border-radius: 16px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
.context {
|
||||
border-radius: 12px;
|
||||
padding: 4px;
|
||||
gap: 4px;
|
||||
border-radius: 12px;
|
||||
padding: 4px;
|
||||
gap: 4px;
|
||||
}
|
||||
.context > .item {
|
||||
border-radius: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
label.selectOption input[type="radio"],
|
||||
label.selectOption input[type="checkbox"] {
|
||||
margin-top: -0.05em;
|
||||
margin-top: -0.05em;
|
||||
}
|
||||
.tooltip {
|
||||
/* padding: 6px 12px; */
|
||||
padding: 8px 12px 5px 12px;
|
||||
border-radius: 8px;
|
||||
/* padding: 6px 12px; */
|
||||
padding: 8px 12px 5px 12px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.toastOverlay > .toast > .body {
|
||||
/* padding: 15px 5px; */
|
||||
padding-top: 18px;
|
||||
/* padding: 15px 5px; */
|
||||
padding-top: 18px;
|
||||
}
|
||||
.popup > .body {
|
||||
padding-top: 5px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
body {
|
||||
--fontDefault: 'Comfortaa';
|
||||
--fontDefault: 'Comfortaa';
|
||||
}
|
||||
|
||||
#main {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#navbar {
|
||||
padding: 8px 16px;
|
||||
border-bottom: 1px solid var(--b3);
|
||||
padding: 8px 16px;
|
||||
border-bottom: 1px solid var(--b3);
|
||||
}
|
||||
|
||||
#fileHeader .icon {
|
||||
font-family: 'Material Symbols Filled Rounded';
|
||||
font-size: 28px;
|
||||
color: var(--f3);
|
||||
user-select: none;
|
||||
font-family: 'Material Symbols Filled Rounded';
|
||||
font-size: 28px;
|
||||
color: var(--f3);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#fileHeader .path {
|
||||
font-size: 14px;
|
||||
color: var(--f4);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 14px;
|
||||
color: var(--f4);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
#fileHeader .name {
|
||||
font-size: 18px;
|
||||
color: var(--f1);
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 18px;
|
||||
color: var(--f1);
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#controls {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid var(--b3);
|
||||
border-width: 0px 0px 1px 0px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid var(--b3);
|
||||
border-width: 0px 0px 1px 0px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
#controls::-webkit-scrollbar {
|
||||
height: 3px;
|
||||
background: transparent;
|
||||
height: 3px;
|
||||
background: transparent;
|
||||
}
|
||||
#controls::-webkit-scrollbar-thumb {
|
||||
background: var(--b4);
|
||||
background: var(--b4);
|
||||
}
|
||||
#controls::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--b5);
|
||||
background: var(--b5);
|
||||
}
|
||||
#controls .sep {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
margin: 0px 5px;
|
||||
background-color: var(--b3);
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
margin: 0px 5px;
|
||||
background-color: var(--b3);
|
||||
}
|
||||
#controls .selectOption {
|
||||
font-size: 14px;
|
||||
color: var(--f2);
|
||||
margin-top: 5px;
|
||||
font-size: 14px;
|
||||
color: var(--f2);
|
||||
margin-top: 5px;
|
||||
}
|
||||
#controls .selectOption input {
|
||||
font-size: 28px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
#fileColHeadings {
|
||||
padding: 10px 24px 6px 24px;
|
||||
font-weight: bold;
|
||||
color: var(--b5);
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
overflow-y: scroll;
|
||||
scrollbar-gutter: stable;
|
||||
padding: 10px 24px 6px 24px;
|
||||
font-weight: bold;
|
||||
color: var(--b5);
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
overflow-y: scroll;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
#fileColHeadings.tiles {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
#files {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
height: 0px;
|
||||
padding: 4px;
|
||||
padding-top: 2px;
|
||||
scrollbar-gutter: stable;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
height: 0px;
|
||||
padding: 4px;
|
||||
padding-top: 2px;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
#files:not(.tiles) > .heading {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
#files.tiles > .heading {
|
||||
display: block;
|
||||
padding: 12px 20px 4px 20px;
|
||||
font-weight: bold;
|
||||
color: var(--b5);
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
flex-shrink: 0;
|
||||
display: block;
|
||||
padding: 12px 20px 4px 20px;
|
||||
font-weight: bold;
|
||||
color: var(--b5);
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
#files > .section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
gap: 2px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
gap: 2px;
|
||||
}
|
||||
#files.tiles > .section {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
}
|
||||
.fileEntry {
|
||||
height: auto;
|
||||
padding: 6px 20px 5px 20px;
|
||||
justify-content: flex-start;
|
||||
--bg: transparent;
|
||||
--fg: var(--f2);
|
||||
--bgHover: var(--b2);
|
||||
--bgActive: var(--b3);
|
||||
font-weight: normal;
|
||||
text-align: left;
|
||||
gap: 10px;
|
||||
height: auto;
|
||||
padding: 6px 20px 5px 20px;
|
||||
justify-content: flex-start;
|
||||
--bg: transparent;
|
||||
--fg: var(--f2);
|
||||
--bgHover: var(--b2);
|
||||
--bgActive: var(--b3);
|
||||
font-weight: normal;
|
||||
text-align: left;
|
||||
gap: 10px;
|
||||
}
|
||||
.fileEntry.search {
|
||||
padding-top: 9px;
|
||||
padding-bottom: 7px;
|
||||
padding-top: 9px;
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
.fileEntry.search .nameCont .path {
|
||||
margin-bottom: -2px;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
#files.tiles .fileEntry {
|
||||
height: auto;
|
||||
padding-top: 11px;
|
||||
padding-bottom: 9px;
|
||||
gap: 12px;
|
||||
height: auto;
|
||||
padding-top: 11px;
|
||||
padding-bottom: 9px;
|
||||
gap: 12px;
|
||||
}
|
||||
#files:not(.showHidden) .fileEntry.hidden {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
.fileEntry > .icon {
|
||||
color: var(--f3);
|
||||
font-family: 'Material Symbols Filled Rounded';
|
||||
color: var(--f3);
|
||||
font-family: 'Material Symbols Filled Rounded';
|
||||
}
|
||||
#files.tiles .fileEntry > .icon {
|
||||
font-size: 32px;
|
||||
font-size: 32px;
|
||||
}
|
||||
.fileEntry > .nameCont {
|
||||
gap: 4px;
|
||||
gap: 4px;
|
||||
}
|
||||
.fileEntry > .nameCont .name {
|
||||
color: var(--f1);
|
||||
color: var(--f1);
|
||||
}
|
||||
.fileEntry > .nameCont .lower {
|
||||
display: none;
|
||||
font-size: 14px;
|
||||
color: var(--f3);
|
||||
display: none;
|
||||
font-size: 14px;
|
||||
color: var(--f3);
|
||||
}
|
||||
#files.tiles .fileEntry > .nameCont .lower {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
.fileEntry * {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
#files.tiles .fileEntry > :not(.icon):not(.nameCont) {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
.fileEntry > .date,
|
||||
#fileColHeadings > .date {
|
||||
width: 150px;
|
||||
width: 150px;
|
||||
}
|
||||
.fileEntry > .size,
|
||||
#fileColHeadings > .size {
|
||||
width: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
.fileEntry > .perms,
|
||||
#fileColHeadings > .perms {
|
||||
width: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
.fileEntry.selected {
|
||||
--bg: var(--blue0);
|
||||
--fg: var(--f1);
|
||||
--bgHover: var(--blue1);
|
||||
--bgActive: var(--blue2);
|
||||
--bg: var(--blue0);
|
||||
--fg: var(--f1);
|
||||
--bgHover: var(--blue1);
|
||||
--bgActive: var(--blue2);
|
||||
}
|
||||
.fileEntry.selected > .icon,
|
||||
.fileEntry.selected > .nameCont .lower {
|
||||
color: var(--f1);
|
||||
color: var(--f1);
|
||||
}
|
||||
.fileEntry.hidden:not(.selected) > * {
|
||||
opacity: 0.5;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.fileEntry.cut:not(.selected) {
|
||||
opacity: 0.5;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.permsMatrix .header,
|
||||
.permsMatrix .cell {
|
||||
width: 70px;
|
||||
height: 40px;
|
||||
width: 70px;
|
||||
height: 40px;
|
||||
}
|
||||
.permsMatrix .cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
}
|
||||
.permsMatrix .header {
|
||||
font-size: 14px;
|
||||
color: var(--f3);
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
color: var(--f3);
|
||||
display: flex;
|
||||
}
|
||||
.permsMatrix .header.top {
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 3px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
.permsMatrix .header.left {
|
||||
width: 50px;
|
||||
text-align: right;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
width: 50px;
|
||||
text-align: right;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#preview {
|
||||
overflow: auto;
|
||||
overflow: auto;
|
||||
}
|
||||
#preview.image,
|
||||
#preview.video {
|
||||
background: black;
|
||||
background: black;
|
||||
}
|
||||
#preview.audio {
|
||||
padding: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
#preview.image {
|
||||
justify-content: initial;
|
||||
align-items: initial;
|
||||
cursor: grab;
|
||||
justify-content: initial;
|
||||
align-items: initial;
|
||||
cursor: grab;
|
||||
}
|
||||
#preview img {
|
||||
flex-shrink: 0;
|
||||
margin: auto;
|
||||
flex-shrink: 0;
|
||||
margin: auto;
|
||||
}
|
||||
#preview video {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
#preview audio {
|
||||
width: 500px;
|
||||
width: 500px;
|
||||
}
|
||||
#preview .CodeMirror {
|
||||
width: 1200px;
|
||||
height: 100%;
|
||||
border: none;
|
||||
border-left: 1px solid var(--b2);
|
||||
border-right: 1px solid var(--b2);
|
||||
width: 1200px;
|
||||
height: 100%;
|
||||
border: none;
|
||||
border-left: 1px solid var(--b2);
|
||||
border-right: 1px solid var(--b2);
|
||||
}
|
||||
#preview.markdown .rendered {
|
||||
width: 1200px;
|
||||
padding: 20px;
|
||||
margin: auto;
|
||||
width: 1200px;
|
||||
padding: 20px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
#progressBar {
|
||||
border-radius: 0px;
|
||||
margin: 0px;
|
||||
height: 3px;
|
||||
display: none;
|
||||
border-radius: 0px;
|
||||
margin: 0px;
|
||||
height: 3px;
|
||||
display: none;
|
||||
}
|
||||
#progressBar.visible {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#statusBar {
|
||||
padding: 8px 10px 6px 10px;
|
||||
font-size: 15px;
|
||||
color: var(--f4);
|
||||
border-top: 1px solid var(--b3);
|
||||
line-height: 1.2;
|
||||
padding: 8px 10px 6px 10px;
|
||||
font-size: 15px;
|
||||
color: var(--f4);
|
||||
border-top: 1px solid var(--b3);
|
||||
line-height: 1.2;
|
||||
}
|
||||
#statusBar.error {
|
||||
color: var(--red2);
|
||||
color: var(--red2);
|
||||
}
|
||||
|
||||
#connectionManager .entry > .icon {
|
||||
font-family: 'Material Symbols Outlined Rounded';
|
||||
font-size: 32px;
|
||||
color: var(--f3);
|
||||
user-select: none;
|
||||
font-family: 'Material Symbols Outlined Rounded';
|
||||
font-size: 32px;
|
||||
color: var(--f3);
|
||||
user-select: none;
|
||||
}
|
||||
#connectionManager .entry > .row {
|
||||
gap: 8px 20px;
|
||||
gap: 8px 20px;
|
||||
}
|
||||
|
||||
.moveFilesPicker .folders {
|
||||
border-radius: 12px;
|
||||
padding: 4px;
|
||||
gap: 2px;
|
||||
border: 1px solid var(--b3);
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
border-radius: 12px;
|
||||
padding: 4px;
|
||||
gap: 2px;
|
||||
border: 1px solid var(--b3);
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 540px */
|
||||
@media (max-width: 640px) {
|
||||
.atLeast640px {
|
||||
display: none;
|
||||
}
|
||||
.atLeast640px {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media (min-width: 641px) {
|
||||
.atMost640px {
|
||||
display: none;
|
||||
}
|
||||
.atMost640px {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
/* 800px */
|
||||
@media (max-width: 800px) {
|
||||
.atLeast800px {
|
||||
display: none;
|
||||
}
|
||||
.atLeast800px {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media (min-width: 801px) {
|
||||
.atMost800px {
|
||||
display: none;
|
||||
}
|
||||
.atMost800px {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
/* 1000px */
|
||||
@media (max-width: 1000px) {
|
||||
.atLeast1000px {
|
||||
display: none;
|
||||
}
|
||||
.atLeast1000px {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1001px) {
|
||||
.atMost1000px {
|
||||
display: none;
|
||||
}
|
||||
.atMost1000px {
|
||||
display: none;
|
||||
}
|
||||
}
|
@ -10,23 +10,23 @@
|
||||
<meta name="theme-color" content="#1f2733">
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="icon" href="/icon.png">
|
||||
<link rel="stylesheet" href="https://src.simplecyber.org/lib/codemirror5.css">
|
||||
<link rel="stylesheet" href="https://src.simplecyber.org/v2/themes.css">
|
||||
<link rel="stylesheet" href="https://src.simplecyber.org/v2/base.css">
|
||||
<link rel="stylesheet" href="https://sftp.my-mc.link/lib/codemirror5.css">
|
||||
<link rel="stylesheet" href="https://sftp.my-mc.link/v2/themes.css">
|
||||
<link rel="stylesheet" href="https://sftp.my-mc.link/v2/base.css">
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<script defer src="https://src.simplecyber.org/lib/axios.min.js"></script>
|
||||
<script defer src="https://src.simplecyber.org/lib/tabbable.min.js"></script>
|
||||
<script defer src="https://src.simplecyber.org/lib/focus-trap.min.js"></script>
|
||||
<script defer src="https://src.simplecyber.org/lib/dayjs.min.js"></script>
|
||||
<script defer src="https://src.simplecyber.org/lib/marked.min.js"></script>
|
||||
<script defer src="https://src.simplecyber.org/lib/codemirror5.js"></script>
|
||||
<script defer src="https://src.simplecyber.org/lib/codemirror5-scrollPastEnd.js"></script>
|
||||
<script defer src="https://src.simplecyber.org/lib/codemirror5-activeLine.js"></script>
|
||||
<script defer src="https://src.simplecyber.org/lib/codemirror5-loadMode.js"></script>
|
||||
<script defer src="https://src.simplecyber.org/lib/codemirror5-closeBrackets.js"></script>
|
||||
<script defer src="https://src.simplecyber.org/lib/codemirror5-overlay.js"></script>
|
||||
<script defer src="https://src.simplecyber.org/v2/base.js"></script>
|
||||
<script defer src="https://src.simplecyber.org/utils.js"></script>
|
||||
<script defer src="https://sftp.my-mc.link/lib/axios.min.js"></script>
|
||||
<script defer src="https://sftp.my-mc.link/lib/tabbable.min.js"></script>
|
||||
<script defer src="https://sftp.my-mc.link/lib/focus-trap.min.js"></script>
|
||||
<script defer src="https://sftp.my-mc.link/lib/dayjs.min.js"></script>
|
||||
<script defer src="https://sftp.my-mc.link/lib/marked.min.js"></script>
|
||||
<script defer src="https://sftp.my-mc.link/lib/codemirror5.js"></script>
|
||||
<script defer src="https://sftp.my-mc.link/lib/codemirror5-scrollPastEnd.js"></script>
|
||||
<script defer src="https://sftp.my-mc.link/lib/codemirror5-activeLine.js"></script>
|
||||
<script defer src="https://sftp.my-mc.link/lib/codemirror5-loadMode.js"></script>
|
||||
<script defer src="https://sftp.my-mc.link/lib/codemirror5-closeBrackets.js"></script>
|
||||
<script defer src="https://sftp.my-mc.link/lib/codemirror5-overlay.js"></script>
|
||||
<script defer src="https://sftp.my-mc.link/v2/base.js"></script>
|
||||
<script defer src="https://sftp.my-mc.link/utils.js"></script>
|
||||
<script defer src="/assets/main.js"></script>
|
||||
<script defer src="/assets/file.js"></script>
|
||||
</head>
|
||||
|
BIN
web/fonts/Comfortaa-Bold.ttf
Normal file
BIN
web/fonts/Comfortaa-Bold.ttf
Normal file
Binary file not shown.
BIN
web/fonts/Comfortaa-Regular.ttf
Normal file
BIN
web/fonts/Comfortaa-Regular.ttf
Normal file
Binary file not shown.
13
web/fonts/cascadia-code.css
Normal file
13
web/fonts/cascadia-code.css
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
@font-face {
|
||||
font-family: 'Cascadia Code';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
src: url(https://src.simplecyber.org/fonts/CascadiaCode.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Cascadia Code';
|
||||
font-style: italic;
|
||||
font-weight: 100 900;
|
||||
src: url(https://src.simplecyber.org/fonts/CascadiaCodeItalic.ttf) format('truetype');
|
||||
}
|
31
web/fonts/comfortaa.css
Normal file
31
web/fonts/comfortaa.css
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
@font-face {
|
||||
font-family: 'Comfortaa';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url(/fonts/Comfortaa-Light.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Comfortaa';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(/fonts/Comfortaa-Regular.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Comfortaa';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url(/fonts/Comfortaa-Medium.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Comfortaa';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url(/fonts/Comfortaa-SemiBold.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Comfortaa';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url(/fonts/Comfortaa-Bold.ttf) format('truetype');
|
||||
}
|
BIN
web/fonts/material-symbols-filled-rounded-300.woff2
Normal file
BIN
web/fonts/material-symbols-filled-rounded-300.woff2
Normal file
Binary file not shown.
BIN
web/fonts/material-symbols-outlined-rounded-300.woff2
Normal file
BIN
web/fonts/material-symbols-outlined-rounded-300.woff2
Normal file
Binary file not shown.
55
web/fonts/material-symbols-outlined.css
Normal file
55
web/fonts/material-symbols-outlined.css
Normal file
@ -0,0 +1,55 @@
|
||||
|
||||
@font-face {
|
||||
font-family: 'Material Symbols Outlined';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url(/fonts/material-symbols-outlined-300.woff2) format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Material Symbols Outlined Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url(/fonts/material-symbols-outlined-rounded-300.woff2) format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Material Symbols Filled';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url(/fonts/material-symbols-filled-300.woff2) format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Material Symbols Filled Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url(/fonts/material-symbols-filled-rounded-300.woff2) format('woff2');
|
||||
}
|
||||
|
||||
.material-symbol {
|
||||
font-family: 'Material Symbols Outlined';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
direction: ltr;
|
||||
-webkit-font-feature-settings: 'liga';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.material-symbol.outlined {
|
||||
font-family: 'Material Symbols Outlined';
|
||||
}
|
||||
.material-symbol.outlined.rounded {
|
||||
font-family: 'Material Symbols Outlined Rounded';
|
||||
}
|
||||
.material-symbol.filled {
|
||||
font-family: 'Material Symbols Filled';
|
||||
}
|
||||
.material-symbol.filled.rounded {
|
||||
font-family: 'Material Symbols Filled Rounded';
|
||||
}
|
55
web/fonts/poppins.css
Normal file
55
web/fonts/poppins.css
Normal file
@ -0,0 +1,55 @@
|
||||
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src: url(/fonts/Poppins-Thin.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url(/fonts/Poppins-ExtraLight.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url(/fonts/Poppins-Light.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(/fonts/Poppins-Regular.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url(/fonts/Poppins-Medium.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url(/fonts/Poppins-SemiBold.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url(/fonts/Poppins-Bold.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: url(/fonts/Poppins-ExtraBold.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url(/fonts/Poppins-Black.ttf) format('truetype');
|
||||
}
|
@ -11,15 +11,15 @@
|
||||
<meta http-equiv="Permissions-Policy" content="clipboard-write=(self 'https://sftp.my-mc.link')">
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="icon" href="/icon.png">
|
||||
<link rel="stylesheet" href="https://src.simplecyber.org/v2/themes.css">
|
||||
<link rel="stylesheet" href="https://src.simplecyber.org/v2/base.css">
|
||||
<link rel="stylesheet" href="https://sftp.my-mc.link/v2/themes.css">
|
||||
<link rel="stylesheet" href="https://sftp.my-mc.link/v2/base.css">
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<script defer src="https://src.simplecyber.org/lib/axios.min.js"></script>
|
||||
<script defer src="https://src.simplecyber.org/lib/tabbable.min.js"></script>
|
||||
<script defer src="https://src.simplecyber.org/lib/focus-trap.min.js"></script>
|
||||
<script defer src="https://src.simplecyber.org/lib/dayjs.min.js"></script>
|
||||
<script defer src="https://src.simplecyber.org/v2/base.js"></script>
|
||||
<script defer src="https://src.simplecyber.org/utils.js"></script>
|
||||
<script defer src="https://sftp.my-mc.link/lib/axios.min.js"></script>
|
||||
<script defer src="https://sftp.my-mc.link/lib/tabbable.min.js"></script>
|
||||
<script defer src="https://sftp.my-mc.link/lib/focus-trap.min.js"></script>
|
||||
<script defer src="https://sftp.my-mc.link/lib/dayjs.min.js"></script>
|
||||
<script defer src="https://sftp.my-mc.link/v2/base.js"></script>
|
||||
<script defer src="https://sftp.my-mc.link/utils.js"></script>
|
||||
<script defer src="/assets/main.js"></script>
|
||||
<script defer src="/assets/index.js"></script>
|
||||
</head>
|
||||
|
2
web/lib/axios.min.js
vendored
Normal file
2
web/lib/axios.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
72
web/lib/codemirror5-activeLine.js
Normal file
72
web/lib/codemirror5-activeLine.js
Normal file
@ -0,0 +1,72 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
var WRAP_CLASS = "CodeMirror-activeline";
|
||||
var BACK_CLASS = "CodeMirror-activeline-background";
|
||||
var GUTT_CLASS = "CodeMirror-activeline-gutter";
|
||||
|
||||
CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) {
|
||||
var prev = old == CodeMirror.Init ? false : old;
|
||||
if (val == prev) return
|
||||
if (prev) {
|
||||
cm.off("beforeSelectionChange", selectionChange);
|
||||
clearActiveLines(cm);
|
||||
delete cm.state.activeLines;
|
||||
}
|
||||
if (val) {
|
||||
cm.state.activeLines = [];
|
||||
updateActiveLines(cm, cm.listSelections());
|
||||
cm.on("beforeSelectionChange", selectionChange);
|
||||
}
|
||||
});
|
||||
|
||||
function clearActiveLines(cm) {
|
||||
for (var i = 0; i < cm.state.activeLines.length; i++) {
|
||||
cm.removeLineClass(cm.state.activeLines[i], "wrap", WRAP_CLASS);
|
||||
cm.removeLineClass(cm.state.activeLines[i], "background", BACK_CLASS);
|
||||
cm.removeLineClass(cm.state.activeLines[i], "gutter", GUTT_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
function sameArray(a, b) {
|
||||
if (a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; i++)
|
||||
if (a[i] != b[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateActiveLines(cm, ranges) {
|
||||
var active = [];
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
var range = ranges[i];
|
||||
var option = cm.getOption("styleActiveLine");
|
||||
if (typeof option == "object" && option.nonEmpty ? range.anchor.line != range.head.line : !range.empty())
|
||||
continue
|
||||
var line = cm.getLineHandleVisualStart(range.head.line);
|
||||
if (active[active.length - 1] != line) active.push(line);
|
||||
}
|
||||
if (sameArray(cm.state.activeLines, active)) return;
|
||||
cm.operation(function() {
|
||||
clearActiveLines(cm);
|
||||
for (var i = 0; i < active.length; i++) {
|
||||
cm.addLineClass(active[i], "wrap", WRAP_CLASS);
|
||||
cm.addLineClass(active[i], "background", BACK_CLASS);
|
||||
cm.addLineClass(active[i], "gutter", GUTT_CLASS);
|
||||
}
|
||||
cm.state.activeLines = active;
|
||||
});
|
||||
}
|
||||
|
||||
function selectionChange(cm, sel) {
|
||||
updateActiveLines(cm, sel.ranges);
|
||||
}
|
||||
});
|
201
web/lib/codemirror5-closeBrackets.js
Normal file
201
web/lib/codemirror5-closeBrackets.js
Normal file
@ -0,0 +1,201 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
var defaults = {
|
||||
pairs: "()[]{}''\"\"",
|
||||
closeBefore: ")]}'\":;>",
|
||||
triples: "",
|
||||
explode: "[]{}"
|
||||
};
|
||||
|
||||
var Pos = CodeMirror.Pos;
|
||||
|
||||
CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
|
||||
if (old && old != CodeMirror.Init) {
|
||||
cm.removeKeyMap(keyMap);
|
||||
cm.state.closeBrackets = null;
|
||||
}
|
||||
if (val) {
|
||||
ensureBound(getOption(val, "pairs"))
|
||||
cm.state.closeBrackets = val;
|
||||
cm.addKeyMap(keyMap);
|
||||
}
|
||||
});
|
||||
|
||||
function getOption(conf, name) {
|
||||
if (name == "pairs" && typeof conf == "string") return conf;
|
||||
if (typeof conf == "object" && conf[name] != null) return conf[name];
|
||||
return defaults[name];
|
||||
}
|
||||
|
||||
var keyMap = {Backspace: handleBackspace, Enter: handleEnter};
|
||||
function ensureBound(chars) {
|
||||
for (var i = 0; i < chars.length; i++) {
|
||||
var ch = chars.charAt(i), key = "'" + ch + "'"
|
||||
if (!keyMap[key]) keyMap[key] = handler(ch)
|
||||
}
|
||||
}
|
||||
ensureBound(defaults.pairs + "`")
|
||||
|
||||
function handler(ch) {
|
||||
return function(cm) { return handleChar(cm, ch); };
|
||||
}
|
||||
|
||||
function getConfig(cm) {
|
||||
var deflt = cm.state.closeBrackets;
|
||||
if (!deflt || deflt.override) return deflt;
|
||||
var mode = cm.getModeAt(cm.getCursor());
|
||||
return mode.closeBrackets || deflt;
|
||||
}
|
||||
|
||||
function handleBackspace(cm) {
|
||||
var conf = getConfig(cm);
|
||||
if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
|
||||
|
||||
var pairs = getOption(conf, "pairs");
|
||||
var ranges = cm.listSelections();
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
if (!ranges[i].empty()) return CodeMirror.Pass;
|
||||
var around = charsAround(cm, ranges[i].head);
|
||||
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
|
||||
}
|
||||
for (var i = ranges.length - 1; i >= 0; i--) {
|
||||
var cur = ranges[i].head;
|
||||
cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete");
|
||||
}
|
||||
}
|
||||
|
||||
function handleEnter(cm) {
|
||||
var conf = getConfig(cm);
|
||||
var explode = conf && getOption(conf, "explode");
|
||||
if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass;
|
||||
|
||||
var ranges = cm.listSelections();
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
if (!ranges[i].empty()) return CodeMirror.Pass;
|
||||
var around = charsAround(cm, ranges[i].head);
|
||||
if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
|
||||
}
|
||||
cm.operation(function() {
|
||||
var linesep = cm.lineSeparator() || "\n";
|
||||
cm.replaceSelection(linesep + linesep, null);
|
||||
moveSel(cm, -1)
|
||||
ranges = cm.listSelections();
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
var line = ranges[i].head.line;
|
||||
cm.indentLine(line, null, true);
|
||||
cm.indentLine(line + 1, null, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function moveSel(cm, dir) {
|
||||
var newRanges = [], ranges = cm.listSelections(), primary = 0
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
var range = ranges[i]
|
||||
if (range.head == cm.getCursor()) primary = i
|
||||
var pos = range.head.ch || dir > 0 ? {line: range.head.line, ch: range.head.ch + dir} : {line: range.head.line - 1}
|
||||
newRanges.push({anchor: pos, head: pos})
|
||||
}
|
||||
cm.setSelections(newRanges, primary)
|
||||
}
|
||||
|
||||
function contractSelection(sel) {
|
||||
var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;
|
||||
return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),
|
||||
head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))};
|
||||
}
|
||||
|
||||
function handleChar(cm, ch) {
|
||||
var conf = getConfig(cm);
|
||||
if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
|
||||
|
||||
var pairs = getOption(conf, "pairs");
|
||||
var pos = pairs.indexOf(ch);
|
||||
if (pos == -1) return CodeMirror.Pass;
|
||||
|
||||
var closeBefore = getOption(conf,"closeBefore");
|
||||
|
||||
var triples = getOption(conf, "triples");
|
||||
|
||||
var identical = pairs.charAt(pos + 1) == ch;
|
||||
var ranges = cm.listSelections();
|
||||
var opening = pos % 2 == 0;
|
||||
|
||||
var type;
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
var range = ranges[i], cur = range.head, curType;
|
||||
var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
|
||||
if (opening && !range.empty()) {
|
||||
curType = "surround";
|
||||
} else if ((identical || !opening) && next == ch) {
|
||||
if (identical && stringStartsAfter(cm, cur))
|
||||
curType = "both";
|
||||
else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
|
||||
curType = "skipThree";
|
||||
else
|
||||
curType = "skip";
|
||||
} else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&
|
||||
cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch) {
|
||||
if (cur.ch > 2 && /\bstring/.test(cm.getTokenTypeAt(Pos(cur.line, cur.ch - 2)))) return CodeMirror.Pass;
|
||||
curType = "addFour";
|
||||
} else if (identical) {
|
||||
var prev = cur.ch == 0 ? " " : cm.getRange(Pos(cur.line, cur.ch - 1), cur)
|
||||
if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = "both";
|
||||
else return CodeMirror.Pass;
|
||||
} else if (opening && (next.length === 0 || /\s/.test(next) || closeBefore.indexOf(next) > -1)) {
|
||||
curType = "both";
|
||||
} else {
|
||||
return CodeMirror.Pass;
|
||||
}
|
||||
if (!type) type = curType;
|
||||
else if (type != curType) return CodeMirror.Pass;
|
||||
}
|
||||
|
||||
var left = pos % 2 ? pairs.charAt(pos - 1) : ch;
|
||||
var right = pos % 2 ? ch : pairs.charAt(pos + 1);
|
||||
cm.operation(function() {
|
||||
if (type == "skip") {
|
||||
moveSel(cm, 1)
|
||||
} else if (type == "skipThree") {
|
||||
moveSel(cm, 3)
|
||||
} else if (type == "surround") {
|
||||
var sels = cm.getSelections();
|
||||
for (var i = 0; i < sels.length; i++)
|
||||
sels[i] = left + sels[i] + right;
|
||||
cm.replaceSelections(sels, "around");
|
||||
sels = cm.listSelections().slice();
|
||||
for (var i = 0; i < sels.length; i++)
|
||||
sels[i] = contractSelection(sels[i]);
|
||||
cm.setSelections(sels);
|
||||
} else if (type == "both") {
|
||||
cm.replaceSelection(left + right, null);
|
||||
cm.triggerElectric(left + right);
|
||||
moveSel(cm, -1)
|
||||
} else if (type == "addFour") {
|
||||
cm.replaceSelection(left + left + left + left, "before");
|
||||
moveSel(cm, 1)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function charsAround(cm, pos) {
|
||||
var str = cm.getRange(Pos(pos.line, pos.ch - 1),
|
||||
Pos(pos.line, pos.ch + 1));
|
||||
return str.length == 2 ? str : null;
|
||||
}
|
||||
|
||||
function stringStartsAfter(cm, pos) {
|
||||
var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1))
|
||||
return /\bstring/.test(token.type) && token.start == pos.ch &&
|
||||
(pos.ch == 0 || !/\bstring/.test(cm.getTokenTypeAt(pos)))
|
||||
}
|
||||
});
|
66
web/lib/codemirror5-loadMode.js
Normal file
66
web/lib/codemirror5-loadMode.js
Normal file
@ -0,0 +1,66 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"), "cjs");
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], function(CM) { mod(CM, "amd"); });
|
||||
else // Plain browser env
|
||||
mod(CodeMirror, "plain");
|
||||
})(function(CodeMirror, env) {
|
||||
if (!CodeMirror.modeURL) CodeMirror.modeURL = "../mode/%N/%N.js";
|
||||
|
||||
var loading = {};
|
||||
function splitCallback(cont, n) {
|
||||
var countDown = n;
|
||||
return function() { if (--countDown == 0) cont(); };
|
||||
}
|
||||
function ensureDeps(mode, cont, options) {
|
||||
var modeObj = CodeMirror.modes[mode], deps = modeObj && modeObj.dependencies;
|
||||
if (!deps) return cont();
|
||||
var missing = [];
|
||||
for (var i = 0; i < deps.length; ++i) {
|
||||
if (!CodeMirror.modes.hasOwnProperty(deps[i]))
|
||||
missing.push(deps[i]);
|
||||
}
|
||||
if (!missing.length) return cont();
|
||||
var split = splitCallback(cont, missing.length);
|
||||
for (var i = 0; i < missing.length; ++i)
|
||||
CodeMirror.requireMode(missing[i], split, options);
|
||||
}
|
||||
|
||||
CodeMirror.requireMode = function(mode, cont, options) {
|
||||
if (typeof mode != "string") mode = mode.name;
|
||||
if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont, options);
|
||||
if (loading.hasOwnProperty(mode)) return loading[mode].push(cont);
|
||||
|
||||
var file = options && options.path ? options.path(mode) : CodeMirror.modeURL.replace(/%N/g, mode);
|
||||
if (options && options.loadMode) {
|
||||
options.loadMode(file, function() { ensureDeps(mode, cont, options) })
|
||||
} else if (env == "plain") {
|
||||
var script = document.createElement("script");
|
||||
script.src = file;
|
||||
var others = document.getElementsByTagName("script")[0];
|
||||
var list = loading[mode] = [cont];
|
||||
CodeMirror.on(script, "load", function() {
|
||||
ensureDeps(mode, function() {
|
||||
for (var i = 0; i < list.length; ++i) list[i]();
|
||||
}, options);
|
||||
});
|
||||
others.parentNode.insertBefore(script, others);
|
||||
} else if (env == "cjs") {
|
||||
require(file);
|
||||
cont();
|
||||
} else if (env == "amd") {
|
||||
requirejs([file], cont);
|
||||
}
|
||||
};
|
||||
|
||||
CodeMirror.autoLoadMode = function(instance, mode, options) {
|
||||
if (!CodeMirror.modes.hasOwnProperty(mode))
|
||||
CodeMirror.requireMode(mode, function() {
|
||||
instance.setOption("mode", instance.getOption("mode"));
|
||||
}, options);
|
||||
};
|
||||
});
|
90
web/lib/codemirror5-overlay.js
Normal file
90
web/lib/codemirror5-overlay.js
Normal file
@ -0,0 +1,90 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||
|
||||
// Utility function that allows modes to be combined. The mode given
|
||||
// as the base argument takes care of most of the normal mode
|
||||
// functionality, but a second (typically simple) mode is used, which
|
||||
// can override the style of text. Both modes get to parse all of the
|
||||
// text, but when both assign a non-null style to a piece of code, the
|
||||
// overlay wins, unless the combine argument was true and not overridden,
|
||||
// or state.overlay.combineTokens was true, in which case the styles are
|
||||
// combined.
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.overlayMode = function(base, overlay, combine) {
|
||||
return {
|
||||
startState: function() {
|
||||
return {
|
||||
base: CodeMirror.startState(base),
|
||||
overlay: CodeMirror.startState(overlay),
|
||||
basePos: 0, baseCur: null,
|
||||
overlayPos: 0, overlayCur: null,
|
||||
streamSeen: null
|
||||
};
|
||||
},
|
||||
copyState: function(state) {
|
||||
return {
|
||||
base: CodeMirror.copyState(base, state.base),
|
||||
overlay: CodeMirror.copyState(overlay, state.overlay),
|
||||
basePos: state.basePos, baseCur: null,
|
||||
overlayPos: state.overlayPos, overlayCur: null
|
||||
};
|
||||
},
|
||||
|
||||
token: function(stream, state) {
|
||||
if (stream != state.streamSeen ||
|
||||
Math.min(state.basePos, state.overlayPos) < stream.start) {
|
||||
state.streamSeen = stream;
|
||||
state.basePos = state.overlayPos = stream.start;
|
||||
}
|
||||
|
||||
if (stream.start == state.basePos) {
|
||||
state.baseCur = base.token(stream, state.base);
|
||||
state.basePos = stream.pos;
|
||||
}
|
||||
if (stream.start == state.overlayPos) {
|
||||
stream.pos = stream.start;
|
||||
state.overlayCur = overlay.token(stream, state.overlay);
|
||||
state.overlayPos = stream.pos;
|
||||
}
|
||||
stream.pos = Math.min(state.basePos, state.overlayPos);
|
||||
|
||||
// state.overlay.combineTokens always takes precedence over combine,
|
||||
// unless set to null
|
||||
if (state.overlayCur == null) return state.baseCur;
|
||||
else if (state.baseCur != null &&
|
||||
state.overlay.combineTokens ||
|
||||
combine && state.overlay.combineTokens == null)
|
||||
return state.baseCur + " " + state.overlayCur;
|
||||
else return state.overlayCur;
|
||||
},
|
||||
|
||||
indent: base.indent && function(state, textAfter, line) {
|
||||
return base.indent(state.base, textAfter, line);
|
||||
},
|
||||
electricChars: base.electricChars,
|
||||
|
||||
innerMode: function(state) { return {state: state.base, mode: base}; },
|
||||
|
||||
blankLine: function(state) {
|
||||
var baseToken, overlayToken;
|
||||
if (base.blankLine) baseToken = base.blankLine(state.base);
|
||||
if (overlay.blankLine) overlayToken = overlay.blankLine(state.overlay);
|
||||
|
||||
return overlayToken == null ?
|
||||
baseToken :
|
||||
(combine && baseToken != null ? baseToken + " " + overlayToken : overlayToken);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
});
|
48
web/lib/codemirror5-scrollPastEnd.js
Normal file
48
web/lib/codemirror5-scrollPastEnd.js
Normal file
@ -0,0 +1,48 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.defineOption("scrollPastEnd", false, function(cm, val, old) {
|
||||
if (old && old != CodeMirror.Init) {
|
||||
cm.off("change", onChange);
|
||||
cm.off("refresh", updateBottomMargin);
|
||||
cm.display.lineSpace.parentNode.style.paddingBottom = "";
|
||||
cm.state.scrollPastEndPadding = null;
|
||||
}
|
||||
if (val) {
|
||||
cm.on("change", onChange);
|
||||
cm.on("refresh", updateBottomMargin);
|
||||
updateBottomMargin(cm);
|
||||
}
|
||||
});
|
||||
|
||||
function onChange(cm, change) {
|
||||
if (CodeMirror.changeEnd(change).line == cm.lastLine())
|
||||
updateBottomMargin(cm);
|
||||
}
|
||||
|
||||
function updateBottomMargin(cm) {
|
||||
var padding = "";
|
||||
if (cm.lineCount() > 1) {
|
||||
var totalH = cm.display.scroller.clientHeight - 30,
|
||||
lastLineH = cm.getLineHandle(cm.lastLine()).height;
|
||||
padding = (totalH - lastLineH) + "px";
|
||||
}
|
||||
if (cm.state.scrollPastEndPadding != padding) {
|
||||
cm.state.scrollPastEndPadding = padding;
|
||||
cm.display.lineSpace.parentNode.style.paddingBottom = padding;
|
||||
cm.off("refresh", updateBottomMargin);
|
||||
cm.setSize();
|
||||
cm.on("refresh", updateBottomMargin);
|
||||
}
|
||||
}
|
||||
});
|
344
web/lib/codemirror5.css
Normal file
344
web/lib/codemirror5.css
Normal file
@ -0,0 +1,344 @@
|
||||
/* BASICS */
|
||||
|
||||
.CodeMirror {
|
||||
/* Set height, width, borders, and global font properties here */
|
||||
font-family: monospace;
|
||||
height: 300px;
|
||||
color: black;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
/* PADDING */
|
||||
|
||||
.CodeMirror-lines {
|
||||
padding: 4px 0; /* Vertical padding around content */
|
||||
}
|
||||
.CodeMirror pre.CodeMirror-line,
|
||||
.CodeMirror pre.CodeMirror-line-like {
|
||||
padding: 0 4px; /* Horizontal padding of content */
|
||||
}
|
||||
|
||||
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
background-color: white; /* The little square between H and V scrollbars */
|
||||
}
|
||||
|
||||
/* GUTTER */
|
||||
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid #ddd;
|
||||
background-color: #f7f7f7;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.CodeMirror-linenumbers {}
|
||||
.CodeMirror-linenumber {
|
||||
padding: 0 3px 0 5px;
|
||||
min-width: 20px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.CodeMirror-guttermarker { color: black; }
|
||||
.CodeMirror-guttermarker-subtle { color: #999; }
|
||||
|
||||
/* CURSOR */
|
||||
|
||||
.CodeMirror-cursor {
|
||||
border-left: 1px solid black;
|
||||
border-right: none;
|
||||
width: 0;
|
||||
}
|
||||
/* Shown when moving in bi-directional text */
|
||||
.CodeMirror div.CodeMirror-secondarycursor {
|
||||
border-left: 1px solid silver;
|
||||
}
|
||||
.cm-fat-cursor .CodeMirror-cursor {
|
||||
width: auto;
|
||||
border: 0 !important;
|
||||
background: #7e7;
|
||||
}
|
||||
.cm-fat-cursor div.CodeMirror-cursors {
|
||||
z-index: 1;
|
||||
}
|
||||
.cm-fat-cursor .CodeMirror-line::selection,
|
||||
.cm-fat-cursor .CodeMirror-line > span::selection,
|
||||
.cm-fat-cursor .CodeMirror-line > span > span::selection { background: transparent; }
|
||||
.cm-fat-cursor .CodeMirror-line::-moz-selection,
|
||||
.cm-fat-cursor .CodeMirror-line > span::-moz-selection,
|
||||
.cm-fat-cursor .CodeMirror-line > span > span::-moz-selection { background: transparent; }
|
||||
.cm-fat-cursor { caret-color: transparent; }
|
||||
@-moz-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@-webkit-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
|
||||
/* Can style cursor different in overwrite (non-insert) mode */
|
||||
.CodeMirror-overwrite .CodeMirror-cursor {}
|
||||
|
||||
.cm-tab { display: inline-block; text-decoration: inherit; }
|
||||
|
||||
.CodeMirror-rulers {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: -50px; bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.CodeMirror-ruler {
|
||||
border-left: 1px solid #ccc;
|
||||
top: 0; bottom: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* DEFAULT THEME */
|
||||
|
||||
.cm-s-default .cm-header {color: blue;}
|
||||
.cm-s-default .cm-quote {color: #090;}
|
||||
.cm-negative {color: #d44;}
|
||||
.cm-positive {color: #292;}
|
||||
.cm-header, .cm-strong {font-weight: bold;}
|
||||
.cm-em {font-style: italic;}
|
||||
.cm-link {text-decoration: underline;}
|
||||
.cm-strikethrough {text-decoration: line-through;}
|
||||
|
||||
.cm-s-default .cm-keyword {color: #708;}
|
||||
.cm-s-default .cm-atom {color: #219;}
|
||||
.cm-s-default .cm-number {color: #164;}
|
||||
.cm-s-default .cm-def {color: #00f;}
|
||||
.cm-s-default .cm-variable,
|
||||
.cm-s-default .cm-punctuation,
|
||||
.cm-s-default .cm-property,
|
||||
.cm-s-default .cm-operator {}
|
||||
.cm-s-default .cm-variable-2 {color: #05a;}
|
||||
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
|
||||
.cm-s-default .cm-comment {color: #a50;}
|
||||
.cm-s-default .cm-string {color: #a11;}
|
||||
.cm-s-default .cm-string-2 {color: #f50;}
|
||||
.cm-s-default .cm-meta {color: #555;}
|
||||
.cm-s-default .cm-qualifier {color: #555;}
|
||||
.cm-s-default .cm-builtin {color: #30a;}
|
||||
.cm-s-default .cm-bracket {color: #997;}
|
||||
.cm-s-default .cm-tag {color: #170;}
|
||||
.cm-s-default .cm-attribute {color: #00c;}
|
||||
.cm-s-default .cm-hr {color: #999;}
|
||||
.cm-s-default .cm-link {color: #00c;}
|
||||
|
||||
.cm-s-default .cm-error {color: #f00;}
|
||||
.cm-invalidchar {color: #f00;}
|
||||
|
||||
.CodeMirror-composing { border-bottom: 2px solid; }
|
||||
|
||||
/* Default styles for common addons */
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
|
||||
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
|
||||
.CodeMirror-activeline-background {background: #e8f2ff;}
|
||||
|
||||
/* STOP */
|
||||
|
||||
/* The rest of this file contains styles related to the mechanics of
|
||||
the editor. You probably shouldn't touch them. */
|
||||
|
||||
.CodeMirror {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
overflow: scroll !important; /* Things will break if this is overridden */
|
||||
/* 50px is the magic margin used to hide the element's real scrollbars */
|
||||
/* See overflow: hidden in .CodeMirror */
|
||||
margin-bottom: -50px; margin-right: -50px;
|
||||
padding-bottom: 50px;
|
||||
height: 100%;
|
||||
outline: none; /* Prevent dragging from highlighting the element */
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
.CodeMirror-sizer {
|
||||
position: relative;
|
||||
border-right: 50px solid transparent;
|
||||
}
|
||||
|
||||
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||
before actual scrolling happens, thus preventing shaking and
|
||||
flickering artifacts. */
|
||||
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
display: none;
|
||||
outline: none;
|
||||
}
|
||||
.CodeMirror-vscrollbar {
|
||||
right: 0; top: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.CodeMirror-hscrollbar {
|
||||
bottom: 0; left: 0;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
.CodeMirror-scrollbar-filler {
|
||||
right: 0; bottom: 0;
|
||||
}
|
||||
.CodeMirror-gutter-filler {
|
||||
left: 0; bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
position: absolute; left: 0; top: 0;
|
||||
min-height: 100%;
|
||||
z-index: 3;
|
||||
}
|
||||
.CodeMirror-gutter {
|
||||
white-space: normal;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-bottom: -50px;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
.CodeMirror-gutter-background {
|
||||
position: absolute;
|
||||
top: 0; bottom: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-elt {
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
|
||||
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
|
||||
|
||||
.CodeMirror-lines {
|
||||
cursor: text;
|
||||
min-height: 1px; /* prevents collapsing before first draw */
|
||||
}
|
||||
.CodeMirror pre.CodeMirror-line,
|
||||
.CodeMirror pre.CodeMirror-line-like {
|
||||
/* Reset some styles that the rest of the page might have set */
|
||||
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||
border-width: 0;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-font-variant-ligatures: contextual;
|
||||
font-variant-ligatures: contextual;
|
||||
}
|
||||
.CodeMirror-wrap pre.CodeMirror-line,
|
||||
.CodeMirror-wrap pre.CodeMirror-line-like {
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.CodeMirror-linebackground {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-linewidget {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding: 0.1px; /* Force widget margins to stay inside of the container */
|
||||
}
|
||||
|
||||
.CodeMirror-widget {}
|
||||
|
||||
.CodeMirror-rtl pre { direction: rtl; }
|
||||
|
||||
.CodeMirror-code {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Force content-box sizing for the elements where we expect it */
|
||||
.CodeMirror-scroll,
|
||||
.CodeMirror-sizer,
|
||||
.CodeMirror-gutter,
|
||||
.CodeMirror-gutters,
|
||||
.CodeMirror-linenumber {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.CodeMirror-measure {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.CodeMirror-cursor {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.CodeMirror-measure pre { position: static; }
|
||||
|
||||
div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
div.CodeMirror-dragcursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-focused div.CodeMirror-cursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-selected { background: #d9d9d9; }
|
||||
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||
.CodeMirror-crosshair { cursor: crosshair; }
|
||||
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
||||
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
||||
|
||||
.cm-searching {
|
||||
background-color: #ffa;
|
||||
background-color: rgba(255, 255, 0, .4);
|
||||
}
|
||||
|
||||
/* Used to force a border model for a node */
|
||||
.cm-force-border { padding-right: .1px; }
|
||||
|
||||
@media print {
|
||||
/* Hide the cursor when printing */
|
||||
.CodeMirror div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* See issue #2901 */
|
||||
.cm-tab-wrap-hack:after { content: ''; }
|
||||
|
||||
/* Help users use markselection to safely style text background */
|
||||
span.CodeMirror-selectedtext { background: none; }
|
9874
web/lib/codemirror5.js
Normal file
9874
web/lib/codemirror5.js
Normal file
File diff suppressed because it is too large
Load Diff
1
web/lib/dayjs.min.js
vendored
Normal file
1
web/lib/dayjs.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
web/lib/focus-trap.min.js
vendored
Normal file
4
web/lib/focus-trap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
web/lib/marked.min.js
vendored
Normal file
6
web/lib/marked.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
web/lib/tabbable.min.js
vendored
Normal file
4
web/lib/tabbable.min.js
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/*!
|
||||
* tabbable 5.3.2
|
||||
* @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE
|
||||
*/ !function(e,t){var n,o;"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):(n=(e="undefined"!=typeof globalThis?globalThis:e||self).tabbable,t(o=e.tabbable={}),o.noConflict=function(){return e.tabbable=n,o})}(this,function(e){"use strict";var t=["input","select","textarea","a[href]","button","[tabindex]:not(slot)","audio[controls]","video[controls]",'[contenteditable]:not([contenteditable="false"])',"details>summary:first-of-type","details"],n=t.join(","),o="undefined"==typeof Element,r=o?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector,a=!o&&Element.prototype.getRootNode?function(e){return e.getRootNode()}:function(e){return e.ownerDocument},i=function e(t,o,a){var i=Array.prototype.slice.apply(t.querySelectorAll(n));return o&&r.call(t,n)&&i.unshift(t),i=i.filter(a)},u=function e(t,o,a){for(var i=[],u=Array.from(t);u.length;){var l=u.shift();if("SLOT"===l.tagName){var c=l.assignedElements(),d=c.length?c:l.children,f=e(d,!0,a);a.flatten?i.push.apply(i,f):i.push({scope:l,candidates:f})}else{r.call(l,n)&&a.filter(l)&&(o||!t.includes(l))&&i.push(l);var s=l.shadowRoot||"function"==typeof a.getShadowRoot&&a.getShadowRoot(l);if(s){var p=e(!0===s?l.children:s.children,!0,a);a.flatten?i.push.apply(i,p):i.push({scope:l,candidates:p})}else u.unshift.apply(u,l.children)}}return i},l=function e(t,n){return t.tabIndex<0&&(n||/^(AUDIO|VIDEO|DETAILS)$/.test(t.tagName)||t.isContentEditable)&&isNaN(parseInt(t.getAttribute("tabindex"),10))?0:t.tabIndex},c=function e(t,n){return t.tabIndex===n.tabIndex?t.documentOrder-n.documentOrder:t.tabIndex-n.tabIndex},d=function e(t){return"INPUT"===t.tagName},f=function e(t,n){for(var o=0;o<t.length;o++)if(t[o].checked&&t[o].form===n)return t[o]},s=function e(t){if(!t.name)return!0;var n,o=t.form||a(t),r=function e(t){return o.querySelectorAll('input[type="radio"][name="'+t+'"]')};if("undefined"!=typeof window&&void 0!==window.CSS&&"function"==typeof window.CSS.escape)n=r(window.CSS.escape(t.name));else try{n=r(t.name)}catch(i){return console.error("Looks like you have a radio button with a name attribute containing invalid CSS selector characters and need the CSS.escape polyfill: %s",i.message),!1}var u=f(n,t.form);return!u||u===t},p=function e(t){var n;return d(n=t)&&"radio"===n.type&&!s(t)},h=function e(t){var n=t.getBoundingClientRect(),o=n.width,r=n.height;return 0===o&&0===r},b=function e(t,n){var o=n.displayCheck,i=n.getShadowRoot;if("hidden"===getComputedStyle(t).visibility)return!0;var u=r.call(t,"details>summary:first-of-type")?t.parentElement:t;if(r.call(u,"details:not([open]) *"))return!0;var l=a(t).host,c=(null==l?void 0:l.ownerDocument.contains(l))||t.ownerDocument.contains(t);if(o&&"full"!==o){if("non-zero-area"===o)return h(t)}else{if("function"==typeof i){for(var d=t;t;){var f=t.parentElement,s=a(t);if(f&&!f.shadowRoot&&!0===i(f))return h(t);t=t.assignedSlot?t.assignedSlot:f||s===t.ownerDocument?f:s.host}t=d}if(c)return!t.getClientRects().length}return!1},m=function e(t){if(/^(INPUT|BUTTON|SELECT|TEXTAREA)$/.test(t.tagName))for(var n=t.parentElement;n;){if("FIELDSET"===n.tagName&&n.disabled){for(var o=0;o<n.children.length;o++){var a=n.children.item(o);if("LEGEND"===a.tagName)return!!r.call(n,"fieldset[disabled] *")||!a.contains(t)}return!0}n=n.parentElement}return!1},y=function e(t,n){var o,r;return!(n.disabled||d(o=n)&&"hidden"===o.type||b(n,t)||(r=n,"DETAILS"===r.tagName&&Array.prototype.slice.apply(r.children).some(function(e){return"SUMMARY"===e.tagName}))||m(n))},v=function e(t,n){return!(p(n)||0>l(n))&&!!y(t,n)},g=function e(t){var n=[],o=[];return t.forEach(function(t,r){var a=!!t.scope,i=a?t.scope:t,u=l(i,a),c=a?e(t.candidates):i;0===u?a?n.push.apply(n,c):n.push(i):o.push({documentOrder:r,tabIndex:u,item:t,isScope:a,content:c})}),o.sort(c).reduce(function(e,t){return t.isScope?e.push.apply(e,t.content):e.push(t.content),e},[]).concat(n)},S=function e(t,n){var o;return o=(n=n||{}).getShadowRoot?u([t],n.includeContainer,{filter:v.bind(null,n),flatten:!1,getShadowRoot:n.getShadowRoot}):i(t,n.includeContainer,v.bind(null,n)),g(o)},$=function e(t,n){var o;return(n=n||{}).getShadowRoot?u([t],n.includeContainer,{filter:y.bind(null,n),flatten:!0,getShadowRoot:n.getShadowRoot}):i(t,n.includeContainer,y.bind(null,n))},w=function e(t,o){if(o=o||{},!t)throw Error("No node provided");return!1!==r.call(t,n)&&v(o,t)},E=t.concat("iframe").join(","),R=function e(t,n){if(n=n||{},!t)throw Error("No node provided");return!1!==r.call(t,E)&&y(n,t)};e.focusable=$,e.isFocusable=R,e.isTabbable=w,e.tabbable=S,Object.defineProperty(e,"__esModule",{value:!0})});
|
323
web/utils.js
Normal file
323
web/utils.js
Normal file
@ -0,0 +1,323 @@
|
||||
|
||||
/**
|
||||
* Generates a random string of a desired length, containing any of the desired characters.
|
||||
* @param {number} length The resulting string's length
|
||||
* @param {string|string[]} chars A list of possible characters to use
|
||||
* @returns {string} The resulting random string
|
||||
*/
|
||||
function randomString(length, chars = '0123456789abcdefghijklmnopqrstuvwxyz') {
|
||||
let str = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
str += chars[randomInt(0, chars.length-1)];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a lowercase hexadecimal string of a desired length.
|
||||
* @param {number} length The length of the random
|
||||
* @returns {String} The resulting string
|
||||
*/
|
||||
function randomHex(length = 8) {
|
||||
return randomString(length, '0123456789abcdef');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a pseudorandom integer between a minimum and maximum.
|
||||
* @param {number} min The minimum
|
||||
* @param {number} max The maximum
|
||||
* @returns {number} The resulting integer
|
||||
*/
|
||||
function randomInt(min, max) {
|
||||
return Math.round(min+(Math.random()*(max-min)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a pseudorandom float between a minimum and maximum.
|
||||
* @param {number} min The minimum
|
||||
* @param {number} max The maximum
|
||||
* @returns {number} The resulting float
|
||||
*/
|
||||
function randomFloat(min, max) {
|
||||
return min+(Math.random()*(max-min));
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a random element from an array.
|
||||
* @param {array} arr The input array
|
||||
* @returns {*} A randomly selected array element
|
||||
*/
|
||||
const getRandomElement = (arr) => arr[(Math.ceil(Math.random()*arr.length)-1)];
|
||||
|
||||
/**
|
||||
* @typedef itemWithWeight
|
||||
* @property {*} value The return value for this item
|
||||
* @property {number} weight The weight of this item
|
||||
*/
|
||||
/**
|
||||
* Selects a random item from a provided list, where each item has a specific weight.
|
||||
* @param {itemWithWeight[]} args An array of items and their weights
|
||||
* @returns A randomly selected value
|
||||
*/
|
||||
const getRandomWeighted = (args) => {
|
||||
// Get the total weight
|
||||
let total = 0;
|
||||
args.forEach((arg) => {
|
||||
total += arg.weight;
|
||||
});
|
||||
// Sort options from lightest to heaviest
|
||||
args.sort((a, b) => {
|
||||
return a.weight-b.weight;
|
||||
});
|
||||
// Get our random number
|
||||
const rand = randomFloat(0, total);
|
||||
// Iterate through options until the current weight (num)
|
||||
// is greater than our number (rand)
|
||||
let num = 0;
|
||||
for (const arg of args) {
|
||||
num += arg.weight;
|
||||
if (rand < num) return arg.value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps a number within a range by preventing it from going above/below its maximum/minimum.
|
||||
* @param {number} num The input number
|
||||
* @param {number} min The minimum number
|
||||
* @param {number} max The maximum number
|
||||
* @returns {number} The resulting number
|
||||
*/
|
||||
function clamp(num, min, max) {
|
||||
if (num < min) return min;
|
||||
if (num > max) return max;
|
||||
return num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps a number within a range by underflowing/overflowing.
|
||||
* @param {number} num The input number
|
||||
* @param {number} min The minimum number
|
||||
* @param {number} max The maximum number
|
||||
* @returns {number} The resulting number
|
||||
*/
|
||||
function overflow(num, min, max) {
|
||||
if (num < min) return max;
|
||||
if (num > max) return min;
|
||||
return num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounds a float to the desired amount of decimal places, clipping any trailing zeros.
|
||||
* @param {number} number The input number
|
||||
* @param {number} decimalPlaces The maximum number of decimal places
|
||||
* @returns {number} The resulting number
|
||||
*/
|
||||
function roundSmart(number, decimalPlaces = 0) {
|
||||
const factorOfTen = Math.pow(10, decimalPlaces);
|
||||
return Math.round(number * factorOfTen) / factorOfTen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Separates a string into its words and returns an array of those words.
|
||||
* @param {string} s The input string
|
||||
* @returns {String[]} An array of words
|
||||
*/
|
||||
function getWords(s){
|
||||
s = s.replace(/(^\s*)|(\s*$)/gi, '');
|
||||
s = s.replace(/[ ]{2,}/gi, ' ');
|
||||
s = s.replace(/\n/g, ' ');
|
||||
return s.split(' ').filter(String);
|
||||
}
|
||||
/**
|
||||
* Returns the number of words in a string.
|
||||
* @param {string} s The input string
|
||||
* @returns {number} The number of words
|
||||
*/
|
||||
function countWords(s){
|
||||
return getWords(s).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause execution for a desired amount of time. Use this in `async` functions with `await sleep(ms)`.
|
||||
* @param {Number} ms The number of milliseconds to sleep for
|
||||
* @returns {Promise<undefined>}
|
||||
*/
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuffles an array using `Math.random()` to swap elements and returns the result.
|
||||
* @param {array} arr The input array
|
||||
* @returns {array} The shuffled array
|
||||
*/
|
||||
function shuffle(arr) {
|
||||
let i = 0;
|
||||
while (i < arr.length) {
|
||||
let tmp = arr[i];
|
||||
let num = Math.round(Math.random()*(arr.length-1));
|
||||
arr[i] = arr[num];
|
||||
arr[num] = tmp;
|
||||
i++;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a number of seconds into `[hh]:[m]m:ss` format.
|
||||
* @param {number} s The number of seconds to format
|
||||
* @returns {String}
|
||||
*/
|
||||
function formatSeconds(s) {
|
||||
s = Math.floor(s || 0);
|
||||
let hours = 0;
|
||||
let minutes = 0;
|
||||
let seconds = s;
|
||||
if (s >= 3600) {
|
||||
hours = Math.floor(s / 3600);
|
||||
s = s % 3600;
|
||||
}
|
||||
if (s >= 60) {
|
||||
minutes = Math.floor(s / 60);
|
||||
s = s % 60;
|
||||
}
|
||||
seconds = s;
|
||||
let timeString = '';
|
||||
timeString += (hours > 0) ? hours.toString() + ':' : '';
|
||||
timeString += minutes.toString().padStart((hours > 0) ? 2 : 1, '0') + ':';
|
||||
timeString += seconds.toString().padStart(2, '0');
|
||||
return timeString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get age as an integer from a Date object.
|
||||
* @param {Date} date The target date
|
||||
* @returns {number}
|
||||
*/
|
||||
function getAgeFromDate(date) {
|
||||
const now = new Date();
|
||||
let age = now.getFullYear()-date.getFullYear();
|
||||
if (now.getMonth() < date.getMonth() || (now.getMonth() == date.getMonth() && now.getDate() < date.getDate())) {
|
||||
age--;
|
||||
}
|
||||
return age;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string describing the relative time between two dates, like "3 days ago" or "2 months from now".
|
||||
* @param {number} target A millisecond-timestamp of the target date.
|
||||
* @param {number} [anchor] A millisecond-timestamp of the date to look from. Defaults to `Date.now()`.
|
||||
* @returns {string} The resulting relative description.
|
||||
*/
|
||||
function getRelativeDate(target, anchor = Date.now()) {
|
||||
const isFuture = (anchor-target < 0) ? true : false;
|
||||
let diff = Math.abs(anchor-target);
|
||||
diff = Math.round(diff/1000);
|
||||
if (diff < 120) // Less than 120 seconds
|
||||
return (isFuture) ? `In a moment` : `Moments ago`;
|
||||
diff = Math.round(diff/60);
|
||||
if (diff < 120) // Less than 120 minutes
|
||||
return (isFuture) ? `${diff} mins from now` : `${diff} mins ago`;
|
||||
diff = Math.round(diff/60);
|
||||
if (diff < 72) // Less than 72 hours
|
||||
return (isFuture) ? `${diff} hours from now` : `${diff} hours ago`;
|
||||
diff = Math.round(diff/24);
|
||||
const days = diff;
|
||||
if (diff < 90) // Less than 90 days
|
||||
return (isFuture) ? `${diff} days from now` : `${diff} days ago`;
|
||||
diff = Math.round(diff/30.43685);
|
||||
if (diff < 36) // Less than 36 months
|
||||
return (isFuture) ? `${diff} months from now` : `${diff} months ago`;
|
||||
diff = Math.round(days/365.2422);
|
||||
return (isFuture) ? `${diff} years from now` : `${diff} years ago`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a number of bytes to a human-readable size, like "230.2 MB" or "7.27 GB".
|
||||
* @param {number} bytes A number of bytes to convert
|
||||
* @returns {string} The human-readable size string
|
||||
*/
|
||||
function formatSize(bytes) {
|
||||
if (bytes < 1000) return `${bytes} B`;
|
||||
bytes /= 1024;
|
||||
if (bytes < (1000)) return `${roundSmart(bytes, 0)} KB`;
|
||||
bytes /= 1024;
|
||||
if (bytes < (1000)) return `${roundSmart(bytes, 1)} MB`;
|
||||
bytes /= 1024;
|
||||
if (bytes < (1000)) return `${roundSmart(bytes, 2)} GB`;
|
||||
bytes /= 1024;
|
||||
if (bytes < (1000)) return `${roundSmart(bytes, 2)} TB`;
|
||||
return "-";
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines of an input string is a valid URL.
|
||||
* @param {string} string The input string
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
function isValidUrl(string) {
|
||||
let url;
|
||||
try {
|
||||
url = new URL(string);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return url.protocol === "http:" || url.protocol === "https:";
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a string is a valid hostname.
|
||||
* @param {string} string The input string
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
function isValidHostname(string) {
|
||||
return string.match(/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/) && !string.match(/^localhost$/);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a string is a valid IPv4 or IPv6 address.
|
||||
* @param {string} string The input string
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
function isValidIp(string) {
|
||||
return string.match(/((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))/) && !string.match(/(^127\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)/) && !string.match(/^::1$/);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes HTML entities present in the input string and returns the result.
|
||||
* @param {string} text The input string
|
||||
* @returns {string} The escaped string
|
||||
*/
|
||||
function escapeHTML(text) {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
try {
|
||||
module.exports = {
|
||||
randomHex: randomHex,
|
||||
randomInt: randomInt,
|
||||
randomFloat: randomFloat,
|
||||
getRandomElement: getRandomElement,
|
||||
getRandomWeighted: getRandomWeighted,
|
||||
clamp: clamp,
|
||||
overflow: overflow,
|
||||
roundSmart: roundSmart,
|
||||
getWords: getWords,
|
||||
countWords: countWords,
|
||||
sleep: sleep,
|
||||
shuffle: shuffle,
|
||||
formatSeconds: formatSeconds,
|
||||
getAgeFromDate: getAgeFromDate,
|
||||
getRelativeDate: getRelativeDate,
|
||||
formatSize: formatSize,
|
||||
isValidUrl: isValidUrl,
|
||||
isValidHostname: isValidHostname,
|
||||
isValidIp: isValidIp,
|
||||
escapeHTML: escapeHTML
|
||||
};
|
||||
} catch (error) {}
|
1178
web/v2/base.css
Normal file
1178
web/v2/base.css
Normal file
File diff suppressed because it is too large
Load Diff
810
web/v2/base.js
Normal file
810
web/v2/base.js
Normal file
@ -0,0 +1,810 @@
|
||||
|
||||
/**
|
||||
* A shortcut for `*.querySelector()`.
|
||||
* @param {string} id The target query selector
|
||||
* @param {HTMLElement} ancestor The ancestor element to start from
|
||||
* @returns {HTMLElement|undefined} The selected element
|
||||
*/
|
||||
function $(selector, ancestor = document) {
|
||||
return ancestor.querySelector(selector);
|
||||
}
|
||||
/**
|
||||
* A shortcut for `*.querySelectorAll()`.
|
||||
* @param {string} id The target query selector
|
||||
* @param {HTMLElement} ancestor The ancestor element to start from
|
||||
* @returns {NodeListOf<any>} The selected elements
|
||||
*/
|
||||
function $$(selector, ancestor = document) {
|
||||
return ancestor.querySelectorAll(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Positions an element at the given coordinates, ensuring it stays within the window.
|
||||
* @param {HTMLElement} element The element
|
||||
* @param {number} x Left
|
||||
* @param {number} y Top
|
||||
*/
|
||||
function positionElement(element, x, y) {
|
||||
const elementWidth = element.offsetWidth;
|
||||
const elementHeight = element.offsetHeight;
|
||||
const windowWidth = window.innerWidth;
|
||||
const windowHeight = window.innerHeight;
|
||||
if (x + elementWidth > windowWidth) {
|
||||
x = x - elementWidth;
|
||||
if (x < 0) x = 0;
|
||||
}
|
||||
if (y + elementHeight > windowHeight) {
|
||||
y = y - elementHeight;
|
||||
if (y < 0) y = 0;
|
||||
}
|
||||
element.style.left = x + 'px';
|
||||
element.style.top = y + 'px';
|
||||
}
|
||||
|
||||
function isElementVisible(el) {
|
||||
if (!el || typeof el.getBoundingClientRect !== 'function') {
|
||||
return false;
|
||||
}
|
||||
const rect = el.getBoundingClientRect();
|
||||
return !!(rect.width || rect.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a popup
|
||||
*/
|
||||
class PopupBuilder {
|
||||
#shouldEscapeClose = true;
|
||||
#elCont; #elTitle; #elBody; #elActions;
|
||||
#onShow = () => {};
|
||||
#onHide = () => {};
|
||||
constructor() {
|
||||
// Create container
|
||||
this.#elCont = document.createElement('div');
|
||||
this.#elCont.classList.add('popupCont');
|
||||
// Create popup element
|
||||
this.el = document.createElement('div');
|
||||
this.el.classList.add('popup');
|
||||
this.#elCont.appendChild(this.el);
|
||||
// Add title
|
||||
this.#elTitle = document.createElement('div');
|
||||
this.#elTitle.classList.add('title', 'flex-no-shrink');
|
||||
this.#elTitle.innerText = 'Popup';
|
||||
this.el.appendChild(this.#elTitle);
|
||||
// Add body
|
||||
this.#elBody = document.createElement('div');
|
||||
this.#elBody.classList.add('body');
|
||||
this.el.appendChild(this.#elBody);
|
||||
// Add actions container
|
||||
this.#elActions = document.createElement('div');
|
||||
this.#elActions.classList.add('actions', 'flex-no-shrink');
|
||||
this.el.appendChild(this.#elActions);
|
||||
// Set up listeners
|
||||
this.#elCont.addEventListener('click', () => {
|
||||
if (this.#shouldEscapeClose) this.hide();
|
||||
});
|
||||
this.el.addEventListener('click', e => e.stopPropagation());
|
||||
// Set up focus trap
|
||||
this.trap = null;
|
||||
try {
|
||||
this.trap = focusTrap.createFocusTrap(this.#elCont, {
|
||||
initialFocus: false,
|
||||
escapeDeactivates: false
|
||||
});
|
||||
} catch(error) {}
|
||||
}
|
||||
/**
|
||||
* Sets the title of the popup.
|
||||
* @param {string} title The title of the popup
|
||||
* @returns {PopupBuilder}
|
||||
*/
|
||||
setTitle(title) {
|
||||
this.#elTitle.innerText = title;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Adds an HTML element to the body of the popup.
|
||||
* @param {HTMLElement} element The element
|
||||
* @returns {PopupBuilder}
|
||||
*/
|
||||
addBody(element) {
|
||||
this.#elBody.appendChild(element);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Appends raw HTML to the body of the popup.
|
||||
* @param {string} html An HTML string
|
||||
* @returns {PopupBuilder}
|
||||
*/
|
||||
addBodyHTML(html) {
|
||||
this.#elBody.insertAdjacentHTML('beforeend', html);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets if clicking outside the popup or pressing escape will close the popup. Defaults to `true`.
|
||||
* @param {boolean} shouldClose
|
||||
* @returns {PopupBuilder}
|
||||
*/
|
||||
setClickOutside(shouldClose) {
|
||||
this.#shouldEscapeClose = shouldClose;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets a callback function to run when the popup is shown.
|
||||
* @param {callback} onShow The callback to run
|
||||
* @returns {PopupBuilder}
|
||||
*/
|
||||
setOnShow(onShow) {
|
||||
this.#onShow = onShow;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets a callback function to run when the popup is hidden.
|
||||
* @param {callback} onHide The callback to run
|
||||
* @returns {PopupBuilder}
|
||||
*/
|
||||
setOnHide(onHide) {
|
||||
this.#onHide = onHide;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Shows the popup.
|
||||
*/
|
||||
show() {
|
||||
document.body.appendChild(this.#elCont);
|
||||
setTimeout(() => {
|
||||
this.#elCont.classList.add('visible');
|
||||
try {
|
||||
this.trap.activate();
|
||||
} catch (error) {
|
||||
console.warn(`Unable to trap focus inside popup. Make sure focus-trap and tabbable are available.`);
|
||||
}
|
||||
this.#onShow();
|
||||
}, 1);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Hides the popup and removes it from the DOM.
|
||||
*/
|
||||
hide() {
|
||||
this.#elCont.classList.remove('visible');
|
||||
try {
|
||||
this.trap.deactivate();
|
||||
} catch (error) {}
|
||||
setTimeout(() => {
|
||||
this.#elCont.parentNode.removeChild(this.#elCont);
|
||||
}, 200);
|
||||
this.#onHide();
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* @callback PopupBuilderAddActionCallback
|
||||
* @param {PopupActionBuilder} actionBuilder An action builder to be modified and returned.
|
||||
*/
|
||||
/**
|
||||
* Adds an action button to the bottom of the popup. These buttons are displayed from right to left.
|
||||
* @param {PopupBuilderAddActionCallback} callback A callback function that returns a `PopupActionBuilder` object. This callback is passed a new `PopupActionBuilder` object that can be modified and returned.
|
||||
*/
|
||||
addAction(callback) {
|
||||
const builder = new PopupActionBuilder(this);
|
||||
const action = callback(builder);
|
||||
this.#elActions.appendChild(action.el);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Builds a popup action button
|
||||
*/
|
||||
class PopupActionBuilder {
|
||||
#shouldClose = true;
|
||||
#onClick = () => {};
|
||||
/**
|
||||
* @param {PopupBuilder} parent The parent popup builder.
|
||||
*/
|
||||
constructor(parent) {
|
||||
this.parent = parent;
|
||||
this.el = document.createElement('button');
|
||||
this.el.classList.add('btn', 'secondary');
|
||||
this.el.innerText = `Button`;
|
||||
this.el.addEventListener('click', () => {
|
||||
if (this.el.disabled) return;
|
||||
this.#onClick();
|
||||
if (this.#shouldClose) parent.hide();
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Sets whether this action button should use the primary button style. Defaults to `false`.
|
||||
* @param {boolean} isPrimary
|
||||
* @returns {PopupActionBuilder}
|
||||
*/
|
||||
setIsPrimary(isPrimary) {
|
||||
if (isPrimary) this.el.classList.remove('secondary');
|
||||
else this.el.classList.add('secondary');
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets whether this action button should use the danger button style. Defaults to `false`.
|
||||
* @param {boolean} isDanger
|
||||
* @returns {PopupActionBuilder}
|
||||
*/
|
||||
setIsDanger(isDanger) {
|
||||
if (isDanger) this.el.classList.add('danger');
|
||||
else this.el.classList.remove('danger');
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets whether clicking this action should close the popup. Defaults to `true`.
|
||||
* @param {boolean} shouldClose
|
||||
* @returns {PopupActionBuilder}
|
||||
*/
|
||||
setShouldClose(shouldClose) {
|
||||
this.#shouldClose = shouldClose;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets the label of this action button.
|
||||
* @param {string} label The button's label
|
||||
* @returns {PopupActionBuilder}
|
||||
*/
|
||||
setLabel(label) {
|
||||
this.el.innerText = label;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Disables the action button.
|
||||
* @returns {PopupActionBuilder}
|
||||
*/
|
||||
disable() {
|
||||
this.el.disabled = true;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Enables the action button.
|
||||
* @returns {PopupActionBuilder}
|
||||
*/
|
||||
enable() {
|
||||
this.el.disabled = false;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets a callback to be run when this action is clicked.
|
||||
* @param {callback} onClick The callback
|
||||
* @returns {PopupActionBuilder}
|
||||
*/
|
||||
setClickHandler(onClick) {
|
||||
this.#onClick = onClick;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a context menu
|
||||
*/
|
||||
class ContextMenuBuilder {
|
||||
#elCont;
|
||||
constructor() {
|
||||
// Create container
|
||||
this.#elCont = document.createElement('div');
|
||||
this.#elCont.classList.add('contextCont');
|
||||
// Create popup element
|
||||
this.el = document.createElement('div');
|
||||
this.el.classList.add('context');
|
||||
this.#elCont.appendChild(this.el);
|
||||
// Hide when clicking outside
|
||||
this.#elCont.addEventListener('click', () => {
|
||||
this.hide();
|
||||
});
|
||||
this.el.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
// Create focus trap
|
||||
try {
|
||||
this.trap = focusTrap.createFocusTrap(this.#elCont, {
|
||||
initialFocus: false,
|
||||
escapeDeactivates: false
|
||||
});
|
||||
} catch(error) {}
|
||||
}
|
||||
/**
|
||||
* @callback ContextMenuBuilderAddItemCallback
|
||||
* @param {ContextMenuItemBuilder} itemBuilder An item builder to be modified and returned.
|
||||
*/
|
||||
/**
|
||||
* Adds a clickable item to the context menu.
|
||||
* @param {ContextMenuBuilderAddItemCallback} callback A callback function that returns a `ContextMenuItemBuilder` object. This callback is passed a new `ContextMenuItemBuilder` object that can be modified and returned.
|
||||
* @returns {ContextMenuBuilder}
|
||||
*/
|
||||
addItem(callback) {
|
||||
const builder = new ContextMenuItemBuilder(this);
|
||||
const item = callback(builder);
|
||||
this.el.appendChild(item.el);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Adds a separator to the context menu.
|
||||
* @returns {ContextMenuBuilder}
|
||||
*/
|
||||
addSeparator() {
|
||||
const el = document.createElement('div');
|
||||
el.classList.add('separator');
|
||||
this.el.appendChild(el);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets if menu item icons should be shown. If `true`, the space an icon takes up is still visible even on items without set icons. If `false`, all item icons are hidden, regardless of whether they're set or not. Defaults to `true`.
|
||||
* @param {boolean} areVisible The new state
|
||||
* @returns {ContextMenuBuilder}
|
||||
*/
|
||||
setIconVisibility(areVisible) {
|
||||
this.el.classList.toggle('hideIcons', !areVisible);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Shows the context menu at the specified fixed coordinates. This method is used by the other show methods.
|
||||
* @param {number} x Left
|
||||
* @param {number} y Top
|
||||
*/
|
||||
showAtCoords(x, y) {
|
||||
document.body.appendChild(this.#elCont);
|
||||
this.#elCont.classList.remove('ani');
|
||||
this.#elCont.classList.remove('visible');
|
||||
setTimeout(() => {
|
||||
this.el.style.scale = '1';
|
||||
positionElement(this.el, x, y);
|
||||
setTimeout(() => {
|
||||
this.#elCont.classList.add('ani');
|
||||
setTimeout(() => {
|
||||
this.#elCont.classList.add('visible');
|
||||
try {
|
||||
this.trap.activate();
|
||||
} catch (error) {
|
||||
console.warn(`Unable to trap focus inside context menu. Make sure focus-trap and tabbable are available.`);
|
||||
}
|
||||
}, 1);
|
||||
}, 10);
|
||||
}, 1);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Shows the context menu originating from the center of an HTML element.
|
||||
* @param {HTMLElement} el The target element
|
||||
* @returns {ContextMenuBuilder}
|
||||
*/
|
||||
showAtElement(el) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
return this.showAtCoords((rect.left+(rect.width/2)), (rect.top+(rect.height/2)));
|
||||
}
|
||||
/**
|
||||
* Shows the context menu at the current mouse position, or fixed in the top left if there hasn't been any mouse movement.
|
||||
* @returns {ContextMenuBuilder}
|
||||
*/
|
||||
showAtCursor() {
|
||||
return this.showAtCoords(mouse.x+5, mouse.y);
|
||||
}
|
||||
/**
|
||||
* Hides the context menu and removes it from the DOM.
|
||||
*/
|
||||
hide() {
|
||||
this.#elCont.classList.remove('visible');
|
||||
try {
|
||||
this.trap.deactivate();
|
||||
} catch (error) {}
|
||||
setTimeout(() => {
|
||||
this.#elCont.parentNode.removeChild(this.#elCont);
|
||||
}, 200);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Builds a clickable context menu item
|
||||
*/
|
||||
class ContextMenuItemBuilder {
|
||||
#onClick = () => {};
|
||||
constructor(parent) {
|
||||
this.parent = parent;
|
||||
// Create item container
|
||||
this.el = document.createElement('button');
|
||||
this.el.classList.add('item');
|
||||
// Create icon
|
||||
this.elIcon = document.createElement('div');
|
||||
this.elIcon.classList.add('icon', 'hidden');
|
||||
this.el.appendChild(this.elIcon);
|
||||
// Create label
|
||||
this.elLabel = document.createElement('div');
|
||||
this.elLabel.classList.add('label');
|
||||
this.elLabel.innerText = `Item`;
|
||||
this.el.appendChild(this.elLabel);
|
||||
// Set up click handler
|
||||
this.el.addEventListener('click', () => {
|
||||
if (this.el.disabled) return;
|
||||
this.#onClick();
|
||||
parent.hide();
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Sets the label for this menu item.
|
||||
* @param {string} label The item's label
|
||||
* @returns {ContextMenuItemBuilder}
|
||||
*/
|
||||
setLabel(label) {
|
||||
this.elLabel.innerText = label;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets the icon for this item. If unset, no icon will be shown.
|
||||
* @param {string} icon A valid [Material Symbol](https://fonts.google.com/icons) string
|
||||
*
|
||||
* To make sure you're getting the right symbol, click on the icon, go to the **Android** tab, and copy the string in the code block.
|
||||
* @returns {ContextMenuItemBuilder}
|
||||
*/
|
||||
setIcon(icon) {
|
||||
this.elIcon.classList.remove('hidden');
|
||||
this.elIcon.innerText = icon;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets hover tooltip text for this item.
|
||||
* @param {string} text The tooltip text
|
||||
* @returns {ContextMenuItemBuilder}
|
||||
*/
|
||||
setTooltip(text) {
|
||||
this.el.title = text;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets whether or not the icon and text of this item should be red.
|
||||
* @param {boolean} isDanger
|
||||
* @returns {ContextMenuItemBuilder}
|
||||
*/
|
||||
setIsDanger(isDanger) {
|
||||
this.elIcon.classList.toggle('text-danger', isDanger);
|
||||
this.elLabel.classList.toggle('text-danger', isDanger);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Disables this menu item.
|
||||
* @returns {ContextMenuItemBuilder}
|
||||
*/
|
||||
disable() {
|
||||
this.el.disabled = true;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Enables this menu item.
|
||||
* @returns {ContextMenuItemBuilder}
|
||||
*/
|
||||
enable() {
|
||||
this.el.disabled = false;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets a callback to be run when this item is clicked.
|
||||
* @param {callback} onClick The callback
|
||||
* @returns {ContextMenuItemBuilder}
|
||||
*/
|
||||
setClickHandler(onClick) {
|
||||
this.#onClick = onClick;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a toast notification overlay container.
|
||||
*/
|
||||
class ToastOverlay {
|
||||
/**
|
||||
* @param {('left'|'center'|'right')} hAlign Horizontal alignment
|
||||
* @param {('top'|'bottom')} vAlign Vertical alignment
|
||||
*/
|
||||
constructor(hAlign = 'left', vAlign = 'bottom') {
|
||||
this.el = document.createElement('div');
|
||||
this.el.classList.add('toastOverlay', 'col', 'gap-10');
|
||||
if (hAlign == 'left') this.el.classList.add('align-start');
|
||||
if (hAlign == 'center') this.el.classList.add('align-center');
|
||||
if (hAlign == 'right') this.el.classList.add('align-end');
|
||||
if (vAlign == 'top') this.el.classList.add('justify-start');
|
||||
if (vAlign == 'bottom') {
|
||||
this.el.classList.add('justify-start');
|
||||
this.el.style.flexDirection = 'column-reverse';
|
||||
}
|
||||
document.body.appendChild(this.el);
|
||||
}
|
||||
/**
|
||||
* @callback ToastOverlayShowCallback
|
||||
* @param {ToastBuilder} itemBuilder An item builder to be modified and returned.
|
||||
*/
|
||||
/**
|
||||
* Shows a new toast notification.
|
||||
* @param {ToastOverlayShowCallback} callback A callback function that returns a `ToastBuilder` object. This callback is passed a new `ToastBuilder` object that can be modified and returned.
|
||||
* @returns {ToastOverlay}
|
||||
*/
|
||||
showToast(callback) {
|
||||
const builder = new ToastBuilder(this);
|
||||
const toast = callback(builder);
|
||||
this.el.insertAdjacentElement('afterbegin', toast.el);
|
||||
setTimeout(() => {
|
||||
const delay = toast.el.dataset.delay;
|
||||
if (delay) setTimeout(() => {
|
||||
toast.close();
|
||||
}, delay);
|
||||
toast.el.classList.add('visible');
|
||||
if (delay) setTimeout(close, delay);
|
||||
}, 100);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Builds a toast notification. This class is to be used alongside the `ToastOverlay` class.
|
||||
*/
|
||||
class ToastBuilder {
|
||||
constructor() {
|
||||
this.el = document.createElement('div');
|
||||
this.el.classList.add('toast', 'row', 'gap-10', 'align-center');
|
||||
this.elIcon = document.createElement('div');
|
||||
this.elIcon.classList.add('icon', 'hidden');
|
||||
this.el.appendChild(this.elIcon);
|
||||
this.elBody = document.createElement('div');
|
||||
this.elBody.classList.add('body');
|
||||
this.elBody.innerText = 'Toast notification';
|
||||
this.el.appendChild(this.elBody);
|
||||
this.elClose = document.createElement('button');
|
||||
this.elClose.classList.add('btn', 'secondary', 'iconOnly', 'small', 'close');
|
||||
this.elClose.innerHTML = '<div class="icon">close</div>';
|
||||
this.elClose.title = 'Dismiss';
|
||||
this.elClose.addEventListener('click', () => this.close());
|
||||
this.el.appendChild(this.elClose);
|
||||
this.el.dataset.delay = 5000;
|
||||
}
|
||||
/**
|
||||
* Sets the icon to show on the left of the toast.
|
||||
* @param {string} icon A valid [Material Symbol](https://fonts.google.com/icons) string
|
||||
* @returns {ToastBuilder}
|
||||
*/
|
||||
setIcon(icon) {
|
||||
this.elIcon.classList.remove('hidden');
|
||||
this.elIcon.innerText = icon;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets the body of the toast.
|
||||
* @param {string} html The body HTML
|
||||
* @returns {ToastBuilder}
|
||||
*/
|
||||
setBodyHTML(html) {
|
||||
this.elBody.innerHTML = html;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets the delay before the toast will automatically close after being shown. Set this to `0` to show the toast indefinitely (until the user closes it).
|
||||
*
|
||||
* Default is `5000` (5 seconds).
|
||||
* @param {number} delayMs The delay in milliseconds.
|
||||
* @returns {ToastBuilder}
|
||||
*/
|
||||
setCloseDelay(delayMs) {
|
||||
this.el.dataset.delay = delayMs;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets whether or not the toast can be closed by the user. Rather, this determines if the close button is visible or not.
|
||||
* @param {boolean} isCloseable
|
||||
* @returns {ToastBuilder}
|
||||
*/
|
||||
setIsCloseable(isCloseable) {
|
||||
if (isCloseable) {
|
||||
this.elClose.style.display = '';
|
||||
} else {
|
||||
this.elClose.style.display = 'none';
|
||||
}
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Closes the toast, assuming that it's visible.
|
||||
* @returns {ToastBuilder}
|
||||
*/
|
||||
close() {
|
||||
this.el.classList.remove('visible');
|
||||
setTimeout(() => {
|
||||
if (!this.el.parentNode) return;
|
||||
this.el.parentNode.removeChild(this.el);
|
||||
}, 200);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
function promptUrlDownload(url, name) {
|
||||
const a = document.createElement('a');
|
||||
a.href = url
|
||||
a.download = name || url.split('/').pop();
|
||||
a.click();
|
||||
}
|
||||
|
||||
// Create and append the tooltip element
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.classList.add('tooltip');
|
||||
tooltip.role = 'tooltip';
|
||||
tooltip.id = Date.now();
|
||||
document.body.appendChild(tooltip);
|
||||
let activeTooltipElement = null;
|
||||
|
||||
// Functions for the tooltip
|
||||
let tooltipTimeout;
|
||||
const showTooltip = el => {
|
||||
hideTooltip();
|
||||
tooltipTimeout = setTimeout(() => {
|
||||
// After 200ms, remove transitions and reset scale
|
||||
tooltip.style.transition = 'none';
|
||||
// Set the tooltip's content
|
||||
tooltip.innerHTML = el.dataset.tooltip;
|
||||
setTimeout(() => {
|
||||
// Position the tooltip and add transitions
|
||||
positionElement(tooltip, mouse.x+5, mouse.y);
|
||||
tooltip.style.transition = '';
|
||||
tooltipTimeout = setTimeout(() => {
|
||||
// Show the tooltip if the mouse is still over the element
|
||||
const isMouseOver = el.dataset.isMouseOver === 'true';
|
||||
if (!isMouseOver || !isElementVisible(el)) return;
|
||||
tooltip.classList.add('visible');
|
||||
}, 200);
|
||||
}, 50);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
const hideTooltip = () => {
|
||||
clearTimeout(tooltipTimeout);
|
||||
tooltip.classList.remove('visible');
|
||||
};
|
||||
|
||||
// Continuously save mouse position
|
||||
const mouse = { x: 0, y: 0 };
|
||||
window.addEventListener('mousemove', (e) => {
|
||||
mouse.x = e.clientX;
|
||||
mouse.y = e.clientY;
|
||||
// Show or hide the tooltip accordingly
|
||||
if (tooltip.classList.contains('visible')) {
|
||||
hideTooltip();
|
||||
}
|
||||
if (activeTooltipElement) {
|
||||
showTooltip(activeTooltipElement);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle dynamic changes
|
||||
document.addEventListener('domChange', () => {
|
||||
|
||||
// Get elements with a title attribute
|
||||
const titleEls = $$('[title]:not([data-no-tooltip])');
|
||||
for (const el of titleEls) {
|
||||
// Remove the title attribute and add it to the tooltip data attribute
|
||||
const title = el.title;
|
||||
el.removeAttribute('title');
|
||||
el.dataset.tooltip = title;
|
||||
// Add aria label
|
||||
el.setAttribute('aria-describedby', tooltip.id);
|
||||
}
|
||||
// Get elements with a tooltip data attribute
|
||||
const tooltipEls = $$('[data-tooltip]:not([data-has-tooltip])');
|
||||
// Loop through 'em
|
||||
for (const el of tooltipEls) {
|
||||
el.dataset.isMouseOver = false;
|
||||
const show = () => {
|
||||
el.dataset.isMouseOver = true;
|
||||
activeTooltipElement = el;
|
||||
};
|
||||
const hide = () => {
|
||||
el.dataset.isMouseOver = false;
|
||||
activeTooltipElement = null;
|
||||
};
|
||||
el.addEventListener('mousemove', show);
|
||||
el.addEventListener('mouseleave', hide);
|
||||
el.addEventListener('mousedown', hide);
|
||||
el.addEventListener('mouseup', hide);
|
||||
// Mark the tooltip as added
|
||||
el.dataset.hasTooltip = true;
|
||||
}
|
||||
|
||||
// Get slider elements
|
||||
const sliders = $$('div.slider:not([data-modified])');
|
||||
// Loop through 'em
|
||||
for (const slider of sliders) {
|
||||
// Create elements
|
||||
const prog = document.createElement('progress');
|
||||
const input = document.createElement('input');
|
||||
// Set attributes
|
||||
let textbox;
|
||||
const onSliderChange = () => {
|
||||
// Collect data values
|
||||
const min = slider.dataset.min || 0;
|
||||
const max = slider.dataset.max || 100;
|
||||
const step = slider.dataset.step || 1;
|
||||
const value = slider.dataset.value || 0;
|
||||
const rangeId = slider.dataset.rangeId;
|
||||
const progId = slider.dataset.progId;
|
||||
textbox = $(slider.dataset.textbox);
|
||||
// Set attributes
|
||||
input.type = 'range';
|
||||
input.min = min;
|
||||
input.max = max;
|
||||
input.value = value;
|
||||
input.step = step;
|
||||
prog.min = min;
|
||||
prog.max = max;
|
||||
prog.value = value;
|
||||
prog.step = step;
|
||||
if (progId) prog.id = progId || '';
|
||||
if (rangeId) input.id = rangeId || '';
|
||||
// Handle the textbox
|
||||
if (textbox) {
|
||||
textbox.type = 'number';
|
||||
textbox.min = min;
|
||||
textbox.max = max;
|
||||
textbox.step = step;
|
||||
textbox.value = value;
|
||||
textbox.oninput = () => {
|
||||
input.value = textbox.value;
|
||||
input.dispatchEvent(new Event('input'));
|
||||
};
|
||||
textbox.onchange = textbox.oninput;
|
||||
}
|
||||
// Dispatch events
|
||||
input.dispatchEvent(new Event('change'));
|
||||
prog.dispatchEvent(new Event('change'));
|
||||
}
|
||||
onSliderChange();
|
||||
// Append elements
|
||||
slider.appendChild(prog);
|
||||
slider.appendChild(input);
|
||||
// Add event listeners
|
||||
input.addEventListener('input', () => {
|
||||
slider.dataset.value = input.value;
|
||||
prog.value = slider.dataset.value;
|
||||
if (textbox) textbox.value = slider.dataset.value;
|
||||
slider.dispatchEvent(new Event('input'));
|
||||
});
|
||||
input.addEventListener('change', () => {
|
||||
slider.dataset.value = input.value;
|
||||
prog.value = slider.dataset.value;
|
||||
if (textbox) textbox.value = slider.dataset.value;
|
||||
});
|
||||
prog.addEventListener('change', () => {
|
||||
input.value = slider.dataset.value;
|
||||
if (textbox) textbox.value = slider.dataset.value;
|
||||
});
|
||||
slider.addEventListener('change', onSliderChange);
|
||||
// Mark the slider as added
|
||||
slider.dataset.modified = true;
|
||||
}
|
||||
|
||||
// Get expandable textareas
|
||||
const textareas = $$('textarea[data-make-expandable]:not([data-modified])');
|
||||
// Loop through 'em
|
||||
for (const textarea of textareas) {
|
||||
textarea.addEventListener('resize', () => {
|
||||
if (!isElementVisible(textarea) || !textarea.scrollHeight) return;
|
||||
textarea.style.height = 'auto';
|
||||
textarea.style.height = textarea.scrollHeight + 'px';
|
||||
});
|
||||
textarea.dispatchEvent(new Event('resize'));
|
||||
textarea.addEventListener('input', () => {
|
||||
textarea.dispatchEvent(new Event('resize'));
|
||||
});
|
||||
window.addEventListener('resize', () => {
|
||||
textarea.dispatchEvent(new Event('resize'));
|
||||
});
|
||||
textarea.dataset.modified = true;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Handle DOM mutations and dispatching the domChange event
|
||||
const mutationObs = new MutationObserver(() => {
|
||||
document.dispatchEvent(new Event('domChange'));
|
||||
});
|
||||
mutationObs.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
characterData: true,
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
window.addEventListener('domcontentloaded', () => {
|
||||
document.dispatchEvent(new Event('domChange'));
|
||||
});
|
338
web/v2/themes.css
Normal file
338
web/v2/themes.css
Normal file
@ -0,0 +1,338 @@
|
||||
|
||||
/*
|
||||
|
||||
See `themes.json` for some metadata about each theme.
|
||||
- The `themeColor` property is the suggested value for the `theme-color` meta tag.
|
||||
- Equal to the theme's `--b3` color.
|
||||
- The `name` property is the theme's canonical name.
|
||||
- The `hue` property is the suggested hue to use for colouring custom elements
|
||||
|
||||
*/
|
||||
|
||||
.lightblue,
|
||||
.lightyellow,
|
||||
.lightgreen,
|
||||
.lightmuted,
|
||||
.lightpurple {
|
||||
/* Background and foreground */
|
||||
--b0: hsl(180, 60%, 95%);
|
||||
--b1: hsl(180, 60%, 90%);
|
||||
--b2: hsl(180, 50%, 85%);
|
||||
--b3: hsl(180, 40%, 80%);
|
||||
--b4: hsl(180, 40%, 70%);
|
||||
--b5: hsl(180, 40%, 60%);
|
||||
--f4: hsl(180, 30%, 50%);
|
||||
--f3: hsl(180, 30%, 35%);
|
||||
--f2: hsl(180, 30%, 15%);
|
||||
--f1: black;
|
||||
/* Green accent */
|
||||
--green0: hsl(120, 60%, 40%);
|
||||
--green1: hsl(120, 60%, 65%);
|
||||
--green2: hsl(120, 60%, 80%);
|
||||
--green3: hsl(120, 60%, 85%);
|
||||
--green4: hsl(120, 60%, 90%);
|
||||
--green5: hsl(120, 60%, 95%);
|
||||
/* Yellow accent */
|
||||
--yellow0: hsl(40, 100%, 40%);
|
||||
--yellow1: hsl(40, 100%, 70%);
|
||||
--yellow2: hsl(40, 100%, 80%);
|
||||
--yellow3: hsl(40, 100%, 85%);
|
||||
--yellow4: hsl(40, 100%, 90%);
|
||||
--yellow5: hsl(40, 100%, 95%);
|
||||
/* Blue accent */
|
||||
--blue0: hsl(210, 80%, 45%);
|
||||
--blue1: hsl(210, 80%, 70%);
|
||||
--blue2: hsl(210, 80%, 80%);
|
||||
--blue3: hsl(210, 80%, 85%);
|
||||
--blue4: hsl(210, 80%, 90%);
|
||||
--blue5: hsl(210, 80%, 95%);
|
||||
/* Danger */
|
||||
--red0: hsl(355, 80%, 55%);
|
||||
--red1: hsl(355, 80%, 75%);
|
||||
--red2: hsl(355, 80%, 80%);
|
||||
--red3: hsl(355, 80%, 85%);
|
||||
--red4: hsl(355, 80%, 90%);
|
||||
--red5: hsl(355, 80%, 95%);
|
||||
/* Code */
|
||||
--syntax-comment: #6e7781;
|
||||
--syntax-constant: #0550ae;
|
||||
--syntax-entity: #8250df;
|
||||
--syntax-storage-modifier-import: #24292f;
|
||||
--syntax-entity-tag: #116329;
|
||||
--syntax-keyword: #cf222e;
|
||||
--syntax-string: #0a3069;
|
||||
--syntax-variable: #953800;
|
||||
}
|
||||
|
||||
.lightpurple {
|
||||
/* Background and foreground */
|
||||
--b0: hsl(280, 60%, 95%);
|
||||
--b1: hsl(280, 60%, 90%);
|
||||
--b2: hsl(280, 60%, 85%);
|
||||
--b3: hsl(280, 50%, 80%);
|
||||
--b4: hsl(290, 50%, 70%);
|
||||
--b5: hsl(300, 40%, 60%);
|
||||
--f4: hsl(310, 30%, 50%);
|
||||
--f3: hsl(320, 30%, 35%);
|
||||
--f2: hsl(320, 30%, 15%);
|
||||
--f1: black;
|
||||
}
|
||||
|
||||
.lightyellow {
|
||||
/* Background and foreground */
|
||||
--b0: hsl(50, 60%, 95%);
|
||||
--b1: hsl(50, 60%, 90%);
|
||||
--b2: hsl(40, 60%, 85%);
|
||||
--b3: hsl(30, 50%, 80%);
|
||||
--b4: hsl(30, 50%, 70%);
|
||||
--b5: hsl(20, 40%, 60%);
|
||||
--f4: hsl(10, 30%, 50%);
|
||||
--f3: hsl(10, 30%, 35%);
|
||||
--f2: hsl(0, 30%, 15%);
|
||||
--f1: black;
|
||||
}
|
||||
|
||||
.lightgreen {
|
||||
/* Background and foreground */
|
||||
--b0: hsl(100, 60%, 95%);
|
||||
--b1: hsl(100, 60%, 90%);
|
||||
--b2: hsl(110, 60%, 85%);
|
||||
--b3: hsl(120, 50%, 80%);
|
||||
--b4: hsl(120, 50%, 70%);
|
||||
--b5: hsl(130, 40%, 60%);
|
||||
--f4: hsl(140, 30%, 50%);
|
||||
--f3: hsl(140, 30%, 35%);
|
||||
--f2: hsl(150, 30%, 15%);
|
||||
--f1: black;
|
||||
--green0: hsl(180, 60%, 50%);
|
||||
--green1: hsl(180, 60%, 70%);
|
||||
--green2: hsl(180, 60%, 80%);
|
||||
--green3: hsl(180, 60%, 85%);
|
||||
--green4: hsl(180, 60%, 90%);
|
||||
}
|
||||
|
||||
.lightmuted {
|
||||
/* Background and foreground */
|
||||
--b0: hsl(160, 10%, 95%);
|
||||
--b1: hsl(170, 10%, 90%);
|
||||
--b2: hsl(180, 10%, 85%);
|
||||
--b3: hsl(190, 10%, 80%);
|
||||
--b4: hsl(200, 10%, 70%);
|
||||
--b5: hsl(210, 10%, 60%);
|
||||
--f4: hsl(220, 10%, 50%);
|
||||
--f3: hsl(230, 10%, 35%);
|
||||
--f2: hsl(240, 10%, 15%);
|
||||
--f1: black;
|
||||
}
|
||||
|
||||
.darkpurple,
|
||||
.darkyellow,
|
||||
.darkgreen,
|
||||
.darkmuted,
|
||||
.darkblue {
|
||||
/* Background and foreground */
|
||||
--b0: hsl(270, 30%, 15%);
|
||||
--b1: hsl(270, 30%, 20%);
|
||||
--b2: hsl(270, 30%, 25%);
|
||||
--b3: hsl(270, 30%, 30%);
|
||||
--b4: hsl(280, 30%, 40%);
|
||||
--b5: hsl(290, 30%, 50%);
|
||||
--f4: hsl(300, 30%, 65%);
|
||||
--f3: hsl(310, 40%, 80%);
|
||||
--f2: hsl(310, 50%, 90%);
|
||||
--f1: white;
|
||||
/* Green accent */
|
||||
--green0: hsl(150, 50%, 30%);
|
||||
--green1: hsl(150, 50%, 40%);
|
||||
--green2: hsl(150, 50%, 50%);
|
||||
--green3: hsl(150, 50%, 60%);
|
||||
--green4: hsl(150, 50%, 70%);
|
||||
--green5: hsl(150, 50%, 80%);
|
||||
/* Yellow accent */
|
||||
--yellow0: hsl(40, 70%, 40%);
|
||||
--yellow1: hsl(40, 70%, 50%);
|
||||
--yellow2: hsl(40, 70%, 60%);
|
||||
--yellow3: hsl(40, 70%, 70%);
|
||||
--yellow4: hsl(40, 70%, 80%);
|
||||
--yellow5: hsl(40, 70%, 90%);
|
||||
/* Blue accent */
|
||||
--blue0: hsl(240, 60%, 45%);
|
||||
--blue1: hsl(240, 60%, 55%);
|
||||
--blue2: hsl(240, 60%, 65%);
|
||||
--blue3: hsl(240, 60%, 75%);
|
||||
--blue4: hsl(240, 60%, 85%);
|
||||
--blue5: hsl(240, 60%, 95%);
|
||||
/* Danger */
|
||||
--red0: hsl(340, 55%, 40%);
|
||||
--red1: hsl(340, 55%, 50%);
|
||||
--red2: hsl(340, 55%, 60%);
|
||||
--red3: hsl(340, 55%, 70%);
|
||||
--red4: hsl(340, 55%, 80%);
|
||||
--red5: hsl(340, 55%, 90%);
|
||||
/* Code */
|
||||
--syntax-comment: #8b949e;
|
||||
--syntax-constant: #79c0ff;
|
||||
--syntax-entity: #d2a8ff;
|
||||
--syntax-storage-modifier-import: #c9d1d9;
|
||||
--syntax-entity-tag: #7ee787;
|
||||
--syntax-keyword: #ff7b72;
|
||||
--syntax-string: #a5d6ff;
|
||||
--syntax-variable: #ffa657;
|
||||
}
|
||||
|
||||
.darkpurple .textbox,
|
||||
.darkyellow .textbox,
|
||||
.darkgreen .textbox,
|
||||
.darkmuted .textbox,
|
||||
.darkblue .textbox {
|
||||
background-color: var(--b0);
|
||||
border-color: var(--b0);
|
||||
}
|
||||
.darkpurple .textbox:focus,
|
||||
.darkyellow .textbox:focus,
|
||||
.darkgreen .textbox:focus,
|
||||
.darkmuted .textbox:focus,
|
||||
.darkblue .textbox:focus,
|
||||
.darkpurple .textbox:focus-within,
|
||||
.darkyellow .textbox:focus-within,
|
||||
.darkgreen .textbox:focus-within,
|
||||
.darkmuted .textbox:focus-within,
|
||||
.darkblue .textbox:focus-within {
|
||||
background-color: var(--b2);
|
||||
border-color: var(--f4);
|
||||
}
|
||||
|
||||
.darkpurple .slider input[type="range"],
|
||||
.darkyellow .slider input[type="range"],
|
||||
.darkgreen .slider input[type="range"],
|
||||
.darkmuted .slider input[type="range"],
|
||||
.darkblue .slider input[type="range"] {
|
||||
--thumbFill: var(--f2);
|
||||
--thumbFillHover: var(--f4);
|
||||
}
|
||||
|
||||
.darkpurple .text-info,
|
||||
.darkyellow .text-info,
|
||||
.darkgreen .text-info,
|
||||
.darkmuted .text-info,
|
||||
.darkblue .text-info {
|
||||
color: var(--blue2) !important;
|
||||
}
|
||||
.darkpurple .text-success,
|
||||
.darkyellow .text-success,
|
||||
.darkgreen .text-success,
|
||||
.darkmuted .text-success,
|
||||
.darkblue .text-success {
|
||||
color: var(--green2) !important;
|
||||
}
|
||||
.darkpurple .text-warning,
|
||||
.darkyellow .text-warning,
|
||||
.darkgreen .text-warning,
|
||||
.darkmuted .text-warning,
|
||||
.darkblue .text-warning {
|
||||
color: var(--yellow2) !important;
|
||||
}
|
||||
.darkpurple .text-danger,
|
||||
.darkyellow .text-danger,
|
||||
.darkgreen .text-danger,
|
||||
.darkmuted .text-danger,
|
||||
.darkblue .text-danger {
|
||||
color: var(--red2) !important;
|
||||
}
|
||||
|
||||
.darkblue {
|
||||
/* Background and foreground */
|
||||
--b0: hsl(230, 30%, 8%);
|
||||
--b1: hsl(230, 30%, 12%);
|
||||
--b2: hsl(230, 30%, 18%);
|
||||
--b3: hsl(230, 30%, 20%);
|
||||
--b4: hsl(220, 30%, 30%);
|
||||
--b5: hsl(210, 30%, 45%);
|
||||
--f4: hsl(200, 30%, 60%);
|
||||
--f3: hsl(190, 40%, 75%);
|
||||
--f2: hsl(190, 50%, 85%);
|
||||
--f1: white;
|
||||
}
|
||||
|
||||
.darkyellow {
|
||||
/* Background and foreground */
|
||||
--b0: hsl(40, 20%, 8%);
|
||||
--b1: hsl(40, 20%, 12%);
|
||||
--b2: hsl(40, 20%, 18%);
|
||||
--b3: hsl(40, 30%, 20%);
|
||||
--b4: hsl(30, 30%, 30%);
|
||||
--b5: hsl(20, 30%, 45%);
|
||||
--f4: hsl(10, 30%, 60%);
|
||||
--f3: hsl(0, 40%, 75%);
|
||||
--f2: hsl(0, 50%, 85%);
|
||||
--f1: white;
|
||||
}
|
||||
|
||||
.darkgreen {
|
||||
/* Background and foreground */
|
||||
--b0: hsl(170, 20%, 8%);
|
||||
--b1: hsl(170, 20%, 12%);
|
||||
--b2: hsl(170, 20%, 18%);
|
||||
--b3: hsl(170, 30%, 20%);
|
||||
--b4: hsl(160, 30%, 30%);
|
||||
--b5: hsl(150, 30%, 45%);
|
||||
--f4: hsl(140, 30%, 60%);
|
||||
--f3: hsl(130, 40%, 75%);
|
||||
--f2: hsl(130, 50%, 85%);
|
||||
--f1: white;
|
||||
--green0: hsl(180, 50%, 30%);
|
||||
--green1: hsl(180, 50%, 40%);
|
||||
--green2: hsl(180, 50%, 50%);
|
||||
--green3: hsl(180, 50%, 60%);
|
||||
--green4: hsl(180, 50%, 70%);
|
||||
}
|
||||
|
||||
.darkmuted {
|
||||
/* Background and foreground */
|
||||
--b0: hsl(180, 10%, 5%);
|
||||
--b1: hsl(190, 10%, 8%);
|
||||
--b2: hsl(200, 10%, 12%);
|
||||
--b3: hsl(210, 10%, 15%);
|
||||
--b4: hsl(220, 10%, 25%);
|
||||
--b5: hsl(230, 10%, 40%);
|
||||
--f4: hsl(240, 10%, 55%);
|
||||
--f3: hsl(250, 10%, 70%);
|
||||
--f2: hsl(260, 10%, 85%);
|
||||
--f1: white;
|
||||
}
|
||||
|
||||
.lightblue,
|
||||
.lightyellow,
|
||||
.lightgreen,
|
||||
.lightmuted,
|
||||
.lightpurple {
|
||||
/* Luminance palette */
|
||||
--l0: var(--f1);
|
||||
--l1: var(--f2);
|
||||
--l2: var(--f3);
|
||||
--l3: var(--f4);
|
||||
--l4: var(--b5);
|
||||
--l5: var(--b4);
|
||||
--l6: var(--b3);
|
||||
--l7: var(--b2);
|
||||
--l8: var(--b1);
|
||||
--l9: var(--b0);
|
||||
}
|
||||
|
||||
.darkpurple,
|
||||
.darkyellow,
|
||||
.darkgreen,
|
||||
.darkmuted,
|
||||
.darkblue {
|
||||
/* Luminance palette */
|
||||
--l0: var(--b0);
|
||||
--l1: var(--b1);
|
||||
--l2: var(--b2);
|
||||
--l3: var(--b3);
|
||||
--l4: var(--b4);
|
||||
--l5: var(--b5);
|
||||
--l6: var(--f4);
|
||||
--l7: var(--f3);
|
||||
--l8: var(--f2);
|
||||
--l9: var(--f1);
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
|
||||
self.addEventListener('activate', (e) => {
|
||||
self.clients.claim();
|
||||
});
|
||||
@ -5,44 +6,24 @@ self.addEventListener('activate', (e) => {
|
||||
self.addEventListener('fetch', (e) => {
|
||||
const reqUrl = e.request.url;
|
||||
e.respondWith((async() => {
|
||||
// Open asset cache
|
||||
// Open asset cache and see if this request is in it
|
||||
const cache = await caches.open('main');
|
||||
const match = await caches.match(e.request);
|
||||
|
||||
// Request from network
|
||||
// Request the resource from the network
|
||||
const netRes = fetch(e.request).then((res) => {
|
||||
// Create a new response with updated headers
|
||||
const newHeaders = new Headers(res.headers);
|
||||
newHeaders.set('Permissions-Policy', 'clipboard-write=(self), clipboard-read=(self)');
|
||||
|
||||
const newRes = new Response(res.body, {
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
headers: newHeaders
|
||||
});
|
||||
|
||||
// Cache non-API responses
|
||||
// If the request was successful and this isn't an API call,
|
||||
// update the cached resource
|
||||
if (res.ok && !reqUrl.match(/\/api\/sftp\/.*$/)) {
|
||||
cache.put(e.request, newRes.clone());
|
||||
cache.put(e.request, res.clone());
|
||||
}
|
||||
|
||||
return newRes;
|
||||
// Return the response
|
||||
return res;
|
||||
}).catch(e => {
|
||||
console.error('Fetch error:', e);
|
||||
if (match) {
|
||||
// Add header to cached response
|
||||
const newHeaders = new Headers(match.headers);
|
||||
newHeaders.set('Permissions-Policy', 'clipboard-write=(self), clipboard-read=(self)');
|
||||
return new Response(match.body, {
|
||||
status: match.status,
|
||||
statusText: match.statusText,
|
||||
headers: newHeaders
|
||||
});
|
||||
}
|
||||
throw e;
|
||||
console.error(e);
|
||||
return match;
|
||||
});
|
||||
|
||||
// Return cached or network response
|
||||
// Return the cached resource if it exists
|
||||
// Otherwise, return the network request
|
||||
return match || netRes;
|
||||
})());
|
||||
});
|
Reference in New Issue
Block a user