mirror of
https://github.com/morten-olsen/refocus.dev.git
synced 2026-02-08 00:46:25 +01:00
feat: desktop version
This commit is contained in:
1
.npmrc
1
.npmrc
@@ -1,2 +1,3 @@
|
|||||||
node-linker=hoisted
|
node-linker=hoisted
|
||||||
|
public-hoist-pattern=*
|
||||||
store-dir=.pnpm-store
|
store-dir=.pnpm-store
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
import { createContext, useCallback, useMemo, useState } from 'react';
|
import {
|
||||||
|
createContext,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import type { Notification } from './types';
|
import type { Notification } from './types';
|
||||||
|
|
||||||
type NotificationsContextValue = {
|
type NotificationsContextValue = {
|
||||||
@@ -9,6 +15,7 @@ type NotificationsContextValue = {
|
|||||||
|
|
||||||
type NotificationsProviderProps = {
|
type NotificationsProviderProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
onNotificationsUpdate?: (notification: Notification[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const NotificationsContext = createContext<NotificationsContextValue | null>(
|
const NotificationsContext = createContext<NotificationsContextValue | null>(
|
||||||
@@ -19,6 +26,7 @@ let nextId = 0;
|
|||||||
|
|
||||||
const NotificationsProvider: React.FC<NotificationsProviderProps> = ({
|
const NotificationsProvider: React.FC<NotificationsProviderProps> = ({
|
||||||
children,
|
children,
|
||||||
|
onNotificationsUpdate,
|
||||||
}) => {
|
}) => {
|
||||||
const [notifications, setNotifications] = useState<Notification[]>([]);
|
const [notifications, setNotifications] = useState<Notification[]>([]);
|
||||||
|
|
||||||
@@ -55,6 +63,10 @@ const NotificationsProvider: React.FC<NotificationsProviderProps> = ({
|
|||||||
[notifications, add, dismiss],
|
[notifications, add, dismiss],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onNotificationsUpdate?.(notifications);
|
||||||
|
}, [notifications, onNotificationsUpdate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NotificationsContext.Provider value={value}>
|
<NotificationsContext.Provider value={value}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
SlackLogin,
|
SlackLogin,
|
||||||
} from './clients';
|
} from './clients';
|
||||||
import { Widget, WidgetsProvider } from './widgets';
|
import { Widget, WidgetsProvider } from './widgets';
|
||||||
import { NotificationsProvider } from './notifications';
|
import { NotificationsProvider, Notification } from './notifications';
|
||||||
import { BoardsLoad, BoardsProvider, BoardsSave } from './boards';
|
import { BoardsLoad, BoardsProvider, BoardsSave } from './boards';
|
||||||
|
|
||||||
type DashboardProviderProps = {
|
type DashboardProviderProps = {
|
||||||
@@ -14,6 +14,7 @@ type DashboardProviderProps = {
|
|||||||
widgets?: Widget<TSchema>[];
|
widgets?: Widget<TSchema>[];
|
||||||
load: BoardsLoad;
|
load: BoardsLoad;
|
||||||
save: BoardsSave;
|
save: BoardsSave;
|
||||||
|
onNotificationsUpdate?: (notifications: Notification[]) => void;
|
||||||
logins: {
|
logins: {
|
||||||
github: GithubLogin;
|
github: GithubLogin;
|
||||||
linear: LinearLogin;
|
linear: LinearLogin;
|
||||||
@@ -27,10 +28,11 @@ const DashboardProvider: React.FC<DashboardProviderProps> = ({
|
|||||||
load,
|
load,
|
||||||
save,
|
save,
|
||||||
logins,
|
logins,
|
||||||
|
onNotificationsUpdate,
|
||||||
}) => (
|
}) => (
|
||||||
<WidgetsProvider widgets={widgets}>
|
<WidgetsProvider widgets={widgets}>
|
||||||
<BoardsProvider load={load} save={save}>
|
<BoardsProvider load={load} save={save}>
|
||||||
<NotificationsProvider>
|
<NotificationsProvider onNotificationsUpdate={onNotificationsUpdate}>
|
||||||
<ClientProvider logins={logins}>{children}</ClientProvider>
|
<ClientProvider logins={logins}>{children}</ClientProvider>
|
||||||
</NotificationsProvider>
|
</NotificationsProvider>
|
||||||
</BoardsProvider>
|
</BoardsProvider>
|
||||||
|
|||||||
@@ -11,3 +11,4 @@ export * from './masonry';
|
|||||||
export * from './code-editor';
|
export * from './code-editor';
|
||||||
export * from './popover';
|
export * from './popover';
|
||||||
export * from './form';
|
export * from './form';
|
||||||
|
export * from './loader';
|
||||||
|
|||||||
37
packages/ui/src/base/loader/index.tsx
Normal file
37
packages/ui/src/base/loader/index.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
import { LuLoader2 } from 'react-icons/lu';
|
||||||
|
|
||||||
|
type LoaderProps = {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const LoaderWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const LoaderIcon = styled.div`
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 5px solid #ccc;
|
||||||
|
border-top-color: #000;
|
||||||
|
animation: spin 1s infinite linear;
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Loader: React.FC<LoaderProps> = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<LoaderWrapper>
|
||||||
|
<LoaderIcon>{children || <LuLoader2 />}</LoaderIcon>
|
||||||
|
</LoaderWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Loader };
|
||||||
@@ -8,10 +8,10 @@ import {
|
|||||||
} from '@refocus/sdk';
|
} from '@refocus/sdk';
|
||||||
import { IoAddCircleOutline } from 'react-icons/io5';
|
import { IoAddCircleOutline } from 'react-icons/io5';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { View } from '../../base';
|
import { Button, Dialog, Form, View } from '../../base';
|
||||||
import { Board } from '../board';
|
import { Board } from '../board';
|
||||||
import { Tabs } from '../../base/tabs';
|
import { Tabs } from '../../base/tabs';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
const Wrapper = styled(View)`
|
const Wrapper = styled(View)`
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
@@ -36,32 +36,38 @@ const Title: React.FC<{ id: string }> = ({ id }) => {
|
|||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
const boards = useBoards();
|
const boards = useBoards();
|
||||||
|
const [boardName, setBoardName] = useState('');
|
||||||
|
const [addOpen, setAddOpen] = useState(false);
|
||||||
const selected = useSelectedBoard();
|
const selected = useSelectedBoard();
|
||||||
const selectBoard = useSelectBoard();
|
const selectBoard = useSelectBoard();
|
||||||
const addBoardAction = useAddBoard();
|
const addBoardAction = useAddBoard();
|
||||||
const removeBoard = useRemoveBoard();
|
const removeBoard = useRemoveBoard();
|
||||||
|
|
||||||
const addBoard = useCallback(() => {
|
const addBoard = useCallback(
|
||||||
const name = prompt('Board name?');
|
(name: string) => {
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setAddOpen(false);
|
||||||
addBoardAction(name);
|
addBoardAction(name);
|
||||||
}, [addBoardAction]);
|
},
|
||||||
|
[addBoardAction],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper $fc>
|
<Wrapper $fc>
|
||||||
<View $f={1}>
|
<View $f={1}>
|
||||||
<Tabs value={selected} onValueChange={selectBoard}>
|
<Tabs value={selected} onValueChange={selectBoard}>
|
||||||
<Tabs.List>
|
<Tabs.List>
|
||||||
{Object.entries(boards).map(([id, board]) => (
|
{Object.entries(boards).map(([id]) => (
|
||||||
<Tabs.Trigger key={id} value={id}>
|
<Tabs.Trigger key={id} value={id}>
|
||||||
<Title id={id} />
|
<Title id={id} />
|
||||||
<Tabs.Close onClick={() => removeBoard(id)} />
|
<Tabs.Close onClick={() => removeBoard(id)} />
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
))}
|
))}
|
||||||
|
<Dialog open={addOpen} onOpenChange={setAddOpen}>
|
||||||
|
<Dialog.Trigger>
|
||||||
<View
|
<View
|
||||||
onClick={addBoard}
|
|
||||||
$fr
|
$fr
|
||||||
$justify="center"
|
$justify="center"
|
||||||
$items="center"
|
$items="center"
|
||||||
@@ -70,6 +76,28 @@ const App: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<IoAddCircleOutline size={16} />
|
<IoAddCircleOutline size={16} />
|
||||||
</View>
|
</View>
|
||||||
|
</Dialog.Trigger>
|
||||||
|
<Dialog.Portal>
|
||||||
|
<Dialog.Overlay />
|
||||||
|
<Dialog.Content>
|
||||||
|
<Form>
|
||||||
|
<Form.Field label="Board Name">
|
||||||
|
<Form.Input
|
||||||
|
value={boardName}
|
||||||
|
onChange={(e) => setBoardName(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Buttons>
|
||||||
|
<Button
|
||||||
|
title="Add Board"
|
||||||
|
onClick={() => addBoard(boardName)}
|
||||||
|
/>
|
||||||
|
</Form.Buttons>
|
||||||
|
</Form>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Portal>
|
||||||
|
</Dialog>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
{Object.entries(boards).map(([id, board]) => (
|
{Object.entries(boards).map(([id, board]) => (
|
||||||
<Tabs.Content key={id} value={id}>
|
<Tabs.Content key={id} value={id}>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DashboardProvider, Widget } from '@refocus/sdk';
|
import { DashboardProvider, Widget, Notification } from '@refocus/sdk';
|
||||||
import { UIProvider } from './theme/provider';
|
import { UIProvider } from './theme/provider';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { GithubLogin } from './github';
|
import { GithubLogin } from './github';
|
||||||
@@ -8,9 +8,14 @@ import { SlackLogin } from './slack';
|
|||||||
type FocusProviderProps = {
|
type FocusProviderProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
widgets: Widget<any>[];
|
widgets: Widget<any>[];
|
||||||
|
onNotificationsUpdate?: (notifications: Notification[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FocusProvider: React.FC<FocusProviderProps> = ({ children, widgets }) => {
|
const FocusProvider: React.FC<FocusProviderProps> = ({
|
||||||
|
children,
|
||||||
|
widgets,
|
||||||
|
onNotificationsUpdate,
|
||||||
|
}) => {
|
||||||
const save = useCallback((data: any) => {
|
const save = useCallback((data: any) => {
|
||||||
localStorage.setItem('boards', JSON.stringify(data));
|
localStorage.setItem('boards', JSON.stringify(data));
|
||||||
}, []);
|
}, []);
|
||||||
@@ -42,6 +47,7 @@ const FocusProvider: React.FC<FocusProviderProps> = ({ children, widgets }) => {
|
|||||||
save={save}
|
save={save}
|
||||||
widgets={widgets}
|
widgets={widgets}
|
||||||
logins={logins}
|
logins={logins}
|
||||||
|
onNotificationsUpdate={onNotificationsUpdate}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</DashboardProvider>
|
</DashboardProvider>
|
||||||
|
|||||||
44
packages/widgets/src/code/edit.tsx
Normal file
44
packages/widgets/src/code/edit.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { Button, CodeEditor, Form } from '@refocus/ui';
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
import { Props } from './schema';
|
||||||
|
|
||||||
|
type EditorProps = {
|
||||||
|
value?: Props;
|
||||||
|
save: (data: Props) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Edit: React.FC<EditorProps> = ({ value, save }) => {
|
||||||
|
const [code, setCode] = useState(value?.code || '');
|
||||||
|
const [language, setLanguage] = useState(value?.language || '');
|
||||||
|
const [name, setName] = useState(value?.name || '');
|
||||||
|
|
||||||
|
const handleSave = useCallback(() => {
|
||||||
|
save({
|
||||||
|
name,
|
||||||
|
code,
|
||||||
|
language,
|
||||||
|
});
|
||||||
|
}, [code, save, language, name]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form>
|
||||||
|
<Form.Field label="Name">
|
||||||
|
<Form.Input value={name} onChange={(e) => setName(e.target.value)} />
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field label="Language">
|
||||||
|
<Form.Input
|
||||||
|
value={language}
|
||||||
|
onChange={(e) => setLanguage(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field label="Code">
|
||||||
|
<CodeEditor language={language} value={code} setValue={setCode} />
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Buttons>
|
||||||
|
<Button onClick={handleSave} title="Save" />
|
||||||
|
</Form.Buttons>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Edit };
|
||||||
17
packages/widgets/src/code/index.tsx
Normal file
17
packages/widgets/src/code/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Widget } from '@refocus/sdk';
|
||||||
|
import { IoLogoMarkdown } from 'react-icons/io';
|
||||||
|
import { schema } from './schema';
|
||||||
|
import { Edit } from './edit';
|
||||||
|
import { View } from './view';
|
||||||
|
|
||||||
|
const widget: Widget<typeof schema> = {
|
||||||
|
name: 'Code',
|
||||||
|
description: 'Write a code file',
|
||||||
|
icon: <IoLogoMarkdown />,
|
||||||
|
id: 'text.code',
|
||||||
|
schema,
|
||||||
|
component: View,
|
||||||
|
edit: Edit,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default widget;
|
||||||
12
packages/widgets/src/code/schema.ts
Normal file
12
packages/widgets/src/code/schema.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
|
|
||||||
|
const schema = Type.Object({
|
||||||
|
name: Type.String(),
|
||||||
|
code: Type.String(),
|
||||||
|
language: Type.String(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type Props = Static<typeof schema>;
|
||||||
|
|
||||||
|
export type { Props };
|
||||||
|
export { schema };
|
||||||
33
packages/widgets/src/code/view.tsx
Normal file
33
packages/widgets/src/code/view.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { useName } from '@refocus/sdk';
|
||||||
|
import { Props } from './schema';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { CodeEditor, View } from '@refocus/ui';
|
||||||
|
import { styled } from 'styled-components';
|
||||||
|
|
||||||
|
const FullHeight = styled(View)`
|
||||||
|
height: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledCodeEditor = styled(CodeEditor)`
|
||||||
|
height: 100%;
|
||||||
|
`;
|
||||||
|
const WidgetView: React.FC<Props> = ({ code, language, name }) => {
|
||||||
|
const [, setName] = useName();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setName(name);
|
||||||
|
}, [name, setName]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FullHeight $fc>
|
||||||
|
<StyledCodeEditor
|
||||||
|
readOnly
|
||||||
|
language={language}
|
||||||
|
value={code}
|
||||||
|
setValue={() => {}}
|
||||||
|
/>
|
||||||
|
</FullHeight>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { WidgetView as View };
|
||||||
@@ -3,12 +3,14 @@ import { github } from './github';
|
|||||||
import { linear } from './linear';
|
import { linear } from './linear';
|
||||||
import { slack } from './slack';
|
import { slack } from './slack';
|
||||||
import markdown from './markdown';
|
import markdown from './markdown';
|
||||||
|
import code from './code';
|
||||||
|
|
||||||
const widgets = [
|
const widgets = [
|
||||||
...linear,
|
...linear,
|
||||||
...github,
|
...github,
|
||||||
...slack,
|
...slack,
|
||||||
markdown,
|
markdown,
|
||||||
|
code,
|
||||||
] satisfies Widget<any>[];
|
] satisfies Widget<any>[];
|
||||||
|
|
||||||
export { widgets };
|
export { widgets };
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const renderElement = (item: Renderable) => {
|
|||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
case 'user':
|
case 'user':
|
||||||
return <User id={item.user_id} />;
|
return <User key={item.user_id} id={item.user_id} />;
|
||||||
case 'emoji':
|
case 'emoji':
|
||||||
return unicodeToEmoji(item.unicode);
|
return unicodeToEmoji(item.unicode);
|
||||||
case 'rich_text_list':
|
case 'rich_text_list':
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { render } from '../../block/render';
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { WidgetProvider, WidgetView } from '@refocus/sdk';
|
import { WidgetProvider, WidgetView } from '@refocus/sdk';
|
||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import { User } from '../../block/elements/user';
|
|
||||||
import { UserAvatar } from '../../block/elements/user-avatar';
|
import { UserAvatar } from '../../block/elements/user-avatar';
|
||||||
|
|
||||||
type Message = Exclude<
|
type Message = Exclude<
|
||||||
@@ -57,7 +56,7 @@ const Message: React.FC<Message> = ({
|
|||||||
{reaction.name}
|
{reaction.name}
|
||||||
<View $fr>
|
<View $fr>
|
||||||
{reaction.users?.map((user) => (
|
{reaction.users?.map((user) => (
|
||||||
<UserAvatar id={user} />
|
<UserAvatar key={user} id={user} />
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import styled from 'styled-components';
|
|||||||
import { Props } from './schema';
|
import { Props } from './schema';
|
||||||
import { ConversationsHistoryResponse } from '@slack/web-api';
|
import { ConversationsHistoryResponse } from '@slack/web-api';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Chat, Slack, Typography, View } from '@refocus/ui';
|
import { Chat, Loader, Slack, Typography, View } from '@refocus/ui';
|
||||||
import { User } from '../block/elements/user';
|
import { User } from '../block/elements/user';
|
||||||
import { Message } from './message/view';
|
import { Message } from './message/view';
|
||||||
|
|
||||||
@@ -42,13 +42,14 @@ const WidgetView = withSlack<Props>(({ conversationId, ts }) => {
|
|||||||
const [, setName] = useName();
|
const [, setName] = useName();
|
||||||
const addNotification = useAddWidgetNotification();
|
const addNotification = useAddWidgetNotification();
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
const { fetch, data } = useSlackQuery(async (client, props: Props) => {
|
const { fetch, data, loading } = useSlackQuery(
|
||||||
|
async (client, props: Props) => {
|
||||||
if (props.ts) {
|
if (props.ts) {
|
||||||
const response = await client.send('conversations.replies', {
|
const response = await client.send('conversations.replies', {
|
||||||
channel: props.conversationId,
|
channel: props.conversationId,
|
||||||
ts: props.ts,
|
ts: props.ts,
|
||||||
});
|
});
|
||||||
return response.messages! as MessageType[];
|
return response.messages!.reverse() as MessageType[];
|
||||||
} else {
|
} else {
|
||||||
const response = await client.send('conversations.history', {
|
const response = await client.send('conversations.history', {
|
||||||
channel: props.conversationId,
|
channel: props.conversationId,
|
||||||
@@ -56,7 +57,8 @@ const WidgetView = withSlack<Props>(({ conversationId, ts }) => {
|
|||||||
});
|
});
|
||||||
return response.messages! as MessageType[];
|
return response.messages! as MessageType[];
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
const info = useSlackQuery(async (client, props: Props) => {
|
const info = useSlackQuery(async (client, props: Props) => {
|
||||||
const response = await client.send('conversations.info', {
|
const response = await client.send('conversations.info', {
|
||||||
channel: props.conversationId,
|
channel: props.conversationId,
|
||||||
@@ -65,16 +67,7 @@ const WidgetView = withSlack<Props>(({ conversationId, ts }) => {
|
|||||||
return response.channel!;
|
return response.channel!;
|
||||||
});
|
});
|
||||||
|
|
||||||
const { fetch: post } = useSlackQuery(
|
const update = useAutoUpdate(
|
||||||
async (client, props: PostMessageOptions) => {
|
|
||||||
client.send('chat.postMessage', {
|
|
||||||
text: props.message,
|
|
||||||
channel: conversationId,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
useAutoUpdate(
|
|
||||||
{
|
{
|
||||||
action: async () => {
|
action: async () => {
|
||||||
await info.fetch({ conversationId, ts });
|
await info.fetch({ conversationId, ts });
|
||||||
@@ -103,10 +96,22 @@ const WidgetView = withSlack<Props>(({ conversationId, ts }) => {
|
|||||||
[conversationId, ts],
|
[conversationId, ts],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { fetch: post } = useSlackQuery(
|
||||||
|
async (client, props: PostMessageOptions) => {
|
||||||
|
await client.send('chat.postMessage', {
|
||||||
|
text: props.message,
|
||||||
|
channel: conversationId,
|
||||||
|
});
|
||||||
|
setMessage('');
|
||||||
|
await update();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper $p="sm" $fc $gap="sm">
|
<Wrapper $p="sm" $fc $gap="sm">
|
||||||
|
{loading && <Loader />}
|
||||||
<MessageList $gap="md" $fc>
|
<MessageList $gap="md" $fc>
|
||||||
{data?.map((message) => {
|
{data?.map((message, index) => {
|
||||||
if ('subtype' in message && message.subtype === 'channel_join') {
|
if ('subtype' in message && message.subtype === 'channel_join') {
|
||||||
return (
|
return (
|
||||||
<Typography key={message.ts}>
|
<Typography key={message.ts}>
|
||||||
@@ -116,7 +121,7 @@ const WidgetView = withSlack<Props>(({ conversationId, ts }) => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Message
|
<Message
|
||||||
key={message.ts}
|
key={message.ts || index}
|
||||||
{...message}
|
{...message}
|
||||||
conversationId={conversationId}
|
conversationId={conversationId}
|
||||||
/>
|
/>
|
||||||
|
|||||||
2087
pnpm-lock.yaml
generated
2087
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,10 @@
|
|||||||
"name": "app",
|
"name": "app",
|
||||||
"path": "packages/app/src"
|
"path": "packages/app/src"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "desktop",
|
||||||
|
"path": "packages/desktop/src"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": ".",
|
"name": ".",
|
||||||
"path": "."
|
"path": "."
|
||||||
|
|||||||
Reference in New Issue
Block a user