feat: support markdown

This commit is contained in:
Nevo David 2025-07-17 14:48:28 +07:00
parent f10c7f56a4
commit 9c4a3d9709
13 changed files with 55 additions and 13 deletions

View File

@ -17,7 +17,7 @@ export const GeneralPreviewComponent: FC<{
const mediaDir = useMediaDirectory();
const renderContent = topValue.map((p) => {
const newContent = stripHtmlValidation(p.content, true)
const newContent = stripHtmlValidation('normal', p.content, true)
.replace(/(@.+?)(\s)/gi, (match, match1, match2) => {
return `<span class="font-bold" style="color: #ae8afc">${match1.trim()}${match2}</span>`;
})

View File

@ -578,7 +578,7 @@ export const Editor: FC<{
});
const valueWithoutHtml = useMemo(() => {
return stripHtmlValidation(props.value || '');
return stripHtmlValidation('normal', props.value || '');
}, [props.value]);
const addText = useCallback(

View File

@ -147,7 +147,7 @@ export const ManageModal: FC<AddEditModalProps> = (props) => {
return p.values.some((a: any) => {
return (
countCharacters(
stripHtmlValidation(a.content),
stripHtmlValidation('normal', a.content),
p?.integration?.identifier || ''
) === 0 && a.media?.length === 0
);
@ -166,6 +166,7 @@ export const ManageModal: FC<AddEditModalProps> = (props) => {
return;
}
console.log(checkAllValid);
for (const item of checkAllValid) {
if (item.valid === false) {
toaster.show('Some fields are not valid', 'warning');

View File

@ -81,7 +81,12 @@ const DevtoSettings: FC = () => {
<SelectOrganization {...form.register('organization')} />
</div>
<div>
<DevtoTags label="Tags (Maximum 4)" {...form.register('tags')} />
<DevtoTags
label="Tags (Maximum 4)"
{...form.register('tags', {
value: [],
})}
/>
</div>
</>
);

View File

@ -180,6 +180,7 @@ export const withProvider = function <T extends object>(params: {
identifier: selectedIntegration.integration.identifier,
integration: selectedIntegration.integration,
valid: await form.trigger(),
err: form.formState.errors,
errors: checkValidity
? await checkValidity(
value.map((p) => p.media || []),

View File

@ -1,4 +1,5 @@
import striptags from 'striptags';
import { NodeHtmlMarkdown } from 'node-html-markdown';
const bold = {
a: '𝗮',
@ -131,10 +132,15 @@ const underlineMap = {
};
export const stripHtmlValidation = (
type: 'normal' | 'markdown' | 'html',
value: string,
replaceBold = false
): string => {
if (value.indexOf("<p>") === -1) {
if (type === 'markdown') {
return NodeHtmlMarkdown.translate(value);
}
if (value.indexOf('<p>') === -1) {
return value;
}
@ -145,7 +151,10 @@ export const stripHtmlValidation = (
.replace(/<\/p>/gi, '');
if (replaceBold) {
return striptags(convertLinkedinMention(convertToAscii(html)), ['ul', 'li']);
return striptags(convertLinkedinMention(convertToAscii(html)), [
'ul',
'li',
]);
}
// Strip all other tags

View File

@ -467,7 +467,7 @@ export class PostsService {
await Promise.all(
(newPosts || []).map(async (p) => ({
id: p.id,
message: stripHtmlValidation(p.content, true),
message: stripHtmlValidation(getIntegration.editor, p.content, true),
settings: JSON.parse(p.settings || '{}'),
media: await this.updateMedia(
p.id,

View File

@ -43,5 +43,5 @@ export class DevToSettingsDto {
@ArrayMaxSize(4)
@Type(() => DevToTagsSettingsDto)
@ValidateNested({ each: true })
tags: DevToTagsSettingsDto[];
tags: DevToTagsSettingsDto[] = [];
}

View File

@ -313,13 +313,13 @@ export class BlueskyProvider extends SocialAbstract implements SocialProvider {
if (postDetails?.[0]?.settings?.active_thread_finisher) {
const rt = new RichText({
text: stripHtmlValidation(postDetails?.[0]?.settings?.thread_finisher, true),
text: stripHtmlValidation('normal', postDetails?.[0]?.settings?.thread_finisher, true),
});
await rt.detectFacets(agent);
await agent.post({
text: stripHtmlValidation(rt.text, true),
text: stripHtmlValidation('normal', rt.text, true),
facets: rt.facets,
createdAt: new Date().toISOString(),
embed: {
@ -460,7 +460,7 @@ export class BlueskyProvider extends SocialAbstract implements SocialProvider {
if (getThread.data.thread.post?.likeCount >= +fields.likesAmount) {
await timer(2000);
const rt = new RichText({
text: stripHtmlValidation(fields.post, true),
text: stripHtmlValidation('normal', fields.post, true),
});
await agent.post({

View File

@ -502,7 +502,7 @@ export class ThreadsProvider extends SocialAbstract implements SocialProvider {
const form = new FormData();
form.append('media_type', 'TEXT');
form.append('text', stripHtmlValidation(fields.post, true));
form.append('text', stripHtmlValidation('normal', fields.post, true));
form.append('reply_to_id', id);
form.append('access_token', integration.token);

View File

@ -152,7 +152,7 @@ export class XProvider extends SocialAbstract implements SocialProvider {
await timer(2000);
await client.v2.tweet({
text: stripHtmlValidation(fields.post, true),
text: stripHtmlValidation('normal', fields.post, true),
reply: { in_reply_to_tweet_id: id },
});
return true;

View File

@ -161,6 +161,7 @@
"nestjs-real-ip": "^3.0.1",
"next": "^14.2.30",
"next-plausible": "^3.12.0",
"node-html-markdown": "^1.3.0",
"node-telegram-bot-api": "^0.66.0",
"nodemailer": "^6.9.15",
"nostr-tools": "^2.10.4",

View File

@ -360,6 +360,9 @@ importers:
next-plausible:
specifier: ^3.12.0
version: 3.12.4(next@14.2.30(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.2)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
node-html-markdown:
specifier: ^1.3.0
version: 1.3.0
node-telegram-bot-api:
specifier: ^0.66.0
version: 0.66.0(request@2.88.2)
@ -9272,6 +9275,10 @@ packages:
hastscript@9.0.1:
resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==}
he@1.2.0:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
header-case@2.0.4:
resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==}
@ -11430,6 +11437,13 @@ packages:
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
hasBin: true
node-html-markdown@1.3.0:
resolution: {integrity: sha512-OeFi3QwC/cPjvVKZ114tzzu+YoR+v9UXW5RwSXGUqGb0qCl0DvP406tzdL7SFn8pZrMyzXoisfG2zcuF9+zw4g==}
engines: {node: '>=10.0.0'}
node-html-parser@6.1.13:
resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==}
node-int64@0.4.0:
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
@ -26406,6 +26420,8 @@ snapshots:
property-information: 7.1.0
space-separated-tokens: 2.0.2
he@1.2.0: {}
header-case@2.0.4:
dependencies:
capital-case: 1.0.4
@ -29253,6 +29269,15 @@ snapshots:
node-gyp-build@4.8.4: {}
node-html-markdown@1.3.0:
dependencies:
node-html-parser: 6.1.13
node-html-parser@6.1.13:
dependencies:
css-select: 5.2.2
he: 1.2.0
node-int64@0.4.0: {}
node-mock-http@1.0.1: {}