add basic support for custom emoji

This commit is contained in:
liquidex 2023-08-27 18:25:21 +02:00
parent dccfddaec1
commit d794e88bdc
10 changed files with 163 additions and 12 deletions

View file

@ -19,6 +19,13 @@
% id = "01H89RFHCQDC73N0MD2ZF629MF" % id = "01H89RFHCQDC73N0MD2ZF629MF"
- wouldn't you make yourself at home? - wouldn't you make yourself at home?
% id = "01H8VWEFHZA94G0DNPD79YV535"
+ …
% content.link = "about/emoji"
id = "01H8VWEFHZ7Z71WJ347WFMC9YT"
+ by the way did you know this website has custom emojis? and quite a lot of them, too
% id = "01H89RFHCQKAPHSGCDN832QRMD" % id = "01H89RFHCQKAPHSGCDN832QRMD"
+ ### the treehouse is a statement of artistic expression + ### the treehouse is a statement of artistic expression
@ -125,6 +132,7 @@
% id = "01H89RFHCQ48R7BCZV8JWPVFCY" % id = "01H89RFHCQ48R7BCZV8JWPVFCY"
+ have I invented something new here? + have I invented something new here?
% id = "01H8VWEFJ1BGA21FBVHC4TFF3V"
- the "Choose Your Own Poem" lol - the "Choose Your Own Poem" lol
% id = "01H89RFHCQAXJ0ST31TP1A104V" % id = "01H89RFHCQAXJ0ST31TP1A104V"

8
content/about/emoji.tree Normal file
View file

@ -0,0 +1,8 @@
% id = "01H8VWE6M29SZXJDDAX50K99V7"
- ### the emojipedia
% id = "01H8VWE6M2EN8YBGPT1RTWE638"
- (no, not that one.)
% id = "emoji/hueh"
- :hueh: - stolen from the Hat in Time Discord server

View file

@ -4,31 +4,32 @@
% id = "01H8V556P1PND8DQ73XBTZZJH7" % id = "01H8V556P1PND8DQ73XBTZZJH7"
- welcome! make yourself at home - welcome! make yourself at home
% id = "01H8VWEHX501SNYQTE61WX7YJC"
- _"owo, what's this?"_ - _"owo, what's this?"_
% id = "about" % id = "about"
content.link = "about" content.link = "about"
+ ## about me + ## about me
% id = "about-treehouse" % id = "about-treehouse"
content.link = "about-treehouse" content.link = "about-treehouse"
+ ## about this + ## about this
% id = "01H8V556P1GRAA3717VH3QJFMV" % id = "01H8V556P1GRAA3717VH3QJFMV"
- hobby corners - hobby corners
% id = "programming" % id = "programming"
content.link = "2023-08-20-under-construction" content.link = "2023-08-20-under-construction"
+ ## programming + ## programming
% id = "music" % id = "music"
content.link = "2023-08-20-under-construction" content.link = "2023-08-20-under-construction"
+ ## music + ## music
% id = "games" % id = "games"
content.link = "2023-08-20-under-construction" content.link = "2023-08-20-under-construction"
+ ## games + ## games
% id = "3d-printing" % id = "3d-printing"
content.link = "2023-08-20-under-construction" content.link = "2023-08-20-under-construction"
+ ## 3D printing + ## 3D printing

View file

