mirror of
https://github.com/harness/drone.git
synced 2025-05-17 01:20:13 +08:00
feat: [code-1028]: add support for image upload (#751)
This commit is contained in:
parent
3a7617a2e6
commit
76e5a32b79
@ -30,6 +30,17 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.attachDiv {
|
||||||
|
padding-top: var(--spacing-xsmall);
|
||||||
|
padding-bottom: var(--spacing-xsmall);
|
||||||
|
padding-left: var(--spacing-medium);
|
||||||
|
color: var(--grey-400);
|
||||||
|
outline: none;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
border-top: 1px dashed var(--grey-200);
|
||||||
|
}
|
||||||
|
|
||||||
.cm-scroller {
|
.cm-scroller {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: var(--spacing-small);
|
padding: var(--spacing-small);
|
||||||
@ -44,3 +55,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editorTest {
|
||||||
|
:global {
|
||||||
|
.cm-editor {
|
||||||
|
&.cm-focused {
|
||||||
|
border-color: var(--primary-7);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -17,3 +17,4 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This is an auto-generated file
|
// This is an auto-generated file
|
||||||
export declare const editor: string
|
export declare const editor: string
|
||||||
|
export declare const editorTest: string
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useMemo, useRef } from 'react'
|
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { Container } from '@harnessio/uicore'
|
import { Container, useToaster } from '@harnessio/uicore'
|
||||||
import { LanguageDescription } from '@codemirror/language'
|
import { LanguageDescription } from '@codemirror/language'
|
||||||
import { indentWithTab } from '@codemirror/commands'
|
import { indentWithTab } from '@codemirror/commands'
|
||||||
import cx from 'classnames'
|
import cx from 'classnames'
|
||||||
@ -28,6 +28,10 @@ import { Compartment, EditorState, Extension } from '@codemirror/state'
|
|||||||
import { color } from '@uiw/codemirror-extensions-color'
|
import { color } from '@uiw/codemirror-extensions-color'
|
||||||
import { hyperLink } from '@uiw/codemirror-extensions-hyper-link'
|
import { hyperLink } from '@uiw/codemirror-extensions-hyper-link'
|
||||||
import { githubLight, githubDark } from '@uiw/codemirror-themes-all'
|
import { githubLight, githubDark } from '@uiw/codemirror-themes-all'
|
||||||
|
import { useStrings } from 'framework/strings'
|
||||||
|
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
||||||
|
import { handleUpload } from 'utils/GitUtils'
|
||||||
|
import { handleFileDrop, handlePaste } from 'utils/Utils'
|
||||||
import css from './Editor.module.scss'
|
import css from './Editor.module.scss'
|
||||||
|
|
||||||
export interface EditorProps {
|
export interface EditorProps {
|
||||||
@ -63,9 +67,14 @@ export const Editor = React.memo(function CodeMirrorReactEditor({
|
|||||||
onViewUpdate,
|
onViewUpdate,
|
||||||
darkTheme
|
darkTheme
|
||||||
}: EditorProps) {
|
}: EditorProps) {
|
||||||
|
const { showError } = useToaster()
|
||||||
|
const { getString } = useStrings()
|
||||||
const view = useRef<EditorView>()
|
const view = useRef<EditorView>()
|
||||||
const ref = useRef<HTMLDivElement>()
|
const ref = useRef<HTMLDivElement>()
|
||||||
|
const { repoMetadata } = useGetRepositoryMetadata()
|
||||||
|
|
||||||
const languageConfig = useMemo(() => new Compartment(), [])
|
const languageConfig = useMemo(() => new Compartment(), [])
|
||||||
|
const [markdownContent, setMarkdownContent] = useState('')
|
||||||
const markdownLanguageSupport = useMemo(() => markdown({ codeLanguages: languages }), [])
|
const markdownLanguageSupport = useMemo(() => markdown({ codeLanguages: languages }), [])
|
||||||
const style = useMemo(() => {
|
const style = useMemo(() => {
|
||||||
if (maxHeight) {
|
if (maxHeight) {
|
||||||
@ -78,7 +87,22 @@ export const Editor = React.memo(function CodeMirrorReactEditor({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onChangeRef.current = onChange
|
onChangeRef.current = onChange
|
||||||
}, [onChange])
|
}, [onChange, markdownContent])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
appendMarkdownContent()
|
||||||
|
}, [markdownContent]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
const appendMarkdownContent = () => {
|
||||||
|
if (view.current && markdownContent) {
|
||||||
|
const currentContent = view.current.state.doc.toString()
|
||||||
|
const updatedContent = currentContent + ``
|
||||||
|
view.current.setState(EditorState.create({ doc: updatedContent }))
|
||||||
|
setUploading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [uploading, setUploading] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const editorView = new EditorView({
|
const editorView = new EditorView({
|
||||||
@ -129,8 +153,13 @@ export const Editor = React.memo(function CodeMirrorReactEditor({
|
|||||||
if (autoFocus) {
|
if (autoFocus) {
|
||||||
editorView.focus()
|
editorView.focus()
|
||||||
}
|
}
|
||||||
|
// Create a new DOM element for the message
|
||||||
|
const messageElement = document.createElement('div')
|
||||||
|
messageElement.className = 'attachDiv'
|
||||||
|
messageElement.textContent = uploading ? 'Uploading your files ...' : getString('attachText')
|
||||||
|
editorView.dom.appendChild(messageElement)
|
||||||
return () => {
|
return () => {
|
||||||
|
messageElement.remove()
|
||||||
editorView.destroy()
|
editorView.destroy()
|
||||||
}
|
}
|
||||||
}, []) // eslint-disable-line react-hooks/exhaustive-deps
|
}, []) // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
@ -153,6 +182,28 @@ export const Editor = React.memo(function CodeMirrorReactEditor({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [filename, forMarkdown, view, languageConfig, markdownLanguageSupport])
|
}, [filename, forMarkdown, view, languageConfig, markdownLanguageSupport])
|
||||||
|
const handleUploadCallback = (file: File) => {
|
||||||
|
handleUpload(file, setMarkdownContent, repoMetadata, showError)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const handleDropForUpload = async (event: any) => {
|
||||||
|
handleFileDrop(event, handleUploadCallback)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const handlePasteForUpload = (event: { preventDefault: () => void; clipboardData: any }) => {
|
||||||
|
handlePaste(event, handleUploadCallback)
|
||||||
|
}
|
||||||
|
|
||||||
return <Container ref={ref} className={cx(css.editor, className)} style={style} />
|
return (
|
||||||
|
<Container
|
||||||
|
onDragOver={event => {
|
||||||
|
event.preventDefault()
|
||||||
|
}}
|
||||||
|
onDrop={handleDropForUpload}
|
||||||
|
onPaste={handlePasteForUpload}
|
||||||
|
ref={ref}
|
||||||
|
className={cx(css.editor, className, css.editorTest)}
|
||||||
|
style={style}
|
||||||
|
/>
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
@ -262,3 +262,11 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.dialog {
|
||||||
|
width: 610px !important;
|
||||||
|
.uploadContainer {
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px dashed var(--grey-200);
|
||||||
|
background: var(--grey-50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
// This is an auto-generated file
|
// This is an auto-generated file
|
||||||
export declare const buttonsBar: string
|
export declare const buttonsBar: string
|
||||||
export declare const container: string
|
export declare const container: string
|
||||||
|
export declare const dialog: string
|
||||||
export declare const hidden: string
|
export declare const hidden: string
|
||||||
export declare const main: string
|
export declare const main: string
|
||||||
export declare const markdownEditor: string
|
export declare const markdownEditor: string
|
||||||
@ -26,4 +27,5 @@ export declare const preview: string
|
|||||||
export declare const tabContent: string
|
export declare const tabContent: string
|
||||||
export declare const tabs: string
|
export declare const tabs: string
|
||||||
export declare const toolbar: string
|
export declare const toolbar: string
|
||||||
|
export declare const uploadContainer: string
|
||||||
export declare const withPreview: string
|
export declare const withPreview: string
|
||||||
|
@ -15,14 +15,28 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { Button, Container, ButtonVariation, Layout, ButtonSize } from '@harnessio/uicore'
|
import {
|
||||||
|
Text,
|
||||||
|
Button,
|
||||||
|
Container,
|
||||||
|
ButtonVariation,
|
||||||
|
Layout,
|
||||||
|
ButtonSize,
|
||||||
|
Dialog,
|
||||||
|
FlexExpander,
|
||||||
|
useToaster
|
||||||
|
} from '@harnessio/uicore'
|
||||||
import type { IconName } from '@harnessio/icons'
|
import type { IconName } from '@harnessio/icons'
|
||||||
import { Color } from '@harnessio/design-system'
|
import { Color, FontVariation } from '@harnessio/design-system'
|
||||||
import cx from 'classnames'
|
import cx from 'classnames'
|
||||||
import type { EditorView } from '@codemirror/view'
|
import type { EditorView } from '@codemirror/view'
|
||||||
import { EditorSelection } from '@codemirror/state'
|
import { EditorSelection } from '@codemirror/state'
|
||||||
import { Editor } from 'components/Editor/Editor'
|
import { Editor } from 'components/Editor/Editor'
|
||||||
import { MarkdownViewer } from 'components/MarkdownViewer/MarkdownViewer'
|
import { MarkdownViewer } from 'components/MarkdownViewer/MarkdownViewer'
|
||||||
|
import { useStrings } from 'framework/strings'
|
||||||
|
import { formatBytes, handleFileDrop, handlePaste } from 'utils/Utils'
|
||||||
|
import { handleUpload } from 'utils/GitUtils'
|
||||||
|
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
||||||
import css from './MarkdownEditorWithPreview.module.scss'
|
import css from './MarkdownEditorWithPreview.module.scss'
|
||||||
|
|
||||||
enum MarkdownEditorTab {
|
enum MarkdownEditorTab {
|
||||||
@ -34,6 +48,7 @@ enum ToolbarAction {
|
|||||||
HEADER = 'HEADER',
|
HEADER = 'HEADER',
|
||||||
BOLD = 'BOLD',
|
BOLD = 'BOLD',
|
||||||
ITALIC = 'ITALIC',
|
ITALIC = 'ITALIC',
|
||||||
|
UPLOAD = 'UPLOAD',
|
||||||
UNORDER_LIST = 'UNORDER_LIST',
|
UNORDER_LIST = 'UNORDER_LIST',
|
||||||
CHECK_LIST = 'CHECK_LIST',
|
CHECK_LIST = 'CHECK_LIST',
|
||||||
CODE_BLOCK = 'CODE_BLOCK'
|
CODE_BLOCK = 'CODE_BLOCK'
|
||||||
@ -48,6 +63,8 @@ const toolbar: ToolbarItem[] = [
|
|||||||
{ icon: 'header', action: ToolbarAction.HEADER },
|
{ icon: 'header', action: ToolbarAction.HEADER },
|
||||||
{ icon: 'bold', action: ToolbarAction.BOLD },
|
{ icon: 'bold', action: ToolbarAction.BOLD },
|
||||||
{ icon: 'italic', action: ToolbarAction.ITALIC },
|
{ icon: 'italic', action: ToolbarAction.ITALIC },
|
||||||
|
{ icon: 'paperclip', action: ToolbarAction.UPLOAD },
|
||||||
|
|
||||||
{ icon: 'properties', action: ToolbarAction.UNORDER_LIST },
|
{ icon: 'properties', action: ToolbarAction.UNORDER_LIST },
|
||||||
{ icon: 'form', action: ToolbarAction.CHECK_LIST },
|
{ icon: 'form', action: ToolbarAction.CHECK_LIST },
|
||||||
{ icon: 'main-code-yaml', action: ToolbarAction.CODE_BLOCK }
|
{ icon: 'main-code-yaml', action: ToolbarAction.CODE_BLOCK }
|
||||||
@ -95,10 +112,17 @@ export function MarkdownEditorWithPreview({
|
|||||||
autoFocusAndPosition,
|
autoFocusAndPosition,
|
||||||
secondarySaveButton: SecondarySaveButton
|
secondarySaveButton: SecondarySaveButton
|
||||||
}: MarkdownEditorWithPreviewProps) {
|
}: MarkdownEditorWithPreviewProps) {
|
||||||
|
const { getString } = useStrings()
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||||
|
const { repoMetadata } = useGetRepositoryMetadata()
|
||||||
const [selectedTab, setSelectedTab] = useState(MarkdownEditorTab.WRITE)
|
const [selectedTab, setSelectedTab] = useState(MarkdownEditorTab.WRITE)
|
||||||
const viewRef = useRef<EditorView>()
|
const viewRef = useRef<EditorView>()
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
const [dirty, setDirty] = useState(false)
|
const [dirty, setDirty] = useState(false)
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const [file, setFile] = useState<File>()
|
||||||
|
const { showError } = useToaster()
|
||||||
|
const [markdownContent, setMarkdownContent] = useState('')
|
||||||
const onToolbarAction = useCallback((action: ToolbarAction) => {
|
const onToolbarAction = useCallback((action: ToolbarAction) => {
|
||||||
const view = viewRef.current
|
const view = viewRef.current
|
||||||
|
|
||||||
@ -136,6 +160,12 @@ export function MarkdownEditorWithPreview({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case ToolbarAction.UPLOAD: {
|
||||||
|
setOpen(true)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
case ToolbarAction.BOLD: {
|
case ToolbarAction.BOLD: {
|
||||||
view.dispatch(
|
view.dispatch(
|
||||||
view.state.changeByRange(range => ({
|
view.state.changeByRange(range => ({
|
||||||
@ -233,11 +263,121 @@ export function MarkdownEditorWithPreview({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (autoFocusAndPosition && !dirty) {
|
if (autoFocusAndPosition && !dirty) {
|
||||||
scrollToAndSetCursorToEnd(containerRef, viewRef, true)
|
scrollToAndSetCursorToEnd(containerRef, viewRef, true)
|
||||||
}
|
} // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [autoFocusAndPosition, viewRef, containerRef, scrollToAndSetCursorToEnd, dirty])
|
}, [autoFocusAndPosition, viewRef, containerRef, scrollToAndSetCursorToEnd, dirty])
|
||||||
|
|
||||||
|
const setFileCallback = (newFile: File) => {
|
||||||
|
setFile(newFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const handlePasteForSetFile = (event: { preventDefault: () => void; clipboardData: any }) => {
|
||||||
|
handlePaste(event, setFileCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const handleDropForSetFile = async (event: any) => {
|
||||||
|
handleFileDrop(event, setFileCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const view = viewRef.current
|
||||||
|
if (markdownContent && view) {
|
||||||
|
const insertText = ``
|
||||||
|
view.dispatch(
|
||||||
|
view.state.changeByRange(range => ({
|
||||||
|
changes: [{ from: range.from, insert: insertText }],
|
||||||
|
range: EditorSelection.range(range.from + insertText.length, range.from + insertText.length)
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, [markdownContent])
|
||||||
|
|
||||||
|
const handleButtonClick = () => {
|
||||||
|
if (fileInputRef.current) {
|
||||||
|
fileInputRef.current.click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const handleFileChange = (event: any) => {
|
||||||
|
setFile(event?.target?.files[0])
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container ref={containerRef} className={cx(css.container, { [css.noBorder]: noBorder }, className)}>
|
<Container ref={containerRef} className={cx(css.container, { [css.noBorder]: noBorder }, className)}>
|
||||||
|
<Dialog
|
||||||
|
onClose={() => {
|
||||||
|
setFile(undefined)
|
||||||
|
setOpen(false)
|
||||||
|
}}
|
||||||
|
className={css.dialog}
|
||||||
|
isOpen={open}>
|
||||||
|
<Text font={{ variation: FontVariation.H4 }}>{getString('imageUpload.title')}</Text>
|
||||||
|
|
||||||
|
<Container
|
||||||
|
margin={{ top: 'small' }}
|
||||||
|
onDragOver={event => {
|
||||||
|
event.preventDefault()
|
||||||
|
}}
|
||||||
|
onDrop={handleDropForSetFile}
|
||||||
|
onPaste={handlePasteForSetFile}
|
||||||
|
flex={{ alignItems: 'center' }}
|
||||||
|
className={css.uploadContainer}
|
||||||
|
width={500}
|
||||||
|
height={81}>
|
||||||
|
{file ? (
|
||||||
|
<Layout.Horizontal
|
||||||
|
width={`100%`}
|
||||||
|
padding={{ left: 'medium', right: 'medium' }}
|
||||||
|
flex={{ justifyContent: 'space-between' }}>
|
||||||
|
<Layout.Horizontal spacing="small">
|
||||||
|
<Text lineClamp={1} width={200}>
|
||||||
|
{file.name}
|
||||||
|
</Text>
|
||||||
|
<Text>{formatBytes(file.size)}</Text>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
<FlexExpander />
|
||||||
|
<Text icon={'tick'} iconProps={{ color: Color.GREEN_800 }} color={Color.GREEN_800}>
|
||||||
|
{getString('imageUpload.readyToUpload')}
|
||||||
|
</Text>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
) : (
|
||||||
|
<Text padding={{ left: 'medium' }} color={Color.GREY_400}>
|
||||||
|
{getString('imageUpload.text')}
|
||||||
|
<input type="file" ref={fileInputRef} onChange={handleFileChange} style={{ display: 'none' }} />
|
||||||
|
<Button
|
||||||
|
margin={{ left: 'small' }}
|
||||||
|
text={getString('browse')}
|
||||||
|
onClick={handleButtonClick}
|
||||||
|
variation={ButtonVariation.SECONDARY}
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
<Container padding={{ top: 'large' }}>
|
||||||
|
<Layout.Horizontal spacing="small">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
text={getString('imageUpload.upload')}
|
||||||
|
variation={ButtonVariation.PRIMARY}
|
||||||
|
disabled={false}
|
||||||
|
onClick={() => {
|
||||||
|
handleUpload(file as File, setMarkdownContent, repoMetadata, showError)
|
||||||
|
setOpen(false)
|
||||||
|
setFile(undefined)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
text={getString('cancel')}
|
||||||
|
variation={ButtonVariation.TERTIARY}
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(false)
|
||||||
|
setFile(undefined)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Container>
|
||||||
|
</Dialog>
|
||||||
<ul className={css.tabs}>
|
<ul className={css.tabs}>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
|
@ -30,6 +30,7 @@ export interface StringsMap {
|
|||||||
approve: string
|
approve: string
|
||||||
ascending: string
|
ascending: string
|
||||||
assignPeople: string
|
assignPeople: string
|
||||||
|
attachText: string
|
||||||
basedOn: string
|
basedOn: string
|
||||||
blame: string
|
blame: string
|
||||||
blameCommitLine: string
|
blameCommitLine: string
|
||||||
@ -304,6 +305,10 @@ export interface StringsMap {
|
|||||||
'homepage.selectSpaceContent': string
|
'homepage.selectSpaceContent': string
|
||||||
'homepage.selectSpaceTitle': string
|
'homepage.selectSpaceTitle': string
|
||||||
'homepage.welcomeText': string
|
'homepage.welcomeText': string
|
||||||
|
'imageUpload.readyToUpload': string
|
||||||
|
'imageUpload.text': string
|
||||||
|
'imageUpload.title': string
|
||||||
|
'imageUpload.upload': string
|
||||||
importGitRepo: string
|
importGitRepo: string
|
||||||
importProgress: string
|
importProgress: string
|
||||||
'importRepo.failedToImportRepo': string
|
'importRepo.failedToImportRepo': string
|
||||||
|
@ -842,12 +842,18 @@ enterBitbucketPlaceholder: https://bitbucket.org/
|
|||||||
changeRepoVis: Change repository visibility
|
changeRepoVis: Change repository visibility
|
||||||
changeRepoVisContent: Are you sure you want to make this repository {repoVis}? {repoText}
|
changeRepoVisContent: Are you sure you want to make this repository {repoVis}? {repoText}
|
||||||
repoVisibility: Repository Visibility
|
repoVisibility: Repository Visibility
|
||||||
|
attachText: Attach files by dragging & dropping, selecting or pasting them.
|
||||||
key: Key
|
key: Key
|
||||||
setting: Setting
|
setting: Setting
|
||||||
mergeCommit: Merge commit
|
mergeCommit: Merge commit
|
||||||
squashMerge: Squash and merge
|
squashMerge: Squash and merge
|
||||||
rebaseMerge: Rebase and merge
|
rebaseMerge: Rebase and merge
|
||||||
Enable: Enable
|
Enable: Enable
|
||||||
|
imageUpload:
|
||||||
|
title: Upload attachment
|
||||||
|
readyToUpload: Ready for upload
|
||||||
|
upload: Upload
|
||||||
|
text: Drag and drop a file here or click browse to select a file.
|
||||||
branchProtection:
|
branchProtection:
|
||||||
namePlaceholder: Enter the rule name here
|
namePlaceholder: Enter the rule name here
|
||||||
descPlaceholder: Enter the description here
|
descPlaceholder: Enter the description here
|
||||||
|
@ -28,6 +28,7 @@ import type {
|
|||||||
TypesPullReq,
|
TypesPullReq,
|
||||||
TypesRepository
|
TypesRepository
|
||||||
} from 'services/code'
|
} from 'services/code'
|
||||||
|
import { getErrorMessage } from './Utils'
|
||||||
|
|
||||||
export interface GitInfoProps {
|
export interface GitInfoProps {
|
||||||
repoMetadata: TypesRepository
|
repoMetadata: TypesRepository
|
||||||
@ -242,6 +243,50 @@ export function formatTriggers(triggers: EnumWebhookTrigger[]) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const handleUpload = (
|
||||||
|
blob: File,
|
||||||
|
setMarkdownContent: (data: string) => void,
|
||||||
|
repoMetadata: TypesRepository | undefined,
|
||||||
|
showError: (message: React.ReactNode, timeout?: number | undefined, key?: string | undefined) => void
|
||||||
|
) => {
|
||||||
|
const reader = new FileReader()
|
||||||
|
// Set up a function to be called when the load event is triggered
|
||||||
|
reader.onload = async function () {
|
||||||
|
const markdown = await uploadImage(reader.result, showError, repoMetadata)
|
||||||
|
setMarkdownContent(markdown) // Set the markdown content
|
||||||
|
}
|
||||||
|
reader.readAsArrayBuffer(blob) // This will trigger the onload function when the reading is complete
|
||||||
|
}
|
||||||
|
|
||||||
|
export const uploadImage = async (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
fileBlob: any,
|
||||||
|
showError: (message: React.ReactNode, timeout?: number | undefined, key?: string | undefined) => void,
|
||||||
|
repoMetadata: TypesRepository | undefined
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${window.location.origin}/api/v1/repos/${repoMetadata?.path}/+/uploads/`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'content-type': 'application/octet-stream'
|
||||||
|
},
|
||||||
|
body: fileBlob,
|
||||||
|
redirect: 'follow'
|
||||||
|
})
|
||||||
|
const result = await response.json()
|
||||||
|
if (!response.ok && result) {
|
||||||
|
showError(getErrorMessage(result))
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
const filePath = result.file_path
|
||||||
|
return window.location.origin + '/' + 'api/v1/repos/' + repoMetadata?.path + '/+/uploads/' + filePath
|
||||||
|
} catch (exception) {
|
||||||
|
showError(getErrorMessage(exception))
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-control-regex
|
// eslint-disable-next-line no-control-regex
|
||||||
const BAD_GIT_REF_REGREX = /(^|[/.])([/.]|$)|^@$|@{|[\x00-\x20\x7f~^:?*[\\]|\.lock(\/|$)/
|
const BAD_GIT_REF_REGREX = /(^|[/.])([/.]|$)|^@$|@{|[\x00-\x20\x7f~^:?*[\\]|\.lock(\/|$)/
|
||||||
const BAD_GIT_BRANCH_REGREX = /^(-|HEAD$)/
|
const BAD_GIT_BRANCH_REGREX = /^(-|HEAD$)/
|
||||||
|
@ -177,6 +177,36 @@ export function formatTime(timestamp: number | string, timeStyle = 'short'): str
|
|||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FileDropCallback = (file: File) => void
|
||||||
|
//handle file drop in image upload
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const handleFileDrop = (event: any, callback: FileDropCallback): void => {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
const file = event?.dataTransfer?.files[0]
|
||||||
|
if (file) {
|
||||||
|
callback(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PasteCallback = (file: File) => void
|
||||||
|
|
||||||
|
// handle file paste in image upload
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const handlePaste = (event: { preventDefault: () => void; clipboardData: any }, callback: PasteCallback) => {
|
||||||
|
event.preventDefault()
|
||||||
|
const clipboardData = event.clipboardData
|
||||||
|
const items = clipboardData.items
|
||||||
|
|
||||||
|
if (items.length > 0) {
|
||||||
|
const firstItem = items[0]
|
||||||
|
if (firstItem.type.startsWith('image/')) {
|
||||||
|
const blob = firstItem.getAsFile()
|
||||||
|
callback(blob)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format a timestamp to medium format date (i.e: Jan 1, 2021)
|
* Format a timestamp to medium format date (i.e: Jan 1, 2021)
|
||||||
* @param timestamp Timestamp
|
* @param timestamp Timestamp
|
||||||
|
Loading…
Reference in New Issue
Block a user