forked from blisstown/campfire
federating my verse (iceshrimp & mastodon API compat, read-only)
This commit is contained in:
parent
8dc8190cdf
commit
da93978820
67 changed files with 2743 additions and 649 deletions
166
src/post/Body.svelte
Normal file
166
src/post/Body.svelte
Normal file
|
@ -0,0 +1,166 @@
|
|||
<script>
|
||||
export let post;
|
||||
</script>
|
||||
|
||||
<div class="post-body">
|
||||
{#if post.warning}
|
||||
<p class="post-warning"><strong>{post.warning}</strong></p>
|
||||
{/if}
|
||||
{#if post.text}
|
||||
<span class="post-text">{@html post.rich_text}</span>
|
||||
{/if}
|
||||
<div class="post-media-container" data-count={post.files.length}>
|
||||
{#each post.files as file}
|
||||
<div class="post-media image">
|
||||
<a href={file.url}>
|
||||
<img src={file.url} alt={file.alt} height="200" loading="lazy" decoding="async">
|
||||
</a>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if post.boost && post.text}
|
||||
<p class="post-warning"><strong>this is quoting a post! quotes are not supported yet.</strong></p>
|
||||
<!-- TODO: quotes support -->
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.post-body {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.post-warning {
|
||||
padding: 4px 8px;
|
||||
--warn-bg: rgba(255,220,30,.2);
|
||||
background-image: repeating-linear-gradient(-45deg, transparent, transparent 10px, var(--warn-bg) 10px, var(--warn-bg) 20px);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.post-text {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.post-text :global(code) {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.post-text :global(code:has(pre)) {
|
||||
margin: 8px 0;
|
||||
padding: 8px;
|
||||
display: block;
|
||||
overflow-x: scroll;
|
||||
border-radius: 8px;
|
||||
background-color: #080808;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.post-text :global(code pre) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.post-text :global(a) {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.post-text :global(a.mention) {
|
||||
color: var(--accent);
|
||||
padding: 6px 6px;
|
||||
margin: -6px 0;
|
||||
background: var(--accent-bg);
|
||||
border-radius: 6px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.post-text :global(a.mention:hover) {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.post-text :global(a.hashtag) {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.post-text :global(.mention-avatar) {
|
||||
position: relative;
|
||||
top: 4px;
|
||||
height: 20px;
|
||||
margin-right: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.post-media-container {
|
||||
max-height: 540px;
|
||||
margin-top: 8px;
|
||||
display: grid;
|
||||
grid-gap: 8px;
|
||||
}
|
||||
|
||||
.post-media-container[data-count="1"] {
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
|
||||
.post-media-container[data-count="2"] {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
|
||||
.post-media-container[data-count="3"] {
|
||||
grid-template-columns: 1fr .5fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
}
|
||||
|
||||
.post-media-container[data-count="4"] {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
}
|
||||
|
||||
.post-media {
|
||||
border-radius: 12px;
|
||||
background-color: #000;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.post-media a {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
.post-media a img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.post-media-container > :nth-child(1) {
|
||||
grid-column: 1/2;
|
||||
grid-row: 1/2;
|
||||
}
|
||||
|
||||
.post-media-container[data-count="3"] > :nth-child(1) {
|
||||
grid-row: 1/3;
|
||||
}
|
||||
|
||||
.post-media-container > :nth-child(2) {
|
||||
grid-column: 2/2;
|
||||
grid-row: 1/2;
|
||||
}
|
||||
|
||||
.post-media-container > :nth-child(3) {
|
||||
grid-column: 1/2;
|
||||
grid-row: 2/2;
|
||||
}
|
||||
|
||||
.post-media-container[data-count="3"] > :nth-child(3) {
|
||||
grid-column: 2/2;
|
||||
grid-row: 2/2;
|
||||
}
|
||||
|
||||
.post-media-container > :nth-child(4) {
|
||||
grid-column: 2/2;
|
||||
grid-row: 2/2;
|
||||
}
|
||||
</style>
|
52
src/post/BoostContext.svelte
Normal file
52
src/post/BoostContext.svelte
Normal file
|
@ -0,0 +1,52 @@
|
|||
<script>
|
||||
import { parse_text as parse_emojis } from '../emoji.js';
|
||||
import { shorthand as short_time } from '../time.js';
|
||||
|
||||
export let post;
|
||||
|
||||
let time_string = post.created_at.toLocaleString();
|
||||
</script>
|
||||
|
||||
<div class="post-context">
|
||||
<span class="post-context-icon">🔁</span>
|
||||
<span class="post-context-action">
|
||||
<a href="/{post.user.mention}">{@html parse_emojis(post.user.name, post.user.emojis, true)}</a> boosted this post.
|
||||
</span>
|
||||
<span class="post-context-time">
|
||||
<time title="{time_string}">{short_time(post.created_at)}</time>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.post-context {
|
||||
margin-bottom: 8px;
|
||||
padding-left: 58px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
color: var(--accent);
|
||||
opacity: .8;
|
||||
transition: opacity .1s;
|
||||
}
|
||||
|
||||
.post-container:hover .post-context {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.post-context-icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.post-context a,
|
||||
.post-context a:visited {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.post-context a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.post-context-time {
|
||||
margin-left: auto;
|
||||
}
|
||||
</style>
|
54
src/post/FooterButton.svelte
Normal file
54
src/post/FooterButton.svelte
Normal file
|
@ -0,0 +1,54 @@
|
|||
<script>
|
||||
import { play_sound } from '../sound.js';
|
||||
|
||||
export let icon = "🔧";
|
||||
export let type = "action";
|
||||
export let label = "Action";
|
||||
export let title = label;
|
||||
export let count = 0;
|
||||
export let sound = "default";
|
||||
</script>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="{type}"
|
||||
aria-label="{label}"
|
||||
title="{title}"
|
||||
on:click={() => (play_sound(sound))}>
|
||||
<span>{@html icon}</span>
|
||||
{#if count}
|
||||
<span class="count">{count}</span>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<style>
|
||||
button {
|
||||
padding: 6px 8px;
|
||||
font-size: 1em;
|
||||
background: none;
|
||||
color: inherit;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
button.active {
|
||||
background: var(--accent);
|
||||
color: var(--bg0);
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #8881;
|
||||
}
|
||||
|
||||
button:active {
|
||||
background: #0001;
|
||||
}
|
||||
|
||||
.count {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
button:hover .count {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
79
src/post/Header.svelte
Normal file
79
src/post/Header.svelte
Normal file
|
@ -0,0 +1,79 @@
|
|||
<script>
|
||||
import { parse_text as parse_emojis } from '../emoji.js';
|
||||
import { shorthand as short_time } from '../time.js';
|
||||
|
||||
export let post;
|
||||
|
||||
let time_string = post.created_at.toLocaleString();
|
||||
</script>
|
||||
|
||||
<div class="post-header-container">
|
||||
<a href="/{post.user.mention}" class="post-avatar-container">
|
||||
<img src={post.user.avatar_url} type={post.user.avatar_type} alt="" width="48" height="48" class="post-avatar" loading="lazy" decoding="async">
|
||||
</a>
|
||||
<header class="post-header">
|
||||
<div class="post-user-info">
|
||||
<a href="/{post.user.mention}" class="name">{@html parse_emojis(post.user.name, post.user.emojis, true)}</a>
|
||||
<span class="username">{post.user.mention}</span>
|
||||
</div>
|
||||
<div class="post-info">
|
||||
<a href={post.url} class="created-at">
|
||||
<time title={time_string}>{short_time(post.created_at)}</time>
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.post-header-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.post-header-container a,
|
||||
.post-header-container a:visited {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.post-header-container a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.post-avatar-container {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.post-avatar {
|
||||
border-radius: 8px;
|
||||
box-shadow: 2px 2px #0004;
|
||||
}
|
||||
|
||||
.post-header {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.post-info {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.post-user-info a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.post-user-info .name :global(.emoji) {
|
||||
position: relative;
|
||||
top: 4px;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
.post-user-info .username {
|
||||
opacity: .5;
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
.post-info .created-at {
|
||||
font-size: .8em;
|
||||
}
|
||||
</style>
|
79
src/post/Post.svelte
Normal file
79
src/post/Post.svelte
Normal file
|
@ -0,0 +1,79 @@
|
|||
<script>
|
||||
import BoostContext from './BoostContext.svelte';
|
||||
import ReplyContext from './ReplyContext.svelte';
|
||||
import Header from './Header.svelte';
|
||||
import Body from './Body.svelte';
|
||||
import FooterButton from './FooterButton.svelte';
|
||||
import { parse_one as parse_reaction } from '../emoji.js';
|
||||
import { play_sound } from '../sound.js';
|
||||
|
||||
export let post;
|
||||
|
||||
let post_context = undefined;
|
||||
let _post = post;
|
||||
let is_boost = false;
|
||||
if (_post.boost) {
|
||||
is_boost = true;
|
||||
post_context = _post;
|
||||
_post = _post.boost;
|
||||
}
|
||||
|
||||
let aria_label = post.user.username + '; ' + post.text + '; ' + post.created_at;
|
||||
</script>
|
||||
|
||||
<div class="post-container" aria-label={aria_label}>
|
||||
{#if _post.reply}
|
||||
<ReplyContext post={_post.reply} />
|
||||
{/if}
|
||||
{#if is_boost && !post_context.text}
|
||||
<BoostContext post={post_context} />
|
||||
{/if}
|
||||
<article class="post">
|
||||
<Header post={_post} />
|
||||
<Body post={_post} />
|
||||
<footer class="post-footer">
|
||||
<div class="post-reactions">
|
||||
{#each Object.keys(_post.reactions) as reaction}
|
||||
<FooterButton icon={parse_reaction(reaction, _post.emojis)} type="reaction" bind:count={_post.reactions[reaction]} title={reaction} label="" />
|
||||
{/each}
|
||||
</div>
|
||||
<div class="post-actions">
|
||||
<FooterButton icon="🗨️" type="reply" label="Reply" bind:count={_post.reply_count} sound="post" />
|
||||
<FooterButton icon="🔁" type="boost" label="Boost" bind:count={_post.boost_count} sound="boost" />
|
||||
<FooterButton icon="⭐" type="favourite" label="Favourite" />
|
||||
<FooterButton icon="😃" type="react" label="React" />
|
||||
<FooterButton icon="🗣️" type="quote" label="Quote" />
|
||||
<FooterButton icon="🛠️" type="more" label="More" />
|
||||
</div>
|
||||
</footer>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.post-container {
|
||||
margin-top: 16px;
|
||||
padding: 28px 32px 20px 32px;
|
||||
border: 1px solid #8884;
|
||||
border-radius: 16px;
|
||||
background-color: var(--bg1);
|
||||
transition: background-color .1s;
|
||||
}
|
||||
|
||||
.post-container:hover {
|
||||
background-color: var(--bg2);
|
||||
}
|
||||
|
||||
.post-reactions {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.post-actions {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.post-container :global(.emoji) {
|
||||
position: relative;
|
||||
top: 6px;
|
||||
height: 26px;
|
||||
}
|
||||
</style>
|
130
src/post/ReplyContext.svelte
Normal file
130
src/post/ReplyContext.svelte
Normal file
|
@ -0,0 +1,130 @@
|
|||
<script>
|
||||
import Header from './Header.svelte';
|
||||
import Body from './Body.svelte';
|
||||
import FooterButton from './FooterButton.svelte';
|
||||
import Post from './Post.svelte';
|
||||
import { parse_text as parse_emojis, parse_one as parse_reaction } from '../emoji.js';
|
||||
import { shorthand as short_time } from '../time.js';
|
||||
|
||||
export let post;
|
||||
|
||||
let time_string = post.created_at.toLocaleString();
|
||||
</script>
|
||||
|
||||
<article class="post-reply">
|
||||
<div class="post-reply-avatar-container">
|
||||
<a href="/{post.user.mention}" class="post-avatar-container">
|
||||
<img src={post.user.avatar_url} type={post.user.avatar_type} alt="" width="48" height="48" class="post-avatar" loading="lazy" decoding="async">
|
||||
</a>
|
||||
<div class="line">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="post-reply-main">
|
||||
<div class="post-header-container">
|
||||
<header class="post-header">
|
||||
<div class="post-user-info">
|
||||
<a href="/{post.user.mention}" class="name">{@html parse_emojis(post.user.name, post.user.emojis, true)}</a>
|
||||
<span class="username">{post.user.mention}</span>
|
||||
</div>
|
||||
<div class="post-info">
|
||||
<a href={post.url} class="created-at">
|
||||
<time title={time_string}>{short_time(post.created_at)}</time>
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<Body post={post} />
|
||||
|
||||
<footer class="post-footer">
|
||||
<div class="post-reactions">
|
||||
{#each Object.keys(post.reactions) as reaction}
|
||||
<FooterButton icon={parse_reaction(reaction, post.emojis)} type="reaction" bind:count={post.reactions[reaction]} title={reaction} label="" />
|
||||
{/each}
|
||||
</div>
|
||||
<div class="post-actions">
|
||||
<FooterButton icon="🗨️" type="reply" label="Reply" bind:count={post.reply_count} />
|
||||
<FooterButton icon="🔁" type="boost" label="Boost" bind:count={post.boost_count} />
|
||||
<FooterButton icon="⭐" type="favourite" label="Favourite" />
|
||||
<FooterButton icon="😃" type="react" label="React" />
|
||||
<FooterButton icon="🗣️" type="quote" label="Quote" />
|
||||
<FooterButton icon="🛠️" type="more" label="More" />
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<style>
|
||||
.post-reply {
|
||||
padding-bottom: 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.post-reply-avatar-container {
|
||||
margin-right: 12px;
|
||||
margin-bottom: -24px;
|
||||
}
|
||||
|
||||
.post-reply-avatar-container .line {
|
||||
position: relative;
|
||||
top: -4px;
|
||||
left: -1px;
|
||||
width: 50%;
|
||||
height: calc(100% - 48px);
|
||||
border-right: 2px solid #8888;
|
||||
}
|
||||
|
||||
.post-reply-main {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.post-header-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.post-header-container a,
|
||||
.post-header-container a:visited {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.post-header-container a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.post-avatar {
|
||||
border-radius: 8px;
|
||||
box-shadow: 2px 2px #0004;
|
||||
}
|
||||
|
||||
.post-header {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.post-info {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.post-user-info a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.post-user-info .name :global(.emoji) {
|
||||
position: relative;
|
||||
top: 4px;
|
||||
max-height: 1.25em;
|
||||
}
|
||||
|
||||
.post-user-info .username {
|
||||
opacity: .5;
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
.post-info .created-at {
|
||||
font-size: .8em;
|
||||
}
|
||||
</style>
|
220
src/post/post.js
Normal file
220
src/post/post.js
Normal file
|
@ -0,0 +1,220 @@
|
|||
import Instance from '../instance.js';
|
||||
import User from '../user/user.js';
|
||||
|
||||
import { parse_one as parse_emoji } from '../emoji.js';
|
||||
|
||||
let post_cache = Object;
|
||||
|
||||
export default class Post {
|
||||
id;
|
||||
created_at;
|
||||
user;
|
||||
text;
|
||||
warning;
|
||||
boost_count;
|
||||
reply_count;
|
||||
mentions;
|
||||
reactions;
|
||||
emojis;
|
||||
files;
|
||||
url;
|
||||
reply;
|
||||
boost;
|
||||
|
||||
static resolve_id(id) {
|
||||
return post_cache[id] || null;
|
||||
}
|
||||
|
||||
static parse(data) {
|
||||
const instance = Instance.get_instance();
|
||||
let post = null;
|
||||
switch (instance.type) {
|
||||
case Instance.types.ICESHRIMP:
|
||||
post = Post.#parse_iceshrimp(data);
|
||||
break;
|
||||
case Instance.types.MASTODON:
|
||||
post = Post.#parse_mastodon(data);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!post) {
|
||||
console.error("Error while parsing post data");
|
||||
return null;
|
||||
}
|
||||
post_cache[post.id] = post;
|
||||
return post;
|
||||
}
|
||||
|
||||
static #parse_iceshrimp(data) {
|
||||
let post = new Post()
|
||||
post.id = data.id;
|
||||
post.created_at = new Date(data.createdAt);
|
||||
post.user = User.parse(data.user);
|
||||
post.text = data.text;
|
||||
post.warning = data.cw;
|
||||
post.boost_count = data.renoteCount;
|
||||
post.reply_count = data.repliesCount;
|
||||
post.mentions = data.mentions;
|
||||
post.reactions = data.reactions;
|
||||
post.emojis = data.emojis;
|
||||
post.files = data.files;
|
||||
post.url = data.url;
|
||||
post.boost = data.renote ? Post.parse(data.renote) : null;
|
||||
post.reply = data.reply ? Post.parse(data.reply) : null;
|
||||
return post;
|
||||
}
|
||||
|
||||
static #parse_mastodon(data) {
|
||||
let post = new Post()
|
||||
post.id = data.id;
|
||||
post.created_at = new Date(data.created_at);
|
||||
post.user = User.parse(data.account);
|
||||
post.text = data.content;
|
||||
post.warning = data.spoiler_text;
|
||||
post.boost_count = data.reblogs_count;
|
||||
post.reply_count = data.replies_count;
|
||||
post.mentions = data.mentions;
|
||||
post.reactions = data.reactions;
|
||||
post.emojis = data.emojis;
|
||||
post.files = data.media_attachments;
|
||||
post.url = data.url;
|
||||
post.boost = data.reblog ? Post.parse(data.reblog) : null;
|
||||
post.reply = data.in_reply_to_id ? Post.resolve_id(data.in_reply_to_id) : null;
|
||||
return post;
|
||||
}
|
||||
|
||||
get rich_text() {
|
||||
let text = this.text;
|
||||
if (!text) return text;
|
||||
|
||||
const markdown_tokens = [
|
||||
{ tag: "pre", token: "```" },
|
||||
{ tag: "code", token: "`" },
|
||||
{ tag: "strong", token: "**", regex: /\*{2}/g },
|
||||
{ tag: "strong", token: "__" },
|
||||
{ tag: "em", token: "*", regex: /\*/g },
|
||||
{ tag: "em", token: "_" },
|
||||
];
|
||||
|
||||
let response = "";
|
||||
let current;
|
||||
let index = 0;
|
||||
while (index < text.length) {
|
||||
let sample = text.substring(index);
|
||||
let allow_new = !current || !current.nostack;
|
||||
|
||||
// handle newlines
|
||||
if (allow_new && sample.startsWith('\n')) {
|
||||
response += "<br>";
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// handle mentions
|
||||
if (allow_new && sample.match(/@[a-z0-9-_.]+@[a-z0-9-_.]+/g)) {
|
||||
// find end of the mention
|
||||
let length = 1;
|
||||
while (index + length < text.length && /[a-z0-9-_.]/.test(text[index + length])) length++;
|
||||
length++; // skim the middle @
|
||||
while (index + length < text.length && /[a-z0-9-_.]/.test(text[index + length])) length++;
|
||||
|
||||
let mention = text.substring(index, index + length);
|
||||
|
||||
// attempt to resolve mention to a user
|
||||
let user = User.resolve_mention(mention);
|
||||
if (user) {
|
||||
const out = `<a href="/${user.mention}" class="mention">` +
|
||||
`<img src="${user.avatar_url}" class="mention-avatar" width="20" height="20">` +
|
||||
"@" + user.name + "</a>";
|
||||
if (current) current.text += out;
|
||||
else response += out;
|
||||
} else {
|
||||
response += mention;
|
||||
}
|
||||
index += mention.length;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Instance.get_instance().type !== Instance.types.MASTODON) {
|
||||
// handle links
|
||||
if (allow_new && sample.match(/^[a-z]{3,6}:\/\/[^\s]+/g)) {
|
||||
// get length of link
|
||||
let length = text.substring(index).search(/\s|$/g);
|
||||
let url = text.substring(index, index + length);
|
||||
let out = `<a href="${url}">${url}</a>`;
|
||||
if (current) current.text += out;
|
||||
else response += out;
|
||||
index += length;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// handle emojis
|
||||
if (allow_new && sample.startsWith(':')) {
|
||||
// lookahead to next invalid emoji character
|
||||
let look = sample.substring(1).search(/[^a-zA-Z0-9-_.]/g) + 1;
|
||||
// if it's ':', we can parse it
|
||||
if (look !== 0 && sample[look] === ':') {
|
||||
let emoji_code = sample.substring(0, look + 1);
|
||||
let out = parse_emoji(emoji_code, this.emojis);
|
||||
if (current) current.text += out;
|
||||
else response += out;
|
||||
index += emoji_code.length;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// handle markdown
|
||||
// TODO: handle misskey-flavoured markdown
|
||||
if (current) {
|
||||
// try to pop stack
|
||||
if (sample.startsWith(current.token)) {
|
||||
index += current.token.length;
|
||||
let out = `<${current.tag}>${current.text}</${current.tag}>`;
|
||||
if (current.token === '```')
|
||||
out = `<code><pre>${current.text}</pre></code>`;
|
||||
if (current.parent) current.parent.text += out;
|
||||
else response += out;
|
||||
current = current.parent;
|
||||
} else {
|
||||
current.text += sample[0];
|
||||
index++;
|
||||
}
|
||||
} else if (allow_new) {
|
||||
// can we add to stack?
|
||||
let pushed = false;
|
||||
for (let i = 0; i < markdown_tokens.length; i++) {
|
||||
let item = markdown_tokens[i];
|
||||
if (sample.startsWith(item.token)) {
|
||||
let new_current = {
|
||||
token: item.token,
|
||||
tag: item.tag,
|
||||
text: "",
|
||||
parent: current,
|
||||
};
|
||||
if (item.token === '```' || item.token === '`') new_current.nostack = true;
|
||||
current = new_current;
|
||||
pushed = true;
|
||||
index += current.token.length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!pushed) {
|
||||
response += sample[0];
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// destroy the remaining stack
|
||||
while (current) {
|
||||
let out = current.token + current.text;
|
||||
if (current.parent) current.parent.text += out;
|
||||
else response += out;
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue