@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.
Files changed (106) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +15 -0
  3. package/dist/lib/browser/chunk-NJP5BGD2.mjs +24 -0
  4. package/dist/lib/browser/chunk-NJP5BGD2.mjs.map +7 -0
  5. package/dist/lib/browser/index.css +7 -0
  6. package/dist/lib/browser/index.css.map +7 -0
  7. package/dist/lib/browser/index.mjs +1531 -0
  8. package/dist/lib/browser/index.mjs.map +7 -0
  9. package/dist/lib/browser/meta.json +1 -0
  10. package/dist/lib/browser/meta.mjs +11 -0
  11. package/dist/lib/browser/meta.mjs.map +7 -0
  12. package/dist/types/src/GithubPlugin.d.ts +4 -0
  13. package/dist/types/src/GithubPlugin.d.ts.map +1 -0
  14. package/dist/types/src/components/EmbeddedMain/EmbeddedMain.d.ts +4 -0
  15. package/dist/types/src/components/EmbeddedMain/EmbeddedMain.d.ts.map +1 -0
  16. package/dist/types/src/components/EmbeddedMain/GfmPreview.d.ts +9 -0
  17. package/dist/types/src/components/EmbeddedMain/GfmPreview.d.ts.map +1 -0
  18. package/dist/types/src/components/EmbeddedMain/index.d.ts +2 -0
  19. package/dist/types/src/components/EmbeddedMain/index.d.ts.map +1 -0
  20. package/dist/types/src/components/ExportDialog.d.ts +8 -0
  21. package/dist/types/src/components/ExportDialog.d.ts.map +1 -0
  22. package/dist/types/src/components/GithubApiProviders/GitHubSettings.d.ts +3 -0
  23. package/dist/types/src/components/GithubApiProviders/GitHubSettings.d.ts.map +1 -0
  24. package/dist/types/src/components/GithubApiProviders/OctokitProvider.d.ts +15 -0
  25. package/dist/types/src/components/GithubApiProviders/OctokitProvider.d.ts.map +1 -0
  26. package/dist/types/src/components/GithubApiProviders/index.d.ts +3 -0
  27. package/dist/types/src/components/GithubApiProviders/index.d.ts.map +1 -0
  28. package/dist/types/src/components/GithubEchoResolverProviders/DocumentTreeItem.d.ts +6 -0
  29. package/dist/types/src/components/GithubEchoResolverProviders/DocumentTreeItem.d.ts.map +1 -0
  30. package/dist/types/src/components/GithubEchoResolverProviders/ResolverContext.d.ts +7 -0
  31. package/dist/types/src/components/GithubEchoResolverProviders/ResolverContext.d.ts.map +1 -0
  32. package/dist/types/src/components/GithubEchoResolverProviders/ResolverDialog.d.ts +6 -0
  33. package/dist/types/src/components/GithubEchoResolverProviders/ResolverDialog.d.ts.map +1 -0
  34. package/dist/types/src/components/GithubEchoResolverProviders/ResolverProps.d.ts +16 -0
  35. package/dist/types/src/components/GithubEchoResolverProviders/ResolverProps.d.ts.map +1 -0
  36. package/dist/types/src/components/GithubEchoResolverProviders/ResolverTree.d.ts +3 -0
  37. package/dist/types/src/components/GithubEchoResolverProviders/ResolverTree.d.ts.map +1 -0
  38. package/dist/types/src/components/GithubEchoResolverProviders/SpacePickerTreeItem.d.ts +11 -0
  39. package/dist/types/src/components/GithubEchoResolverProviders/SpacePickerTreeItem.d.ts.map +1 -0
  40. package/dist/types/src/components/GithubEchoResolverProviders/index.d.ts +3 -0
  41. package/dist/types/src/components/GithubEchoResolverProviders/index.d.ts.map +1 -0
  42. package/dist/types/src/components/GithubEchoResolverProviders/spaceResolvers.d.ts +6 -0
  43. package/dist/types/src/components/GithubEchoResolverProviders/spaceResolvers.d.ts.map +1 -0
  44. package/dist/types/src/components/ImportDialog.d.ts +7 -0
  45. package/dist/types/src/components/ImportDialog.d.ts.map +1 -0
  46. package/dist/types/src/components/Issue.d.ts +5 -0
  47. package/dist/types/src/components/Issue.d.ts.map +1 -0
  48. package/dist/types/src/components/Loading/Loading.d.ts +15 -0
  49. package/dist/types/src/components/Loading/Loading.d.ts.map +1 -0
  50. package/dist/types/src/components/Loading/Loading.stories.d.ts +52 -0
  51. package/dist/types/src/components/Loading/Loading.stories.d.ts.map +1 -0
  52. package/dist/types/src/components/Loading/index.d.ts +2 -0
  53. package/dist/types/src/components/Loading/index.d.ts.map +1 -0
  54. package/dist/types/src/components/MarkdownActions.d.ts +7 -0
  55. package/dist/types/src/components/MarkdownActions.d.ts.map +1 -0
  56. package/dist/types/src/components/UrlDialog.d.ts +6 -0
  57. package/dist/types/src/components/UrlDialog.d.ts.map +1 -0
  58. package/dist/types/src/components/index.d.ts +9 -0
  59. package/dist/types/src/components/index.d.ts.map +1 -0
  60. package/dist/types/src/hooks/index.d.ts +3 -0
  61. package/dist/types/src/hooks/index.d.ts.map +1 -0
  62. package/dist/types/src/hooks/useDocGhId.d.ts +6 -0
  63. package/dist/types/src/hooks/useDocGhId.d.ts.map +1 -0
  64. package/dist/types/src/hooks/useGhIdFromUrl.d.ts +2 -0
  65. package/dist/types/src/hooks/useGhIdFromUrl.d.ts.map +1 -0
  66. package/dist/types/src/index.d.ts +4 -0
  67. package/dist/types/src/index.d.ts.map +1 -0
  68. package/dist/types/src/meta.d.ts +16 -0
  69. package/dist/types/src/meta.d.ts.map +1 -0
  70. package/dist/types/src/translations.d.ts +56 -0
  71. package/dist/types/src/translations.d.ts.map +1 -0
  72. package/dist/types/src/types.d.ts +30 -0
  73. package/dist/types/src/types.d.ts.map +1 -0
  74. package/package.json +76 -0
  75. package/src/GithubPlugin.tsx +129 -0
  76. package/src/components/EmbeddedMain/EmbeddedMain.tsx +296 -0
  77. package/src/components/EmbeddedMain/GfmPreview.tsx +110 -0
  78. package/src/components/EmbeddedMain/embedded.css +3 -0
  79. package/src/components/EmbeddedMain/index.ts +5 -0
  80. package/src/components/ExportDialog.tsx +157 -0
  81. package/src/components/GithubApiProviders/GitHubSettings.tsx +72 -0
  82. package/src/components/GithubApiProviders/OctokitProvider.tsx +60 -0
  83. package/src/components/GithubApiProviders/index.ts +6 -0
  84. package/src/components/GithubEchoResolverProviders/DocumentTreeItem.tsx +26 -0
  85. package/src/components/GithubEchoResolverProviders/ResolverContext.tsx +140 -0
  86. package/src/components/GithubEchoResolverProviders/ResolverDialog.tsx +34 -0
  87. package/src/components/GithubEchoResolverProviders/ResolverProps.ts +23 -0
  88. package/src/components/GithubEchoResolverProviders/ResolverTree.tsx +86 -0
  89. package/src/components/GithubEchoResolverProviders/SpacePickerTreeItem.tsx +105 -0
  90. package/src/components/GithubEchoResolverProviders/index.ts +6 -0
  91. package/src/components/GithubEchoResolverProviders/spaceResolvers.ts +86 -0
  92. package/src/components/ImportDialog.tsx +72 -0
  93. package/src/components/Issue.tsx +62 -0
  94. package/src/components/Loading/Loading.stories.tsx +28 -0
  95. package/src/components/Loading/Loading.tsx +70 -0
  96. package/src/components/Loading/index.ts +5 -0
  97. package/src/components/MarkdownActions.tsx +136 -0
  98. package/src/components/UrlDialog.tsx +52 -0
  99. package/src/components/index.ts +12 -0
  100. package/src/hooks/index.ts +6 -0
  101. package/src/hooks/useDocGhId.ts +38 -0
  102. package/src/hooks/useGhIdFromUrl.ts +26 -0
  103. package/src/index.ts +9 -0
  104. package/src/meta.tsx +21 -0
  105. package/src/translations.ts +72 -0
  106. package/src/types.ts +51 -0
