Fix JS
FossilOrigin-Name: 9d198513fc108e603d50e1f21082a5970f6996504c0541d54e376f9c98a6975c
This commit is contained in:
parent
cb0b676cd7
commit
fe114a4995
5 changed files with 297 additions and 289 deletions
|
@ -10,9 +10,9 @@ The goal is to create a frontend that's lightweight enough to be viewed without
|
|||
usable enough to improve the experience with JS.
|
||||
|
||||
Treebird uses C with FCGI, mastodont-c (library designed for Treebird, but can be used
|
||||
for other applications as well), and **optional** JavaScript for the frontend (100% functional without
|
||||
javascript, it only helps). Uses [RE:DOM](https://redom.js.org/) (3kb js library) to assist with DOM
|
||||
creation and native JS apis. (bundled, no need to build)
|
||||
for other applications as well), Perl, and **optional** JavaScript for the frontend (100% functional without
|
||||
javascript, it only helps). Uses [RE:DOM](https://redom.js.org/) (2kb JS library) to assist with DOM
|
||||
creation and native JS apis. (Already bundled)
|
||||
|
||||
## Why?
|
||||
|
||||
|
@ -26,7 +26,7 @@ This led me to one choice, to develop my own frontend.
|
|||
|
||||
Treebird respects compatibility with old browsers, and thus uses HTML table layouts, which are
|
||||
supported even by most modern terminal web browsers. The core browser we aim to at least maintain compatibility
|
||||
with is Netsurf, but most other browsers like GNU Emacs EWW, elinks, render Treebird wonderfully.
|
||||
with is Netsurf, but most other browsers like GNU Emacs EWW, elinks, render Treebird just alright.
|
||||
|
||||
## Credits
|
||||
|
||||
|
|
570
dist/js/main.js
vendored
570
dist/js/main.js
vendored
|
@ -1,312 +1,318 @@
|
|||
(function(){
|
||||
Element.prototype.insertAfter = function(element) {
|
||||
element.parentNode.insertBefore(this, element.nextSibling);
|
||||
const { el, mount } = redom;
|
||||
|
||||
'use strict';
|
||||
|
||||
function get_cookie(cookiestr)
|
||||
{
|
||||
return document.cookie
|
||||
.split(';')
|
||||
.find(row => row.startsWith(cookiestr+'='))
|
||||
.split('=')[1];
|
||||
}
|
||||
|
||||
// TODO Check if logged in .acct value is the same
|
||||
function reply_get_mentions(reply, content)
|
||||
{
|
||||
const regexpr = /<a target="_parent" class="mention" href="https?:\/\/.*?\/@(.*?)@(.*?)">.*?<\/a>/g;
|
||||
const arr = [...content.matchAll(regexpr)];
|
||||
let res = reply ? `@${reply} ` : "";
|
||||
const matches = content.matchAll(regexpr);
|
||||
|
||||
for (let x of matches)
|
||||
{
|
||||
res += `@${x[1]}@${x[2]} `;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
function form_enter_submit(e, that)
|
||||
{
|
||||
if ((e.ctrlKey || e.metaKey) && e.keyCode === 13)
|
||||
that.closest('form').submit();
|
||||
}
|
||||
|
||||
// Submit form entry on enter when in textbox/textarea
|
||||
document.querySelectorAll("input[type=text], input[type=url], input[type=email], input[type=password], textarea").forEach((i) => {
|
||||
i.addEventListener("keydown", e => form_enter_submit(e, i));
|
||||
});
|
||||
|
||||
function construct_query(query)
|
||||
{
|
||||
let query_string = "";
|
||||
let keys = Object.keys(query);
|
||||
let vals = Object.values(query);
|
||||
const len = keys.length;
|
||||
|
||||
for (let i = 0; i < keys.length; ++i)
|
||||
{
|
||||
query_string += keys[i] + "=" + vals[i];
|
||||
if (i !== keys.length-1)
|
||||
query_string += "&";
|
||||
}
|
||||
return query_string;
|
||||
}
|
||||
|
||||
function send_request(url, query, type, cb, cb_args)
|
||||
{
|
||||
let query_str = construct_query(query);
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open(type, url);
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
xhr.onreadystatechange = function() {
|
||||
if (this.readyState === XMLHttpRequest.DONE)
|
||||
cb(this, cb_args);
|
||||
};
|
||||
xhr.send(query_str);
|
||||
return xhr;
|
||||
}
|
||||
|
||||
function get_cookie(cookiestr)
|
||||
{
|
||||
return document.cookie
|
||||
.split(';')
|
||||
.find(row => row.startsWith(cookiestr+'='))
|
||||
.split('=')[1];
|
||||
}
|
||||
function upload_file(url, file_name, file, cb, cb_args, onprogress, onload)
|
||||
{
|
||||
let xhr = new XMLHttpRequest();
|
||||
let form_data = new FormData();
|
||||
xhr.open("post", url);
|
||||
form_data.append(file_name, file);
|
||||
// xhr.setRequestHeader("Content-Type", "multipart/form-data");
|
||||
xhr.onreadystatechange = function() {
|
||||
if (this.readyState === XMLHttpRequest.DONE)
|
||||
cb(this, cb_args);
|
||||
};
|
||||
xhr.upload.onprogress = onprogress;
|
||||
xhr.upload.onload = onload;
|
||||
xhr.send(form_data);
|
||||
return xhr;
|
||||
}
|
||||
|
||||
// TODO Check if logged in .acct value is the same
|
||||
function reply_get_mentions(reply, content)
|
||||
{
|
||||
const regexpr = /<a target="_parent" class="mention" href="https?:\/\/.*?\/@(.*?)@(.*?)">.*?<\/a>/g;
|
||||
const arr = [...content.matchAll(regexpr)];
|
||||
let res = reply ? `@${reply} ` : "";
|
||||
const matches = content.matchAll(regexpr);
|
||||
function change_count_text(val, sum)
|
||||
{
|
||||
if (val === "")
|
||||
val = 0
|
||||
else
|
||||
val = parseInt(val);
|
||||
val += sum;
|
||||
return val > 0 ? val.toString() : "";
|
||||
}
|
||||
|
||||
for (let x of matches)
|
||||
function interact_action(status, type)
|
||||
{
|
||||
let like = status.querySelector(".statbtn .like");
|
||||
let repeat = status.querySelector(".statbtn .repeat");
|
||||
|
||||
let svg;
|
||||
if (type.value === "like" || type.value === "unlike")
|
||||
svg = [ like ];
|
||||
else if (type.value === "repeat" || type.value === "unrepeat")
|
||||
svg = [ repeat ];
|
||||
else if (type.value === "likeboost")
|
||||
svg = [ like, repeat ];
|
||||
|
||||
if (svg)
|
||||
svg.forEach(that => {
|
||||
let label = that.parentNode;
|
||||
let counter = label.querySelector(".count");
|
||||
let is_interacted = label.classList.contains("interacted");
|
||||
|
||||
if (counter)
|
||||
{
|
||||
res += `@${x[1]}@${x[2]} `;
|
||||
counter.innerHTML = change_count_text(counter.innerHTML, is_interacted ? -1 : 1);
|
||||
}
|
||||
else {
|
||||
// Nobody interacted with this yet, create counter
|
||||
const counter = el("span.count", 1)
|
||||
mount(label, counter);
|
||||
is_interacted = false;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function form_enter_submit(e, that)
|
||||
{
|
||||
if ((e.ctrlKey || e.metaKey) && e.keyCode === 13)
|
||||
that.closest('form').submit();
|
||||
}
|
||||
|
||||
// Submit form entry on enter when in textbox/textarea
|
||||
document.querySelectorAll("input[type=text], input[type=url], input[type=email], input[type=password], textarea").forEach((i) => {
|
||||
i.addEventListener("keydown", e => form_enter_submit(e, i));
|
||||
if (is_interacted)
|
||||
{
|
||||
// Animation
|
||||
that.classList.remove("interacted-anim");
|
||||
label.classList.remove("interacted");
|
||||
|
||||
// Flip itype value
|
||||
type.value = type.value.replace("un", "");
|
||||
}
|
||||
else {
|
||||
that.classList.add("interacted-anim");
|
||||
label.classList.add("interacted");
|
||||
type.value = "un" + type.value;
|
||||
}
|
||||
});
|
||||
|
||||
function construct_query(query)
|
||||
}
|
||||
|
||||
function status_event(e)
|
||||
{
|
||||
let target = e.target.closest(".statbtn");
|
||||
if (target)
|
||||
{
|
||||
query_string = "";
|
||||
let keys = Object.keys(query);
|
||||
let vals = Object.values(query);
|
||||
const len = keys.length;
|
||||
console.log('huh');
|
||||
// Don't JS these
|
||||
if (target.classList.contains("reply-btn") ||
|
||||
target.classList.contains("view-btn") ||
|
||||
target.classList.contains("statbtn-last"))
|
||||
return true;
|
||||
let type = target.parentNode.querySelector(".itype");
|
||||
let status = e.target.closest(".status");
|
||||
|
||||
for (let i = 0; i < keys.length; ++i)
|
||||
{
|
||||
query_string += keys[i] + "=" + vals[i];
|
||||
if (i !== keys.length-1)
|
||||
query_string += "&";
|
||||
}
|
||||
return query_string;
|
||||
}
|
||||
|
||||
function send_request(url, query, type, cb, cb_args)
|
||||
{
|
||||
let query_str = construct_query(query);
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open(type, url);
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
xhr.onreadystatechange = function() {
|
||||
if (this.readyState === XMLHttpRequest.DONE)
|
||||
cb(this, cb_args);
|
||||
};
|
||||
xhr.send(query_str);
|
||||
return xhr;
|
||||
}
|
||||
|
||||
function upload_file(url, file_name, file, cb, cb_args, onprogress, onload)
|
||||
{
|
||||
let xhr = new XMLHttpRequest();
|
||||
let form_data = new FormData();
|
||||
xhr.open("post", url);
|
||||
form_data.append(file_name, file);
|
||||
// xhr.setRequestHeader("Content-Type", "multipart/form-data");
|
||||
xhr.onreadystatechange = function() {
|
||||
if (this.readyState === XMLHttpRequest.DONE)
|
||||
cb(this, cb_args);
|
||||
};
|
||||
xhr.upload.onprogress = onprogress;
|
||||
xhr.upload.onload = onload;
|
||||
xhr.send(form_data);
|
||||
return xhr;
|
||||
}
|
||||
|
||||
function change_count_text(val, sum)
|
||||
{
|
||||
if (val === "")
|
||||
val = 0
|
||||
else
|
||||
val = parseInt(val);
|
||||
val += sum;
|
||||
return val > 0 ? val.toString() : "";
|
||||
}
|
||||
|
||||
function interact_action(status, type)
|
||||
{
|
||||
let like = status.querySelector(".statbtn .like");
|
||||
let repeat = status.querySelector(".statbtn .repeat");
|
||||
|
||||
let svg;
|
||||
if (type.value === "like" || type.value === "unlike")
|
||||
svg = [ like ];
|
||||
else if (type.value === "repeat" || type.value === "unrepeat")
|
||||
svg = [ repeat ];
|
||||
else if (type.value === "likeboost")
|
||||
svg = [ like, repeat ];
|
||||
|
||||
svg.forEach(that => {
|
||||
let label = that.parentNode;
|
||||
let counter = label.querySelector(".count");
|
||||
|
||||
let is_active = that.classList.contains("active");
|
||||
|
||||
that.classList.toggle("active");
|
||||
|
||||
|
||||
if (is_active)
|
||||
{
|
||||
// Animation
|
||||
that.classList.remove("active-anim");
|
||||
|
||||
// Flip itype value
|
||||
type.value = type.value.replace("un", "");
|
||||
}
|
||||
else {
|
||||
that.classList.add("active-anim");
|
||||
type.value = "un" + type.value;
|
||||
}
|
||||
|
||||
counter.innerHTML = change_count_text(counter.innerHTML, is_active ? -1 : 1);
|
||||
});
|
||||
}
|
||||
|
||||
function status_event(e)
|
||||
{
|
||||
let target = e.target.closest(".statbtn");
|
||||
console.log(target);
|
||||
if (target)
|
||||
{
|
||||
// Don't JS these
|
||||
if (target.classList.contains("reply-btn") || target.classList.contains("view-btn"))
|
||||
return true;
|
||||
let type = target.parentNode.querySelector(".itype");
|
||||
if (type === null)
|
||||
return true;
|
||||
let status = e.srcElement;
|
||||
|
||||
send_request("/treebird_api/v1/interact",
|
||||
send_request("/treebird_api/v1/interact",
|
||||
{
|
||||
id: status.id,
|
||||
itype: type.value
|
||||
},
|
||||
"POST",
|
||||
(xhr, args) => {
|
||||
if (xhr.status !== 200)
|
||||
{
|
||||
id: status.id,
|
||||
itype: type.value
|
||||
},
|
||||
"POST",
|
||||
(xhr, args) => {
|
||||
if (xhr.status !== 200)
|
||||
{
|
||||
// Undo action if failure
|
||||
interact_action(status, type);
|
||||
}
|
||||
}, null);
|
||||
// Undo action if failure
|
||||
interact_action(status, type);
|
||||
}
|
||||
}, null);
|
||||
|
||||
interact_action(status, type);
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
interact_action(status, type);
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function frame_resize()
|
||||
function frame_resize()
|
||||
{
|
||||
let rightbar_frame = document.querySelector("#rightbar .sidebar-frame");
|
||||
let rbar_frame_win = rightbar_frame.contentWindow;
|
||||
rightbar_frame.height = rbar_frame_win.document.body.scrollHeight;
|
||||
}
|
||||
|
||||
function filesize_to_str(bs)
|
||||
{
|
||||
const val = bs === 0 ? 0 : Math.floor(Math.log(bs) / Math.log(1024));
|
||||
return (bs / 1024**val).toFixed(1) * 1 + ['B', 'kB', 'MB', 'GB', 'TB'][val];
|
||||
}
|
||||
|
||||
function html_encode(str)
|
||||
{
|
||||
let en = document.createElement("span");
|
||||
en.textContent = str;
|
||||
return en.innerHTML;
|
||||
}
|
||||
|
||||
function construct_file_upload(file, file_content)
|
||||
{
|
||||
let container = document.createElement("div");
|
||||
let content = document.createElement("img");
|
||||
let info = document.createElement("span");
|
||||
container.className = "file-upload";
|
||||
|
||||
info.className = "upload-info";
|
||||
info.innerHTML = `<span class="filesize">${filesize_to_str(file.size)}</span> • <span class="filename">${html_encode(file.name)}</span>`;
|
||||
let progress_div = document.createElement("div");
|
||||
progress_div.className = "file-progress";
|
||||
info.appendChild(progress_div);
|
||||
|
||||
content.src = file_content;
|
||||
content.className = "upload-content";
|
||||
|
||||
container.appendChild(content);
|
||||
container.appendChild(info);
|
||||
return container;
|
||||
}
|
||||
|
||||
function update_uploads_json(dom)
|
||||
{
|
||||
let root = dom.parentNode;
|
||||
let items = root.getElementsByClassName("file-upload");
|
||||
let ids = [];
|
||||
|
||||
for (let i of items)
|
||||
{
|
||||
let rightbar_frame = document.querySelector("#rightbar .sidebar-frame");
|
||||
let rbar_frame_win = rightbar_frame.contentWindow;
|
||||
rightbar_frame.height = rbar_frame_win.document.body.scrollHeight;
|
||||
if (i.dataset.id)
|
||||
ids.push(i.dataset.id);
|
||||
}
|
||||
|
||||
function filesize_to_str(bs)
|
||||
// Goto statusbox
|
||||
root = root.parentNode;
|
||||
let file_ids = root.querySelector(".file-ids-json");
|
||||
if (!file_ids)
|
||||
{
|
||||
const val = bs === 0 ? 0 : Math.floor(Math.log(bs) / Math.log(1024));
|
||||
return (bs / 1024**val).toFixed(1) * 1 + ['B', 'kB', 'MB', 'GB', 'TB'][val];
|
||||
// Create if doesn't exist
|
||||
file_ids = document.createElement("input");
|
||||
file_ids.type = "hidden";
|
||||
file_ids.className = "file-ids-json";
|
||||
file_ids.name = "fileids";
|
||||
root.appendChild(file_ids);
|
||||
}
|
||||
|
||||
function html_encode(str)
|
||||
file_ids.value = JSON.stringify(ids);
|
||||
}
|
||||
|
||||
function evt_file_upload(e)
|
||||
{
|
||||
let target = e.target;
|
||||
let file_upload_dom = this.closest("form").querySelector(".file-uploads-container");
|
||||
file_upload_dom.className = "file-uploads-container";
|
||||
const files = [...this.files];
|
||||
|
||||
let reader;
|
||||
|
||||
// Clear file input
|
||||
this.value = '';
|
||||
|
||||
// Create file upload
|
||||
for (let file of files)
|
||||
{
|
||||
let en = document.createElement("span");
|
||||
en.textContent = str;
|
||||
return en.innerHTML;
|
||||
}
|
||||
reader = new FileReader();
|
||||
reader.onload = (() => {
|
||||
return (e) => {
|
||||
let file_dom = construct_file_upload(file, e.target.result);
|
||||
|
||||
file_upload_dom.appendChild(file_dom);
|
||||
|
||||
function construct_file_upload(file, file_content)
|
||||
let xhr = upload_file("/treebird_api/v1/attachment",
|
||||
"file",
|
||||
file,
|
||||
(xhr, args) => {
|
||||
// TODO errors
|
||||
file_dom.dataset.id = JSON.parse(xhr.response).id;
|
||||
update_uploads_json(file_dom);
|
||||
}, null,
|
||||
(e) => {
|
||||
let upload_file_progress = file_dom
|
||||
.querySelector(".file-progress");
|
||||
// Add offset of 3
|
||||
upload_file_progress.style.width = 3+((e.loaded/e.total)*97);
|
||||
},
|
||||
(e) => {
|
||||
file_dom.querySelector(".upload-content").style.opacity = "1.0";
|
||||
file_dom.querySelector(".file-progress").remove();
|
||||
});
|
||||
}
|
||||
})(file);
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Main (when loaded)
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
let interact_btn = document.getElementsByClassName("status");
|
||||
|
||||
// Add event listener to add specificied buttons
|
||||
for (let i = 0; i < interact_btn.length; ++i)
|
||||
{
|
||||
let container = document.createElement("div");
|
||||
let content = document.createElement("img");
|
||||
let info = document.createElement("span");
|
||||
container.className = "file-upload";
|
||||
|
||||
info.className = "upload-info";
|
||||
info.innerHTML = `<span class="filesize">${filesize_to_str(file.size)}</span> • <span class="filename">${html_encode(file.name)}</span>`;
|
||||
let progress_div = document.createElement("div");
|
||||
progress_div.className = "file-progress";
|
||||
info.appendChild(progress_div);
|
||||
|
||||
content.src = file_content;
|
||||
content.className = "upload-content";
|
||||
|
||||
container.appendChild(content);
|
||||
container.appendChild(info);
|
||||
return container;
|
||||
interact_btn[i].addEventListener('click', status_event);
|
||||
}
|
||||
|
||||
function update_uploads_json(dom)
|
||||
|
||||
// Resize notifications iFrame to full height
|
||||
let rightbar_frame = document.querySelector("#rightbar .sidebar-frame");
|
||||
if (rightbar_frame)
|
||||
{
|
||||
let root = dom.parentNode;
|
||||
let items = root.getElementsByClassName("file-upload");
|
||||
let ids = [];
|
||||
|
||||
for (let i of items)
|
||||
{
|
||||
if (i.dataset.id)
|
||||
ids.push(i.dataset.id);
|
||||
}
|
||||
|
||||
// Goto statusbox
|
||||
root = root.parentNode;
|
||||
let file_ids = root.querySelector(".file-ids-json");
|
||||
if (!file_ids)
|
||||
{
|
||||
// Create if doesn't exist
|
||||
file_ids = document.createElement("input");
|
||||
file_ids.type = "hidden";
|
||||
file_ids.className = "file-ids-json";
|
||||
file_ids.name = "fileids";
|
||||
root.appendChild(file_ids);
|
||||
}
|
||||
|
||||
file_ids.value = JSON.stringify(ids);
|
||||
rightbar_frame.contentWindow.addEventListener('DOMContentLoaded', frame_resize);
|
||||
}
|
||||
|
||||
function evt_file_upload(e)
|
||||
// File upload
|
||||
let file_inputs = document.querySelectorAll(".statusbox input[type=file]");
|
||||
for (let file_input of file_inputs)
|
||||
{
|
||||
let target = e.target;
|
||||
let file_upload_dom = this.closest("form").querySelector(".file-uploads-container");
|
||||
file_upload_dom.className = "file-uploads-container";
|
||||
const files = [...this.files];
|
||||
|
||||
let reader;
|
||||
|
||||
// Clear file input
|
||||
this.value = '';
|
||||
|
||||
// Create file upload
|
||||
for (let file of files)
|
||||
{
|
||||
reader = new FileReader();
|
||||
reader.onload = (() => {
|
||||
return (e) => {
|
||||
let file_dom = construct_file_upload(file, e.target.result);
|
||||
|
||||
file_upload_dom.appendChild(file_dom);
|
||||
|
||||
let xhr = upload_file("/treebird_api/v1/attachment",
|
||||
"file",
|
||||
file,
|
||||
(xhr, args) => {
|
||||
// TODO errors
|
||||
file_dom.dataset.id = JSON.parse(xhr.response).id;
|
||||
update_uploads_json(file_dom);
|
||||
}, null,
|
||||
(e) => {
|
||||
let upload_file_progress = file_dom
|
||||
.querySelector(".file-progress");
|
||||
// Add offset of 3
|
||||
upload_file_progress.style.width = 3+((e.loaded/e.total)*97);
|
||||
},
|
||||
(e) => {
|
||||
file_dom.querySelector(".upload-content").style.opacity = "1.0";
|
||||
file_dom.querySelector(".file-progress").remove();
|
||||
});
|
||||
}
|
||||
})(file);
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
}
|
||||
file_input.addEventListener('change', evt_file_upload);
|
||||
}
|
||||
|
||||
// Main (when loaded)
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
let interact_btn = document.getElementsByClassName("status");
|
||||
|
||||
// Add event listener to add specificied buttons
|
||||
for (let i = 0; i < interact_btn.length; ++i)
|
||||
{
|
||||
interact_btn[i].addEventListener('click', status_event);
|
||||
}
|
||||
|
||||
// Resize notifications iFrame to full height
|
||||
let rightbar_frame = document.querySelector("#rightbar .sidebar-frame");
|
||||
if (rightbar_frame)
|
||||
{
|
||||
rightbar_frame.contentWindow.addEventListener('DOMContentLoaded', frame_resize);
|
||||
}
|
||||
|
||||
// File upload
|
||||
let file_inputs = document.querySelectorAll(".statusbox input[type=file]");
|
||||
for (let file_input of file_inputs)
|
||||
{
|
||||
file_input.addEventListener('change', evt_file_upload);
|
||||
}
|
||||
});
|
||||
})();
|
||||
});
|
||||
|
|
2
dist/treebird.css
vendored
2
dist/treebird.css
vendored
|
@ -1894,7 +1894,7 @@ p}
|
|||
stroke: #303030;
|
||||
}
|
||||
|
||||
.active-anim
|
||||
.interacted-anim
|
||||
{
|
||||
animation: interact .7s 1;
|
||||
}
|
||||
|
|
|
@ -207,7 +207,9 @@
|
|||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Soy filled JS lib -->
|
||||
<script src="$prefix/js/redom.min.js"></script>
|
||||
<!-- Source -->
|
||||
<script src="$prefix/js/main.js"></script>
|
||||
<script src="$prefix/js/emoji.js"></script>
|
||||
|
|
|
@ -172,7 +172,7 @@
|
|||
</label>
|
||||
</form>
|
||||
[% END %]
|
||||
<a target="_parent" class="statbtn statbtn-last" href="$prefix/status/[% status.id %]/react#[% status.id %]" class="pointer statbtn react-btn">
|
||||
<a target="_parent" class="statbtn pointer statbtn-last react-btn" href="$prefix/status/[% status.id %]/react#[% status.id %]">
|
||||
[% icon('emoji') %]
|
||||
</a>
|
||||
[% IF emoji_picker -%]
|
||||
|
|
Loading…
Reference in a new issue