add basic support for custom emoji
This commit is contained in:
parent
dccfddaec1
commit
d794e88bdc
|
@ -19,6 +19,13 @@
|
|||
% id = "01H89RFHCQDC73N0MD2ZF629MF"
|
||||
- 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"
|
||||
+ ### the treehouse is a statement of artistic expression
|
||||
|
||||
|
@ -125,6 +132,7 @@
|
|||
% id = "01H89RFHCQ48R7BCZV8JWPVFCY"
|
||||
+ have I invented something new here?
|
||||
|
||||
% id = "01H8VWEFJ1BGA21FBVHC4TFF3V"
|
||||
- the "Choose Your Own Poem" lol
|
||||
|
||||
% id = "01H89RFHCQAXJ0ST31TP1A104V"
|
||||
|
|
8
content/about/emoji.tree
Normal file
8
content/about/emoji.tree
Normal 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
|
|
@ -4,31 +4,32 @@
|
|||
% id = "01H8V556P1PND8DQ73XBTZZJH7"
|
||||
- welcome! make yourself at home
|
||||
|
||||
% id = "01H8VWEHX501SNYQTE61WX7YJC"
|
||||
- _"owo, what's this?"_
|
||||
|
||||
% id = "about"
|
||||
content.link = "about"
|
||||
content.link = "about"
|
||||
+ ## about me
|
||||
|
||||
% id = "about-treehouse"
|
||||
content.link = "about-treehouse"
|
||||
content.link = "about-treehouse"
|
||||
+ ## about this
|
||||
|
||||
% id = "01H8V556P1GRAA3717VH3QJFMV"
|
||||
- hobby corners
|
||||
|
||||
% id = "programming"
|
||||
content.link = "2023-08-20-under-construction"
|
||||
content.link = "2023-08-20-under-construction"
|
||||
+ ## programming
|
||||
|
||||
% id = "music"
|
||||
content.link = "2023-08-20-under-construction"
|
||||
content.link = "2023-08-20-under-construction"
|
||||
+ ## music
|
||||
|
||||
% id = "games"
|
||||
content.link = "2023-08-20-under-construction"
|
||||
content.link = "2023-08-20-under-construction"
|
||||
+ ## games
|
||||
|
||||
% id = "3d-printing"
|
||||
content.link = "2023-08-20-under-construction"
|
||||
content.link = "2023-08-20-under-construction"
|
||||
+ ## 3D printing
|
||||
|
|
|
@ -15,6 +15,12 @@ pub struct Config {
|
|||
|
||||
/// Links exported to Markdown for use with reference syntax `[text][def:key]`.
|
||||
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 {
|
||||
|
|
|
@ -30,12 +30,18 @@ use pulldown_cmark::escape::{escape_href, escape_html, StrWrite};
|
|||
use pulldown_cmark::{Alignment, CodeBlockKind, Event, LinkType, Tag};
|
||||
use pulldown_cmark::{CowStr, Event::*};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::state::Treehouse;
|
||||
|
||||
enum TableState {
|
||||
Head,
|
||||
Body,
|
||||
}
|
||||
|
||||
struct HtmlWriter<'a, I, W> {
|
||||
treehouse: &'a Treehouse,
|
||||
config: &'a Config,
|
||||
|
||||
/// Iterator supplying events.
|
||||
iter: I,
|
||||
|
||||
|
@ -49,6 +55,8 @@ struct HtmlWriter<'a, I, W> {
|
|||
table_alignments: Vec<Alignment>,
|
||||
table_cell_index: usize,
|
||||
numbers: HashMap<CowStr<'a>, usize>,
|
||||
|
||||
in_code_block: bool,
|
||||
}
|
||||
|
||||
impl<'a, I, W> HtmlWriter<'a, I, W>
|
||||
|
@ -56,8 +64,10 @@ where
|
|||
I: Iterator<Item = Event<'a>>,
|
||||
W: StrWrite,
|
||||
{
|
||||
fn new(iter: I, writer: W) -> Self {
|
||||
fn new(treehouse: &'a Treehouse, config: &'a Config, iter: I, writer: W) -> Self {
|
||||
Self {
|
||||
treehouse,
|
||||
config,
|
||||
iter,
|
||||
writer,
|
||||
end_newline: true,
|
||||
|
@ -65,6 +75,7 @@ where
|
|||
table_alignments: vec![],
|
||||
table_cell_index: 0,
|
||||
numbers: HashMap::new(),
|
||||
in_code_block: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,7 +106,7 @@ where
|
|||
self.end_tag(tag)?;
|
||||
}
|
||||
Text(text) => {
|
||||
escape_html(&mut self.writer, &text)?;
|
||||
self.run_text(&text)?;
|
||||
self.end_newline = text.ends_with('\n');
|
||||
}
|
||||
Code(text) => {
|
||||
|
@ -211,6 +222,7 @@ where
|
|||
}
|
||||
}
|
||||
Tag::CodeBlock(info) => {
|
||||
self.in_code_block = true;
|
||||
if !self.end_newline {
|
||||
self.write_newline()?;
|
||||
}
|
||||
|
@ -342,6 +354,7 @@ where
|
|||
}
|
||||
Tag::CodeBlock(_) => {
|
||||
self.write("</code></pre>\n")?;
|
||||
self.in_code_block = false;
|
||||
}
|
||||
Tag::List(Some(_)) => {
|
||||
self.write("</ol>\n")?;
|
||||
|
@ -372,6 +385,107 @@ where
|
|||
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
|
||||
fn raw_text(&mut self) -> io::Result<()> {
|
||||
let mut nest = 0;
|
||||
|
@ -431,9 +545,9 @@ where
|
|||
/// </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
|
||||
I: Iterator<Item = Event<'a>>,
|
||||
{
|
||||
HtmlWriter::new(iter, s).run().unwrap();
|
||||
HtmlWriter::new(treehouse, config, iter, s).run().unwrap();
|
||||
}
|
||||
|
|
|
@ -111,7 +111,7 @@ pub fn branch_to_html(
|
|||
},
|
||||
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 {
|
||||
write!(
|
||||
|
|
|
@ -237,3 +237,10 @@ nav .logo {
|
|||
opacity: 100%;
|
||||
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
BIN
static/emoji/hueh.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
|
@ -135,6 +135,7 @@ async function navigateToBranch(fragment) {
|
|||
let [_root, ...path] = fullPath;
|
||||
if (path !== undefined) {
|
||||
let isNotAtIndexHtml = window.location.pathname != "/index.html";
|
||||
let lastBranch = null;
|
||||
for (let linked of path) {
|
||||
let branch = LinkedBranch.byLink.get(linked);
|
||||
|
||||
|
@ -144,7 +145,10 @@ async function navigateToBranch(fragment) {
|
|||
}
|
||||
|
||||
await branch.loadTree("navigateToBranch");
|
||||
branch.details.open = true;
|
||||
lastBranch = branch;
|
||||
}
|
||||
if (lastBranch != null) {
|
||||
expandDetailsRecursively(lastBranch.details);
|
||||
}
|
||||
window.location.hash = window.location.hash;
|
||||
}
|
||||
|
|
|
@ -7,3 +7,6 @@ author = "liquidex"
|
|||
[defs]
|
||||
"stitchkit/repo" = "https://github.com/liquidev/stitchkit"
|
||||
"dawd3/repo" = "https://github.com/liquidev/dawd3"
|
||||
|
||||
[emoji]
|
||||
hueh = "hueh.png"
|
||||
|
|
Loading…
Reference in a new issue