@dxos/plugin-github 0.6.8-main.046e6cf
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +8 -0
- package/README.md +15 -0
- package/dist/lib/browser/chunk-NJP5BGD2.mjs +24 -0
- package/dist/lib/browser/chunk-NJP5BGD2.mjs.map +7 -0
- package/dist/lib/browser/index.css +7 -0
- package/dist/lib/browser/index.css.map +7 -0
- package/dist/lib/browser/index.mjs +1531 -0
- package/dist/lib/browser/index.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -0
- package/dist/lib/browser/meta.mjs +11 -0
- package/dist/lib/browser/meta.mjs.map +7 -0
- package/dist/types/src/GithubPlugin.d.ts +4 -0
- package/dist/types/src/GithubPlugin.d.ts.map +1 -0
- package/dist/types/src/components/EmbeddedMain/EmbeddedMain.d.ts +4 -0
- package/dist/types/src/components/EmbeddedMain/EmbeddedMain.d.ts.map +1 -0
- package/dist/types/src/components/EmbeddedMain/GfmPreview.d.ts +9 -0
- package/dist/types/src/components/EmbeddedMain/GfmPreview.d.ts.map +1 -0
- package/dist/types/src/components/EmbeddedMain/index.d.ts +2 -0
- package/dist/types/src/components/EmbeddedMain/index.d.ts.map +1 -0
- package/dist/types/src/components/ExportDialog.d.ts +8 -0
- package/dist/types/src/components/ExportDialog.d.ts.map +1 -0
- package/dist/types/src/components/GithubApiProviders/GitHubSettings.d.ts +3 -0
- package/dist/types/src/components/GithubApiProviders/GitHubSettings.d.ts.map +1 -0
- package/dist/types/src/components/GithubApiProviders/OctokitProvider.d.ts +15 -0
- package/dist/types/src/components/GithubApiProviders/OctokitProvider.d.ts.map +1 -0
- package/dist/types/src/components/GithubApiProviders/index.d.ts +3 -0
- package/dist/types/src/components/GithubApiProviders/index.d.ts.map +1 -0
- package/dist/types/src/components/GithubEchoResolverProviders/DocumentTreeItem.d.ts +6 -0
- package/dist/types/src/components/GithubEchoResolverProviders/DocumentTreeItem.d.ts.map +1 -0
- package/dist/types/src/components/GithubEchoResolverProviders/ResolverContext.d.ts +7 -0
- package/dist/types/src/components/GithubEchoResolverProviders/ResolverContext.d.ts.map +1 -0
- package/dist/types/src/components/GithubEchoResolverProviders/ResolverDialog.d.ts +6 -0
- package/dist/types/src/components/GithubEchoResolverProviders/ResolverDialog.d.ts.map +1 -0
- package/dist/types/src/components/GithubEchoResolverProviders/ResolverProps.d.ts +16 -0
- package/dist/types/src/components/GithubEchoResolverProviders/ResolverProps.d.ts.map +1 -0
- package/dist/types/src/components/GithubEchoResolverProviders/ResolverTree.d.ts +3 -0
- package/dist/types/src/components/GithubEchoResolverProviders/ResolverTree.d.ts.map +1 -0
- package/dist/types/src/components/GithubEchoResolverProviders/SpacePickerTreeItem.d.ts +11 -0
- package/dist/types/src/components/GithubEchoResolverProviders/SpacePickerTreeItem.d.ts.map +1 -0
- package/dist/types/src/components/GithubEchoResolverProviders/index.d.ts +3 -0
- package/dist/types/src/components/GithubEchoResolverProviders/index.d.ts.map +1 -0
- package/dist/types/src/components/GithubEchoResolverProviders/spaceResolvers.d.ts +6 -0
- package/dist/types/src/components/GithubEchoResolverProviders/spaceResolvers.d.ts.map +1 -0
- package/dist/types/src/components/ImportDialog.d.ts +7 -0
- package/dist/types/src/components/ImportDialog.d.ts.map +1 -0
- package/dist/types/src/components/Issue.d.ts +5 -0
- package/dist/types/src/components/Issue.d.ts.map +1 -0
- package/dist/types/src/components/Loading/Loading.d.ts +15 -0
- package/dist/types/src/components/Loading/Loading.d.ts.map +1 -0
- package/dist/types/src/components/Loading/Loading.stories.d.ts +52 -0
- package/dist/types/src/components/Loading/Loading.stories.d.ts.map +1 -0
- package/dist/types/src/components/Loading/index.d.ts +2 -0
- package/dist/types/src/components/Loading/index.d.ts.map +1 -0
- package/dist/types/src/components/MarkdownActions.d.ts +7 -0
- package/dist/types/src/components/MarkdownActions.d.ts.map +1 -0
- package/dist/types/src/components/UrlDialog.d.ts +6 -0
- package/dist/types/src/components/UrlDialog.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +9 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +3 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -0
- package/dist/types/src/hooks/useDocGhId.d.ts +6 -0
- package/dist/types/src/hooks/useDocGhId.d.ts.map +1 -0
- package/dist/types/src/hooks/useGhIdFromUrl.d.ts +2 -0
- package/dist/types/src/hooks/useGhIdFromUrl.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +16 -0
- package/dist/types/src/meta.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +56 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +30 -0
- package/dist/types/src/types.d.ts.map +1 -0
- package/package.json +76 -0
- package/src/GithubPlugin.tsx +129 -0
- package/src/components/EmbeddedMain/EmbeddedMain.tsx +296 -0
- package/src/components/EmbeddedMain/GfmPreview.tsx +110 -0
- package/src/components/EmbeddedMain/embedded.css +3 -0
- package/src/components/EmbeddedMain/index.ts +5 -0
- package/src/components/ExportDialog.tsx +157 -0
- package/src/components/GithubApiProviders/GitHubSettings.tsx +72 -0
- package/src/components/GithubApiProviders/OctokitProvider.tsx +60 -0
- package/src/components/GithubApiProviders/index.ts +6 -0
- package/src/components/GithubEchoResolverProviders/DocumentTreeItem.tsx +26 -0
- package/src/components/GithubEchoResolverProviders/ResolverContext.tsx +140 -0
- package/src/components/GithubEchoResolverProviders/ResolverDialog.tsx +34 -0
- package/src/components/GithubEchoResolverProviders/ResolverProps.ts +23 -0
- package/src/components/GithubEchoResolverProviders/ResolverTree.tsx +86 -0
- package/src/components/GithubEchoResolverProviders/SpacePickerTreeItem.tsx +105 -0
- package/src/components/GithubEchoResolverProviders/index.ts +6 -0
- package/src/components/GithubEchoResolverProviders/spaceResolvers.ts +86 -0
- package/src/components/ImportDialog.tsx +72 -0
- package/src/components/Issue.tsx +62 -0
- package/src/components/Loading/Loading.stories.tsx +28 -0
- package/src/components/Loading/Loading.tsx +70 -0
- package/src/components/Loading/index.ts +5 -0
- package/src/components/MarkdownActions.tsx +136 -0
- package/src/components/UrlDialog.tsx +52 -0
- package/src/components/index.ts +12 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/useDocGhId.ts +38 -0
- package/src/hooks/useGhIdFromUrl.ts +26 -0
- package/src/index.ts +9 -0
- package/src/meta.tsx +21 -0
- package/src/translations.ts +72 -0
- package/src/types.ts +51 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Octokit } from 'octokit';
|
|
6
|
+
import React, { type Context, createContext, type PropsWithChildren, useContext, useEffect, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { log } from '@dxos/log';
|
|
9
|
+
|
|
10
|
+
export type OctokitContextValue = {
|
|
11
|
+
pat?: string;
|
|
12
|
+
setPat: (pat?: string) => void;
|
|
13
|
+
patError: 'failed' | null; // TODO(burdon): Remove/generalize?
|
|
14
|
+
octokit: Octokit | null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const OctokitContext: Context<OctokitContextValue> = createContext<OctokitContextValue>({
|
|
18
|
+
setPat: () => {},
|
|
19
|
+
patError: null,
|
|
20
|
+
octokit: null,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const useOctokitContext = () => useContext(OctokitContext);
|
|
24
|
+
|
|
25
|
+
export const OctokitProvider = ({
|
|
26
|
+
pat,
|
|
27
|
+
onPatChanged,
|
|
28
|
+
children,
|
|
29
|
+
}: PropsWithChildren<{ pat?: string; onPatChanged?: (pat: string | undefined) => void }>) => {
|
|
30
|
+
const [octokit, setOctokit] = useState<Octokit | null>(null);
|
|
31
|
+
const [patError, setPatError] = useState<OctokitContextValue['patError']>(null);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (pat && !octokit) {
|
|
35
|
+
void setPat(pat);
|
|
36
|
+
}
|
|
37
|
+
}, [pat, octokit]);
|
|
38
|
+
|
|
39
|
+
const setPat = async (pat?: string) => {
|
|
40
|
+
onPatChanged?.(pat);
|
|
41
|
+
if (pat) {
|
|
42
|
+
const octokit = new Octokit({ auth: pat });
|
|
43
|
+
return octokit.rest.users.getAuthenticated().then(
|
|
44
|
+
() => {
|
|
45
|
+
setPatError(null);
|
|
46
|
+
setOctokit(octokit);
|
|
47
|
+
},
|
|
48
|
+
(err) => {
|
|
49
|
+
log.warn('Failed to authenticate Octokit from PAT', err);
|
|
50
|
+
setPatError('failed');
|
|
51
|
+
setOctokit(null);
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
} else {
|
|
55
|
+
setOctokit(null);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return <OctokitContext.Provider value={{ pat, setPat, patError, octokit }}>{children}</OctokitContext.Provider>;
|
|
60
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { TextAa } from '@phosphor-icons/react';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
import { type DocumentType } from '@dxos/plugin-markdown/types';
|
|
9
|
+
import { useTranslation, TreeItem } from '@dxos/react-ui';
|
|
10
|
+
import { getSize, mx } from '@dxos/react-ui-theme';
|
|
11
|
+
|
|
12
|
+
import { GITHUB_PLUGIN } from '../../meta';
|
|
13
|
+
|
|
14
|
+
export const DocumentTreeItem = ({ document }: { document: DocumentType }) => {
|
|
15
|
+
const { t } = useTranslation(GITHUB_PLUGIN);
|
|
16
|
+
return (
|
|
17
|
+
<TreeItem.Root classNames='flex gap-2'>
|
|
18
|
+
<TreeItem.Heading classNames='contents'>
|
|
19
|
+
<TextAa weight='regular' className={mx(getSize(4), 'shrink-0 mbs-2')} />
|
|
20
|
+
<span className='grow mbs-2 text-sm no-leading'>
|
|
21
|
+
{document.name || t('document title placeholder', { ns: 'dxos.org/plugin/markdown' })}
|
|
22
|
+
</span>
|
|
23
|
+
</TreeItem.Heading>
|
|
24
|
+
</TreeItem.Root>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, {
|
|
6
|
+
type Context,
|
|
7
|
+
createContext,
|
|
8
|
+
type PropsWithChildren,
|
|
9
|
+
useContext,
|
|
10
|
+
useEffect,
|
|
11
|
+
useMemo,
|
|
12
|
+
useState,
|
|
13
|
+
} from 'react';
|
|
14
|
+
|
|
15
|
+
import { create } from '@dxos/echo-schema';
|
|
16
|
+
import { log } from '@dxos/log';
|
|
17
|
+
import { DocumentType, TextType } from '@dxos/plugin-markdown/types';
|
|
18
|
+
import { useMulticastObservable } from '@dxos/react-client';
|
|
19
|
+
import { getMeta, type Space, SpaceState, useQuery, useSpaces } from '@dxos/react-client/echo';
|
|
20
|
+
import { useIdentity } from '@dxos/react-client/halo';
|
|
21
|
+
|
|
22
|
+
import { type DocumentResolverProps, type SpaceResolverProps } from './ResolverProps';
|
|
23
|
+
import { displayName, matchSpace } from './spaceResolvers';
|
|
24
|
+
|
|
25
|
+
const getLocationIdentifier = () => {
|
|
26
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
27
|
+
const location = searchParams.get('location');
|
|
28
|
+
let source, id;
|
|
29
|
+
try {
|
|
30
|
+
const url = location ? new URL(location) : undefined;
|
|
31
|
+
source = url?.hostname.split('.').reverse().join('.');
|
|
32
|
+
id = url?.pathname.slice(1);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
log.catch(err);
|
|
35
|
+
}
|
|
36
|
+
return [source, id];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const SpaceResolverContext: Context<SpaceResolverProps> = createContext<SpaceResolverProps>({
|
|
40
|
+
space: null,
|
|
41
|
+
setSpace: (_space) => {},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const SpaceResolverProvider = ({ children }: PropsWithChildren<{}>) => {
|
|
45
|
+
const identity = useIdentity();
|
|
46
|
+
const identityHex = identity?.identityKey.toHex();
|
|
47
|
+
const [source, id] = getLocationIdentifier();
|
|
48
|
+
const spaces = useSpaces({ all: true });
|
|
49
|
+
|
|
50
|
+
const [nextSpace, setSpace] = useState<Space | null>(null);
|
|
51
|
+
|
|
52
|
+
const space = useMemo(
|
|
53
|
+
() =>
|
|
54
|
+
nextSpace ?? (identityHex ? spaces.find((space) => matchSpace(space, identityHex, source, id)) ?? null : null),
|
|
55
|
+
[spaces, nextSpace, identityHex, source, id],
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<SpaceResolverContext.Provider value={{ space, setSpace, source, id, identityHex }}>
|
|
60
|
+
{children}
|
|
61
|
+
</SpaceResolverContext.Provider>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const defaultDocumentResolverContext: DocumentResolverProps = {
|
|
66
|
+
document: null,
|
|
67
|
+
setDocument: (_document) => {},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const DocumentResolverContext: Context<DocumentResolverProps> =
|
|
71
|
+
createContext<DocumentResolverProps>(defaultDocumentResolverContext);
|
|
72
|
+
|
|
73
|
+
const DocumentResolverProviderImpl = ({
|
|
74
|
+
space,
|
|
75
|
+
source,
|
|
76
|
+
id,
|
|
77
|
+
children,
|
|
78
|
+
}: PropsWithChildren<{ space: Space; source: string; id: string }>) => {
|
|
79
|
+
const spaceState = useMulticastObservable(space.state);
|
|
80
|
+
const [document, setDocument] = useState<DocumentType | null>(null);
|
|
81
|
+
const defaultDisplayName = displayName(source, id);
|
|
82
|
+
|
|
83
|
+
const documents = useQuery(space, (obj) => {
|
|
84
|
+
const keys = getMeta(obj)?.keys;
|
|
85
|
+
return keys?.find((key: any) => key.source === source && key.id === id);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
const handler = (event: MessageEvent) => {
|
|
90
|
+
if (event.source !== window.parent || spaceState !== SpaceState.SPACE_READY) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (event.data.type === 'initial-data') {
|
|
95
|
+
const nextDocument = space.db.add(
|
|
96
|
+
create(
|
|
97
|
+
DocumentType,
|
|
98
|
+
{
|
|
99
|
+
name: defaultDisplayName,
|
|
100
|
+
content: create(TextType, { content: event.data.content }),
|
|
101
|
+
threads: [],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
keys: [{ source, id }],
|
|
105
|
+
},
|
|
106
|
+
),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
setDocument(nextDocument);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// todo(thure): Won't this always be the case initially?
|
|
114
|
+
if (documents.length === 0) {
|
|
115
|
+
window.addEventListener('message', handler);
|
|
116
|
+
window.parent.postMessage({ type: 'request-initial-data' }, 'https://github.com');
|
|
117
|
+
return () => window.removeEventListener('message', handler);
|
|
118
|
+
} else {
|
|
119
|
+
setDocument(documents[0] as DocumentType);
|
|
120
|
+
}
|
|
121
|
+
}, [spaceState, space, documents]);
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<DocumentResolverContext.Provider value={{ document, setDocument }}>{children}</DocumentResolverContext.Provider>
|
|
125
|
+
);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export const DocumentResolverProvider = ({ children }: PropsWithChildren<{}>) => {
|
|
129
|
+
const { space, source, id } = useContext(SpaceResolverContext);
|
|
130
|
+
|
|
131
|
+
return space && source && id ? (
|
|
132
|
+
<DocumentResolverProviderImpl space={space} source={source} id={id}>
|
|
133
|
+
{children}
|
|
134
|
+
</DocumentResolverProviderImpl>
|
|
135
|
+
) : (
|
|
136
|
+
<DocumentResolverContext.Provider value={defaultDocumentResolverContext}>
|
|
137
|
+
{children}
|
|
138
|
+
</DocumentResolverContext.Provider>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { Button, useTranslation } from '@dxos/react-ui';
|
|
8
|
+
|
|
9
|
+
import { ResolverTree } from './ResolverTree';
|
|
10
|
+
import { GITHUB_PLUGIN } from '../../meta';
|
|
11
|
+
|
|
12
|
+
export const ResolverDialog = ({
|
|
13
|
+
handleJoinSpace,
|
|
14
|
+
handleCreateSpace,
|
|
15
|
+
}: {
|
|
16
|
+
handleJoinSpace?: () => void;
|
|
17
|
+
handleCreateSpace?: () => void;
|
|
18
|
+
}) => {
|
|
19
|
+
const { t } = useTranslation(GITHUB_PLUGIN);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<ResolverTree />
|
|
24
|
+
<div role='group' className='shrink-0 flex is-full gap-2'>
|
|
25
|
+
<Button classNames='grow' onClick={handleCreateSpace}>
|
|
26
|
+
{t('create space label', { ns: 'appkit' })}
|
|
27
|
+
</Button>
|
|
28
|
+
<Button classNames='grow' onClick={handleJoinSpace}>
|
|
29
|
+
{t('join space label', { ns: 'appkit' })}
|
|
30
|
+
</Button>
|
|
31
|
+
</div>
|
|
32
|
+
</>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Dispatch, type SetStateAction } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type DocumentType } from '@dxos/plugin-markdown/types';
|
|
8
|
+
import { type Space } from '@dxos/react-client/echo';
|
|
9
|
+
|
|
10
|
+
export type SpaceResolverProps = {
|
|
11
|
+
space: Space | null;
|
|
12
|
+
setSpace: Dispatch<SetStateAction<Space | null>>;
|
|
13
|
+
source?: string;
|
|
14
|
+
id?: string;
|
|
15
|
+
identityHex?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type DocumentResolverProps = {
|
|
19
|
+
document: DocumentType | null;
|
|
20
|
+
setDocument: Dispatch<SetStateAction<DocumentType | null>>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type ResolverProps = SpaceResolverProps & DocumentResolverProps;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback, useContext } from 'react';
|
|
6
|
+
|
|
7
|
+
import { useClient } from '@dxos/react-client';
|
|
8
|
+
import { useSpaces, type Space } from '@dxos/react-client/echo';
|
|
9
|
+
import { Button, Dialog, Tree, useId, useTranslation } from '@dxos/react-ui';
|
|
10
|
+
|
|
11
|
+
import { SpaceResolverContext } from './ResolverContext';
|
|
12
|
+
import { SpacePickerTreeItem } from './SpacePickerTreeItem';
|
|
13
|
+
import { bindSpace, unbindSpace } from './spaceResolvers';
|
|
14
|
+
import { GITHUB_PLUGIN } from '../../meta';
|
|
15
|
+
|
|
16
|
+
export const ResolverTree = () => {
|
|
17
|
+
const client = useClient();
|
|
18
|
+
const spaces = useSpaces({ all: true });
|
|
19
|
+
const treeLabel = useId('treeLabel');
|
|
20
|
+
const { t } = useTranslation(GITHUB_PLUGIN);
|
|
21
|
+
const {
|
|
22
|
+
space: selectedSpace,
|
|
23
|
+
setSpace: setSelectedSpace,
|
|
24
|
+
source,
|
|
25
|
+
id,
|
|
26
|
+
identityHex,
|
|
27
|
+
} = useContext(SpaceResolverContext);
|
|
28
|
+
const setSpace = useCallback(
|
|
29
|
+
(nextSpace: Space | null) => {
|
|
30
|
+
if (identityHex && source && id) {
|
|
31
|
+
selectedSpace && unbindSpace(selectedSpace, identityHex, source, id);
|
|
32
|
+
nextSpace && bindSpace(nextSpace, identityHex, source, id);
|
|
33
|
+
}
|
|
34
|
+
setSelectedSpace(nextSpace);
|
|
35
|
+
},
|
|
36
|
+
[selectedSpace, setSelectedSpace, identityHex, source, id],
|
|
37
|
+
);
|
|
38
|
+
return spaces.length ? (
|
|
39
|
+
<>
|
|
40
|
+
{' '}
|
|
41
|
+
<Dialog.Title tabIndex={-1} classNames='shrink-0'>
|
|
42
|
+
{t('resolver tree label')}
|
|
43
|
+
</Dialog.Title>
|
|
44
|
+
<Dialog.Description>{t('resolver tree description')}</Dialog.Description>
|
|
45
|
+
<div role='separator' className='shrink-0 bs-px bg-neutral-500/20 mlb-1' />
|
|
46
|
+
<Tree.Root
|
|
47
|
+
aria-label={t('resolver tree label')}
|
|
48
|
+
data-testid='composer.sidebarTree'
|
|
49
|
+
classNames='overflow-y-auto -mli-2 p-2'
|
|
50
|
+
>
|
|
51
|
+
{spaces.map((space) => {
|
|
52
|
+
return (
|
|
53
|
+
<SpacePickerTreeItem
|
|
54
|
+
key={space.key.toHex()}
|
|
55
|
+
{...{
|
|
56
|
+
space,
|
|
57
|
+
selected: selectedSpace === space,
|
|
58
|
+
setSpace,
|
|
59
|
+
identityHex: identityHex!,
|
|
60
|
+
source: source!,
|
|
61
|
+
id: id!,
|
|
62
|
+
}}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
})}
|
|
66
|
+
</Tree.Root>
|
|
67
|
+
</>
|
|
68
|
+
) : (
|
|
69
|
+
<>
|
|
70
|
+
<h1 className='text-lg font-normal' id={treeLabel}>
|
|
71
|
+
{t('resolver no spaces message')}
|
|
72
|
+
</h1>
|
|
73
|
+
<div role='separator' className='bs-px bg-neutral-500/20 mlb-2' />
|
|
74
|
+
<Button
|
|
75
|
+
classNames='block is-full'
|
|
76
|
+
onClick={async () => {
|
|
77
|
+
const space = await client.spaces.create();
|
|
78
|
+
bindSpace(space, identityHex!, source!, id!);
|
|
79
|
+
setSpace(space);
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
{t('resolver create space label')}
|
|
83
|
+
</Button>
|
|
84
|
+
</>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { CaretDown, CaretRight } from '@phosphor-icons/react';
|
|
6
|
+
import React, { useMemo, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { DocumentType } from '@dxos/plugin-markdown/types';
|
|
9
|
+
import { getSpaceDisplayName } from '@dxos/plugin-space';
|
|
10
|
+
import { useMulticastObservable } from '@dxos/react-client';
|
|
11
|
+
import { type Space, SpaceState, useQuery, Filter } from '@dxos/react-client/echo';
|
|
12
|
+
import { Button, Tag, Tooltip, Tree, TreeItem, useTranslation } from '@dxos/react-ui';
|
|
13
|
+
import { staticDisabled } from '@dxos/react-ui-theme';
|
|
14
|
+
|
|
15
|
+
import { DocumentTreeItem } from './DocumentTreeItem';
|
|
16
|
+
import { matchSpace } from './spaceResolvers';
|
|
17
|
+
import { GITHUB_PLUGIN } from '../../meta';
|
|
18
|
+
|
|
19
|
+
export const SpacePickerTreeItem = ({
|
|
20
|
+
identityHex,
|
|
21
|
+
space,
|
|
22
|
+
source,
|
|
23
|
+
id,
|
|
24
|
+
setSpace,
|
|
25
|
+
selected,
|
|
26
|
+
}: {
|
|
27
|
+
identityHex: string;
|
|
28
|
+
space: Space;
|
|
29
|
+
setSpace: (nextSpace: Space | null) => void;
|
|
30
|
+
source: string;
|
|
31
|
+
id: string;
|
|
32
|
+
selected?: boolean;
|
|
33
|
+
}) => {
|
|
34
|
+
const { t } = useTranslation(GITHUB_PLUGIN);
|
|
35
|
+
const spaceSate = useMulticastObservable(space.state);
|
|
36
|
+
const disabled = spaceSate !== SpaceState.SPACE_READY;
|
|
37
|
+
const spaceDisplayName = getSpaceDisplayName(space);
|
|
38
|
+
const documents = useQuery(space, Filter.schema(DocumentType));
|
|
39
|
+
const hasDocuments = documents.length > 0;
|
|
40
|
+
|
|
41
|
+
const [open, setOpen] = useState(false);
|
|
42
|
+
|
|
43
|
+
const nBoundMembers = useMemo(() => {
|
|
44
|
+
return Object.keys(space.properties.members ?? {}).filter((identityHex) =>
|
|
45
|
+
matchSpace(space, identityHex, source, id),
|
|
46
|
+
).length;
|
|
47
|
+
}, [source, id, space.properties]);
|
|
48
|
+
|
|
49
|
+
const OpenTriggerIcon = open ? CaretDown : CaretRight;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<TreeItem.Root
|
|
53
|
+
collapsible
|
|
54
|
+
open={open}
|
|
55
|
+
onOpenChange={setOpen}
|
|
56
|
+
classNames={['mbe-2 block', disabled && staticDisabled]}
|
|
57
|
+
{...(disabled && { 'aria-disabled': true })}
|
|
58
|
+
>
|
|
59
|
+
<div role='none' className='flex items-center gap-2'>
|
|
60
|
+
{hasDocuments ? (
|
|
61
|
+
<TreeItem.OpenTrigger>
|
|
62
|
+
<OpenTriggerIcon />
|
|
63
|
+
</TreeItem.OpenTrigger>
|
|
64
|
+
) : (
|
|
65
|
+
<TreeItem.OpenTrigger disabled classNames={staticDisabled}>
|
|
66
|
+
<CaretRight />
|
|
67
|
+
</TreeItem.OpenTrigger>
|
|
68
|
+
)}
|
|
69
|
+
<TreeItem.Heading
|
|
70
|
+
classNames='grow break-words pbs-1 text-base font-medium'
|
|
71
|
+
data-testid='composer.spaceTreeItemHeading'
|
|
72
|
+
>
|
|
73
|
+
{Array.isArray(spaceDisplayName) ? t(...spaceDisplayName) : spaceDisplayName}
|
|
74
|
+
</TreeItem.Heading>
|
|
75
|
+
{nBoundMembers > 0 && (
|
|
76
|
+
<Tooltip.Root>
|
|
77
|
+
<Tooltip.Trigger asChild>
|
|
78
|
+
<Tag palette='info' aria-label={t('bound members message', { count: nBoundMembers })}>
|
|
79
|
+
{nBoundMembers}
|
|
80
|
+
</Tag>
|
|
81
|
+
</Tooltip.Trigger>
|
|
82
|
+
<Tooltip.Portal>
|
|
83
|
+
<Tooltip.Content side='bottom'>
|
|
84
|
+
<Tooltip.Arrow />
|
|
85
|
+
{t('bound members message', { count: nBoundMembers })}
|
|
86
|
+
</Tooltip.Content>
|
|
87
|
+
</Tooltip.Portal>
|
|
88
|
+
</Tooltip.Root>
|
|
89
|
+
)}
|
|
90
|
+
<Button disabled={selected} density='fine' classNames='shrink-0' onClick={() => setSpace(space)}>
|
|
91
|
+
{t(selected ? 'selected label' : 'select label')}
|
|
92
|
+
</Button>
|
|
93
|
+
</div>
|
|
94
|
+
<TreeItem.Body className='pis-4'>
|
|
95
|
+
{hasDocuments && (
|
|
96
|
+
<Tree.Branch>
|
|
97
|
+
{documents.map((document) => (
|
|
98
|
+
<DocumentTreeItem key={document.id} document={document} />
|
|
99
|
+
))}
|
|
100
|
+
</Tree.Branch>
|
|
101
|
+
)}
|
|
102
|
+
</TreeItem.Body>
|
|
103
|
+
</TreeItem.Root>
|
|
104
|
+
);
|
|
105
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import get from 'lodash.get';
|
|
6
|
+
import update from 'lodash.update';
|
|
7
|
+
|
|
8
|
+
import { type Space } from '@dxos/react-client/echo';
|
|
9
|
+
|
|
10
|
+
// todo(thure): Why is the value that gets set in `ghBind` always undefined by the time this is called?
|
|
11
|
+
const ghMatch = (space: Space, identityHex: string, id: string): boolean => {
|
|
12
|
+
const [ghOwner, ghRepo, ..._ghEtc] = id.split('/');
|
|
13
|
+
return get(space.properties, ['members', identityHex, 'github', 'repos'], []).includes(`${ghOwner}/${ghRepo}`);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const matchSpace = (space: Space, identityHex: string, source?: string, id?: string) => {
|
|
17
|
+
if (!id) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
switch (source) {
|
|
21
|
+
case 'github.com':
|
|
22
|
+
return ghMatch(space, identityHex, id);
|
|
23
|
+
default:
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// todo(thure): Why does the new space.properties value not propagate?
|
|
29
|
+
const ghBind = (space: Space, identityHex: string, id: string): string[] => {
|
|
30
|
+
const [ghOwner, ghRepo, ..._ghEtc] = id.split('/');
|
|
31
|
+
update(space.properties, ['members', identityHex, 'github', 'repos'], (ghBindings) => {
|
|
32
|
+
return [...(ghBindings ?? []), `${ghOwner}/${ghRepo}`];
|
|
33
|
+
});
|
|
34
|
+
return space.properties.members?.[identityHex]?.github?.repos ?? [];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const bindSpace = (space: Space, identityHex: string, source: string, id: string): string[] => {
|
|
38
|
+
switch (source) {
|
|
39
|
+
case 'github.com':
|
|
40
|
+
return ghBind(space, identityHex, id);
|
|
41
|
+
default:
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const ghUnbind = (space: Space, identityHex: string, id: string): string[] => {
|
|
46
|
+
const [ghOwner, ghRepo, ..._ghEtc] = id.split('/');
|
|
47
|
+
update(space.properties, ['members', identityHex, 'github', 'repos'], (ghBindings) => {
|
|
48
|
+
const index = ghBindings.indexOf(`${ghOwner}/${ghRepo}`);
|
|
49
|
+
if (index >= 0) {
|
|
50
|
+
const result = [...ghBindings];
|
|
51
|
+
result.splice(index, 1);
|
|
52
|
+
return result;
|
|
53
|
+
} else {
|
|
54
|
+
return ghBindings;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
return space.properties.members?.[identityHex]?.github?.repos ?? [];
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const unbindSpace = (space: Space, identityHex: string, source: string, id: string): string[] => {
|
|
61
|
+
switch (source) {
|
|
62
|
+
case 'github.com':
|
|
63
|
+
return ghUnbind(space, identityHex, id);
|
|
64
|
+
default:
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const ghDisplayName = (id: string): string => {
|
|
70
|
+
const [ghOwner, ghRepo, ghResource, ...etc] = id.split('/');
|
|
71
|
+
switch (ghResource) {
|
|
72
|
+
case 'issues':
|
|
73
|
+
return `${ghOwner}/${ghRepo} #${etc[0]}`;
|
|
74
|
+
default:
|
|
75
|
+
return `${ghOwner}/${ghRepo} ${[ghResource, ...etc].join('/')}`;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const displayName = (source: string, id: string): string => {
|
|
80
|
+
switch (source) {
|
|
81
|
+
case 'github.com':
|
|
82
|
+
return ghDisplayName(id);
|
|
83
|
+
default:
|
|
84
|
+
return id;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback } from 'react';
|
|
6
|
+
|
|
7
|
+
import { log } from '@dxos/log';
|
|
8
|
+
import { Button, Dialog, useTranslation } from '@dxos/react-ui';
|
|
9
|
+
|
|
10
|
+
import { useOctokitContext } from './GithubApiProviders';
|
|
11
|
+
import { GITHUB_PLUGIN } from '../meta';
|
|
12
|
+
import type { GhFileIdentifier, GhIdentifier, GhIssueIdentifier } from '../types';
|
|
13
|
+
|
|
14
|
+
export const ImportDialog = ({ docGhId, onUpdate }: { docGhId: GhIdentifier; onUpdate: (content: string) => void }) => {
|
|
15
|
+
const { t } = useTranslation(GITHUB_PLUGIN);
|
|
16
|
+
const { octokit } = useOctokitContext();
|
|
17
|
+
|
|
18
|
+
const importGhIssueContent = useCallback(async () => {
|
|
19
|
+
if (octokit && docGhId && 'issueNumber') {
|
|
20
|
+
try {
|
|
21
|
+
const { owner, repo, issueNumber } = docGhId as GhIssueIdentifier;
|
|
22
|
+
const { data } = await octokit.rest.issues.get({ owner, repo, issue_number: issueNumber });
|
|
23
|
+
onUpdate(data.body ?? '');
|
|
24
|
+
} catch (err) {
|
|
25
|
+
log.error('Failed to import from Github issue', err);
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
log.error('Not prepared to import from Github issue when requested.');
|
|
29
|
+
}
|
|
30
|
+
}, [octokit, docGhId]);
|
|
31
|
+
|
|
32
|
+
const importGhFileContent = useCallback(async () => {
|
|
33
|
+
if (octokit && docGhId && 'path' in docGhId) {
|
|
34
|
+
try {
|
|
35
|
+
const { owner, repo, path } = docGhId as GhFileIdentifier;
|
|
36
|
+
const { data } = await octokit.rest.repos.getContent({ owner, repo, path });
|
|
37
|
+
if (!Array.isArray(data) && data.type === 'file') {
|
|
38
|
+
onUpdate(atob(data.content));
|
|
39
|
+
} else {
|
|
40
|
+
log.error('Did not receive file with content from Github.');
|
|
41
|
+
}
|
|
42
|
+
} catch (err) {
|
|
43
|
+
log.error('Failed to import from Github file', err);
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
log.error('Not prepared to import from Github file when requested.');
|
|
47
|
+
}
|
|
48
|
+
}, [octokit, docGhId]);
|
|
49
|
+
|
|
50
|
+
const handleGhImport = useCallback(() => {
|
|
51
|
+
return (
|
|
52
|
+
docGhId && ('issueNumber' in docGhId ? importGhIssueContent() : 'path' in docGhId ? importGhFileContent() : null)
|
|
53
|
+
);
|
|
54
|
+
}, [importGhIssueContent, importGhFileContent, docGhId]);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<Dialog.Content>
|
|
58
|
+
<Dialog.Title>{t('confirm import title')}</Dialog.Title>
|
|
59
|
+
<p className='plb-2'>{t('confirm import body')}</p>
|
|
60
|
+
<div role='none' className='flex justify-end gap-2'>
|
|
61
|
+
<Dialog.Close asChild>
|
|
62
|
+
<Button>{t('cancel label', { ns: 'appkit' })}</Button>
|
|
63
|
+
</Dialog.Close>
|
|
64
|
+
<Dialog.Close asChild>
|
|
65
|
+
<Button variant='primary' onClick={handleGhImport}>
|
|
66
|
+
{t('import from github label')}
|
|
67
|
+
</Button>
|
|
68
|
+
</Dialog.Close>
|
|
69
|
+
</div>
|
|
70
|
+
</Dialog.Content>
|
|
71
|
+
);
|
|
72
|
+
};
|