feat: editor type + fix removing integrations

This commit is contained in:
Nevo David 2025-07-17 01:06:03 +07:00
parent 4907bb3572
commit f10c7f56a4
44 changed files with 280 additions and 177 deletions

View File

@ -33,7 +33,10 @@ import {
} from '@gitroom/nestjs-libraries/integrations/social.abstract';
import { timer } from '@gitroom/helpers/utils/timer';
import { TelegramProvider } from '@gitroom/nestjs-libraries/integrations/social/telegram.provider';
import { AuthorizationActions, Sections } from '@gitroom/backend/services/auth/permissions/permission.exception.class';
import {
AuthorizationActions,
Sections,
} from '@gitroom/backend/services/auth/permissions/permission.exception.class';
@ApiTags('Integrations')
@Controller('/integrations')
@ -95,6 +98,7 @@ export class IntegrationsController {
id: p.id,
internalId: p.internalId,
disabled: p.disabled,
editor: findIntegration.editor,
picture: p.picture || '/no-picture.jpg',
identifier: p.providerIdentifier,
inBetweenSteps: p.inBetweenSteps,
@ -255,85 +259,62 @@ export class IntegrationsController {
throw new Error('Invalid integration');
}
if (getIntegration.type === 'social') {
const integrationProvider = this._integrationManager.getSocialIntegration(
getIntegration.providerIdentifier
);
if (!integrationProvider) {
throw new Error('Invalid provider');
}
if (integrationProvider[body.name]) {
try {
const load = await integrationProvider[body.name](
getIntegration.token,
body.data,
getIntegration.internalId,
getIntegration
);
return load;
} catch (err) {
if (err instanceof RefreshToken) {
const { accessToken, refreshToken, expiresIn, additionalSettings } =
await integrationProvider.refreshToken(
getIntegration.refreshToken
);
if (accessToken) {
await this._integrationService.createOrUpdateIntegration(
additionalSettings,
!!integrationProvider.oneTimeToken,
getIntegration.organizationId,
getIntegration.name,
getIntegration.picture!,
'social',
getIntegration.internalId,
getIntegration.providerIdentifier,
accessToken,
refreshToken,
expiresIn
);
getIntegration.token = accessToken;
if (integrationProvider.refreshWait) {
await timer(10000);
}
return this.functionIntegration(org, body);
} else {
await this._integrationService.disconnectChannel(
org.id,
getIntegration
);
return false;
}
}
return false;
}
}
throw new Error('Function not found');
const integrationProvider = this._integrationManager.getSocialIntegration(
getIntegration.providerIdentifier
);
if (!integrationProvider) {
throw new Error('Invalid provider');
}
if (getIntegration.type === 'article') {
const integrationProvider =
this._integrationManager.getArticlesIntegration(
getIntegration.providerIdentifier
);
if (!integrationProvider) {
throw new Error('Invalid provider');
}
if (integrationProvider[body.name]) {
return integrationProvider[body.name](
if (integrationProvider[body.name]) {
try {
const load = await integrationProvider[body.name](
getIntegration.token,
body.data,
getIntegration.internalId
getIntegration.internalId,
getIntegration
);
return load;
} catch (err) {
if (err instanceof RefreshToken) {
const { accessToken, refreshToken, expiresIn, additionalSettings } =
await integrationProvider.refreshToken(getIntegration.refreshToken);
if (accessToken) {
await this._integrationService.createOrUpdateIntegration(
additionalSettings,
!!integrationProvider.oneTimeToken,
getIntegration.organizationId,
getIntegration.name,
getIntegration.picture!,
'social',
getIntegration.internalId,
getIntegration.providerIdentifier,
accessToken,
refreshToken,
expiresIn
);
getIntegration.token = accessToken;
if (integrationProvider.refreshWait) {
await timer(10000);
}
return this.functionIntegration(org, body);
} else {
await this._integrationService.disconnectChannel(
org.id,
getIntegration
);
return false;
}
}
return false;
}
throw new Error('Function not found');
}
throw new Error('Function not found');
}
@Post('/social/:integration/connect')

View File

@ -543,4 +543,13 @@ html[dir='rtl'] [dir='ltr'] {
.ProseMirror .mention {
font-weight: bold;
color: #ae8afc;
}
.ProseMirror ul, .preview ul {
list-style: disc;
padding-left: 20px;
}
.preview ul, .preview li {
white-space: nowrap;
}

View File

@ -68,6 +68,7 @@ export interface Integrations {
id: string;
disabled?: boolean;
inBetweenSteps: boolean;
editor: 'normal' | 'markdown' | 'html';
display: string;
identifier: string;
type: string;

View File

@ -40,7 +40,6 @@ export const GeneralPreviewComponent: FC<{
return { text: finalValue, images: p.image };
});
console.log(renderContent);
return (
<div className={clsx('w-full md:w-[555px] px-[16px]')}>
<div className="w-full h-full relative flex flex-col">
@ -103,8 +102,8 @@ export const GeneralPreviewComponent: FC<{
: integration?.display || '@username'}
</div>
</div>
<pre
className={clsx('text-wrap', interClass)}
<div
className={clsx('text-wrap whitespace-pre', 'preview', interClass)}
dangerouslySetInnerHTML={{
__html: value.text,
}}

View File

@ -0,0 +1,17 @@
'use client';
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
import { useCallback } from 'react';
import useSWR from 'swr';
export const useIntegrationList = () => {
const fetch = useFetch();
const load = useCallback(async (path: string) => {
return (await (await fetch(path)).json()).integrations;
}, []);
return useSWR('/integrations/list', load, {
fallbackData: [],
});
};

View File

@ -24,6 +24,8 @@ import { GeneratorComponent } from './generator/generator';
import { useVariables } from '@gitroom/react/helpers/variable.context';
import { NewPost } from '@gitroom/frontend/components/launches/new.post';
import { useT } from '@gitroom/react/translation/get.transation.service.client';
import { useIntegrationList } from '@gitroom/frontend/components/launches/helpers/use.integration.list';
interface MenuComponentInterface {
refreshChannel: (
integration: Integration & {
@ -283,16 +285,13 @@ export const LaunchesComponent = () => {
const fireEvents = useFireEvents();
const t = useT();
const [reload, setReload] = useState(false);
const load = useCallback(async (path: string) => {
return (await (await fetch(path)).json()).integrations;
}, []);
const {
isLoading,
data: integrations,
mutate,
} = useSWR('/integrations/list', load, {
fallbackData: [],
});
} = useIntegrationList();
const totalNonDisabledChannels = useMemo(() => {
return (
integrations?.filter((integration: any) => !integration.disabled)
@ -453,7 +452,9 @@ export const LaunchesComponent = () => {
user?.tier?.ai &&
billingEnabled && <GeneratorComponent />}
<div className="mt-[5px]">
{process.env.NEXT_PUBLIC_VERSION ? `${process.env.NEXT_PUBLIC_VERSION}` : ''}
{process.env.NEXT_PUBLIC_VERSION
? `${process.env.NEXT_PUBLIC_VERSION}`
: ''}
</div>
</div>
</div>

View File

@ -115,6 +115,7 @@ export const AddEditModalInnerInner: FC<AddEditModalProps> = (props) => {
setCurrent,
internal,
setTags,
setEditor,
} = useLaunchStore(
useShallow((state) => ({
reset: state.reset,
@ -124,6 +125,7 @@ export const AddEditModalInnerInner: FC<AddEditModalProps> = (props) => {
global: state.global,
internal: state.internal,
setTags: state.setTags,
setEditor: state.setEditor,
}))
);
@ -154,6 +156,9 @@ export const AddEditModalInnerInner: FC<AddEditModalProps> = (props) => {
);
setCurrent(existingData.integration);
}
else {
setEditor('normal');
}
if (props.focusedChannel) {
setCurrent(props.focusedChannel);

View File

@ -0,0 +1,20 @@
'use client';
import { FC, useCallback } from 'react';
export const Bullets: FC<{
editor: any;
currentValue: string;
}> = ({ editor }) => {
const bullet = () => {
editor.commands.toggleBulletList();
};
return (
<div
onClick={bullet}
className="select-none cursor-pointer w-[40px] p-[5px] text-center"
>
A
</div>
);
};

View File

@ -45,6 +45,8 @@ import Paragraph from '@tiptap/extension-paragraph';
import Underline from '@tiptap/extension-underline';
import { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';
import { History } from '@tiptap/extension-history';
import { BulletList, ListItem } from '@tiptap/extension-list';
import { Bullets } from '@gitroom/frontend/components/new-launch/bullets.component';
const InterceptBoldShortcut = Extension.create({
name: 'preventBoldWithUnderline',
@ -147,6 +149,9 @@ export const EditorWrapper: FC<{
totalChars,
postComment,
dummy,
editor,
loadedState,
setLoadedState,
} = useLaunchStore(
useShallow((state) => ({
internal: state.internal.find((p) => p.integration.id === state.current),
@ -172,6 +177,9 @@ export const EditorWrapper: FC<{
appendInternalValueMedia: state.appendInternalValueMedia,
appendGlobalValueMedia: state.appendGlobalValueMedia,
postComment: state.postComment,
editor: state.editor,
loadedState: state.loaded,
setLoadedState: state.setLoaded
}))
);
@ -179,12 +187,13 @@ export const EditorWrapper: FC<{
const [loaded, setLoaded] = useState(true);
useEffect(() => {
if (loaded) {
if (loaded && loadedState) {
return;
}
setLoadedState(true);
setLoaded(true);
}, [loaded]);
}, [loaded, loadedState]);
const canEdit = useMemo(() => {
return current === 'global' || !!internal;
@ -341,7 +350,7 @@ export const EditorWrapper: FC<{
[current, global, internal]
);
if (!loaded) {
if (!loaded || !loadedState) {
return null;
}
@ -371,6 +380,7 @@ export const EditorWrapper: FC<{
<div className="flex gap-[5px]">
<div className="flex-1">
<Editor
editorType={editor}
allValues={items}
onChange={changeValue(index)}
key={index}
@ -451,6 +461,7 @@ export const EditorWrapper: FC<{
};
export const Editor: FC<{
editorType?: 'normal' | 'markdown' | 'html';
totalPosts: number;
value: string;
num?: number;
@ -466,6 +477,7 @@ export const Editor: FC<{
dummy: boolean;
}> = (props) => {
const {
editorType = 'normal',
allValues,
pictures,
setImages,
@ -520,7 +532,24 @@ export const Editor: FC<{
[uppy]
);
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
const { getRootProps, isDragActive } = useDropzone({ onDrop });
const editorOptions = useMemo(() => {
if (editorType === 'normal') {
return [];
}
const list = [];
if (
editorType === ('markdown' as const) ||
editorType === ('html' as const)
) {
list.push(BulletList, ListItem);
}
return list;
}, [editorType]);
const editor = useEditor({
extensions: [
@ -536,6 +565,7 @@ export const Editor: FC<{
depth: 100, // default is 100
newGroupDelay: 100, // default is 500ms
}),
...editorOptions,
],
content: props.value || '',
shouldRerenderOnTransaction: true,
@ -590,6 +620,9 @@ export const Editor: FC<{
<SignatureBox editor={editor} />
<UText editor={editor} currentValue={props.value!} />
<BoldText editor={editor} currentValue={props.value!} />
{(editorType === 'markdown' || editorType === 'html') && (
<Bullets editor={editor} currentValue={props.value!} />
)}
<div
className="select-none cursor-pointer w-[40px] p-[5px] text-center"
onClick={() => setEmojiPickerOpen(!emojiPickerOpen)}

View File

@ -74,7 +74,6 @@ export const withProvider = function <T extends object>(params: {
const fetch = useFetch();
const {
current,
integrations,
selectedIntegration,
setCurrent,
internal,
@ -87,6 +86,7 @@ export const withProvider = function <T extends object>(params: {
justCurrent,
allIntegrations,
setPostComment,
setEditor,
dummy,
} = useLaunchStore(
useShallow((state) => ({
@ -104,6 +104,7 @@ export const withProvider = function <T extends object>(params: {
setCurrent: state.setCurrent,
setTotalChars: state.setTotalChars,
setPostComment: state.setPostComment,
setEditor: state.setEditor,
selectedIntegration: state.selectedIntegrations.find(
(p) => p.integration.id === props.id
),
@ -118,9 +119,11 @@ export const withProvider = function <T extends object>(params: {
if (isGlobal) {
setPostComment(PostComment.ALL);
setTotalChars(0);
setEditor('normal');
}
if (current) {
setEditor(selectedIntegration?.integration.editor);
setPostComment(postComment);
setTotalChars(
typeof maximumCharacters === 'number'
@ -246,25 +249,27 @@ export const withProvider = function <T extends object>(params: {
{t('preview', 'Preview')}
</Button>
</div>
{current && (!!SettingsComponent || !!data?.internalPlugs?.length) && (
<div className="flex-1 flex">
<Button
onClick={() => setTab(1)}
secondary={tab !== 1}
className="rounded-[4px] flex-1 overflow-hidden whitespace-nowrap"
>
{t('settings', 'Settings')} (
{capitalize(
selectedIntegration.integration.identifier.split('-')[0]
)}
)
</Button>
</div>
)}
{current &&
(!!SettingsComponent || !!data?.internalPlugs?.length) && (
<div className="flex-1 flex">
<Button
onClick={() => setTab(1)}
secondary={tab !== 1}
className="rounded-[4px] flex-1 overflow-hidden whitespace-nowrap"
>
{t('settings', 'Settings')} (
{capitalize(
selectedIntegration.integration.identifier.split('-')[0]
)}
)
</Button>
</div>
)}
</div>
{current && (tab === 0 ||
(!SettingsComponent && !data?.internalPlugs?.length)) &&
{current &&
(tab === 0 ||
(!SettingsComponent && !data?.internalPlugs?.length)) &&
!value?.[0]?.content?.length && (
<div>
{t(

View File

@ -24,11 +24,12 @@ import NostrProvider from '@gitroom/frontend/components/new-launch/providers/nos
import VkProvider from '@gitroom/frontend/components/new-launch/providers/vk/vk.provider';
import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';
import { useShallow } from 'zustand/react/shallow';
import React, { createRef, FC, forwardRef, useImperativeHandle } from 'react';
import React, { FC, forwardRef, useEffect, useImperativeHandle } from 'react';
import { GeneralPreviewComponent } from '@gitroom/frontend/components/launches/general.preview.component';
import { IntegrationContext } from '@gitroom/frontend/components/launches/helpers/use.integration';
import { Button } from '@gitroom/react/form/button';
import { useT } from '@gitroom/react/translation/get.transation.service.client';
import { PostComment } from '@gitroom/frontend/components/new-launch/providers/high.order.provider';
export const Providers = [
{
@ -154,8 +155,10 @@ export const ShowAllProviders = forwardRef((props, ref) => {
);
},
triggerAll: () => {
return selectedIntegrations.map(async (p) => await p.ref?.current.trigger());
}
return selectedIntegrations.map(
async (p) => await p.ref?.current.trigger()
);
},
}));
return (
@ -176,15 +179,18 @@ export const ShowAllProviders = forwardRef((props, ref) => {
>
<div className="flex gap-[4px] mb-[20px]">
<div className="flex-1 flex">
<Button
className="rounded-[4px] flex-1 overflow-hidden whitespace-nowrap"
>
<Button className="rounded-[4px] flex-1 overflow-hidden whitespace-nowrap">
{t('preview', 'Preview')}
</Button>
</div>
</div>
{global?.[0]?.content?.length === 0 ? (
<div>{t('start_writing_your_post', 'Start writing your post for a preview')}</div>
<div>
{t(
'start_writing_your_post',
'Start writing your post for a preview'
)}
</div>
) : (
<GeneralPreviewComponent maximumCharacters={100000000} />
)}

View File

@ -25,6 +25,8 @@ interface SelectedIntegrations {
}
interface StoreState {
editor: undefined | 'normal' | 'markdown' | 'html';
loaded: boolean;
date: dayjs.Dayjs;
postComment: PostComment;
dummy: boolean;
@ -118,9 +120,13 @@ interface StoreState {
setPostComment: (postComment: PostComment) => void;
setActivateExitButton?: (activateExitButton: boolean) => void;
setDummy: (dummy: boolean) => void;
setEditor: (editor: 'normal' | 'markdown' | 'html') => void;
setLoaded?: (loaded: boolean) => void;
}
const initialState = {
editor: undefined as undefined,
loaded: true,
dummy: false,
activateExitButton: true,
date: dayjs(),
@ -154,13 +160,22 @@ export const useLaunchStore = create<StoreState>()((set) => ({
);
if (existing) {
const selectedList = state.selectedIntegrations.filter(
(s, index) => s.integration.id !== existing.integration.id
);
return {
...(existing.integration.id === state.current
? { current: 'global' }
: {}),
selectedIntegrations: state.selectedIntegrations.filter(
(s, index) => s.integration.id !== existing.integration.id
),
loaded: false,
selectedIntegrations: selectedList,
...(selectedList.length === 0
? {
current: 'global',
editor: 'normal',
}
: {}),
};
}
@ -512,4 +527,12 @@ export const useLaunchStore = create<StoreState>()((set) => ({
set((state) => ({
dummy,
})),
setEditor: (editor: 'normal' | 'markdown' | 'html') =>
set((state) => ({
editor,
})),
setLoaded: (loaded: boolean) =>
set((state) => ({
loaded,
})),
}));

View File

@ -145,11 +145,11 @@ export const stripHtmlValidation = (
.replace(/<\/p>/gi, '');
if (replaceBold) {
return striptags(convertLinkedinMention(convertToAscii(html)));
return striptags(convertLinkedinMention(convertToAscii(html)), ['ul', 'li']);
}
// Strip all other tags
return striptags(html);
return striptags(html, ['ul', 'li']);
};
export const convertLinkedinMention = (value: string) => {

View File

@ -1,19 +0,0 @@
export interface ArticleIntegrationsInterface {
authenticate(token: string): Promise<{
id: string;
name: string;
token: string;
picture: string;
username: string;
}>;
post(
token: string,
content: string,
settings: object
): Promise<{ postId: string; releaseURL: string }>;
}
export interface ArticleProvider extends ArticleIntegrationsInterface {
identifier: string;
name: string;
}

View File

@ -1,16 +1,13 @@
import 'reflect-metadata';
import {
Injectable,
} from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { XProvider } from '@gitroom/nestjs-libraries/integrations/social/x.provider';
import { SocialProvider } from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
import { LinkedinProvider } from '@gitroom/nestjs-libraries/integrations/social/linkedin.provider';
import { RedditProvider } from '@gitroom/nestjs-libraries/integrations/social/reddit.provider';
import { DevToProvider } from '@gitroom/nestjs-libraries/integrations/social/dev.to.provider';
import { HashnodeProvider } from '@gitroom/nestjs-libraries/integrations/social/hashnode.provider';
import { MediumProvider } from '@gitroom/nestjs-libraries/integrations/article/medium.provider';
import { ArticleProvider } from '@gitroom/nestjs-libraries/integrations/article/article.integrations.interface';
import { MediumProvider } from '@gitroom/nestjs-libraries/integrations/social/medium.provider';
import { FacebookProvider } from '@gitroom/nestjs-libraries/integrations/social/facebook.provider';
import { InstagramProvider } from '@gitroom/nestjs-libraries/integrations/social/instagram.provider';
import { YoutubeProvider } from '@gitroom/nestjs-libraries/integrations/social/youtube.provider';
@ -58,9 +55,6 @@ export const socialIntegrationList: SocialProvider[] = [
// new MastodonCustomProvider(),
];
const articleIntegrationList: ArticleProvider[] = [
];
@Injectable()
export class IntegrationManager {
async getAllIntegrations() {
@ -70,15 +64,13 @@ export class IntegrationManager {
name: p.name,
identifier: p.identifier,
toolTip: p.toolTip,
editor: p.editor,
isExternal: !!p.externalUrl,
isWeb3: !!p.isWeb3,
...(p.customFields ? { customFields: await p.customFields() } : {}),
}))
),
article: articleIntegrationList.map((p) => ({
name: p.name,
identifier: p.identifier,
})),
article: [] as any[],
};
}
@ -123,10 +115,4 @@ export class IntegrationManager {
getSocialIntegration(integration: string): SocialProvider {
return socialIntegrationList.find((i) => i.identifier === integration)!;
}
getAllowedArticlesIntegrations() {
return articleIntegrationList.map((p) => p.identifier);
}
getArticlesIntegration(integration: string): ArticleProvider {
return articleIntegrationList.find((i) => i.identifier === integration)!;
}
}

View File

@ -131,6 +131,7 @@ export class BlueskyProvider extends SocialAbstract implements SocialProvider {
name = 'Bluesky';
isBetweenSteps = false;
scopes = ['write:statuses', 'profile', 'write:media'];
editor = 'normal' as const;
async customFields() {
return [

View File

@ -13,6 +13,7 @@ export class DevToProvider extends SocialAbstract implements SocialProvider {
identifier = 'devto';
name = 'Dev.to';
isBetweenSteps = false;
editor = 'markdown' as const;
scopes = [] as string[];
async generateAuthUrl() {

View File

@ -11,6 +11,7 @@ export class DiscordProvider extends SocialAbstract implements SocialProvider {
identifier = 'discord';
name = 'Discord';
isBetweenSteps = false;
editor = 'markdown' as const;
scopes = ['identify', 'guilds'];
async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {
const { access_token, expires_in, refresh_token } = await (

View File

@ -17,6 +17,7 @@ export class DribbbleProvider extends SocialAbstract implements SocialProvider {
name = 'Dribbble';
isBetweenSteps = false;
scopes = ['public', 'upload'];
editor = 'normal' as const;
async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {
const { access_token, expires_in } = await (

View File

@ -22,6 +22,7 @@ export class FacebookProvider extends SocialAbstract implements SocialProvider {
'pages_read_engagement',
'read_insights',
];
editor = 'normal' as const;
override handleErrors(body: string):
| {

View File

@ -25,6 +25,7 @@ export class FarcasterProvider
isBetweenSteps = false;
isWeb3 = true;
scopes = [] as string[];
editor = 'normal' as const;
async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {
return {

View File

@ -5,7 +5,7 @@ import {
SocialProvider,
} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
import { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';
import { tags } from '@gitroom/nestjs-libraries/integrations/article/hashnode.tags';
import { tags } from '@gitroom/nestjs-libraries/integrations/social/hashnode.tags';
import { jsonToGraphQLQuery } from 'json-to-graphql-query';
import { HashnodeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/hashnode.settings.dto';
import dayjs from 'dayjs';
@ -17,6 +17,7 @@ export class HashnodeProvider extends SocialAbstract implements SocialProvider {
name = 'Hashnode';
isBetweenSteps = false;
scopes = [] as string[];
editor = 'markdown' as const;
async generateAuthUrl() {
const state = makeId(6);

View File

@ -11,7 +11,6 @@ import dayjs from 'dayjs';
import { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';
import { InstagramDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/instagram.dto';
import { Integration } from '@prisma/client';
import { number } from 'yup';
export class InstagramProvider
extends SocialAbstract
@ -30,6 +29,7 @@ export class InstagramProvider
'instagram_manage_comments',
'instagram_manage_insights',
];
editor = 'normal' as const;
async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {
return {

View File

@ -27,6 +27,8 @@ export class InstagramStandaloneProvider
'instagram_business_manage_insights',
];
editor = 'normal' as const;
public override handleErrors(body: string): { type: "refresh-token" | "bad-body"; value: string } | undefined {
return instagramProvider.handleErrors(body);
}

View File

@ -17,6 +17,7 @@ export class LemmyProvider extends SocialAbstract implements SocialProvider {
name = 'Lemmy';
isBetweenSteps = false;
scopes = [] as string[];
editor = 'normal' as const;
async customFields() {
return [

View File

@ -30,6 +30,8 @@ export class LinkedinPageProvider
'r_organization_social',
];
editor = 'normal' as const;
override async refreshToken(
refresh_token: string
): Promise<AuthTokenDetails> {

View File

@ -31,6 +31,7 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider {
'r_organization_social',
];
refreshWait = true;
editor = 'normal' as const;
async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {
const {

View File

@ -9,6 +9,8 @@ import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
export class MastodonCustomProvider extends MastodonProvider {
override identifier = 'mastodon-custom';
override name = 'M. Instance';
editor = 'normal' as const;
async externalUrl(url: string) {
const form = new FormData();
form.append('client_name', 'Postiz');

View File

@ -13,6 +13,7 @@ export class MastodonProvider extends SocialAbstract implements SocialProvider {
name = 'Mastodon';
isBetweenSteps = false;
scopes = ['write:statuses', 'profile', 'write:media'];
editor = 'normal' as const;
async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {
return {

View File

@ -1,5 +1,3 @@
import { ArticleProvider } from '@gitroom/nestjs-libraries/integrations/article/article.integrations.interface';
import { MediumSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/medium.settings.dto';
import {
AuthTokenDetails,
PostDetails,
@ -16,6 +14,7 @@ export class MediumProvider extends SocialAbstract implements SocialProvider {
name = 'Medium';
isBetweenSteps = false;
scopes = [] as string[];
editor = 'markdown' as const;
async generateAuthUrl() {
const state = makeId(6);

View File

@ -27,7 +27,8 @@ export class NostrProvider extends SocialAbstract implements SocialProvider {
identifier = 'nostr';
name = 'Nostr';
isBetweenSteps = false;
scopes = [];
scopes = [] as string[];
editor = 'normal' as const;
async customFields() {
return [

View File

@ -28,6 +28,8 @@ export class PinterestProvider
'user_accounts:read',
];
editor = 'normal' as const;
async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {
const { access_token, expires_in } = await (
await this.fetch('https://api.pinterest.com/v5/oauth/token', {

View File

@ -15,6 +15,7 @@ export class RedditProvider extends SocialAbstract implements SocialProvider {
name = 'Reddit';
isBetweenSteps = false;
scopes = ['read', 'identity', 'submit', 'flair'];
editor = 'normal' as const;
async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {
const {

View File

@ -13,6 +13,7 @@ export class SlackProvider extends SocialAbstract implements SocialProvider {
identifier = 'slack';
name = 'Slack';
isBetweenSteps = false;
editor = 'normal' as const;
scopes = [
'channels:read',
'chat:write',

View File

@ -114,6 +114,7 @@ export interface SocialProvider
refreshWait?: boolean;
convertToJPEG?: boolean;
isWeb3?: boolean;
editor: 'normal' | 'markdown' | 'html';
customFields?: () => Promise<
{
key: string;

View File

@ -23,6 +23,7 @@ export class TelegramProvider extends SocialAbstract implements SocialProvider {
isBetweenSteps = false;
isWeb3 = true;
scopes = [] as string[];
editor = 'normal' as const;
async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {
return {

View File

@ -25,6 +25,8 @@ export class ThreadsProvider extends SocialAbstract implements SocialProvider {
'threads_manage_insights',
];
editor = 'normal' as const;
async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {
const { access_token } = await (
await this.fetch(

View File

@ -25,6 +25,8 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {
'user.info.profile',
];
editor = 'normal' as const;
override handleErrors(body: string):
| {
type: 'refresh-token' | 'bad-body';

View File

@ -26,6 +26,8 @@ export class VkProvider extends SocialAbstract implements SocialProvider {
'video',
];
editor = 'normal' as const;
async refreshToken(refresh: string): Promise<AuthTokenDetails> {
const [oldRefreshToken, device_id] = refresh.split('&&&&');
const formData = new FormData();

View File

@ -26,6 +26,8 @@ export class XProvider extends SocialAbstract implements SocialProvider {
toolTip =
'You will be logged in into your current account, if you would like a different account, change it first on X';
editor = 'normal' as const;
@Plug({
identifier: 'x-autoRepostPost',
title: 'Auto Repost Posts',

View File

@ -63,6 +63,8 @@ export class YoutubeProvider extends SocialAbstract implements SocialProvider {
'https://www.googleapis.com/auth/yt-analytics.readonly',
];
editor = 'normal' as const;
async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {
const { client, oauth2 } = clientAndYoutube();
client.setCredentials({ refresh_token });

View File

@ -82,6 +82,7 @@
"@tiptap/extension-bold": "^3.0.6",
"@tiptap/extension-document": "^3.0.6",
"@tiptap/extension-history": "^3.0.7",
"@tiptap/extension-list": "^3.0.7",
"@tiptap/extension-paragraph": "^3.0.6",
"@tiptap/extension-text": "^3.0.6",
"@tiptap/extension-underline": "^3.0.6",

View File

@ -123,6 +123,9 @@ importers:
'@tiptap/extension-history':
specifier: ^3.0.7
version: 3.0.7(@tiptap/extensions@3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))
'@tiptap/extension-list':
specifier: ^3.0.7
version: 3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
'@tiptap/extension-paragraph':
specifier: ^3.0.6
version: 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))
@ -5524,11 +5527,11 @@ packages:
peerDependencies:
'@tiptap/extension-list': ^3.0.6
'@tiptap/extension-list@3.0.6':
resolution: {integrity: sha512-pg4R8cjUSMKuMjfOJexFt961U0UDUXfChXQh7PI7BCRCxLAtUvm2l0kGg4OMXiM1PM/ylTApdtUDal0gdOuSrQ==}
'@tiptap/extension-list@3.0.7':
resolution: {integrity: sha512-rwu5dXRO0YLyxndMHI17PoxK0x0ZaMZKRZflqOy8fSnXNwd3Tdy8/6a9tsmpgO38kOZEYuvMVaeB7J/+UeBVLg==}
peerDependencies:
'@tiptap/core': ^3.0.6
'@tiptap/pm': ^3.0.6
'@tiptap/core': ^3.0.7
'@tiptap/pm': ^3.0.7
'@tiptap/extension-ordered-list@3.0.6':
resolution: {integrity: sha512-9SbeGO6kGKoX8GwhaSgpFNCGxlzfGu5otK5DE+Unn5F8/gIYGBJkXTZE1tj8XzPmH6lWhmKJQPudANnW6yuKqg==}
@ -21155,9 +21158,9 @@ snapshots:
'@tiptap/pm': 3.0.6
optional: true
'@tiptap/extension-bullet-list@3.0.6(@tiptap/extension-list@3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))':
'@tiptap/extension-bullet-list@3.0.6(@tiptap/extension-list@3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))':
dependencies:
'@tiptap/extension-list': 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
'@tiptap/extension-list': 3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
'@tiptap/extension-code-block@3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)':
dependencies:
@ -21214,22 +21217,22 @@ snapshots:
'@tiptap/pm': 3.0.6
linkifyjs: 4.3.1
'@tiptap/extension-list-item@3.0.6(@tiptap/extension-list@3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))':
'@tiptap/extension-list-item@3.0.6(@tiptap/extension-list@3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))':
dependencies:
'@tiptap/extension-list': 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
'@tiptap/extension-list': 3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
'@tiptap/extension-list-keymap@3.0.6(@tiptap/extension-list@3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))':
'@tiptap/extension-list-keymap@3.0.6(@tiptap/extension-list@3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))':
dependencies:
'@tiptap/extension-list': 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
'@tiptap/extension-list': 3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
'@tiptap/extension-list@3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)':
'@tiptap/extension-list@3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)':
dependencies:
'@tiptap/core': 3.0.6(@tiptap/pm@3.0.6)
'@tiptap/pm': 3.0.6
'@tiptap/extension-ordered-list@3.0.6(@tiptap/extension-list@3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))':
'@tiptap/extension-ordered-list@3.0.6(@tiptap/extension-list@3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))':
dependencies:
'@tiptap/extension-list': 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
'@tiptap/extension-list': 3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
'@tiptap/extension-paragraph@3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))':
dependencies:
@ -21293,7 +21296,7 @@ snapshots:
'@tiptap/core': 3.0.6(@tiptap/pm@3.0.6)
'@tiptap/extension-blockquote': 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))
'@tiptap/extension-bold': 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))
'@tiptap/extension-bullet-list': 3.0.6(@tiptap/extension-list@3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))
'@tiptap/extension-bullet-list': 3.0.6(@tiptap/extension-list@3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))
'@tiptap/extension-code': 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))
'@tiptap/extension-code-block': 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
'@tiptap/extension-document': 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))
@ -21304,10 +21307,10 @@ snapshots:
'@tiptap/extension-horizontal-rule': 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
'@tiptap/extension-italic': 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))
'@tiptap/extension-link': 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
'@tiptap/extension-list': 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
'@tiptap/extension-list-item': 3.0.6(@tiptap/extension-list@3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))
'@tiptap/extension-list-keymap': 3.0.6(@tiptap/extension-list@3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))
'@tiptap/extension-ordered-list': 3.0.6(@tiptap/extension-list@3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))
'@tiptap/extension-list': 3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
'@tiptap/extension-list-item': 3.0.6(@tiptap/extension-list@3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))
'@tiptap/extension-list-keymap': 3.0.6(@tiptap/extension-list@3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))
'@tiptap/extension-ordered-list': 3.0.6(@tiptap/extension-list@3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))
'@tiptap/extension-paragraph': 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))
'@tiptap/extension-strike': 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))
'@tiptap/extension-text': 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))