Skip to content

Commit 0e27a47

Browse files
EvieePyLostLuma
andauthored
Add automatic new files and various CSS changes. (#38)
* 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]>
1 parent 33a4200 commit 0e27a47

File tree

16 files changed

+403
-252
lines changed

16 files changed

+403
-252
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
Easily share code and text.
44

5+
[Website](https://mystb.in)
56

6-
[Staging Website](https://staging.mystb.in)
7+
[API Documentation](https://mystb.in/api/documentation)
8+
9+
[Install on VSCode](https://marketplace.visualstudio.com/items?itemName=PythonistaGuild.mystbin)
710

811

912
### Running without Docker

core/database.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,8 @@ async def create_paste(self, *, data: dict[str, Any]) -> PasteModel:
244244
async with connection.transaction():
245245
for index, file in enumerate(files, 1):
246246
name: str = (file.get("filename") or f"file_{index}")[-CONFIG["PASTES"]["name_limit"] :]
247+
name = "_".join(name.splitlines())
248+
247249
content: str = file["content"]
248250
loc: int = file["content"].count("\n") + 1
249251
annotation: str = ""

views/api.py

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def __init__(self, app: Application) -> None:
3737
self.app: Application = app
3838

3939
@starlette_plus.route("/paste/{id}", methods=["GET"])
40+
@starlette_plus.route("/pastes/{id}", methods=["GET"], include_in_schema=False)
4041
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_get"])
4142
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_get_day"])
4243
async def paste_get(self, request: starlette_plus.Request) -> starlette_plus.Response:
@@ -91,20 +92,20 @@ async def paste_get(self, request: starlette_plus.Request) -> starlette_plus.Res
9192
files:
9293
type: array
9394
items:
94-
type: object
95-
properties:
96-
parent_id:
97-
type: string
98-
content:
99-
type: string
100-
filename:
101-
type: string
102-
loc:
103-
type: integer
104-
charcount:
105-
type: integer
106-
annotation:
107-
type: string
95+
type: object
96+
properties:
97+
parent_id:
98+
type: string
99+
content:
100+
type: string
101+
filename:
102+
type: string
103+
loc:
104+
type: integer
105+
charcount:
106+
type: integer
107+
annotation:
108+
type: string
108109
109110
404:
110111
description: The paste does not exist or has been previously deleted.
@@ -154,6 +155,7 @@ async def paste_get(self, request: starlette_plus.Request) -> starlette_plus.Res
154155
return starlette_plus.JSONResponse(to_return)
155156

156157
@starlette_plus.route("/paste", methods=["POST"])
158+
@starlette_plus.route("/pastes", methods=["POST"], include_in_schema=False)
157159
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_post"])
158160
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_post_day"])
159161
async def paste_post(self, request: starlette_plus.Request) -> starlette_plus.Response:
@@ -184,14 +186,14 @@ async def paste_post(self, request: starlette_plus.Request) -> starlette_plus.Re
184186
files:
185187
type: array
186188
items:
187-
type: object
188-
properties:
189-
filename:
190-
type: string
191-
required: false
192-
content:
193-
type: string
194-
required: true
189+
type: object
190+
properties:
191+
filename:
192+
type: string
193+
required: false
194+
content:
195+
type: string
196+
required: true
195197
example:
196198
- filename: thing.py
197199
content: print(\"Hello World!\")

views/htmx.py

Lines changed: 87 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import datetime
2222
import json
2323
from typing import TYPE_CHECKING, Any, cast
24-
from urllib.parse import unquote
24+
from urllib.parse import unquote, urlsplit
2525

2626
import bleach
2727
import humanize
@@ -37,14 +37,19 @@
3737
from core import Application
3838

3939

40+
with open("web/paste.html") as fp:
41+
PASTE_HTML: str = fp.read()
42+
43+
4044
class HTMXView(starlette_plus.View, prefix="htmx"):
4145
def __init__(self, app: Application) -> None:
4246
self.app: Application = app
4347

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

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

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

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

81+
@starlette_plus.route("/protected/{id}", prefix=False)
82+
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_get"])
83+
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_get_day"])
84+
async def protected_paste(self, request: starlette_plus.Request) -> starlette_plus.Response:
85+
return starlette_plus.FileResponse("web/password.html")
86+
7687
@starlette_plus.route("/{id}", prefix=False)
7788
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_get"])
7889
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_get_day"])
7990
async def paste(self, request: starlette_plus.Request) -> starlette_plus.Response:
8091
if resp := self.check_discord(request=request):
8192
return resp
8293

83-
identifier: str = request.path_params.get("id", "")
84-
headers = {"Link": f'</htmx/fetch?id=%2F{identifier}>; rel="preload"; as="fetch" crossorigin="anonymous"'}
94+
identifier: str = request.path_params.get("id", "pass")
95+
htmx_url: str | None = request.headers.get("HX-Current-URL", None)
96+
97+
if htmx_url and identifier == "pass":
98+
identifier = urlsplit(htmx_url).path.removeprefix("/protected/")
99+
100+
not_found: str = """
101+
<div class="notFound">
102+
<h2>404 - This page or paste could not be found</h2>
103+
<a href="/">Return Home...</a>
104+
</div>
105+
"""
106+
107+
password: str = unquote(request.query_params.get("pastePassword", ""))
108+
paste = await self.app.database.fetch_paste(identifier, password=password)
109+
110+
if not paste:
111+
return starlette_plus.HTMLResponse(PASTE_HTML.format(__PASTES__=not_found))
112+
113+
if paste.has_password and not paste.password_ok:
114+
if not password:
115+
return starlette_plus.RedirectResponse(f"/protected/{identifier}")
116+
117+
error_headers: dict[str, str] = {"HX-Retarget": "#errorResponse", "HX-Reswap": "outerHTML"}
118+
return starlette_plus.HTMLResponse(
119+
"""<span id="errorResponse">Incorrect Password.</span>""",
120+
headers=error_headers,
121+
)
122+
123+
data: dict[str, Any] = paste.serialize(exclude=["password", "password_ok"])
124+
files: list[dict[str, Any]] = data["files"]
125+
created_delta: datetime.timedelta = datetime.datetime.now(tz=datetime.timezone.utc) - paste.created_at.replace(
126+
tzinfo=datetime.timezone.utc
127+
)
128+
129+
url: str = f"/{identifier}"
130+
raw_url: str = f"/raw/{identifier}"
131+
security_html: str = ""
132+
133+
stored: list[str] = request.session.get("pastes", [])
134+
if identifier in stored:
135+
security_url: str = f"/api/security/info/{data['safety']}"
136+
137+
security_html = f"""
138+
<div class="identifierHeaderSection">
139+
<a class="security" href="{security_url}">Security Info</a>
140+
</div>"""
141+
142+
html: str = f"""
143+
<div class="identifierHeader">
144+
<div class="identifierHeaderLeft">
145+
<a href="{url}">/{identifier}</a>
146+
<span>Created {humanize.naturaltime(created_delta)}...</span>
147+
</div>
148+
{security_html}
149+
<div class="identifierHeaderSection">
150+
<a href="{raw_url}">Raw</a>
151+
<!-- <a href="#">Download</a> -->
152+
</div>
153+
</div>
154+
"""
155+
156+
for i, file in enumerate(files):
157+
html += self.highlight_code(
158+
file["filename"],
159+
file["content"],
160+
index=i,
161+
raw_url=raw_url,
162+
annotation=file["annotation"],
163+
)
164+
165+
if htmx_url and password:
166+
return starlette_plus.HTMLResponse(html, headers={"HX-Replace-Url": f"{url}?pastePassword={password}"})
85167

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

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

147229
return starlette_plus.PlainTextResponse(text)
148230

149-
@starlette_plus.route("/fetch")
150-
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_get"])
151-
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_get_day"])
152-
async def fetch_paste(self, request: starlette_plus.Request) -> starlette_plus.Response:
153-
if resp := self.check_discord(request=request):
154-
return resp
155-
156-
not_found: str = """
157-
<div class="notFound">
158-
<h2>404 - This page or paste could not be found</h2>
159-
<a href="/">Return Home...</a>
160-
</div>
161-
"""
162-
163-
no_reload: bool = request.query_params.get("noReload", False) == "true"
164-
password: str = unquote(request.query_params.get("pastePassword", ""))
165-
166-
identifier_quoted: str | None = request.query_params.get("id", None)
167-
if not identifier_quoted:
168-
return starlette_plus.HTMLResponse(not_found)
169-
170-
identifier: str = unquote(identifier_quoted).replace("/", "")
171-
paste = await self.app.database.fetch_paste(identifier, password=password)
172-
173-
if not paste:
174-
return starlette_plus.HTMLResponse(not_found)
175-
176-
if paste.has_password and not paste.password_ok:
177-
error_headers: dict[str, str] = {"HX-Retarget": "#errorResponse", "HX-Reswap": "outerHTML"}
178-
179-
if no_reload:
180-
return starlette_plus.HTMLResponse(
181-
"""<span id="errorResponse">Incorrect Password.</span>""",
182-
headers=error_headers,
183-
)
184-
185-
return starlette_plus.FileResponse("web/password.html")
186-
187-
data: dict[str, Any] = paste.serialize(exclude=["password", "password_ok"])
188-
files: list[dict[str, Any]] = data["files"]
189-
created_delta: datetime.timedelta = datetime.datetime.now(tz=datetime.timezone.utc) - paste.created_at.replace(
190-
tzinfo=datetime.timezone.utc
191-
)
192-
193-
url: str = f"/{identifier}"
194-
raw_url: str = f"/raw/{identifier}"
195-
security_html: str = ""
196-
197-
stored: list[str] = request.session.get("pastes", [])
198-
if identifier in stored:
199-
security_url: str = f"/api/security/info/{data['safety']}"
200-
201-
security_html = f"""
202-
<div class="identifierHeaderSection">
203-
<a class="security" href="{security_url}">Security Info</a>
204-
</div>"""
205-
206-
html: str = f"""
207-
<div class="identifierHeader">
208-
<div class="identifierHeaderLeft">
209-
<a href="{url}">/{identifier}</a>
210-
<span>Created {humanize.naturaltime(created_delta)}...</span>
211-
</div>
212-
{security_html}
213-
<div class="identifierHeaderSection">
214-
<a href="{raw_url}">Raw</a>
215-
<!-- <a href="#">Download</a> -->
216-
</div>
217-
</div>
218-
"""
219-
220-
for i, file in enumerate(files):
221-
html += self.highlight_code(
222-
file["filename"],
223-
file["content"],
224-
index=i,
225-
raw_url=raw_url,
226-
annotation=file["annotation"],
227-
)
228-
229-
return starlette_plus.HTMLResponse(html)
230-
231231
@starlette_plus.route("/save", methods=["POST"])
232232
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_post"])
233233
@starlette_plus.limit(**CONFIG["LIMITS"]["paste_post_day"])

0 commit comments

Comments
 (0)