add basic support for custom emoji
This commit is contained in:
parent
dccfddaec1
commit
d794e88bdc
|
@ -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
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"
|
% 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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
|
@ -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
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;
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue