Compare commits

...

3 Commits

Author SHA1 Message Date
d006862422
add js to quote part of a post 2025-07-06 22:31:54 +03:00
e60c74a90f
put emoji shortcodes in code blocks in guide 2025-07-06 19:47:05 +03:00
80fec756a9
add favicon 2025-07-06 19:44:55 +03:00
7 changed files with 172 additions and 3 deletions

View File

@ -43,7 +43,7 @@
</tr>
{% for emoji in __emoji %}
<tr>
<td>:{{ emoji }}:</td>
<td>{{ ("[code]:%s:[/code]" % emoji) | babycode | safe }}</td>
<td>{{ __emoji[emoji] | safe }}</td>
</tr>
{% endfor %}

View File

@ -9,6 +9,7 @@
<title>Porom</title>
{% endif %}
<link rel="stylesheet" href="/static/style.css">
<link rel="icon" type="image/png" href="/static/favicon.png">
</head>
<body>
{% include 'common/topnav.html' %}

View File

@ -94,6 +94,7 @@
{% if editing %}
{% set postclass = postclass + " editing" %}
{% endif %}
{% set post_permalink = url_for("threads.thread", slug = post['thread_slug'], after = post['id'], _anchor = ("post-" + (post['id'] | string))) %}
<div class=" {{ postclass }}" id="post-{{ post['id'] }}">
<div class="usercard">
<div class="usercard-inner">
@ -109,7 +110,6 @@
<div class="post-content-container" {{ "id=latest-post" if is_latest else "" }}>
<div class="post-info">
{% set post_permalink = url_for("threads.thread", slug = post['thread_slug'], after = post['id'], _anchor = ("post-" + (post['id'] | string))) %}
<a href="{{ post_permalink }}" title="Permalink"><i>
{% if (post['edited_at'] | int) > (post['created_at'] | int) %}
Edited on {{ timestamp(post['edited_at']) }}
@ -157,7 +157,7 @@
</div>
<div class="post-content">
{% if not editing %}
<div class="post-inner">{{ post['content'] | safe }}</div>
<div class="post-inner" data-post-permalink="{{ post_permalink }}" data-author-username="{{ post.username }}">{{ post['content'] | safe }}</div>
{% if render_sig and post['signature_rendered'] %}
<div class="signature-container">
<hr>

BIN
data/static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -9,6 +9,125 @@
})
}
function supportsPopover() {
return Object.hasOwn(HTMLElement.prototype, "popover");
}
if (supportsPopover()){
let quotedPostContainer = null;
function isQuoteSelectionValid() {
const selection = document.getSelection();
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
return false;
}
const range = selection.getRangeAt(0);
const commonAncestor = range.commonAncestorContainer;
const ancestorElement = commonAncestor.nodeType === Node.TEXT_NODE
? commonAncestor.parentNode
: commonAncestor;
const container = ancestorElement.closest(".post-inner");
if (!container) {
return false;
}
const success = container.contains(ancestorElement);
if (success) {
quotedPostContainer = container;
}
return success;
}
let quotePopover = null;
let isSelecting = false;
document.addEventListener("mousedown", () => {
isSelecting = true;
})
document.addEventListener("mouseup", () => {
isSelecting = false;
handlePossibleSelection();
})
document.addEventListener("keyup", (e) => {
if (e.shiftKey && (e.key.startsWith('Arrow') || e.key === 'Home' || e.key === 'End')) {
handlePossibleSelection();
}
})
function handlePossibleSelection() {
setTimeout(() => {
const valid = isQuoteSelectionValid();
if (isSelecting || !valid) {
removePopover();
return;
}
const selection = document.getSelection();
const selectionStr = selection.toString().trim();
if (selection.isCollapsed || selectionStr === "") {
removePopover();
return;
}
showPopover();
}, 50)
}
function removePopover() {
quotePopover?.hidePopover();
}
function createPopover() {
quotePopover = document.createElement("div");
quotePopover.popover = "auto";
quotePopover.className = "quote-popover";
const quoteButton = document.createElement("button");
quoteButton.textContent = "Quote fragment"
quoteButton.className = "reduced"
quotePopover.appendChild(quoteButton);
document.body.appendChild(quotePopover);
return quoteButton;
}
function showPopover() {
if (!quotePopover) {
const quoteButton = createPopover();
quoteButton.addEventListener("click", () => {
console.log("Quoting:", document.getSelection().toString());
const postPermalink = quotedPostContainer.dataset.postPermalink;
const authorUsername = quotedPostContainer.dataset.authorUsername;
console.log(postPermalink, authorUsername);
if (ta.value.trim() !== "") {
ta.value += "\n"
}
ta.value += `[url=${postPermalink}]${authorUsername} said:[/url]\n[quote]${document.getSelection().toString()}[/quote]\n`;
ta.scrollIntoView()
ta.focus();
document.getSelection().empty();
removePopover();
})
}
const range = document.getSelection().getRangeAt(0);
const rect = range.getBoundingClientRect();
const scrollY = window.scrollY || window.pageYOffset;
quotePopover.style.setProperty("top", `${rect.top + scrollY - 55}px`)
quotePopover.style.setProperty("left", `${rect.left + rect.width/2}px`)
if (!quotePopover.matches(':popover-open')) {
quotePopover.showPopover();
}
}
}
const deleteDialog = document.getElementById("delete-dialog");
const deleteDialogCloseButton = document.getElementById("post-delete-dialog-close");
let deletionTargetPostContainer;

