Skip to content

Commit

Permalink
Add automatic new files and various CSS changes. (#38)
Browse files Browse the repository at this point in the history
* Disable preloading until investigated further.

* Update some docs

* Add "/pastes" for API compat.

* Possible theme flash fix.

* Invalidate theme JS cache.

* Possible speedups; Removed a fetch call and unnecessary CSS/JS loading.

* Sadly this needs to be defered.

* Remove all newlines from filenames

* Add line numbers to single line pastes.

* Allow HTML comments.

* Added automatic files; removed add file button

* Some CSS adjustments

* Add favicon

* Some CSS changes and fixes to new file behaviour.

* Revert light theme paste background to white

* Changes to README

* More README changes.

* Paste Header style changes

* Add some annotations style changes

---------

Co-authored-by: LostLuma <[email protected]>
  • Loading branch information
EvieePy and LostLuma authored May 15, 2024
1 parent 33a4200 commit 0e27a47
Show file tree
Hide file tree
Showing 16 changed files with 403 additions and 252 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

Easily share code and text.

[Website](https://mystb.in)

[Staging Website](https://staging.mystb.in)
[API Documentation](https://mystb.in/api/documentation)

[Install on VSCode](https://marketplace.visualstudio.com/items?itemName=PythonistaGuild.mystbin)


### Running without Docker
Expand Down
2 changes: 2 additions & 0 deletions core/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ async def create_paste(self, *, data: dict[str, Any]) -> PasteModel:
async with connection.transaction():
for index, file in enumerate(files, 1):
name: str = (file.get("filename") or f"file_{index}")[-CONFIG["PASTES"]["name_limit"] :]
name = "_".join(name.splitlines())

content: str = file["content"]
loc: int = file["content"].count("\n") + 1
annotation: str = ""
Expand Down
46 changes: 24 additions & 22 deletions views/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def __init__(self, app: Application) -> None:
self.app: Application = app

@starlette_plus.route("/paste/{id}", methods=["GET"])
@starlette_plus.route("/pastes/{id}", methods=["GET"], include_in_schema=False)
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_get"])
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_get_day"])
async def paste_get(self, request: starlette_plus.Request) -> starlette_plus.Response:
Expand Down Expand Up @@ -91,20 +92,20 @@ async def paste_get(self, request: starlette_plus.Request) -> starlette_plus.Res
files:
type: array
items:
type: object
properties:
parent_id:
type: string
content:
type: string
filename:
type: string
loc:
type: integer
charcount:
type: integer
annotation:
type: string
type: object
properties:
parent_id:
type: string
content:
type: string
filename:
type: string
loc:
type: integer
charcount:
type: integer
annotation:
type: string
404:
description: The paste does not exist or has been previously deleted.
Expand Down Expand Up @@ -154,6 +155,7 @@ async def paste_get(self, request: starlette_plus.Request) -> starlette_plus.Res
return starlette_plus.JSONResponse(to_return)

@starlette_plus.route("/paste", methods=["POST"])
@starlette_plus.route("/pastes", methods=["POST"], include_in_schema=False)
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_post"])
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_post_day"])
async def paste_post(self, request: starlette_plus.Request) -> starlette_plus.Response:
Expand Down Expand Up @@ -184,14 +186,14 @@ async def paste_post(self, request: starlette_plus.Request) -> starlette_plus.Re
files:
type: array
items:
type: object
properties:
filename:
type: string
required: false
content:
type: string
required: true
type: object
properties:
filename:
type: string
required: false
content:
type: string
required: true
example:
- filename: thing.py
content: print(\"Hello World!\")
Expand Down
174 changes: 87 additions & 87 deletions views/htmx.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import datetime
import json
from typing import TYPE_CHECKING, Any, cast
from urllib.parse import unquote
from urllib.parse import unquote, urlsplit

import bleach
import humanize
Expand All @@ -37,14 +37,19 @@
from core import Application


with open("web/paste.html") as fp:
PASTE_HTML: str = fp.read()


class HTMXView(starlette_plus.View, prefix="htmx"):
def __init__(self, app: Application) -> None:
self.app: Application = app

def highlight_code(self, filename: str, content: str, *, index: int, raw_url: str, annotation: str) -> str:
filename = bleach.clean(filename, attributes=[], tags=[])
content = bleach.clean(content, attributes=[], tags=[])
filename = "_".join(filename.splitlines())

content = bleach.clean(content.replace("<!", "&lt;&#33;"), attributes=[], tags=[], strip_comments=False)
annotations: str = f'<small class="annotations">❌ {annotation}</small>' if annotation else ""

return f"""
Expand Down Expand Up @@ -73,17 +78,94 @@ async def home(self, request: starlette_plus.Request) -> starlette_plus.Response

return starlette_plus.FileResponse("web/index.html")

@starlette_plus.route("/protected/{id}", prefix=False)
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_get"])
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_get_day"])
async def protected_paste(self, request: starlette_plus.Request) -> starlette_plus.Response:
return starlette_plus.FileResponse("web/password.html")

@starlette_plus.route("/{id}", prefix=False)
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_get"])
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_get_day"])
async def paste(self, request: starlette_plus.Request) -> starlette_plus.Response:
if resp := self.check_discord(request=request):
return resp

identifier: str = request.path_params.get("id", "")
headers = {"Link": f'</htmx/fetch?id=%2F{identifier}>; rel="preload"; as="fetch" crossorigin="anonymous"'}
identifier: str = request.path_params.get("id", "pass")
htmx_url: str | None = request.headers.get("HX-Current-URL", None)

if htmx_url and identifier == "pass":
identifier = urlsplit(htmx_url).path.removeprefix("/protected/")

not_found: str = """
<div class="notFound">
<h2>404 - This page or paste could not be found</h2>
<a href="/">Return Home...</a>
</div>
"""

password: str = unquote(request.query_params.get("pastePassword", ""))
paste = await self.app.database.fetch_paste(identifier, password=password)

if not paste:
return starlette_plus.HTMLResponse(PASTE_HTML.format(__PASTES__=not_found))

if paste.has_password and not paste.password_ok:
if not password:
return starlette_plus.RedirectResponse(f"/protected/{identifier}")

error_headers: dict[str, str] = {"HX-Retarget": "#errorResponse", "HX-Reswap": "outerHTML"}
return starlette_plus.HTMLResponse(
"""<span id="errorResponse">Incorrect Password.</span>""",
headers=error_headers,
)

data: dict[str, Any] = paste.serialize(exclude=["password", "password_ok"])
files: list[dict[str, Any]] = data["files"]
created_delta: datetime.timedelta = datetime.datetime.now(tz=datetime.timezone.utc) - paste.created_at.replace(
tzinfo=datetime.timezone.utc
)

url: str = f"/{identifier}"
raw_url: str = f"/raw/{identifier}"
security_html: str = ""

stored: list[str] = request.session.get("pastes", [])
if identifier in stored:
security_url: str = f"/api/security/info/{data['safety']}"

security_html = f"""
<div class="identifierHeaderSection">
<a class="security" href="{security_url}">Security Info</a>
</div>"""

html: str = f"""
<div class="identifierHeader">
<div class="identifierHeaderLeft">
<a href="{url}">/{identifier}</a>
<span>Created {humanize.naturaltime(created_delta)}...</span>
</div>
{security_html}
<div class="identifierHeaderSection">
<a href="{raw_url}">Raw</a>
<!-- <a href="#">Download</a> -->
</div>
</div>
"""

for i, file in enumerate(files):
html += self.highlight_code(
file["filename"],
file["content"],
index=i,
raw_url=raw_url,
annotation=file["annotation"],
)

if htmx_url and password:
return starlette_plus.HTMLResponse(html, headers={"HX-Replace-Url": f"{url}?pastePassword={password}"})

return starlette_plus.FileResponse("web/paste.html", headers=headers)
return starlette_plus.HTMLResponse(PASTE_HTML.format(__PASTES__=html), media_type="text/html")

@starlette_plus.route("/raw/{id}", prefix=False)
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_get"])
Expand Down Expand Up @@ -146,88 +228,6 @@ async def paste_raw_page(self, request: starlette_plus.Request) -> starlette_plu

return starlette_plus.PlainTextResponse(text)

@starlette_plus.route("/fetch")
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_get"])
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_get_day"])
async def fetch_paste(self, request: starlette_plus.Request) -> starlette_plus.Response:
if resp := self.check_discord(request=request):
return resp

not_found: str = """
<div class="notFound">
<h2>404 - This page or paste could not be found</h2>
<a href="/">Return Home...</a>
</div>
"""

no_reload: bool = request.query_params.get("noReload", False) == "true"
password: str = unquote(request.query_params.get("pastePassword", ""))

identifier_quoted: str | None = request.query_params.get("id", None)
if not identifier_quoted:
return starlette_plus.HTMLResponse(not_found)

identifier: str = unquote(identifier_quoted).replace("/", "")
paste = await self.app.database.fetch_paste(identifier, password=password)

if not paste:
return starlette_plus.HTMLResponse(not_found)

if paste.has_password and not paste.password_ok:
error_headers: dict[str, str] = {"HX-Retarget": "#errorResponse", "HX-Reswap": "outerHTML"}

if no_reload:
return starlette_plus.HTMLResponse(
"""<span id="errorResponse">Incorrect Password.</span>""",
headers=error_headers,
)

return starlette_plus.FileResponse("web/password.html")

data: dict[str, Any] = paste.serialize(exclude=["password", "password_ok"])
files: list[dict[str, Any]] = data["files"]
created_delta: datetime.timedelta = datetime.datetime.now(tz=datetime.timezone.utc) - paste.created_at.replace(
tzinfo=datetime.timezone.utc
)

url: str = f"/{identifier}"
raw_url: str = f"/raw/{identifier}"
security_html: str = ""

stored: list[str] = request.session.get("pastes", [])
if identifier in stored:
security_url: str = f"/api/security/info/{data['safety']}"

security_html = f"""
<div class="identifierHeaderSection">
<a class="security" href="{security_url}">Security Info</a>
</div>"""

html: str = f"""
<div class="identifierHeader">
<div class="identifierHeaderLeft">
<a href="{url}">/{identifier}</a>
<span>Created {humanize.naturaltime(created_delta)}...</span>
</div>
{security_html}
<div class="identifierHeaderSection">
<a href="{raw_url}">Raw</a>
<!-- <a href="#">Download</a> -->
</div>
</div>
"""

for i, file in enumerate(files):
html += self.highlight_code(
file["filename"],
file["content"],
index=i,
raw_url=raw_url,
annotation=file["annotation"],
)

return starlette_plus.HTMLResponse(html)

@starlette_plus.route("/save", methods=["POST"])
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_post"])
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_post_day"])
Expand Down
Loading

0 comments on commit 0e27a47

Please sign in to comment.