Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OUT-106 Add Embed Type #59

Merged
merged 4 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions src/app/client-preview/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
const token = tokenParsed.data

const clientId = z.string().uuid().parse(searchParams.clientId)
const allCustomFields = await getCustomFields(searchParams.token)

let settings: ISettings = {
content: '',
Expand All @@ -74,20 +73,22 @@
displayTasks: false,
}

const defaultSetting = await getSettings(token)
const [defaultSetting, allCustomFields, _client] = await Promise.all([
getSettings(token),
getCustomFields(searchParams.token),
getClient(clientId, token),
])

const company = await getCompany(_client.companyId, token)

if (defaultSetting) {
settings = defaultSetting
}

const _client = await getClient(clientId, token)

const company = await getCompany(_client.companyId, token)

const template = Handlebars?.compile(settings?.content)

//add comma separator for custom fields
const customFields: any = _client?.customFields

Check warning on line 91 in src/app/client-preview/page.tsx

View workflow job for this annotation

GitHub Actions / Run linters

Unexpected any. Specify a different type

for (const key in customFields) {
// Check if the value is an array and if the key exists in allCustomFields
Expand All @@ -97,9 +98,9 @@
) {
// Map the values to their corresponding labels
customFields[key] = customFields[key].map((value: string[]) => {
const option: any = (allCustomFields as any)

Check warning on line 101 in src/app/client-preview/page.tsx

View workflow job for this annotation

GitHub Actions / Run linters

Unexpected any. Specify a different type

Check warning on line 101 in src/app/client-preview/page.tsx

View workflow job for this annotation

GitHub Actions / Run linters

Unexpected any. Specify a different type
.find((field: any) => field.key === key)

Check warning on line 102 in src/app/client-preview/page.tsx

View workflow job for this annotation

GitHub Actions / Run linters

Unexpected any. Specify a different type
.options.find((opt: any) => opt.key === value)

Check warning on line 103 in src/app/client-preview/page.tsx

View workflow job for this annotation

GitHub Actions / Run linters

Unexpected any. Specify a different type
return option ? ' ' + option.label : ' ' + value
})
}
Expand Down
4 changes: 4 additions & 0 deletions src/app/components/ClientPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import OrderedList from '@tiptap/extension-ordered-list'
import BulletList from '@tiptap/extension-bullet-list'
import ListItem from '@tiptap/extension-list-item'
import Image from '@tiptap/extension-image'

Check warning on line 12 in src/app/components/ClientPreview.tsx

View workflow job for this annotation

GitHub Actions / Run linters

'Image' is defined but never used

Check warning on line 12 in src/app/components/ClientPreview.tsx

View workflow job for this annotation

GitHub Actions / Run linters

'Image' is defined but never used
import Table from '@tiptap/extension-table'
import TableCell from '@tiptap/extension-table-cell'
import TableHeader from '@tiptap/extension-table-header'
Expand All @@ -35,11 +35,12 @@
import { AutofillExtension } from '@/components/tiptap/autofieldSelector/ext_autofill'
import { NotificationWidgetExtension } from '@/components/tiptap/notificationWidget/ext_notification_widget'
import { useAppState } from '@/hooks/useAppState'
import { INotification, ISettings } from '@/types/interfaces'

Check warning on line 38 in src/app/components/ClientPreview.tsx

View workflow job for this annotation

GitHub Actions / Run linters

'INotification' is defined but never used

Check warning on line 38 in src/app/components/ClientPreview.tsx

View workflow job for this annotation

GitHub Actions / Run linters

'INotification' is defined but never used
import { defaultBannerImagePath } from '@/utils/constants'

Check warning on line 39 in src/app/components/ClientPreview.tsx

View workflow job for this annotation

GitHub Actions / Run linters

'defaultBannerImagePath' is defined but never used
import { defaultState } from '../../../defaultState'
import useSWR from 'swr'
import { fetcher } from '@/utils/fetcher'
import { IframeExtension } from '@/components/tiptap/iframe/ext_iframe'