@ -15,6 +15,12 @@ pub struct Config {
/// Links exported to Markdown for use with reference syntax `[text][def:key]`. /// Links exported to Markdown for use with reference syntax `[text][def:key]`.
pub defs: HashMap<String, String>, pub defs: HashMap<String, String>,
/// Overrides for emoji filenames. Useful for setting up aliases.
///
/// On top of this, emojis are autodiscovered by walking the `static/emoji` directory.
#[serde(default)]
pub emoji: HashMap<String, String>,
} }
impl Config { impl Config {

View file

@ -30,12 +30,18 @@ use pulldown_cmark::escape::{escape_href, escape_html, StrWrite};
use pulldown_cmark::{Alignment, CodeBlockKind, Event, LinkType, Tag}; use pulldown_cmark::{Alignment, CodeBlockKind, Event, LinkType, Tag};
use pulldown_cmark::{CowStr, Event::*}; use pulldown_cmark::{CowStr, Event::*};
use crate::config::Config;
use crate::state::Treehouse;
enum TableState { enum TableState {
Head, Head,
Body, Body,
} }
struct HtmlWriter<'a, I, W> { struct HtmlWriter<'a, I, W> {
treehouse: &'a Treehouse,
config: &'a Config,
/// Iterator supplying events. /// Iterator supplying events.
iter: I, iter: I,
@ -49,6 +55,8 @@ struct HtmlWriter<'a, I, W> {
table_alignments: Vec<Alignment>, table_alignments: Vec<Alignment>,
table_cell_index: usize, table_cell_index: usize,
numbers: HashMap<CowStr<'a>, usize>, numbers: HashMap<CowStr<'a>, usize>,
in_code_block: bool,
} }
impl<'a, I, W> HtmlWriter<'a, I, W> impl<'a, I, W> HtmlWriter<'a, I, W>
@ -56,8 +64,10 @@ where
I: Iterator<Item = Event<'a>>, I: Iterator<Item = Event<'a>>,
W: StrWrite, W: StrWrite,
{ {
fn new(iter: I, writer: W) -> Self { fn new(treehouse: &'a Treehouse, config: &'a Config, iter: I, writer: W) -> Self {
Self { Self {
treehouse,
config,
iter, iter,
writer, writer,
end_newline: true, end_newline: true,
@ -65,6 +75,7 @@ where
table_alignments: vec![], table_alignments: vec![],
table_cell_index: 0, table_cell_index: 0,
numbers: HashMap::new(), numbers: HashMap::new(),
in_code_block: false,
} }
} }
@ -95,7 +106,7 @@ where
self.end_tag(tag)?; self.end_tag(tag)?;
} }
Text(text) => { Text(text) => {
escape_html(&mut self.writer, &text)?; self.run_text(&text)?;
self.end_newline = text.ends_with('\n'); self.end_newline = text.ends_with('\n');
} }
Code(text) => { Code(text) => {
@ -211,6 +222,7 @@ where
} }
} }
Tag::CodeBlock(info) => { Tag::CodeBlock(info) => {
self.in_code_block = true;
if !self.end_newline { if !self.end_newline {
self.write_newline()?; self.write_newline()?;
} }
@ -342,6 +354,7 @@ where
} }
Tag::CodeBlock(_) => { Tag::CodeBlock(_) => {
self.write("</code></pre>\n")?; self.write("</code></pre>\n")?;
self.in_code_block = false;
} }
Tag::List(Some(_)) => { Tag::List(Some(_)) => {
self.write("</ol>\n")?; self.write("</ol>\n")?;
@ -372,6 +385,107 @@ where
Ok(()) Ok(())
} }
fn run_text(&mut self, text: &str) -> io::Result<()> {
struct EmojiParser<'a> {
text: &'a str,
position: usize,
}
enum Token<'a> {
Text(&'a str),
Emoji(&'a str),
}
impl<'a> EmojiParser<'a> {
fn current(&self) -> Option<char> {
self.text[self.position..].chars().next()
}
fn next_token(&mut self) -> Option<Token<'a>> {
match self.current() {
Some(':') => {
let text_start = self.position;
self.position += 1;
if self.current().is_some_and(|c| c.is_alphabetic()) {
let name_start = self.position;
while let Some(c) = self.current() {
if c.is_alphanumeric() || c == '_' {
self.position += c.len_utf8();
} else {
break;
}
}
if self.current() == Some(':') {
let name_end = self.position;
self.position += 1;
Some(Token::Emoji(&self.text[name_start..name_end]))
} else {
Some(Token::Text(&self.text[text_start..self.position]))
}
} else {
Some(Token::Text(&self.text[text_start..self.position]))
}
}
Some(_) => {
let start = self.position;
while let Some(c) = self.current() {
if c == ':' {
break;
} else {
self.position += c.len_utf8();
}
}
let end = self.position;
Some(Token::Text(&self.text[start..end]))
}
None => None,
}
}
}
if self.in_code_block {
escape_html(&mut self.writer, text)?;
} else {
let mut parser = EmojiParser { text, position: 0 };
while let Some(token) = parser.next_token() {
match token {
Token::Text(text) => escape_html(&mut self.writer, text)?,
Token::Emoji(name) => {
if let Some(filename) = self.config.emoji.get(name) {
let branch_id = self
.treehouse
.branches_by_named_id
.get(&format!("emoji/{name}"))
.copied();
if let Some(branch) = branch_id.map(|id| self.treehouse.tree.branch(id))
{
self.writer.write_str("<a href=\"#")?;
escape_html(&mut self.writer, &branch.html_id)?;
self.writer.write_str("\">")?;
}
self.writer.write_str("<img class=\"emoji\" title=\":")?;
escape_html(&mut self.writer, name)?;
self.writer.write_str(":\" src=\"")?;
escape_html(&mut self.writer, &self.config.site)?;
self.writer.write_str("/static/emoji/")?;
escape_html(&mut self.writer, filename)?;
self.writer.write_str("\">")?;
if branch_id.is_some() {
self.writer.write_str("</a>")?;
}
} else {
self.writer.write_str(":")?;
escape_html(&mut self.writer, name)?;
self.writer.write_str(":")?;
}
}
}
}
}
Ok(())
}
// run raw text, consuming end tag // run raw text, consuming end tag
fn raw_text(&mut self) -> io::Result<()> { fn raw_text(&mut self) -> io::Result<()> {
let mut nest = 0; let mut nest = 0;
@ -431,9 +545,9 @@ where
/// </ul> /// </ul>
/// "#); /// "#);
/// ``` /// ```
pub fn push_html<'a, I>(s: &mut String, iter: I) pub fn push_html<'a, I>(s: &mut String, treehouse: &'a Treehouse, config: &'a Config, iter: I)
where where
I: Iterator<Item = Event<'a>>, I: Iterator<Item = Event<'a>>,
{ {
HtmlWriter::new(iter, s).run().unwrap(); HtmlWriter::new(treehouse, config, iter, s).run().unwrap();
} }

View file

@ -111,7 +111,7 @@ pub fn branch_to_html(
}, },
Some(broken_link_callback), Some(broken_link_callback),
); );
markdown::push_html(s, markdown_parser); markdown::push_html(s, treehouse, config, markdown_parser);
if let Content::Link(link) = &branch.attributes.content { if let Content::Link(link) = &branch.attributes.content {
write!( write!(

View file

@ -237,3 +237,10 @@ nav .logo {
opacity: 100%; opacity: 100%;
color: var(--text-color); color: var(--text-color);
} }
/* Style emojis to be readable */
img.emoji {
max-height: 1.375em;
vertical-align: bottom;
}

BIN
static/emoji/hueh.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -135,6 +135,7 @@ async function navigateToBranch(fragment) {
let [_root, ...path] = fullPath; let [_root, ...path] = fullPath;
if (path !== undefined) { if (path !== undefined) {
let isNotAtIndexHtml = window.location.pathname != "/index.html"; let isNotAtIndexHtml = window.location.pathname != "/index.html";
let lastBranch = null;
for (let linked of path) { for (let linked of path) {
let branch = LinkedBranch.byLink.get(linked); let branch = LinkedBranch.byLink.get(linked);
@ -144,7 +145,10 @@ async function navigateToBranch(fragment) {
} }
await branch.loadTree("navigateToBranch"); await branch.loadTree("navigateToBranch");
branch.details.open = true; lastBranch = branch;
}
if (lastBranch != null) {
expandDetailsRecursively(lastBranch.details);
} }
window.location.hash = window.location.hash; window.location.hash = window.location.hash;
} }

View file

@ -7,3 +7,6 @@ author = "liquidex"
[defs] [defs]
"stitchkit/repo" = "https://github.com/liquidev/stitchkit" "stitchkit/repo" = "https://github.com/liquidev/stitchkit"
"dawd3/repo" = "https://github.com/liquidev/dawd3" "dawd3/repo" = "https://github.com/liquidev/dawd3"
[emoji]
hueh = "hueh.png"