@@ -0,0 +1,296 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import './embedded.css';
6
+
7
+ import {
8
+ ArrowSquareOut,
9
+ CaretDown,
10
+ DotsThreeVertical,
11
+ Eye,
12
+ Option,
13
+ PencilSimpleLine,
14
+ UserPlus,
15
+ } from '@phosphor-icons/react';
16
+ import React, { useCallback, useContext, useRef, useState } from 'react';
17
+
18
+ import { Surface, useIntent } from '@dxos/app-framework';
19
+ import { SPACE_PLUGIN, SpaceAction, getSpaceDisplayName } from '@dxos/plugin-space';
20
+ import { useClient } from '@dxos/react-client';
21
+ import { getMeta } from '@dxos/react-client/echo';
22
+ import {
23
+ Avatar,
24
+ Button,
25
+ ButtonGroup,
26
+ DensityProvider,
27
+ Dialog,
28
+ DropdownMenu,
29
+ Main,
30
+ Toggle,
31
+ Tooltip,
32
+ toLocalizedString,
33
+ useTranslation,
34
+ } from '@dxos/react-ui';
35
+ import { defaultTx, descriptionText, getSize, mx } from '@dxos/react-ui-theme';
36
+ import { hexToFallback } from '@dxos/util';
37
+
38
+ import { GfmPreview } from './GfmPreview';
39
+ import { useDocGhId } from '../../hooks';
40
+ import { GITHUB_PLUGIN } from '../../meta';
41
+ import type { EditorViewState } from '../../types';
42
+ import {
43
+ DocumentResolverProvider,
44
+ DocumentResolverContext,
45
+ ResolverDialog,
46
+ SpaceResolverProvider,
47
+ SpaceResolverContext,
48
+ } from '../GithubEchoResolverProviders';
49
+
50
+ const overlayAttrs = { side: 'top' as const, sideOffset: 4 };
51
+
52
+ const EmbeddedLayoutImpl = () => {
53
+ const { t } = useTranslation(GITHUB_PLUGIN);
54
+ const { space, source, id, identityHex } = useContext(SpaceResolverContext);
55
+ const { document } = useContext(DocumentResolverContext);
56
+ const { dispatch } = useIntent();
57
+ const client = useClient();
58
+
59
+ const handleCloseEmbed = useCallback(() => {
60
+ window.parent.postMessage({ type: 'close-embed' }, 'https://github.com');
61
+ }, []);
62
+
63
+ const handleSaveAndCloseEmbed = useCallback(() => {
64
+ document &&
65
+ window.parent.postMessage({ type: 'save-data', content: document.content?.content }, 'https://github.com');
66
+ }, [document]);
67
+
68
+ const handleCreateSpace = () => dispatch({ action: SpaceAction.CREATE });
69
+ const handleJoinSpace = () => dispatch({ action: SpaceAction.JOIN });
70
+
71
+ const handleInvite = useCallback(() => {
72
+ if (client && space) {
73
+ void client.shell.shareSpace({ spaceKey: space.key });
74
+ }
75
+ }, [client, space]);
76
+
77
+ const [editorViewState, setEditorViewState] = useState<EditorViewState>('editor');
78
+ const isPreviewing = editorViewState === 'preview';
79
+
80
+ const suppressNextTooltip = useRef<boolean>(false);
81
+ const [optionsTooltipOpen, setOptionsTooltipOpen] = useState(false);
82
+ const [optionsMenuOpen, setOptionsMenuOpen] = useState(false);
83
+
84
+ const [renameDialogOpen, setRenameDialogOpen] = useState(false);
85
+ const [resolverDialogOpen, setResolverDialogOpen] = useState(false);
86
+
87
+ const spaceFallbackValue = hexToFallback(space?.key.toHex() ?? '0');
88
+
89
+ const docGhId = useDocGhId((document ? getMeta(document)?.keys : []) ?? []);
90
+ const name = space && getSpaceDisplayName(space);
91
+
92
+ return (
93
+ <>
94
+ <style type='text/css'>{`
95
+ :root {
96
+ --surface-bg: #F6F8FA;
97
+ }
98
+ .dark:root {
99
+ --surface-bg: #161b22;
100
+ }
101
+ `}</style>
102
+ <DensityProvider density='fine'>
103
+ <div role='none' className='fixed inline-end-2 block-end-2 z-10 flex gap-2'>
104
+ <Tooltip.Root>
105
+ <Tooltip.Trigger asChild>
106
+ <Button disabled={!space} onClick={handleInvite}>
107
+ <span className='sr-only'>{t('create invitation label', { ns: 'appkit' })}</span>
108
+ <UserPlus className={getSize(5)} />
109
+ </Button>
110
+ </Tooltip.Trigger>
111
+ <Tooltip.Content {...overlayAttrs}>
112
+ {t('create invitation label', { ns: 'appkit' })}
113
+ <Tooltip.Arrow />
114
+ </Tooltip.Content>
115
+ </Tooltip.Root>
116
+ <Tooltip.Root>
117
+ <Tooltip.Trigger asChild>
118
+ <Toggle
119
+ disabled={!(space && document)}
120
+ pressed={isPreviewing}
121
+ onPressedChange={(nextPressed) => setEditorViewState(nextPressed ? 'preview' : 'editor')}
122
+ >
123
+ <span className='sr-only'>{t(isPreviewing ? 'exit gfm preview label' : 'preview gfm label')}</span>
124
+ {isPreviewing ? <PencilSimpleLine className={getSize(5)} /> : <Eye className={getSize(5)} />}
125
+ </Toggle>
126
+ </Tooltip.Trigger>
127
+ <Tooltip.Content {...overlayAttrs}>
128
+ {t(isPreviewing ? 'exit gfm preview label' : 'preview gfm label')}
129
+ <Tooltip.Arrow />
130
+ </Tooltip.Content>
131
+ </Tooltip.Root>
132
+ <Tooltip.Root
133
+ open={optionsTooltipOpen}
134
+ onOpenChange={(nextOpen) => {
135
+ if (suppressNextTooltip.current) {
136
+ setOptionsTooltipOpen(false);
137
+ suppressNextTooltip.current = false;
138
+ } else {
139
+ setOptionsTooltipOpen(nextOpen);
140
+ }
141
+ }}
142
+ >
143
+ <DropdownMenu.Root
144
+ {...{
145
+ modal: false,
146
+ open: optionsMenuOpen,
147
+ onOpenChange: (nextOpen: boolean) => {
148
+ if (!nextOpen) {
149
+ suppressNextTooltip.current = true;
150
+ }
151
+ return setOptionsMenuOpen(nextOpen);
152
+ },
153
+ }}
154
+ >
155
+ <Tooltip.Trigger asChild>
156
+ <DropdownMenu.Trigger asChild disabled={!(space && document)}>
157
+ <Button>
158
+ <DotsThreeVertical className={getSize(5)} />
159
+ <span className='sr-only'>{t('embedded options label')}</span>
160
+ </Button>
161
+ </DropdownMenu.Trigger>
162
+ </Tooltip.Trigger>
163
+ <DropdownMenu.Content {...overlayAttrs}>
164
+ <DropdownMenu.Viewport>
165
+ <DropdownMenu.Item asChild>
166
+ <a
167
+ target='_blank'
168
+ rel='noreferrer'
169
+ href={
170
+ space && document
171
+ ? `${location.origin}/dxos/space/${space?.key.toHex() ?? 'never'}/${document.id}`
172
+ : '#'
173
+ }
174
+ >
175
+ <ArrowSquareOut className={mx('shrink-0', getSize(5))} />
176
+ <span className='grow'>{t('open in composer label', { ns: GITHUB_PLUGIN })}</span>
177
+ </a>
178
+ </DropdownMenu.Item>
179
+ <DropdownMenu.Separator />
180
+ <DropdownMenu.GroupLabel>
181
+ {t('active space label', { ns: SPACE_PLUGIN })}
182
+ <Avatar.Root size={5} variant='circle'>
183
+ <div role='none' className='flex gap-1 mlb-1 items-center'>
184
+ <Avatar.Frame>
185
+ <Avatar.Fallback text={spaceFallbackValue.emoji} />
186
+ </Avatar.Frame>
187
+ <Avatar.Label classNames='text-sm text-[--surface-text]'>
188
+ {name && toLocalizedString(name, t)}
189
+ </Avatar.Label>
190
+ </div>
191
+ </Avatar.Root>
192
+ </DropdownMenu.GroupLabel>
193
+ <DropdownMenu.Item onClick={() => setRenameDialogOpen(true)}>
194
+ <span className='grow'>{t('rename space label', { ns: SPACE_PLUGIN })}</span>
195
+ <PencilSimpleLine className={mx('shrink-0', getSize(5))} />
196
+ </DropdownMenu.Item>
197
+ <DropdownMenu.Item onClick={() => setResolverDialogOpen(true)}>
198
+ <span className='grow'>{t('unset repo space label')}</span>
199
+ <Option className={mx('shrink-0', getSize(5))} />
200
+ </DropdownMenu.Item>
201
+ </DropdownMenu.Viewport>
202
+ <DropdownMenu.Arrow />
203
+ </DropdownMenu.Content>
204
+ <Tooltip.Content {...overlayAttrs}>
205
+ {t('embedded options label')}
206
+ <Tooltip.Arrow />
207
+ </Tooltip.Content>
208
+ </DropdownMenu.Root>
209
+ </Tooltip.Root>
210
+ <ButtonGroup classNames={[!(space && document) && 'shadow-none']}>
211
+ <Button disabled={!(space && document)} onClick={handleSaveAndCloseEmbed}>
212
+ {t('save and close label')}
213
+ </Button>
214
+ <DropdownMenu.Root>
215
+ <DropdownMenu.Trigger asChild disabled={!(space && document)}>
216
+ <Button>
217
+ <CaretDown />
218
+ </Button>
219
+ </DropdownMenu.Trigger>
220
+ <DropdownMenu.Portal>
221
+ <DropdownMenu.Content {...overlayAttrs}>
222
+ <DropdownMenu.Viewport>
223
+ <DropdownMenu.Item onClick={handleSaveAndCloseEmbed} classNames='block'>
224
+ <p>{t('save and close label')}</p>
225
+ <p className={descriptionText}>{t('save and close description')}</p>
226
+ </DropdownMenu.Item>
227
+ <DropdownMenu.Item onClick={handleCloseEmbed} classNames='block'>
228
+ <p>{t('close label', { ns: 'appkit' })}</p>
229
+ <p className={descriptionText}>{t('close embed description')}</p>
230
+ </DropdownMenu.Item>
231
+ </DropdownMenu.Viewport>
232
+ <DropdownMenu.Arrow />
233
+ </DropdownMenu.Content>
234
+ </DropdownMenu.Portal>
235
+ </DropdownMenu.Root>
236
+ </ButtonGroup>
237
+ </div>
238
+ {space && document ? (
239
+ editorViewState === 'preview' ? (
240
+ <Main.Content classNames='min-bs-[100dvh] flex flex-col p-0.5'>
241
+ <GfmPreview
242
+ markdown={document.content?.toString() ?? ''}
243
+ {...(docGhId && { owner: docGhId.owner, repo: docGhId.repo })}
244
+ />
245
+ </Main.Content>
246
+ ) : (
247
+ // TODO(burdon): This will break since model is not defined.
248
+ <Surface role='main' data={{ properties: document, view: 'embedded' }} />
249
+ )
250
+ ) : source && id && identityHex ? (
251
+ <Dialog.Root open onOpenChange={() => true}>
252
+ <div role='none' className={defaultTx('dialog.overlay', 'dialog--resolver__overlay', {}, 'static bs-full')}>
253
+ <div
254
+ role='none'
255
+ className={defaultTx(
256
+ 'dialog.content',
257
+ 'dialog--resolver__content',
258
+ {},
259
+ 'p-2 bs-72 flex flex-col shadow-none bg-transparent',
260
+ )}
261
+ >
262
+ <ResolverDialog handleCreateSpace={handleCreateSpace} handleJoinSpace={handleJoinSpace} />
263
+ </div>
264
+ </div>
265
+ </Dialog.Root>
266
+ ) : null}
267
+ {space && (
268
+ <Dialog.Root open={renameDialogOpen} onOpenChange={setRenameDialogOpen}>
269
+ <Dialog.Overlay classNames='backdrop-blur'>
270
+ <Dialog.Content>
271
+ <Surface role='dialog' data={{ content: 'dxos.org/plugin/space/RenameSpaceDialog', subject: space }} />
272
+ </Dialog.Content>
273
+ </Dialog.Overlay>
274
+ </Dialog.Root>
275
+ )}
276
+ <Dialog.Root open={resolverDialogOpen} onOpenChange={setResolverDialogOpen}>
277
+ <Dialog.Overlay classNames='backdrop-blur'>
278
+ <Dialog.Content>
279
+ <ResolverDialog handleCreateSpace={handleCreateSpace} handleJoinSpace={handleJoinSpace} />
280
+ </Dialog.Content>
281
+ </Dialog.Overlay>
282
+ </Dialog.Root>
283
+ </DensityProvider>
284
+ </>
285
+ );
286
+ };
287
+
288
+ export const EmbeddedMain = () => {
289
+ return (
290
+ <SpaceResolverProvider>
291
+ <DocumentResolverProvider>
292
+ <EmbeddedLayoutImpl />
293
+ </DocumentResolverProvider>
294
+ </SpaceResolverProvider>
295
+ );
296
+ };
@@ -0,0 +1,110 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import 'github-markdown-css/github-markdown.css';
6
+ import DOMPurify from 'dompurify';
7
+ import React, { useEffect, useState } from 'react';
8
+
9
+ import { useTranslation } from '@dxos/react-ui';
10
+
11
+ import { GITHUB_PLUGIN } from '../../meta';
12
+ import { GitHubSettings, useOctokitContext } from '../GithubApiProviders';
13
+ import { Loading } from '../Loading';
14
+
15
+ const defaultOptions: Parameters<typeof DOMPurify.sanitize>[1] = {
16
+ ALLOWED_TAGS: [
17
+ 'span',
18
+ 'div',
19
+ 'b',
20
+ 'i',
21
+ 'em',
22
+ 'strong',
23
+ 'a',
24
+ 'p',
25
+ 'h1',
26
+ 'h2',
27
+ 'h3',
28
+ 'h4',
29
+ 'h5',
30
+ 'h6',
31
+ 'table',
32
+ 'td',
33
+ 'th',
34
+ 'tr',
35
+ 'blockquote',
36
+ 'ul',
37
+ 'ol',
38
+ 'li',
39
+ 'dd',
40
+ 'tt',
41
+ 'dl',
42
+ 'dt',
43
+ 'details',
44
+ 'figcaption',
45
+ 'figure',
46
+ 'summary',
47
+ 'abbr',
48
+ 'dfn',
49
+ 'mark',
50
+ 'sub',
51
+ 'sup',
52
+ 'img',
53
+ 'code',
54
+ 'kbd',
55
+ 'pre',
56
+ 'samp',
57
+ 'hr',
58
+ 'input',
59
+ 'pre',
60
+ 'del',
61
+ 'g-emoji',
62
+ 'label',
63
+ ],
64
+ ALLOWED_ATTR: ['class', 'href', 'target', 'hidden', 'title', 'type', 'role', 'align', 'data-footnote-ref'],
65
+ };
66
+
67
+ const sanitize = (dirty: string, options: Partial<Parameters<typeof DOMPurify.sanitize>[1]>) => ({
68
+ __html: DOMPurify.sanitize(dirty, { ...defaultOptions, ...options }) as string,
69
+ });
70
+
71
+ export type GfmPreviewProps = {
72
+ markdown: string;
73
+ owner?: string;
74
+ repo?: string;
75
+ };
76
+
77
+ export const GfmPreview = ({ markdown, owner, repo }: GfmPreviewProps) => {
78
+ const [html, setHtml] = useState('');
79
+ const { octokit } = useOctokitContext();
80
+ const { t } = useTranslation(GITHUB_PLUGIN);
81
+
82
+ useEffect(() => {
83
+ if (octokit && markdown) {
84
+ void octokit
85
+ .request('POST /markdown', {
86
+ text: markdown,
87
+ ...(owner && repo && { context: `${owner}/${repo}` }),
88
+ })
89
+ .then(
90
+ ({ data }) => setHtml(data),
91
+ () => {},
92
+ );
93
+ }
94
+ }, [markdown, octokit]);
95
+
96
+ return html ? (
97
+ <>
98
+ <style>{'.markdown-body ol, .markdown-body ul {list-style-type: disc; list-style-position: outside;}'}</style>
99
+ <article
100
+ className='markdown-body grow is-full max-is-[980px] mli-auto p-[15px] sm:pli-[45px] sm:plb-[30px]'
101
+ dangerouslySetInnerHTML={sanitize(html, {})}
102
+ />
103
+ </>
104
+ ) : (
105
+ <div role='none' className='mli-auto max-is-md pli-2 text-center'>
106
+ {!octokit && <p className='mlb-4 text-lg font-medium'>{t('empty github pat message')}</p>}
107
+ {octokit ? <Loading label={t('loading preview message')} /> : <GitHubSettings />}
108
+ </div>
109
+ );
110
+ };
@@ -0,0 +1,3 @@
1
+ html, body, #root {
2
+ height: 100%;
3
+ }
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './EmbeddedMain';
@@ -0,0 +1,157 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React, { useCallback, useState } from 'react';
6
+
7
+ import { log } from '@dxos/log';
8
+ import { Button, Dialog, Input, Trans, useTranslation } from '@dxos/react-ui';
9
+
10
+ import { useOctokitContext } from './GithubApiProviders';
11
+ import { GITHUB_PLUGIN } from '../meta';
12
+ import type { ExportViewState, GhFileIdentifier, GhIdentifier } from '../types';
13
+
14
+ export const ExportDialog = ({
15
+ type,
16
+ target,
17
+ docGhId,
18
+ }: {
19
+ type: ExportViewState;
20
+ target: string | null;
21
+ docGhId: GhIdentifier;
22
+ }) => {
23
+ // TODO(burdon): Model removed.
24
+ const content = '';
25
+
26
+ const { octokit } = useOctokitContext();
27
+ const { t } = useTranslation(GITHUB_PLUGIN);
28
+ const [exportViewState, setExportViewState] = useState<ExportViewState>(type);
29
+ const [ghResponseUrl, setGhResponseUrl] = useState<string | null>(target);
30
+ const [ghBranchValue, setGhBranchValue] = useState('');
31
+ const [ghMessageValue, setGhMessageValue] = useState('');
32
+
33
+ const exportGhFileContent = useCallback(async () => {
34
+ if (octokit && docGhId && 'path' in docGhId && content) {
35
+ const branchName = ghBranchValue || t('github branch name placeholder');
36
+ const commitMessage = ghMessageValue || t('github commit message placeholder');
37
+ setExportViewState('pending');
38
+ try {
39
+ const { owner, repo, path, ref } = docGhId as GhFileIdentifier;
40
+ const { data: fileData } = await octokit.rest.repos.getContent({ owner, repo, path });
41
+ if (Array.isArray(fileData) || fileData.type !== 'file') {
42
+ log.error('Attempted to export to a destination in Github that is not a file.');
43
+ return;
44
+ }
45
+ const { sha: fileSha } = fileData;
46
+ const {
47
+ data: {
48
+ object: { sha: baseSha },
49
+ },
50
+ } = await octokit.rest.git.getRef({ owner, repo, ref: `heads/${ref}` });
51
+ await octokit.rest.git.createRef({
52
+ owner,
53
+ repo,
54
+ ref: `refs/heads/${branchName}`,
55
+ sha: baseSha,
56
+ });
57
+ await octokit.rest.repos.createOrUpdateFileContents({
58
+ owner,
59
+ repo,
60
+ path,
61
+ message: commitMessage,
62
+ branch: branchName,
63
+ sha: fileSha,
64
+ content: btoa(content),
65
+ });
66
+ const {
67
+ data: { html_url: prUrl },
68
+ } = await octokit.rest.pulls.create({
69
+ owner,
70
+ repo,
71
+ head: branchName,
72
+ base: ref,
73
+ title: commitMessage,
74
+ });
75
+ setGhResponseUrl(prUrl);
76
+ setExportViewState('response');
77
+ } catch (err) {
78
+ log.error('Failed to export to Github file', err);
79
+ setExportViewState('create-pr');
80
+ setGhResponseUrl(null);
81
+ }
82
+ } else {
83
+ log.error('Not prepared to export to Github file when requested.');
84
+ setGhResponseUrl(null);
85
+ }
86
+ }, [octokit, docGhId, content, ghBranchValue, ghMessageValue, t]);
87
+
88
+ return (
89
+ <Dialog.Content>
90
+ <Dialog.Title>{t('confirm export title')}</Dialog.Title>
91
+ {exportViewState === 'response' ? (
92
+ <>
93
+ <p>
94
+ <Trans
95
+ {...{
96
+ t,
97
+ i18nKey: 'export to github success message',
98
+ values: { linkText: ghResponseUrl },
99
+ components: {
100
+ resultStyle: <span className='text-success-600 dark:text-success-300'>_</span>,
101
+ prLink: (
102
+ <a
103
+ href={ghResponseUrl!}
104
+ target='_blank'
105
+ rel='noreferrer'
106
+ className='text-primary-600 dark:text-primary-300 hover:text-primary-700 dark:hover:text-primary-400'
107
+ >
108
+ _
109
+ </a>
110
+ ),
111
+ },
112
+ }}
113
+ />
114
+ </p>
115
+ <div role='none' className='flex justify-end'>
116
+ <Dialog.Close asChild>
117
+ <Button variant='primary' onClick={() => setExportViewState(null)}>
118
+ {t('done label', { ns: 'os' })}
119
+ </Button>
120
+ </Dialog.Close>
121
+ </div>
122
+ </>
123
+ ) : (
124
+ <div role='none' className='mbs-2 space-b-2'>
125
+ <Input.Root>
126
+ <Input.Label>{t('github branch name label')}</Input.Label>
127
+ <Input.TextInput
128
+ autoFocus
129
+ classNames='font-mono'
130
+ disabled={exportViewState === 'pending'}
131
+ placeholder={t('github branch name placeholder')}
132
+ value={ghBranchValue}
133
+ onChange={({ target: { value } }) => setGhBranchValue(value)}
134
+ />
135
+ </Input.Root>
136
+ <Input.Root>
137
+ <Input.Label>{t('github commit message label')}</Input.Label>
138
+ <Input.TextArea
139
+ disabled={exportViewState === 'pending'}
140
+ placeholder={t('github commit message placeholder')}
141
+ value={ghMessageValue}
142
+ onChange={({ target: { value } }) => setGhMessageValue(value)}
143
+ />
144
+ </Input.Root>
145
+ <div role='none' className='flex justify-end gap-2'>
146
+ <Dialog.Close asChild>
147
+ <Button onClick={() => setExportViewState(null)}>{t('close label', { ns: 'os' })}</Button>
148
+ </Dialog.Close>
149
+ <Button disabled={exportViewState === 'pending'} variant='primary' onClick={() => exportGhFileContent()}>
150
+ {t('continue label', { ns: 'os' })}
151
+ </Button>
152
+ </div>
153
+ </div>
154
+ )}
155
+ </Dialog.Content>
156
+ );
157
+ };
@@ -0,0 +1,72 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { ArrowSquareOut } from '@phosphor-icons/react';
6
+ import React, { type PropsWithChildren, useEffect, useState } from 'react';
7
+
8
+ import { SettingsValue } from '@dxos/plugin-settings';
9
+ import { Link, Input, Trans, useTranslation, useId } from '@dxos/react-ui';
10
+ import { getSize, mx } from '@dxos/react-ui-theme';
11
+
12
+ import { useOctokitContext } from './OctokitProvider';
13
+ import { GITHUB_PLUGIN } from '../../meta';
14
+
15
+ // TODO(burdon): Factor out with PluginList.
16
+ const ExternalLink = ({ children }: PropsWithChildren<{}>) => {
17
+ const { t } = useTranslation(GITHUB_PLUGIN);
18
+ const descriptionId = useId('link--external');
19
+ return (
20
+ <>
21
+ <Link href={t('github pat description href')} target='_blank' rel='noreferrer' aria-describedby={descriptionId}>
22
+ {children}
23
+ <ArrowSquareOut weight='bold' className={mx(getSize(3), 'inline-block leading-none -mbs-0.5 mli-px')} />
24
+ </Link>
25
+ <span className='sr-only' id={descriptionId}>
26
+ {t('target blank description', { ns: GITHUB_PLUGIN })}
27
+ </span>
28
+ </>
29
+ );
30
+ };
31
+
32
+ // TODO(burdon): Pass in settings.
33
+ export const GitHubSettings = () => {
34
+ const { t } = useTranslation(GITHUB_PLUGIN);
35
+ const { pat, setPat } = useOctokitContext();
36
+ const [patValue, setPatValue] = useState(pat);
37
+
38
+ useEffect(() => {
39
+ setPatValue(pat);
40
+ }, [pat]);
41
+
42
+ useEffect(() => {
43
+ void setPat(patValue);
44
+ }, [patValue]);
45
+
46
+ return (
47
+ <>
48
+ <SettingsValue
49
+ label={t('github pat label')}
50
+ description={
51
+ <Trans
52
+ {...{
53
+ t,
54
+ i18nKey: 'github pat description',
55
+ components: {
56
+ docsLink: <ExternalLink />,
57
+ },
58
+ }}
59
+ />
60
+ }
61
+ >
62
+ <Input.TextInput
63
+ autoFocus
64
+ spellCheck={false}
65
+ classNames='font-mono mlb-1'
66
+ value={patValue ?? ''}
67
+ onChange={({ target: { value } }) => setPatValue(value)}
68
+ />
69
+ </SettingsValue>
70
+ </>
71
+ );
72
+ };