const ClientPreview = ({
content,
Expand All @@ -62,6 +63,9 @@
extensions: [
AutofillExtension,
NotificationWidgetExtension,
IframeExtension.configure({
allowFullscreen: true,
}),
Document,
Paragraph,
Heading,
Expand Down
12 changes: 12 additions & 0 deletions src/app/components/EditorInterface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
import { NotificationWidgetExtension } from '@/components/tiptap/notificationWidget/ext_notification_widget'
import { useAppDataContext } from '@/hooks/useAppData'
import { defaultNotificationOptions } from '@/utils/notifications'
import { IframeExtension } from '@/components/tiptap/iframe/ext_iframe'
import BubbleEmbedInput from '@/components/tiptap/iframe/IFrameInput'

interface IEditorInterface {
settings: ISettings | null
Expand All @@ -69,6 +71,9 @@
extensions: [
AutofillExtension,
NotificationWidgetExtension,
IframeExtension.configure({
allowFullscreen: true,
}),
Document,
Paragraph,
Heading,
Expand Down Expand Up @@ -251,7 +256,7 @@
}, [editor])

useEffect(() => {
;(async () => {

Check failure on line 259 in src/app/components/EditorInterface.tsx

View workflow job for this annotation

GitHub Actions / Run linters

Unnecessary semicolon
appState?.setLoading(true)
if (token) {
const _settings: ISettings = {
Expand All @@ -274,10 +279,10 @@
appState?.setSettings(
settings
? {
...settings,

Check failure on line 282 in src/app/components/EditorInterface.tsx

View workflow job for this annotation

GitHub Actions / Run linters

Expected indentation of 14 spaces but found 16
notifications:

Check failure on line 283 in src/app/components/EditorInterface.tsx

View workflow job for this annotation

GitHub Actions / Run linters

Expected indentation of 14 spaces but found 16
settings?.notifications || defaultNotificationOptions,
}

Check failure on line 285 in src/app/components/EditorInterface.tsx

View workflow job for this annotation

GitHub Actions / Run linters

Expected indentation of 12 spaces but found 14
: _settings,
)
appState?.setToken(token)
Expand All @@ -297,7 +302,7 @@
}, [appState?.appState.settings])

useEffect(() => {
;(async () => {

Check failure on line 305 in src/app/components/EditorInterface.tsx

View workflow job for this annotation

GitHub Actions / Run linters

Unnecessary semicolon
const imagePickerUtils = new ImagePickerUtils()
if (appState?.appState.bannerImgUrl instanceof Blob) {
setBannerImage(
Expand Down Expand Up @@ -401,6 +406,13 @@
>
<BubbleLinkInput />
</ControlledBubbleMenu>
<ControlledBubbleMenu
editor={editor}
open={() => appState?.appState.showEmbedInput as boolean}
offset={[0, 6]}
>
<BubbleEmbedInput />
</ControlledBubbleMenu>
<ControlledBubbleMenu
editor={editor}
open={() => {
Expand Down
17 changes: 17 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@
@tailwind components;
@tailwind utilities;

.iframe-wrapper {
position: relative;
padding-bottom: calc(100 / 16 * 9%);
height: 0;
overflow: hidden;
width: 100%;
height: auto;
}

.iframe-wrapper iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}

.editable .pill-extension {
display: inline-flex;
flex-wrap: wrap;
Expand Down
5 changes: 5 additions & 0 deletions src/components/tiptap/floatingMenu/FloatingMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ const FloatingContainerBtn = ({
<TableIcon />
) : label === 'Callout' ? (
<CalloutIcon />
) : label === 'Embed' ? (
<EmbedIcon />
) : (
<></>
)}
Expand All @@ -77,6 +79,9 @@ export const FloatingMenu = forwardRef((props: any, ref: any) => {

if (item) {
props.command({ id: item })
if (item.title === 'Embed') {
appState?.setShowEmbedInput(true)
}
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/components/tiptap/floatingMenu/floatingMenuSuggestion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ export const floatingMenuSuggestion = {
tiptapEditorUtils.insertCallout('')
},
},
{
title: 'Embed',
command: ({ editor, range }: { editor: Editor; range: any }) => {
const tiptapEditorUtils = new TiptapEditorUtils(editor)
tiptapEditorUtils.deleteRange(range)
},
},
]
.filter((item) =>
item.title
Expand Down
86 changes: 86 additions & 0 deletions src/components/tiptap/iframe/IFrameInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useAppState } from '@/hooks/useAppState'
import { fixUrl } from '@/utils/fixUrl'
import { TiptapEditorUtils } from '@/utils/tiptapEditorUtils'
import { Cancel } from '@mui/icons-material'
import { InputAdornment, TextField } from '@mui/material'
import { Editor } from '@tiptap/react'
import React, { SyntheticEvent, useEffect, useRef, useState } from 'react'

const BubbleEmbedInput = () => {
const appState = useAppState()

const [url, setUrl] = useState('')

const urlInputRef = useRef<HTMLInputElement>(null)

const tiptapEditorUtils = new TiptapEditorUtils(
appState?.appState.editor as Editor,
)

const handleKeyDown = (event: SyntheticEvent<HTMLDivElement>) => {
//@ts-expect-error event should contain code
if (event.code === 'Escape') {
event.preventDefault()
appState?.setShowEmbedInput(false)
}

//@ts-expect-error event should contain code
if (event.code === 'Enter') {
event.preventDefault()
tiptapEditorUtils.insertEmbed(fixUrl(url))
appState?.setShowEmbedInput(false)
setUrl('')
}
}

useEffect(() => {
if (urlInputRef.current) {
urlInputRef.current.focus()
}
}, [urlInputRef.current])

return (
<TextField
type='text'
variant='outlined'
InputProps={{
endAdornment: (
<InputAdornment
position='end'
sx={{
cursor: 'pointer',
}}
>
<Cancel
onClick={() => {
appState?.setShowEmbedInput(false)
}}
/>
</InputAdornment>
),
}}
sx={{
'& .MuiInputBase-input': {
padding: '8px 12px',
},
'& .MuiOutlinedInput-root': {
'&.Mui-focused fieldset': {
borderColor: '#DFE1E4',
},
'&:hover fieldset': {
borderColor: '#DFE1E4',
},
},
background: '#fff',
borderRadius: '8px',
}}
onChange={(e) => setUrl(e.target.value)}
ref={urlInputRef}
onKeyDown={handleKeyDown}
value={url}
autoFocus
/>
)
}

export default BubbleEmbedInput
80 changes: 80 additions & 0 deletions src/components/tiptap/iframe/ext_iframe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Node } from '@tiptap/core'

export interface IframeOptions {
allowFullscreen: boolean
HTMLAttributes: {
[key: string]: any
}
}

declare module '@tiptap/core' {
interface Commands<ReturnType> {
iframe: {
/**
* Add an iframe
*/
setIframe: (options: { src: string }) => ReturnType
}
}
}

export const IframeExtension = Node.create<IframeOptions>({
name: 'iframe',

group: 'block',

atom: true,

addOptions() {
return {
allowFullscreen: true,
HTMLAttributes: {
class: 'iframe-wrapper',
},
}
},

addAttributes() {
return {
src: {
default: null,
},
frameborder: {
default: 0,
},
allowfullscreen: {
default: this.options.allowFullscreen,
parseHTML: () => this.options.allowFullscreen,
},
}
},

parseHTML() {
return [
{
tag: 'iframe',
},
]
},

renderHTML({ HTMLAttributes }) {
return ['div', this.options.HTMLAttributes, ['iframe', HTMLAttributes]]
},

addCommands() {
return {
setIframe:
(options: { src: string }) =>
({ tr, dispatch }) => {

Check failure on line 68 in src/components/tiptap/iframe/ext_iframe.ts

View workflow job for this annotation

GitHub Actions / Run linters

Expected indentation of 10 spaces but found 8

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see formating error here @aatbip

const { selection } = tr
const node = this.type.create(options)

if (dispatch) {
tr.replaceRangeWith(selection.from, selection.to, node)
}

return true
},
}
},
})
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ export const NotificationWidget = () => {
datatype='draggable-item'
onMouseOver={() => setHovered(true)}
onMouseOut={() => setHovered(false)}
style={{ position: 'relative', cursor: 'pointer' }}
style={{
position: 'relative',
cursor: appState?.appState.readOnly ? 'auto' : 'pointer',
}}
>
<Typography variant='h2' datatype='draggable-item'>
You have {taskCount} task
Expand Down
8 changes: 8 additions & 0 deletions src/context/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface IAppState {
customFields: ICustomField[]
token: string
notifications: INotification | undefined
showEmbedInput: boolean
}

export interface IAppContext {
Expand All @@ -52,6 +53,7 @@ export interface IAppContext {
setBannerImgId: (imageId: string) => void
setToken: (token: string) => void
setNotification: (notification: INotification) => void
setShowEmbedInput: (v: boolean) => void
}

interface IAppCoreProvider {
Expand Down Expand Up @@ -80,6 +82,7 @@ export const AppContextProvider: FC<IAppCoreProvider> = ({ children }) => {
token: '',
showNotificationsModal: false,
notifications: undefined,
showEmbedInput: false,
})

const toggleShowLinkInput = (v: boolean) => {
Expand Down Expand Up @@ -160,6 +163,10 @@ export const AppContextProvider: FC<IAppCoreProvider> = ({ children }) => {
setState((prev) => ({ ...prev, notifications: notification }))
}

const setShowEmbedInput = (v: boolean) => {
setState((prev) => ({ ...prev, showEmbedInput: v }))
}

return (
<AppContext.Provider
value={{
Expand All @@ -182,6 +189,7 @@ export const AppContextProvider: FC<IAppCoreProvider> = ({ children }) => {
setBannerImgId,
setToken,
setNotification,
setShowEmbedInput,
}}
>
<AppDataProvider>{children}</AppDataProvider>
Expand Down
4 changes: 4 additions & 0 deletions src/utils/tiptapEditorUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ export class TiptapEditorUtils {
.run()
}

insertEmbed(url: string) {
this.editor.chain().focus().setIframe({ src: url }).run()
}

getSelectedText() {
const { view, state } = this.editor
const { from, to } = view.state.selection
Expand Down
Loading