bring remote assets to local

This commit is contained in:
MCHost
2025-06-24 00:32:24 -04:00
parent e3ad40a023
commit 9749663996
28 changed files with 13769 additions and 273 deletions

View File

@ -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;
}
}

View File

@ -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>

Binary file not shown.

Binary file not shown.

View 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
View 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');
}

Binary file not shown.

Binary file not shown.

View 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
View 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');
}

View File

@ -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

File diff suppressed because one or more lines are too long

View 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);
}
});

View 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)))
}
});

View 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);
};
});

View 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);
}
};
};
});

View 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
View 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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because one or more lines are too long

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
View 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
View 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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
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

File diff suppressed because it is too large Load Diff

810
web/v2/base.js Normal file
View 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
View 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);
}

View File

@ -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;
})());
});