mirror of
https://github.com/stoatchat/javascript-client-sdk.git
synced 2026-07-18 08:31:20 -04:00
feat: Add the ability to parse markdown and replace tags asynchronously, and fetch the missing users and channels. (#131)
* chore: Update ulid to v3 to allow use in service workers This commit was made without the use of generative AI. Signed-off-by: Jacob Schlecht <dadadah@echoha.us> * feat: Add async message parsing that calls fetch for service workers. This commit was made without the use of generative AI. Signed-off-by: Jacob Schlecht <dadadah@echoha.us> * feat: add custom emoji text replacements to the markdown parser This commit was made without the use of generative AI. Signed-off-by: Jacob Schlecht <dadadah@echoha.us> * feat: dedupe regex matches, wait for all requests at once This commit was made without the use of generative AI. Signed-off-by: Jacob Schlecht <dadadah@echoha.us> * fix: run lint for Client.ts This commit was made without the use of generative AI. Signed-off-by: Jacob Schlecht <dadadah@echoha.us> * refactor: Wait for all promises at once This commit was made without the use of generative AI. Signed-off-by: Jacob Schlecht <dadadah@echoha.us> * Using spread operator Signed-off-by: Christopher Hultin <chris.hultin@gmail.com> * fix: run linter This commit was made without the use of generative AI. Signed-off-by: Jacob Schlecht <dadadah@echoha.us> --------- Signed-off-by: Jacob Schlecht <dadadah@echoha.us> Signed-off-by: Christopher Hultin <chris.hultin@gmail.com> Co-authored-by: Christopher Hultin <chris.hultin@gmail.com>
This commit is contained in:
+1
-1
@@ -32,7 +32,7 @@
|
||||
"json-with-bigint": "^3.4.4",
|
||||
"solid-js": "^1.9.6",
|
||||
"stoat-api": "0.8.9-4",
|
||||
"ulid": "^2.4.0"
|
||||
"ulid": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mxssfd/typedoc-theme": "^1.1.7",
|
||||
|
||||
Generated
+5
-5
@@ -27,8 +27,8 @@ importers:
|
||||
specifier: 0.8.9-4
|
||||
version: 0.8.9-4
|
||||
ulid:
|
||||
specifier: ^2.4.0
|
||||
version: 2.4.0
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2
|
||||
devDependencies:
|
||||
'@mxssfd/typedoc-theme':
|
||||
specifier: ^1.1.7
|
||||
@@ -1046,8 +1046,8 @@ packages:
|
||||
uc.micro@2.1.0:
|
||||
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
|
||||
|
||||
ulid@2.4.0:
|
||||
resolution: {integrity: sha512-fIRiVTJNcSRmXKPZtGzFQv9WRrZ3M9eoptl/teFJvjOzmpU+/K/JH6HZ8deBfb5vMEpicJcLn7JmvdknlMq7Zg==}
|
||||
ulid@3.0.2:
|
||||
resolution: {integrity: sha512-yu26mwteFYzBAot7KVMqFGCVpsF6g8wXfJzQUHvu1no3+rRRSFcSV2nKeYvNPLD2J4b08jYBDhHUjeH0ygIl9w==}
|
||||
hasBin: true
|
||||
|
||||
undici-types@6.21.0:
|
||||
@@ -2132,7 +2132,7 @@ snapshots:
|
||||
|
||||
uc.micro@2.1.0: {}
|
||||
|
||||
ulid@2.4.0: {}
|
||||
ulid@3.0.2: {}
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
|
||||
+100
-2
@@ -34,7 +34,12 @@ import type { HydratedMessage } from "./hydration/message.js";
|
||||
import type { HydratedServer } from "./hydration/server.js";
|
||||
import type { HydratedServerMember } from "./hydration/serverMember.js";
|
||||
import type { HydratedUser } from "./hydration/user.js";
|
||||
import { RE_CHANNELS, RE_MENTIONS, RE_SPOILER } from "./lib/regex.js";
|
||||
import {
|
||||
RE_CHANNELS,
|
||||
RE_CUSTOM_EMOJI,
|
||||
RE_MENTIONS,
|
||||
RE_SPOILER,
|
||||
} from "./lib/regex.js";
|
||||
|
||||
export type Session = { _id: string; token: string; user_id: string } | string;
|
||||
|
||||
@@ -242,7 +247,9 @@ export class Client extends AsyncEventEmitter<Events> {
|
||||
baseURL: this.options.baseURL,
|
||||
});
|
||||
|
||||
const [configured, setConfigured] = createSignal(configuration !== undefined);
|
||||
const [configured, setConfigured] = createSignal(
|
||||
configuration !== undefined,
|
||||
);
|
||||
this.configured = configured;
|
||||
this.#setConfigured = setConfigured;
|
||||
|
||||
@@ -416,6 +423,97 @@ export class Client extends AsyncEventEmitter<Events> {
|
||||
.replace(RE_SPOILER, "<spoiler>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a markdown-based message to be displayed to the user as plain text. This method will fetch each user or channel if they are missing. Useful for serviceworkers.
|
||||
* @param source Source markdown text
|
||||
* @returns Modified plain text
|
||||
*/
|
||||
async markdownToTextFetch(source: string): Promise<string> {
|
||||
// Get all user matches, create a map to dedupe
|
||||
const userMatches = Object.fromEntries(
|
||||
Array.from(source.matchAll(RE_MENTIONS), (match) => {
|
||||
return [match[0], match[1]];
|
||||
}),
|
||||
);
|
||||
|
||||
// Get all channel matches, create a map to dedupe
|
||||
const channelMatches = Object.fromEntries(
|
||||
Array.from(source.matchAll(RE_CHANNELS), (match) => {
|
||||
return [match[0], match[1]];
|
||||
}),
|
||||
);
|
||||
|
||||
// Get all custom emoji matches, create a map to dedupe
|
||||
const customEmojiMatches = Object.fromEntries(
|
||||
Array.from(source.matchAll(RE_CUSTOM_EMOJI), (match) => {
|
||||
return [match[0], match[1]];
|
||||
}),
|
||||
);
|
||||
|
||||
// Send requests to replace user ids
|
||||
const userReplacementPromises = Object.keys(userMatches).map(
|
||||
async (key) => {
|
||||
const substr = userMatches[key];
|
||||
if (substr) {
|
||||
const user = await this.users.fetch(substr);
|
||||
|
||||
if (user) {
|
||||
return [key, `@${user.username}`];
|
||||
}
|
||||
}
|
||||
|
||||
return [key, key];
|
||||
},
|
||||
);
|
||||
|
||||
// Send requests to replace channel ids
|
||||
const channelReplacementPromises = Object.keys(channelMatches).map(
|
||||
async (key) => {
|
||||
const substr = channelMatches[key];
|
||||
if (substr) {
|
||||
const channel = await this.channels.fetch(substr);
|
||||
|
||||
if (channel) {
|
||||
return [key, `#${channel.displayName}`];
|
||||
}
|
||||
}
|
||||
|
||||
return [key, key];
|
||||
},
|
||||
);
|
||||
|
||||
// Send requests to replace custom emojis
|
||||
const customEmojiReplacementPromises = Object.keys(customEmojiMatches).map(
|
||||
async (key) => {
|
||||
const substr = customEmojiMatches[key];
|
||||
if (substr) {
|
||||
const emoji = await this.emojis.fetch(substr);
|
||||
|
||||
if (emoji) {
|
||||
return [key, `:${emoji.name}:`];
|
||||
}
|
||||
}
|
||||
|
||||
return [key, key];
|
||||
},
|
||||
);
|
||||
|
||||
// Await for all promises to get the strings to replace with.
|
||||
const replacements = await Promise.all([
|
||||
...userReplacementPromises,
|
||||
...channelReplacementPromises,
|
||||
...customEmojiReplacementPromises,
|
||||
]);
|
||||
|
||||
const replacementsMap = Object.fromEntries(replacements);
|
||||
|
||||
return source
|
||||
.replace(RE_MENTIONS, (match) => replacementsMap[match])
|
||||
.replace(RE_CHANNELS, (match) => replacementsMap[match])
|
||||
.replace(RE_CUSTOM_EMOJI, (match) => replacementsMap[match])
|
||||
.replace(RE_SPOILER, "<spoiler>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy a file through January.
|
||||
* @param url URL to proxy
|
||||
|
||||
@@ -8,6 +8,11 @@ export const RE_MENTIONS = /<@([0-9ABCDEFGHJKMNPQRSTVWXYZ]{26})>/g;
|
||||
*/
|
||||
export const RE_CHANNELS = /<#([0-9ABCDEFGHJKMNPQRSTVWXYZ]{26})>/g;
|
||||
|
||||
/**
|
||||
* Regular expression for stripping custom emojis.
|
||||
*/
|
||||
export const RE_CUSTOM_EMOJI = /:([0-9ABCDEFGHJKMNPQRSTVWXYZ]{26}):/g;
|
||||
|
||||
/**
|
||||
* Regular expression for spoilers.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user