diff --git a/views/htmx.py b/views/htmx.py index f2d1e83..1b96c8a 100644 --- a/views/htmx.py +++ b/views/htmx.py @@ -92,7 +92,12 @@ def highlight_code(self, *, files: list[dict[str, Any]]) -> str: position += length + 1 - content = bleach.clean(original.replace("\n{"".join(numbers)}\n""" html += f""" @@ -214,23 +219,31 @@ async def paste_raw(self, request: starlette_plus.Request) -> starlette_plus.Res password: str | None = request.headers.get("authorization", None) identifier: str = request.path_params["id"] + htmx_url: str | None = request.headers.get("HX-Current-URL", None) + if identifier == "0" and htmx_url: + identifier = htmx_url.removeprefix(f'{CONFIG["SERVER"]["domain"]}/') + + headers: dict[str, str] = {"HX-Redirect": f"/raw/{identifier}"} paste = await self.app.database.fetch_paste(identifier, password=password) + if not paste: return starlette_plus.JSONResponse( {"error": f'A paste with the id "{identifier}" could not be found or has expired.'}, status_code=404, + headers=headers, ) if paste.has_password and not paste.password_ok: return starlette_plus.JSONResponse( {"error": "Unauthorized. Raw pastes can not be viewed when protected by passwords."}, status_code=401, + headers=headers, ) to_return: dict[str, Any] = paste.serialize(exclude=["safety", "password", "password_ok"]) text: str = "\n\n\n\n".join([f"# MystBin ! - {f['filename']}\n{f['content']}" for f in to_return["files"]]) - return starlette_plus.PlainTextResponse(text) + return starlette_plus.PlainTextResponse(text, headers=headers) @starlette_plus.route("/raw/{id}/{page:int}", prefix=False) @starlette_plus.limit(**CONFIG["LIMITS"]["paste_get"]) diff --git a/web/index.html b/web/index.html index 25f2cde..75754c9 100644 --- a/web/index.html +++ b/web/index.html @@ -19,11 +19,12 @@ + - + @@ -46,7 +47,13 @@ MystBin -
+
+ +
@@ -55,7 +62,9 @@
-
+
@@ -65,21 +74,24 @@ Filling in all current files will create a new one (Up to 5)" maxlength="300000" onkeyup="addFile(0)">
-
+
Delete File
- +

- Save Paste + Save Paste
diff --git a/web/maint.html b/web/maint.html index 945c2a6..25f4e3d 100644 --- a/web/maint.html +++ b/web/maint.html @@ -8,14 +8,14 @@ - - - - + + + + - - - + + + @@ -39,7 +39,13 @@ MystBin -
+
+ +
diff --git a/web/password.html b/web/password.html index 2bcde32..b4e83cf 100644 --- a/web/password.html +++ b/web/password.html @@ -20,11 +20,12 @@ + - + @@ -48,15 +49,21 @@ MystBin -
+
+ +
+ hx-vals="js:{id: window.location.pathname}" hx-include="[name='pastePassword']" + hx-trigger="keyup[keyCode==13] from:[name='pastePassword']" onSubmit="return false;">

Password Protected!

@@ -85,6 +92,8 @@

Password Protected!

GitHub
+ +
\ No newline at end of file diff --git a/web/paste.html b/web/paste.html index 29a210a..52983b0 100644 --- a/web/paste.html +++ b/web/paste.html @@ -9,7 +9,7 @@ - + @@ -19,11 +19,12 @@ + - + @@ -47,7 +48,13 @@ MystBin -
+
+ +
@@ -72,6 +79,8 @@ GitHub
+ +
\ No newline at end of file diff --git a/web/static/images/keyboard-light.svg b/web/static/images/keyboard-light.svg new file mode 100644 index 0000000..be1bc8e --- /dev/null +++ b/web/static/images/keyboard-light.svg @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/web/static/images/keyboard.svg b/web/static/images/keyboard.svg new file mode 100644 index 0000000..a58cc81 --- /dev/null +++ b/web/static/images/keyboard.svg @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/web/static/scripts/shortcuts.js b/web/static/scripts/shortcuts.js new file mode 100644 index 0000000..8b59793 --- /dev/null +++ b/web/static/scripts/shortcuts.js @@ -0,0 +1,16 @@ +window.addEventListener("keydown", (e) => { + + // Ctrl + s === Save Paste + if (e.ctrlKey && e.key === "s") { + e.preventDefault(); + e.stopPropagation(); + return; + } + + // Ctrl + Shift + R === Raw Paste + else if (e.ctrlKey && e.shiftKey && e.key === "R") { + e.preventDefault(); + e.stopPropagation(); + return; + } +}); \ No newline at end of file diff --git a/web/static/styles/global.css b/web/static/styles/global.css index 1dc87d0..0ee7de1 100644 --- a/web/static/styles/global.css +++ b/web/static/styles/global.css @@ -18,6 +18,10 @@ --button--brightness-hover: brightness(0.85); --button--brightness-active: brightness(0.95); + .keyboardLight { + display: none !important; + } + .deleteFile { background-color: var(--color-background); filter: brightness(0.99); @@ -66,6 +70,10 @@ --button--brightness: brightness(1.2); --button--brightness-hover: brightness(1.1); --button--brightness-active: brightness(1.1); + + .keyboardDark { + display: none !important; + } } * { @@ -121,6 +129,24 @@ a { text-decoration: none; } +.headerRight { + display: flex; + gap: 1rem; + align-items: center; +} + +.keyboard { + user-select: none; + width: 2.75rem; + padding-top: 0.19rem; + height: auto; + opacity: 0.6; +} + +.keyboard:hover { + cursor: help; +} + .footer { display: flex; flex-direction: row; @@ -169,7 +195,6 @@ a { background-color: var(--color-background--pastes); border-radius: 0.25rem; border: 1px solid transparent; - } .dragging { @@ -235,6 +260,7 @@ input[type="password"] { font-family: "JetBrains Mono", monospace; font-optical-sizing: auto; font-style: normal; + font-size: smaller; outline: none; border: var(--color-foreground--border) 1px solid; padding: 0.25rem; @@ -540,13 +566,14 @@ textarea { border-spacing: 0; border: none; font-family: "JetBrains Mono", monospace; - font-size: 0.8em; + font-size: 0.8rem; + line-height: 1.2rem; user-select: none; } .lineNumRow { padding: 0; - padding-right: 16px!important; + padding-right: 16px !important; opacity: 0.7; } @@ -561,7 +588,8 @@ textarea { code { font-family: "JetBrains Mono", monospace; - font-size: 0.8em; + font-size: 0.8rem; + line-height: 1.2rem; z-index: 2; } @@ -619,27 +647,53 @@ code { } .annotationSecond { - position:relative; + position: relative; } .annotationSecond:after { - position:absolute; + position: absolute; content: attr(data-text); color: var(--color-foreground); background-color: var(--color-background); - top:50%; - transform:translateY(-50%); - left:100%; + top: 50%; + transform: translateY(-50%); + left: 100%; width: max-content; border-radius: 0.25rem; margin-left: 0.5rem; padding: 0.5rem; - display:none; /* hide by default */ + display: none; + /* hide by default */ opacity: 1; } .annotationSecond:hover:after { - display:block; + display: block; +} + +.keyboardTool { + position: relative; +} + +.keyboardTool:after { + position: absolute; + content: attr(data-text); + color: var(--color-foreground); + background-color: var(--color-background); + width: max-content; + border-radius: 0.25rem; + padding: 0.75rem; + display: none; + /* hide by default */ + opacity: 1; + right: 0; + white-space: pre; + z-index: 9; + cursor: help; +} + +.keyboardTool:hover:after { + display: inline-block; } @media screen and (max-width: 600px) {