Sanitize, parsing, fix issues, CSS changes, and lots more - I forgot to commit all day lol

FossilOrigin-Name: 8b9833d6b00afa365e4fda12551947a20076e8ad6106aea80e8304e667e1110a
This commit is contained in:
nekobit 2022-07-02 04:31:21 +00:00
parent dab66cf833
commit c020158001
13 changed files with 207 additions and 104 deletions

2
dist/svg/menu.svg vendored
View File

@ -1 +1 @@
<svg width="20" height="20" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g stroke-linecap="butt" stroke-linejoin="miter" stroke-width="1.6404"><line x1="5.6" x2="18.6" y1="7.7996" y2="7.7996"/><line x1="5.6" x2="18.6" y1="12" y2="12"/><line x1="5.6" x2="18.6" y1="16.2" y2="16.2"/></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>

Before

Width:  |  Height:  |  Size: 410 B

After

Width:  |  Height:  |  Size: 313 B

1
dist/svg/search.svg vendored Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>

After

Width:  |  Height:  |  Size: 272 B

66
dist/treebird20.css vendored
View File

@ -6,9 +6,8 @@
html, body
{
margin: 0;
margin: 5px;
padding: 0;
margin-top: 0px;
}
html
@ -39,11 +38,6 @@ ul
margin: 12px;
}
#main-page-container
{
padding: 8px;
}
#main-page
{
margin: 8px;
@ -119,7 +113,6 @@ table.ui-table td
z-index: 999;
}
#navbar .navbar-btn
{
width: 20px;
@ -129,15 +122,22 @@ table.ui-table td
#navbar .leftbar-btn
{
float: left;
margin: 10px 0 10px 15px;
width: 30px;
height: 30px;
margin: 15px 0px 15px 15px;
/* width: 30px; */
/* height: 30px; */
}
#navbar .rightbar-btn
{
float: right;
margin: 15px;
margin: 16px 15px 15px 7px;
}
#navbar .search-btn-show
{
float: right;
display: none;
margin: 16px 12px 15px 16px;
}
#navbar img
@ -175,6 +175,13 @@ table.ui-table td
float: right;
}
/* Only show these on mobile */
.leftbar-btn, .rightbar-btn
{
display: none;
stroke: #404040;
}
#login-header
{
vertical-align: middle;
@ -2164,18 +2171,34 @@ input[type=checkbox].hidden:not(:checked) + .list-edit-content
z-index: 100;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
}
.rightbar-btn
{
display: inline-block;
}
.menu
{
margin-left: -30px;
}
}
/* Requires flexbox */
@media only screen and (max-width: 760px)
{
html, body
{
margin: 0;
padding: 0;
}
input[type=checkbox].hidden#leftbar-show:not(:checked) + input + .ui-table #leftbar
{
display: none;
}
#main-page-container
#main-page
{
padding: 0;
margin: 0;
@ -2205,8 +2228,17 @@ input[type=checkbox].hidden:not(:checked) + .list-edit-content
z-index: 100;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
}
.leftbar-btn
{
display: inline-block;
}
#navbar .search-btn-show
{
display: inline-block;
}
#navbar-right-container
{
width: 100%;
@ -2218,6 +2250,12 @@ input[type=checkbox].hidden:not(:checked) + .list-edit-content
margin: 0;
}
input[type=checkbox].hidden#searchbar-show:not(:checked) + #navbar-right-container
{
display: none;
}
.statusbox textarea
{
width: calc(100% - 11px);

View File

@ -26,6 +26,7 @@
#include "easprintf.h"
#include "status.h"
#include "http.h"
#include "string.h"
#include "base_page.h"
#include "scrobble.h"
#include "string_helpers.h"
@ -70,10 +71,20 @@ char* load_account_info(struct mstdnt_account* acct,
char* construct_account_sidebar(struct mstdnt_account* acct, size_t* size)
{
char* result = NULL;
char* sanitized_display_name = NULL;
char* display_name = NULL;
if (acct->display_name)
{
sanitized_display_name = sanitize_html(acct->display_name);
display_name = emojify(sanitized_display_name,
acct->emojis,
acct->emojis_len);
}
struct account_sidebar_template data = {
.prefix = config_url_prefix,
.avatar = acct->avatar,
.username = acct->display_name,
.username = display_name,
.statuses_text = L10N[L10N_EN_US][L10N_TAB_STATUSES],
.following_text = L10N[L10N_EN_US][L10N_TAB_FOLLOWING],
.followers_text = L10N[L10N_EN_US][L10N_TAB_FOLLOWERS],
@ -82,7 +93,12 @@ char* construct_account_sidebar(struct mstdnt_account* acct, size_t* size)
.followers_count = acct->followers_count,
.acct = acct->acct,
};
return tmpl_gen_account_sidebar(&data, size);
result = tmpl_gen_account_sidebar(&data, size);
if (sanitized_display_name != acct->display_name) free(sanitized_display_name);
if (display_name != sanitized_display_name &&
display_name != acct->display_name)
free(display_name);
return result;
}
// TODO put account stuff into one function to cleanup a bit
@ -273,7 +289,7 @@ void get_account_info(mastodont_t* api, struct session* ssn)
{
struct mstdnt_args m_args;
set_mstdnt_args(&m_args, ssn);
if (mastodont_verify_credentials(api, &m_args, &(ssn->acct), &(ssn->acct_storage)) == 0)
if (ssn->cookies.access_token.is_set && mastodont_verify_credentials(api, &m_args, &(ssn->acct), &(ssn->acct_storage)) == 0)
{
ssn->logged_in = 1;
}
@ -356,9 +372,11 @@ size_t construct_account_page(struct session* ssn,
* info_html = NULL,
* is_blocked = NULL,
* menubar = NULL,
* display_name = NULL;
display_name = emojify(page->display_name,
* display_name = NULL,
* sanitized_display_name = NULL;
sanitized_display_name = sanitize_html(page->display_name);
display_name = emojify(sanitized_display_name,
page->account->emojis,
page->account->emojis_len);
@ -412,7 +430,7 @@ size_t construct_account_page(struct session* ssn,
}
struct account_template acct_data = {
.block_text = STR_NULL_EMPTY(is_blocked),
.is_blocked = STR_NULL_EMPTY(is_blocked),
.header = page->header_image,
.menubar = menubar,
.display_name = display_name,
@ -465,7 +483,9 @@ size_t construct_account_page(struct session* ssn,
free(follow_btn);
free(is_blocked);
free(menubar);
if (display_name != page->display_name)
if (sanitized_display_name != page->display_name) free(sanitized_display_name);
if (display_name != page->display_name &&
display_name != sanitized_display_name)
free(display_name);
return size;
}
@ -475,14 +495,18 @@ char* construct_account(mastodont_t* api,
uint8_t flags,
size_t* size)
{
char* result;
char* sanitized_display_name = sanitize_html(acct->display_name);
struct account_stub_template data = {
.prefix = config_url_prefix,
.acct = acct->acct,
.avatar = acct->avatar,
.display_name = acct->display_name
.display_name = sanitized_display_name,
};
return tmpl_gen_account_stub(&data, size);
result = tmpl_gen_account_stub(&data, size);
if (sanitized_display_name != acct->display_name) free(sanitized_display_name);
return result;
}
static char* construct_account_voidwrap(void* passed, size_t index, size_t* res)
@ -550,7 +574,7 @@ void content_account_statuses(struct session* ssn, mastodont_t* api, char** data
.max_id = keystr(ssn->post.max_id),
.min_id = keystr(ssn->post.min_id),
.since_id = NULL,
.offset = 0,
.offset = keyint(ssn->query.offset),
.limit = 20,
};

View File

@ -55,7 +55,7 @@ void render_base_page(struct base_page* page, struct session* ssn, mastodont_t*
struct mstdnt_notification* notifs = NULL;
size_t notifs_len = 0;
if (ssn->config.logged_in)
if (keyint(ssn->cookies.logged_in))
login_string = "";
if (ssn->config.background_url)

View File

@ -19,7 +19,7 @@
#include "../config.h"
#include "global_cache.h"
struct global_cache g_cache = { 0 };
struct global_cache g_cache = { {} };
void load_instance_info_cache(mastodont_t* api)
{

View File

@ -23,6 +23,7 @@
#include "account.h"
#include "easprintf.h"
#include "error.h"
#include "string.h"
#include "status.h"
#include "lists.h"
#include "string_helpers.h"
@ -34,12 +35,18 @@
char* construct_list(struct mstdnt_list* list, size_t* size)
{
char* result;
char* title = list->title;
char* list_name = sanitize_html(title);
struct list_template data = {
.list = list->title,
.list = list_name,
.prefix = config_url_prefix,
.list_id = list->id
};
return tmpl_gen_list(&data, size);
result = tmpl_gen_list(&data, size);
if (list_name != title)
free(list_name);
return result;
}
static char* construct_list_voidwrap(void* passed, size_t index, size_t* res)

View File

@ -19,6 +19,7 @@
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include "string.h"
#include "helpers.h"
#include "notifications.h"
#include "base_page.h"
@ -69,7 +70,8 @@ char* construct_notification(struct session* ssn,
char* construct_notification_action(struct mstdnt_notification* notif, size_t* size)
{
char* res;
char* display_name = emojify(notif->account->display_name,
char* serialized_display_name = sanitize_html(notif->account->display_name);
char* display_name = emojify(serialized_display_name,
notif->account->emojis,
notif->account->emojis_len);
struct notification_action_template tdata = {
@ -81,8 +83,12 @@ char* construct_notification_action(struct mstdnt_notification* notif, size_t* s
.notif_svg = notification_type_svg(notif->type)
};
res = tmpl_gen_notification_action(&tdata, size);
if (display_name != notif->account->display_name)
/* // Cleanup */
if (display_name != notif->account->display_name &&
display_name != serialized_display_name)
free(display_name);
if (serialized_display_name != notif->account->display_name)
free(serialized_display_name);
return res;
}
@ -109,7 +115,8 @@ char* construct_notification_compact(struct session* ssn,
notif->status->emojis_len);
}
char* display_name = emojify(notif->account->display_name,
char* serialized_display_name = sanitize_html(notif->account->display_name);
char* display_name = emojify(serialized_display_name,
notif->account->emojis,
notif->account->emojis_len);
struct notification_compact_template tdata = {
@ -130,7 +137,10 @@ char* construct_notification_compact(struct session* ssn,
if (status_format &&
status_format != notif->status->content) free(status_format);
if (notif_stats) free(notif_stats);
if (display_name != notif->account->display_name)
if (serialized_display_name != notif->account->display_name)
free(serialized_display_name);
if (display_name != notif->account->display_name &&
display_name != serialized_display_name)
free(display_name);
return notif_html;
}

View File

@ -644,6 +644,7 @@ char* construct_status(struct session* ssn,
char* formatted_display_name = NULL;
char* attachments = NULL;
char* emoji_reactions = NULL;
char* serialized_display_name = NULL;
char* interaction_btns = NULL;
char* notif_info = NULL;
char* in_reply_to_str = NULL;
@ -709,10 +710,10 @@ char* construct_status(struct session* ssn,
}
// Format username with emojis
formatted_display_name = emojify(status->account.display_name,
serialized_display_name = sanitize_html(status->account.display_name);
formatted_display_name = emojify(serialized_display_name,
status->account.emojis,
status->account.emojis_len);
// Format status
char* parse_content = reformat_status(ssn, status->content, status->emojis, status->emojis_len);
@ -758,7 +759,8 @@ char* construct_status(struct session* ssn,
emoji_reactions = construct_emoji_reactions(status->id, status->pleroma.emoji_reactions, status->pleroma.emoji_reactions_len, NULL);
if (notif && notif->type != MSTDNT_NOTIFICATION_MENTION)
{
char* notif_display_name = emojify(notif->account->display_name,
char* notif_serialized_name = sanitize_html(notif->account->display_name);
char* notif_display_name = emojify(notif_serialized_name,
notif->account->emojis,
notif->account->emojis_len);
struct notification_template tdata = {
@ -770,6 +772,9 @@ char* construct_status(struct session* ssn,
notif_info = tmpl_gen_notification(&tdata, NULL);
if (notif_display_name != notif->account->display_name)
free(notif_display_name);
if (notif_serialized_name != notif_display_name &&
notif_serialized_name != notif->account->display_name)
free(notif_serialized_name);
}
if (status->in_reply_to_id && status->in_reply_to_account_id)
@ -805,7 +810,10 @@ char* construct_status(struct session* ssn,
// Cleanup
if (formatted_display_name != status->account.display_name &&
formatted_display_name) free(formatted_display_name);
formatted_display_name != serialized_display_name)
free(formatted_display_name);
if (serialized_display_name != status->account.display_name)
free(serialized_display_name);
free(interaction_btns);
free(in_reply_to_str);
free(attachments);

View File

@ -128,3 +128,15 @@ char* strrepl(char* source, char* find, char* repl, int flags)
return result ? result : source;
}
char* sanitize_html(char* html)
{
char* amp = strrepl(html, "&", "&amp;", STRREPL_ALL);
char* left = strrepl(amp, "<", "&lt;", STRREPL_ALL);
char* right = strrepl(left, ">", "&gt;", STRREPL_ALL);
char* quot = strrepl(right, "\"", "&quot;", STRREPL_ALL);
if (quot != right && right != html && right != left) free(right);
if (left != html && left != amp) free(left);
if (amp != html) free(amp);
return quot;
}

View File

@ -39,4 +39,6 @@ int strneql(char* cmp1, char* cmp2, size_t cmp1_n);
char* strnstr(const char* haystack, const char* needle, size_t s);
char* strrepl(char* source, char* find, char* replace, int flags);
char* sanitize_html(char* html);
#endif // TREE_STRING_H

View File

@ -120,7 +120,7 @@ void content_timeline(struct session* ssn,
void tl_home(struct session* ssn, mastodont_t* api, int local)
{
struct mstdnt_args m_args;
struct mstdnt_args m_args = { 0 };
set_mstdnt_args(&m_args, ssn);
size_t statuses_len = 0;
struct mstdnt_status* statuses = NULL;
@ -137,7 +137,7 @@ void tl_home(struct session* ssn, mastodont_t* api, int local)
.max_id = keystr(ssn->post.max_id),
.since_id = NULL,
.min_id = keystr(ssn->post.min_id),
.offset = 4,
.offset = 0,
.limit = 20,
.remote = MSTDNT_BOOL_UNSET,
};
@ -151,7 +151,7 @@ void tl_home(struct session* ssn, mastodont_t* api, int local)
void tl_direct(struct session* ssn, mastodont_t* api)
{
struct mstdnt_args m_args;
struct mstdnt_args m_args = { 0 };
set_mstdnt_args(&m_args, ssn);
size_t statuses_len = 0;
struct mstdnt_status* statuses = NULL;
@ -179,7 +179,7 @@ void tl_direct(struct session* ssn, mastodont_t* api)
void tl_public(struct session* ssn, mastodont_t* api, int local, enum base_category cat)
{
struct mstdnt_args m_args;
struct mstdnt_args m_args = { 0 };
set_mstdnt_args(&m_args, ssn);
size_t statuses_len = 0;
struct mstdnt_status* statuses = NULL;

View File

@ -9,71 +9,72 @@
<meta name="description" content="{{ %s : title }} is a decentralized social media platform">
</head>
<body {{ %s : background_url }}>
<div></div>
<div id="main-page-container">
<div id="main-page">
<header id="navbar">
<label for="leftbar-show">
<svg class="leftbar-btn navbar-btn" width="20" height="20" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g stroke-linecap="butt" stroke-linejoin="miter" stroke-width="1.6404"><line x1="5.6" x2="18.6" y1="7.7996" y2="7.7996"/><line x1="5.6" x2="18.6" y1="12" y2="12"/><line x1="5.6" x2="18.6" y1="16.2" y2="16.2"/></g></svg>
</label>
<a href="{{ %s : prefix }}/"><img src="/treebird_logo.png" height="42"></a>
<span class="info">{{ %s : name }}</span>
<label for="rightbar-show">
<svg class="rightbar-btn navbar-btn" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 17H2a3 3 0 0 0 3-3V9a7 7 0 0 1 14 0v5a3 3 0 0 0 3 3zm-8.27 4a2 2 0 0 1-3.46 0"></path></svg>
</label>
<div id="navbar-right-container">
<div id="navbar-right">
{{ %s : sidebar_cnt }}
<!-- Searchbox -->
<form action="{{ %s : prefix }}/search" method="get">
<input type="textbox" class="group group-left group-inputbox" placeholder="{{ %s : placeholder }}" id="searchbox" name="q"><!-- i hate HTML
--><input type="submit" class="btn group group-right" value="{{ %s : search_btn }}">
</form>
</div>
<div id="main-page">
<header id="navbar">
<label for="leftbar-show">
<svg class="leftbar-btn navbar-btn" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
</label>
<a href="{{ %s : prefix }}/"><img src="/treebird_logo.png" height="42"></a>
<span class="info">{{ %s : name }}</span>
<label for="rightbar-show">
<svg class="rightbar-btn navbar-btn" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 17H2a3 3 0 0 0 3-3V9a7 7 0 0 1 14 0v5a3 3 0 0 0 3 3zm-8.27 4a2 2 0 0 1-3.46 0"></path></svg>
</label>
<label for="searchbar-show">
<svg class="search-btn-show" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
</label>
<input type="checkbox" class="hidden" id="searchbar-show">
<div id="navbar-right-container">
<div id="navbar-right">
{{ %s : sidebar_cnt }}
<!-- Searchbox -->
<form action="{{ %s : prefix }}/search" method="get">
<input type="textbox" class="group group-left group-inputbox" placeholder="{{ %s : placeholder }}" id="searchbox" name="q"><!-- i hate HTML
--><input type="submit" class="btn group group-right" value="{{ %s : search_btn }}">
</form>
</div>
</header>
<input type="checkbox" id="leftbar-show" class="hidden">
<input type="checkbox" id="rightbar-show" class="hidden">
<table id="content" class="ui-table">
<!-- Navigation -->
<tr>
<td id="leftbar" class="sidebar">
<ul>
<li><a class="sidebarbtn {{ %s : active_home }}" href="{{ %s : prefix}}/">{{ %s : home }}</a></li>
<li><a class="sidebarbtn {{ %s : active_local }}" href="{{ %s : prefix}}/local/">{{ %s : local}}</a></li>
<li><a class="sidebarbtn {{ %s : active_federated }}" href="{{ %s : prefix}}/federated/">{{ %s : federated }}</a></li>
<li><a class="sidebarbtn {{ %s : active_notifications }}" href="{{ %s : prefix}}/notifications">{{ %s : notifications }}</a></li>
<li><a class="sidebarbtn {{ %s : active_lists }}" href="{{ %s : prefix}}/lists">{{ %s : lists }}</a></li>
<li><a class="sidebarbtn {{ %s : active_favourites }}" href="{{ %s : prefix}}/favourites">{{ %s : favourites }}</a></li>
<li><a class="sidebarbtn {{ %s : active_bookmarks }}" href="{{ %s : prefix}}/bookmarks">{{ %s : bookmarks }}</a></li>
<li><a class="sidebarbtn {{ %s : active_direct }}" href="{{ %s : prefix}}/direct">{{ %s : direct }}</a></li>
<li><a class="sidebarbtn {{ %s : active_config }}" href="{{ %s : prefix}}/config">{{ %s : config }}</a></li>
</ul>
{{ %s : sidebar_leftbar }}
{{ %s : instance_panel }}
<div class="mini-links">
<a href="{{%s:prefix}}/about">{{ %s : about_link_str }}</a>
<span class="bullet-separate">&bull;</span>
<a href="{{%s:prefix}}/about/license">{{ %s : license_link_str }}</a>
<span class="bullet-separate">&bull;</span>
<a href="https://fossil.nekobit.net/treebird">{{ %s : source_link_str }}</a>
</div>
</td>
<!-- Display for posts -->
<td id="main">
{{ %s : main }}
</td>
</div>
</header>
<input type="checkbox" id="leftbar-show" class="hidden">
<input type="checkbox" id="rightbar-show" class="hidden">
<table id="content" class="ui-table">
<!-- Navigation -->
<tr>
<td id="leftbar" class="sidebar">
<ul>
<li><a class="sidebarbtn {{ %s : active_home }}" href="{{ %s : prefix}}/">{{ %s : home }}</a></li>
<li><a class="sidebarbtn {{ %s : active_local }}" href="{{ %s : prefix}}/local/">{{ %s : local}}</a></li>
<li><a class="sidebarbtn {{ %s : active_federated }}" href="{{ %s : prefix}}/federated/">{{ %s : federated }}</a></li>
<li><a class="sidebarbtn {{ %s : active_notifications }}" href="{{ %s : prefix}}/notifications">{{ %s : notifications }}</a></li>
<li><a class="sidebarbtn {{ %s : active_lists }}" href="{{ %s : prefix}}/lists">{{ %s : lists }}</a></li>
<li><a class="sidebarbtn {{ %s : active_favourites }}" href="{{ %s : prefix}}/favourites">{{ %s : favourites }}</a></li>
<li><a class="sidebarbtn {{ %s : active_bookmarks }}" href="{{ %s : prefix}}/bookmarks">{{ %s : bookmarks }}</a></li>
<li><a class="sidebarbtn {{ %s : active_direct }}" href="{{ %s : prefix}}/direct">{{ %s : direct }}</a></li>
<li><a class="sidebarbtn {{ %s : active_config }}" href="{{ %s : prefix}}/config">{{ %s : config }}</a></li>
</ul>
{{ %s : sidebar_leftbar }}
{{ %s : instance_panel }}
<div class="mini-links">
<a href="{{%s:prefix}}/about">{{ %s : about_link_str }}</a>
<span class="bullet-separate">&bull;</span>
<a href="{{%s:prefix}}/about/license">{{ %s : license_link_str }}</a>
<span class="bullet-separate">&bull;</span>
<a href="https://fossil.nekobit.net/treebird">{{ %s : source_link_str }}</a>
</div>
</td>
<!-- Display for posts -->
<td id="main">
{{ %s : main }}
</td>
<!-- Notifications and such -->
<td id="rightbar" class="sidebar">
{{ %s : sidebar_rightbar }}
</td>
</tr>
</table>
</div>
<!-- Notifications and such -->
<td id="rightbar" class="sidebar">
{{ %s : sidebar_rightbar }}
</td>
</tr>
</table>
</div>
<!-- Source -->
<script src="/js/main.js"></script>
<script src="/js/emoji.js"></script>