View File

@ -310,6 +310,10 @@ button:active, input[type=submit]:active, .linkbutton:active {
button:disabled, input[type=submit]:disabled, .linkbutton:disabled {
background-color: rgb(209.535, 211.565, 211.46);
}
button.reduced, input[type=submit].reduced, .linkbutton.reduced {
margin: 0;
padding: 5px;
}
button.critical, input[type=submit].critical, .linkbutton.critical {
color: white;
background-color: red;
@ -323,6 +327,10 @@ button.critical:active, input[type=submit].critical:active, .linkbutton.critical
button.critical:disabled, input[type=submit].critical:disabled, .linkbutton.critical:disabled {
background-color: rgb(174.675, 156.825, 156.825);
}
button.critical.reduced, input[type=submit].critical.reduced, .linkbutton.critical.reduced {
margin: 0;
padding: 5px;
}
button.warn, input[type=submit].warn, .linkbutton.warn {
background-color: #fbfb8d;
}
@ -335,6 +343,10 @@ button.warn:active, input[type=submit].warn:active, .linkbutton.warn:active {
button.warn:disabled, input[type=submit].warn:disabled, .linkbutton.warn:disabled {
background-color: rgb(217.55, 217.55, 209.85);
}
button.warn.reduced, input[type=submit].warn.reduced, .linkbutton.warn.reduced {
margin: 0;
padding: 5px;
}
input[type=file]::file-selector-button {
background-color: rgb(177, 206, 204.5);
@ -349,6 +361,10 @@ input[type=file]::file-selector-button:active {
input[type=file]::file-selector-button:disabled {
background-color: rgb(209.535, 211.565, 211.46);
}
input[type=file]::file-selector-button.reduced {
margin: 0;
padding: 5px;
}
p {
margin: 15px 0;
@ -371,6 +387,10 @@ p {
.pagebutton:disabled {
background-color: rgb(209.535, 211.565, 211.46);
}
.pagebutton.reduced {
margin: 0;
padding: 5px;
}
.currentpage {
border: none;
@ -632,6 +652,10 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus
.tab-button:disabled {
background-color: rgb(209.535, 211.565, 211.46);
}
.tab-button.reduced {
margin: 0;
padding: 5px;
}
.tab-button.active {
background-color: #beb1ce;
padding-top: 8px;
@ -737,3 +761,13 @@ ul, ol {
.babycode-button > * {
font-size: 1rem;
}
.quote-popover {
position: absolute;
transform: translateX(-50%);
margin: 0;
border: none;
border-radius: 4px;
background-color: rgba(0, 0, 0, 0.5019607843);
padding: 5px 10px;
}

View File

@ -75,6 +75,11 @@ $accordion_color: color.adjust($accent_color, $hue: 140, $lightness: -10%, $satu
&:disabled {
background-color: color.scale($color, $lightness: 30%, $saturation: -90%);
}
&.reduced {
margin: 0;
padding: 5px;
}
}
@mixin navbar($color) {
@ -746,3 +751,13 @@ ul, ol {
font-size: 1rem;
}
}
.quote-popover {
position: absolute;
transform: translateX(-50%);
margin: 0;
border: none;
border-radius: 4px;
background-color: #00000080;
padding: 5px 10px;
}