([])
const setInternalMetadata = useMexEditorStore((store) => store.setInternalMetadata)
const isEmpty = useMultipleEditors((store) => store.isEmpty)
-
const { selectBlock } = useFocusBlock()
const clearHighlights = useBlockHighlightStore((store) => store.clearAllHighlightedBlockIds)
const highlightedBlockIds = useBlockHighlightStore((store) => store.highlighted.editor)
@@ -112,6 +112,7 @@ export const MexEditorBase = (props: MexEditorProps) => {
>
{props.options?.withBalloonToolbar && props.BalloonMarkToolbarButtons}
{isEmpty && }
+
{props.options?.withGlobalListener !== false && }
{props.debug && {JSON.stringify(content, null, 2)}
}
diff --git a/apps/webapp/src/Editor/Plugins/parseTwitterUrl.ts b/apps/webapp/src/Editor/Plugins/parseTwitterUrl.ts
index 2ece3f6e9..065422504 100644
--- a/apps/webapp/src/Editor/Plugins/parseTwitterUrl.ts
+++ b/apps/webapp/src/Editor/Plugins/parseTwitterUrl.ts
@@ -1,12 +1,9 @@
import { MediaPlugin } from '@udecode/plate'
import { getPluginOptions, PlateEditor, RenderFunction, Value } from '@udecode/plate-core'
-import { mog } from '@mexit/core'
-
const twitterRegex = /^https?:\/\/twitter\.com\/(?:#!\/)?(\w+)\/status(es)?\/(?\d+)/
export const parseTwitterUrl = (url: string): EmbedUrlData | undefined => {
- mog('URL IS', { url })
if (url?.match(twitterRegex)) {
return {
provider: 'twitter',
diff --git a/apps/webapp/src/Editor/constants.ts b/apps/webapp/src/Editor/constants.ts
index d4af8c9eb..3e79dc694 100644
--- a/apps/webapp/src/Editor/constants.ts
+++ b/apps/webapp/src/Editor/constants.ts
@@ -12,6 +12,8 @@ export enum CategoryType {
tag = 'Tags'
}
+export const AI_RENDER_TYPE = 'ai-render-type'
+
export enum QuickLinkType {
backlink = 'Backlinks',
publicNotes = 'Public Notes',
diff --git a/apps/webapp/src/Hooks/useAIOptions.ts b/apps/webapp/src/Hooks/useAIOptions.ts
new file mode 100644
index 000000000..558cee7a8
--- /dev/null
+++ b/apps/webapp/src/Hooks/useAIOptions.ts
@@ -0,0 +1,39 @@
+import { toast } from 'react-hot-toast'
+
+import { AIEvent, API, SupportedAIEventTypes, useHistoryStore } from '@mexit/core'
+
+export const useAIOptions = () => {
+ const addInAIEventsHistory = useHistoryStore((store) => store.addInAIHistory)
+
+ const performAIAction = async (type: SupportedAIEventTypes, content?: string): Promise => {
+ const aiEventsHistory = useHistoryStore.getState().ai
+ const userQuery: AIEvent = {
+ role: 'user',
+ type
+ }
+
+ if (content) {
+ userQuery.content = content
+ }
+
+ const reqData = {
+ context: [...aiEventsHistory.flat().filter((item) => item), userQuery]
+ }
+
+ try {
+ const assistantResponse = await API.ai.perform(reqData)
+
+ if (assistantResponse?.content) {
+ addInAIEventsHistory(userQuery, assistantResponse)
+ }
+ } catch (err) {
+ // * Write cute error message
+ toast('Something went wrong!')
+ console.error('Unable to perform AI action', err)
+ }
+ }
+
+ return {
+ performAIAction
+ }
+}
diff --git a/apps/webapp/src/Hooks/useCreateNewMenu.tsx b/apps/webapp/src/Hooks/useCreateNewMenu.tsx
index 06f7d4bf7..23caf95eb 100644
--- a/apps/webapp/src/Hooks/useCreateNewMenu.tsx
+++ b/apps/webapp/src/Hooks/useCreateNewMenu.tsx
@@ -11,6 +11,7 @@ import {
isReservedNamespace,
MIcon,
ModalsType,
+ SupportedAIEventTypes,
useDataStore,
useLayoutStore,
useMetadataStore,
@@ -25,8 +26,10 @@ import { useDeleteStore } from '../Components/Refactor/DeleteModal'
import { doesLinkRemain } from '../Components/Refactor/doesLinkRemain'
import { useTaskViewModalStore } from '../Components/TaskViewModal'
import { useBlockMenu } from '../Editor/Components/useBlockMenu'
+import useUpdateBlock from '../Editor/Hooks/useUpdateBlock'
import { useViewStore } from '../Stores/useViewStore'
+import { useAIOptions } from './useAIOptions'
import { useCreateNewNote } from './useCreateNewNote'
import { useNamespaces } from './useNamespaces'
import { useNavigation } from './useNavigation'
@@ -73,9 +76,11 @@ export const useCreateNewMenu = () => {
const deleteNamespace = useDataStore((store) => store.deleteNamespace)
const { goTo } = useRouting()
+ const { getSelectionInMarkdown } = useUpdateBlock()
const { push } = useNavigation()
const { deleteView } = useViews()
const { addSnippet } = useSnippets()
+ const { performAIAction } = useAIOptions()
const { execRefactorAsync } = useRefactor()
const { createNewNote } = useCreateNewNote()
const blockMenuItems = useBlockMenu()
@@ -325,6 +330,37 @@ export const useCreateNewMenu = () => {
]
}
+ // * AI functions
+ const handleAIQuery = async (type: SupportedAIEventTypes, callback: any) => {
+ performAIAction(type).then((res) => {
+ callback(res)
+ })
+ }
+
+ const getAIMenuItems = () => {
+ return [
+ getMenuItem(
+ 'Continue',
+ (c) => handleAIQuery(SupportedAIEventTypes.EXPAND, c),
+ false,
+ getMIcon('ICON', 'system-uicons:write')
+ ),
+ getMenuItem(
+ 'Explain',
+ (c) => handleAIQuery(SupportedAIEventTypes.EXPLAIN, c),
+ false,
+ getMIcon('ICON', 'ri:question-line')
+ ),
+ getMenuItem('Summarize', (c) => handleAIQuery(SupportedAIEventTypes.SUMMARIZE, c), false, DefaultMIcons.AI),
+ getMenuItem(
+ 'Actionable',
+ (c) => handleAIQuery(SupportedAIEventTypes.ACTIONABLE, c),
+ false,
+ getMIcon('ICON', 'ic:round-view-list')
+ )
+ ]
+ }
+
return {
getCreateNewMenuItems,
getSnippetsMenuItems,
@@ -332,6 +368,7 @@ export const useCreateNewMenu = () => {
getBlockMenuItems,
getTreeMenuItems,
getViewMenuItems,
+ getAIMenuItems,
// * Handlers
handleCreateSnippet,
diff --git a/apps/webapp/src/Hooks/useUpdater.ts b/apps/webapp/src/Hooks/useUpdater.ts
index d61421efc..0114a96c8 100644
--- a/apps/webapp/src/Hooks/useUpdater.ts
+++ b/apps/webapp/src/Hooks/useUpdater.ts
@@ -47,7 +47,6 @@ export const useUpdater = () => {
const todos = getTodosFromContent(content)
updateNodeTodos(noteId, todos)
- // console.log('updateFromContent', noteId, todos, content)
await updateDocument({
id: noteId,
contents: content,
diff --git a/apps/webapp/src/Workers/controller.ts b/apps/webapp/src/Workers/controller.ts
index 8338a9fa8..9da1789b5 100644
--- a/apps/webapp/src/Workers/controller.ts
+++ b/apps/webapp/src/Workers/controller.ts
@@ -62,7 +62,6 @@ export const runBatchWorker = async (
args: any[] | Record
) => {
const res = await requestsWorker.runBatchWorker(requestType, batchSize, args)
- mog('RUN_BATCH_WORKER', { res })
return res
}
diff --git a/libs/core/src/API/AI.ts b/libs/core/src/API/AI.ts
new file mode 100644
index 000000000..325a738bf
--- /dev/null
+++ b/libs/core/src/API/AI.ts
@@ -0,0 +1,17 @@
+import { type Options } from 'ky'
+
+import { type KYClient } from '@workduck-io/dwindle'
+
+import { AIEvent } from '../Types'
+import { apiURLs } from '../Utils/routes'
+
+export class AiAPI {
+ private client: KYClient
+ constructor(client: KYClient) {
+ this.client = client
+ }
+
+ async perform(data: any, options?: Options): Promise {
+ return await this.client.post(apiURLs.openAi.perform, data, options)
+ }
+}
diff --git a/libs/core/src/API/Base.ts b/libs/core/src/API/Base.ts
index 6ab75f8a4..d729efed6 100644
--- a/libs/core/src/API/Base.ts
+++ b/libs/core/src/API/Base.ts
@@ -2,6 +2,7 @@ import { type KyInstance } from 'ky/distribution/types/ky'
import { KYClient } from '@workduck-io/dwindle'
+import { AiAPI } from './AI'
import { BookmarkAPI } from './Bookmarks'
import { CommentAPI } from './Comment'
import { HighlightAPI } from './Highlight'
@@ -30,6 +31,7 @@ class APIClass {
public namespace: NamespaceAPI
public view: ViewAPI
public loch: LochAPI
+ public ai: AiAPI
public link: LinkAPI
public prompt: PromptAPI
public reminder: ReminderAPI
@@ -56,6 +58,7 @@ class APIClass {
this.prompt = new PromptAPI(this.client)
this.view = new ViewAPI(this.client)
this.link = new LinkAPI(this.client)
+ this.ai = new AiAPI(this.client)
this.reminder = new ReminderAPI(this.client)
this.user = new UserAPI(this.client)
this.highlight = new HighlightAPI(this.client)
diff --git a/libs/core/src/Data/defaultCommands.ts b/libs/core/src/Data/defaultCommands.ts
index 905240225..5ebc52830 100644
--- a/libs/core/src/Data/defaultCommands.ts
+++ b/libs/core/src/Data/defaultCommands.ts
@@ -2,6 +2,12 @@ import { CategoryType, SlashCommand } from '../Types/Editor'
import { getMIcon } from '../Types/Store'
export const defaultCommands: SlashCommand[] = [
+ {
+ command: 'ai',
+ text: 'Start with AI',
+ icon: getMIcon('ICON', 'fluent:text-grammar-wand-24-filled'),
+ type: CategoryType.action
+ },
{ command: 'task', text: 'Create a Task', icon: getMIcon('ICON', 'ri:task-line'), type: CategoryType.action },
{ command: 'table', text: 'Insert Table', icon: getMIcon('ICON', 'ri:table-line'), type: CategoryType.action },
// { command: 'canvas', text: 'Insert Drawing canvas', icon: getMIcon('ICON', 'ri:markup-line'), type: CategoryType.action },
diff --git a/libs/core/src/Stores/floating.store.ts b/libs/core/src/Stores/floating.store.ts
new file mode 100644
index 000000000..5e8c9b302
--- /dev/null
+++ b/libs/core/src/Stores/floating.store.ts
@@ -0,0 +1,27 @@
+import { StoreIdentifier } from '../Types/Store'
+import { createStore } from '../Utils/storeCreator'
+
+export enum FloatingElementType {
+ BALLON_TOOLBAR = 'BALLON_TOOLBAR',
+ AI_POPOVER = 'AI_POPOVER'
+}
+
+const floatingStoreConfig = (set, get) => ({
+ floatingElement: undefined as FloatingElementType | undefined,
+ state: {} as Record,
+ updateFloatingElementState: (floatingElementId: string, state: Record) => {
+ const existingState = get().state
+ set({ state: { ...existingState, [floatingElementId]: { ...existingState[floatingElementId], ...state } } })
+ },
+ getFloatingElementState: (element: FloatingElementType) => get().state[element],
+ setFloatingElement: (element: FloatingElementType, state?: any) => {
+ if (state) {
+ set({ floatingElement: element, state: { ...get().state, [element]: state } })
+ } else {
+ const { [element]: _, ...rest } = get().state
+ set({ floatingElement: element, state: rest })
+ }
+ }
+})
+
+export const useFloatingStore = createStore(floatingStoreConfig, StoreIdentifier.FLOATING, false)
diff --git a/libs/core/src/Stores/history.store.ts b/libs/core/src/Stores/history.store.ts
index 0ca3c5433..c3e084999 100644
--- a/libs/core/src/Stores/history.store.ts
+++ b/libs/core/src/Stores/history.store.ts
@@ -1,3 +1,4 @@
+import { AIEvent, AIEventsHistory } from '../Types/History'
import { StoreIdentifier } from '../Types/Store'
import { createStore } from '../Utils/storeCreator'
@@ -6,6 +7,35 @@ const MAX_HISTORY_SIZE = 25
export const historyStoreConfig = (set, get) => ({
stack: [] as Array,
currentNodeIndex: -1,
+ ai: [] as AIEventsHistory,
+ activeEventIndex: -1,
+ setActiveEventIndex: (index: number) => set({ activeEventIndex: index }),
+ addInitialEvent: (event: AIEvent) => {
+ set({ ai: [[event, undefined]] })
+ },
+ addInAIHistory: (userQuery: AIEvent, assistantResponse: AIEvent) => {
+ const aiEventsHistory = get().ai as AIEventsHistory
+ const activeEventIndex = get().activeEventIndex
+ const lastEvent = aiEventsHistory.at(activeEventIndex)?.[0]
+ set({
+ ai: [...aiEventsHistory.slice(0, activeEventIndex), [lastEvent, userQuery], [assistantResponse, undefined]],
+ activeEventIndex: -1
+ })
+ },
+ clearAIResponses: () => {
+ const aiEventsHistory = get().ai as AIEventsHistory
+ if (aiEventsHistory?.length) {
+ const initialEvent = aiEventsHistory[0]
+ set({ ai: [[initialEvent[0], undefined]], activeEventIndex: -1 })
+ }
+ },
+ clearAIHistory: () => set({ ai: [], activeEventIndex: -1 }),
+ getLastEvent: (): string | undefined => {
+ const aiHistory = get().ai as AIEventsHistory
+ const lastEvent = aiHistory.at(-1)?.at(-1)
+
+ return lastEvent?.content
+ },
/**
* Push will remove all elements above the currentNodeIndex
diff --git a/libs/core/src/Stores/index.ts b/libs/core/src/Stores/index.ts
index d589977bf..fd367b60c 100644
--- a/libs/core/src/Stores/index.ts
+++ b/libs/core/src/Stores/index.ts
@@ -14,6 +14,7 @@ export * from './data.store'
export * from './description.store'
export * from './editor.store'
export * from './editors.store'
+export * from './floating.store'
export * from './help.store'
export * from './highlight.store'
export * from './history.store'
diff --git a/libs/core/src/Types/History.ts b/libs/core/src/Types/History.ts
new file mode 100644
index 000000000..3ebb47976
--- /dev/null
+++ b/libs/core/src/Types/History.ts
@@ -0,0 +1,17 @@
+export enum SupportedAIEventTypes {
+ SUMMARIZE = 'SUMMARIZE',
+ EXPLAIN = 'EXPLAIN',
+ EXPAND = 'EXPAND',
+ ACTIONABLE = 'ACTIONABLE',
+ PROMPT = 'PROMPT'
+}
+
+export interface AIEvent {
+ role: 'user' | 'assistant'
+ content?: string
+ type?: SupportedAIEventTypes
+}
+
+export type AIEventHistory = [AIEvent, AIEvent]
+
+export type AIEventsHistory = Array
diff --git a/libs/core/src/Types/List.ts b/libs/core/src/Types/List.ts
index 514ab1b75..4180fd49e 100644
--- a/libs/core/src/Types/List.ts
+++ b/libs/core/src/Types/List.ts
@@ -14,6 +14,15 @@ export interface ListItemType {
extras?: Partial
}
+export interface MenuListItemType {
+ id: string
+ label: string
+ disabled?: boolean
+ icon?: MIcon
+ onSelect: any
+ options?: Array
+}
+
export interface ItemExtraType {
nodeid: string
blockid: string
diff --git a/libs/core/src/Types/Store.ts b/libs/core/src/Types/Store.ts
index 4a9e153cc..ccb5284dd 100644
--- a/libs/core/src/Types/Store.ts
+++ b/libs/core/src/Types/Store.ts
@@ -48,6 +48,7 @@ export enum StoreIdentifier {
API = 'api',
AUTH = 'auth',
BLOCK = 'block',
+ FLOATING = 'floating',
COMMENTS = 'comments',
HELP = 'help',
HISTORY = 'history',
diff --git a/libs/core/src/Types/index.ts b/libs/core/src/Types/index.ts
new file mode 100644
index 000000000..40b267152
--- /dev/null
+++ b/libs/core/src/Types/index.ts
@@ -0,0 +1,26 @@
+export * from './Actions'
+export * from './Auth'
+export * from './Comment'
+export * from './Editor'
+export * from './FeatureFlags'
+export * from './Filters'
+export * from './Help'
+export * from './Highlight'
+export * from './History'
+export * from './Kanban'
+export * from './List'
+export * from './Mentions'
+export * from './Metadata'
+export * from './MultiCombobox'
+export * from './Prompt'
+export * from './Reaction'
+export * from './Reminders'
+export * from './Search'
+export * from './Shortcut'
+export * from './Shortener'
+export * from './SmartCapture'
+export * from './Store'
+export * from './Sync'
+export * from './Todo'
+export * from './UserPreference'
+export * from './View'
diff --git a/libs/core/src/Utils/helpers.ts b/libs/core/src/Utils/helpers.ts
index 9191645da..efbf7eb78 100644
--- a/libs/core/src/Utils/helpers.ts
+++ b/libs/core/src/Utils/helpers.ts
@@ -14,7 +14,7 @@ export function wrapErr(f: (result: T) => void) {
export const defaultContent: NodeContent = {
type: 'init',
- content: [{ type: ELEMENT_PARAGRAPH, children: [{ text: '' }] }],
+ content: [{ type: ELEMENT_PARAGRAPH, children: [{ text: 'hello world' }] }],
version: -1
}
diff --git a/libs/core/src/Utils/index.ts b/libs/core/src/Utils/index.ts
new file mode 100644
index 000000000..e77b5df56
--- /dev/null
+++ b/libs/core/src/Utils/index.ts
@@ -0,0 +1,30 @@
+export * from './actionUtils'
+export * from './batchPromise'
+export * from './config'
+export * from './content'
+export * from './dataTransform'
+export * from './defaults'
+export * from './editorElements'
+export * from './events'
+export * from './fuzzysearch'
+export * from './getNewBlockData'
+export * from './heirarchy'
+export * from './helpers'
+export * from './heuristichelper'
+export * from './idbStorageAdapter'
+export * from './idGenerator'
+export * from './keyMap'
+export * from './links'
+export * from './linkUtils'
+export * from './lodashUtils'
+export * from './mog'
+export * from './niceTry'
+export * from './parseData'
+export * from './parsers'
+export * from './path'
+export * from './reminders'
+export * from './routes'
+export * from './serializer'
+export * from './strings'
+export * from './time'
+export * from './treeUtils'
diff --git a/libs/core/src/Utils/routes.ts b/libs/core/src/Utils/routes.ts
index 1e911f461..85d952cbf 100644
--- a/libs/core/src/Utils/routes.ts
+++ b/libs/core/src/Utils/routes.ts
@@ -48,6 +48,10 @@ export const apiURLs = {
archiveInNamespace: (namespaceID: string) => `${API_BASE_URLS.archive}?namespaceID=${namespaceID}`
},
+ openAi: {
+ perform: `${API_BASE_URLS.prompt}/chat`
+ },
+
// Namespaces
namespaces: {
getHierarchy: `${API_BASE_URLS.namespace}/all/hierarchy?getMetadata=true`,
diff --git a/libs/core/src/index.ts b/libs/core/src/index.ts
index e16036e70..4e4ca2237 100644
--- a/libs/core/src/index.ts
+++ b/libs/core/src/index.ts
@@ -9,58 +9,5 @@ export * from './Data/outline'
export * from './Data/search'
export * from './Data/views'
export * from './Stores'
-export * from './Types/Actions'
-export * from './Types/Auth'
-export * from './Types/Comment'
-export * from './Types/Editor'
-export * from './Types/FeatureFlags'
-export * from './Types/Filters'
-export * from './Types/Help'
-export * from './Types/Highlight'
-export * from './Types/Kanban'
-export * from './Types/List'
-export * from './Types/Mentions'
-export * from './Types/Metadata'
-export * from './Types/MultiCombobox'
-export * from './Types/Prompt'
-export * from './Types/Reaction'
-export * from './Types/Reminders'
-export * from './Types/Search'
-export * from './Types/Shortcut'
-export * from './Types/Shortener'
-export * from './Types/SmartCapture'
-export * from './Types/Store'
-export * from './Types/Sync'
-export * from './Types/Todo'
-export * from './Types/UserPreference'
-export * from './Types/View'
-export * from './Utils/actionUtils'
-export * from './Utils/batchPromise'
-export * from './Utils/config'
-export * from './Utils/content'
-export * from './Utils/dataTransform'
-export * from './Utils/defaults'
-export * from './Utils/editorElements'
-export * from './Utils/events'
-export * from './Utils/fuzzysearch'
-export * from './Utils/getNewBlockData'
-export * from './Utils/heirarchy'
-export * from './Utils/helpers'
-export * from './Utils/heuristichelper'
-export * from './Utils/idbStorageAdapter'
-export * from './Utils/idGenerator'
-export * from './Utils/keyMap'
-export * from './Utils/links'
-export * from './Utils/linkUtils'
-export * from './Utils/lodashUtils'
-export * from './Utils/mog'
-export * from './Utils/niceTry'
-export * from './Utils/parseData'
-export * from './Utils/parsers'
-export * from './Utils/path'
-export * from './Utils/reminders'
-export * from './Utils/routes'
-export * from './Utils/serializer'
-export * from './Utils/strings'
-export * from './Utils/time'
-export * from './Utils/treeUtils'
+export * from './Types'
+export * from './Utils'
diff --git a/libs/shared/src/Components/FloatingElements/Autocomplete.style.tsx b/libs/shared/src/Components/FloatingElements/Autocomplete.style.tsx
new file mode 100644
index 000000000..54ea68966
--- /dev/null
+++ b/libs/shared/src/Components/FloatingElements/Autocomplete.style.tsx
@@ -0,0 +1,60 @@
+import styled from 'styled-components'
+
+import { Group } from '../../Style/Layouts'
+import { BodyFont } from '../../Style/Search'
+
+import { MenuWrapper } from './Dropdown.style'
+
+export const AutoCompleteSelector = styled.div<{ focusOnActive?: boolean }>`
+ display: flex;
+ align-items: center;
+ flex: 1;
+ :hover {
+ cursor: pointer;
+ background: ${({ theme }) => theme.tokens.surfaces.s[3]};
+ box-shadow: ${({ theme }) => theme.tokens.shadow.small};
+ }
+
+ transition: all 0.2s ease-in-out;
+ gap: ${({ theme }) => theme.spacing.small};
+ padding: ${({ theme }) => theme.spacing.small};
+ border-radius: ${({ theme }) => theme.borderRadius.small};
+`
+
+export const StyledLoading = styled.div`
+ display: flex;
+ align-items: center;
+ width: 100%;
+`
+
+export const AutoCompleteActions = styled(Group)`
+ color: ${({ theme }) => theme.tokens.text.fade};
+
+ ${BodyFont};
+
+ * {
+ color: ${({ theme }) => theme.tokens.text.fade};
+ opacity: 0.8;
+ white-space: nowrap;
+ }
+`
+
+export const AutoCompleteInput = styled.input`
+ border: none;
+ ${BodyFont}
+ outline: none;
+ background: none;
+ width: 100%;
+ color: ${({ theme }) => theme.tokens.text.default};
+
+ &:focus-visible,
+ :hover,
+ :focus {
+ border: none;
+ outline: none;
+ }
+`
+
+export const AutoCompleteSuggestions = styled(MenuWrapper)`
+ max-width: fit-content;
+`
diff --git a/libs/shared/src/Components/FloatingElements/Autocomplete.tsx b/libs/shared/src/Components/FloatingElements/Autocomplete.tsx
new file mode 100644
index 000000000..1e26e602c
--- /dev/null
+++ b/libs/shared/src/Components/FloatingElements/Autocomplete.tsx
@@ -0,0 +1,203 @@
+import React, { useEffect, useMemo, useRef, useState } from 'react'
+
+import {
+ autoUpdate,
+ flip,
+ FloatingFocusManager,
+ FloatingPortal,
+ offset,
+ size,
+ useDismiss,
+ useFloating,
+ useInteractions,
+ useListNavigation,
+ useRole
+} from '@floating-ui/react'
+
+import { fuzzySearch, MenuListItemType } from '@mexit/core'
+
+import { Loading } from '../../Style/Loading'
+import { DisplayShortcut } from '../../Style/Tooltip'
+import { IconDisplay } from '../IconDisplay'
+import { DefaultMIcons } from '../Icons'
+
+import {
+ AutoCompleteActions,
+ AutoCompleteInput,
+ AutoCompleteSelector,
+ AutoCompleteSuggestions,
+ StyledLoading
+} from './Autocomplete.style'
+import { MenuItem } from './Dropdown'
+import { MenuClassName, MenuItemClassName } from './Dropdown.classes'
+
+export const AutoComplete: React.FC<{
+ onEnter: any
+ clearOnEnter?: boolean
+ disableMenu?: boolean
+ defaultItems: Array
+ defaultValue?: string
+}> = ({ defaultItems = [], disableMenu, defaultValue, onEnter, clearOnEnter }) => {
+ const [open, setOpen] = useState(false)
+ const [isLoading, setIsLoading] = useState(false)
+ const [inputValue, setInputValue] = useState(defaultValue ?? '')
+ const [activeIndex, setActiveIndex] = useState(null)
+
+ const listRef = useRef>([])
+
+ const { x, y, strategy, refs, context } = useFloating({
+ whileElementsMounted: autoUpdate,
+ open,
+ onOpenChange: setOpen,
+ placement: 'bottom-start',
+ middleware: [
+ offset({ mainAxis: 15 }),
+ flip({ padding: 10 }),
+ size({
+ apply({ rects, availableHeight, elements }) {
+ Object.assign(elements.floating.style, {
+ width: `${rects.reference.width}px`,
+ maxHeight: `${availableHeight}px`
+ })
+ },
+ padding: 10
+ })
+ ]
+ })
+
+ const role = useRole(context, { role: 'listbox' })
+ const dismiss = useDismiss(context)
+ const listNav = useListNavigation(context, {
+ listRef,
+ activeIndex,
+ onNavigate: setActiveIndex,
+ virtual: true,
+ loop: true
+ })
+
+ const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([role, dismiss, listNav])
+
+ function onChange(event: React.ChangeEvent) {
+ const value = event.target.value
+ setInputValue(value)
+
+ if (value) {
+ setOpen(true)
+ setActiveIndex(0)
+ }
+ }
+
+ useEffect(() => {
+ setInputValue(defaultValue ?? '')
+ }, [defaultValue])
+
+ const items = useMemo(() => {
+ if (!inputValue) return defaultItems
+
+ const res = fuzzySearch(defaultItems, inputValue, (item) => item.label)
+ return res
+ }, [inputValue])
+
+ const handleOnSelect = (item: MenuListItemType) => {
+ setInputValue(item.label)
+ setIsLoading(true)
+ item.onSelect(() => {
+ setIsLoading(false)
+ setInputValue('')
+ })
+ setActiveIndex(null)
+ setOpen(false)
+ }
+
+ return (
+ <>
+
+
+ {
+ if (clearOnEnter) {
+ setInputValue('')
+ setActiveIndex(null)
+ setIsLoading(false)
+ setOpen(false)
+ }
+ })
+ }
+ }
+ }
+ }
+ })}
+ />
+
+ {isLoading ? (
+
+
+
+ ) : (
+ <>
+
+ to send
+ >
+ )}
+
+
+
+ {open && items.length > 0 && !disableMenu && (
+
+
+ {items.map((menuItem, index) => {
+ return (
+
+ )
+ })}
+
+
+ )}
+
+ >
+ )
+}
diff --git a/libs/shared/src/Components/FloatingElements/Dropdown.style.tsx b/libs/shared/src/Components/FloatingElements/Dropdown.style.tsx
index 5f1242574..e4af14417 100644
--- a/libs/shared/src/Components/FloatingElements/Dropdown.style.tsx
+++ b/libs/shared/src/Components/FloatingElements/Dropdown.style.tsx
@@ -5,6 +5,7 @@ import { generateStyle } from '@workduck-io/mex-themes'
import { GenericFlex } from '../../Style/Filter.style'
import { ScrollStyles } from '../../Style/Helpers'
import { Ellipsis } from '../../Style/NodeSelect.style'
+import { BodyFont, MainFont } from '../../Style/Search'
import { MenuItemClassName } from './Dropdown.classes'
@@ -58,9 +59,23 @@ const MenuItemStyles = css`
}
`
-export const ItemLabel = styled.div`
+export const ItemLabel = styled.div<{ fontSize?: 'small' | 'regular' }>`
${Ellipsis}
max-width: 12rem;
+
+ ${({ fontSize }) => {
+ switch (fontSize) {
+ case 'small':
+ return css`
+ ${BodyFont}
+ `
+ case 'regular':
+ default:
+ return css`
+ ${MainFont}
+ `
+ }
+ }}
`
export const RootMenuWrapper = styled.button<{ border: boolean; noHover?: boolean; noBackground?: boolean }>`
@@ -121,6 +136,12 @@ export const MenuWrapper = styled.div`
${({ theme }) => ScrollStyles(theme.tokens.surfaces.s[0])}
`
-export const MenuItemWrapper = styled.button`
+export const MenuItemWrapper = styled.button<{ isActive?: boolean }>`
${MenuItemStyles}
+
+ ${({ isActive, theme }) =>
+ isActive &&
+ css`
+ background: ${theme.tokens.surfaces.s[3]};
+ `}
`
diff --git a/libs/shared/src/Components/FloatingElements/Dropdown.tsx b/libs/shared/src/Components/FloatingElements/Dropdown.tsx
index 7375f353f..159aee2a2 100644
--- a/libs/shared/src/Components/FloatingElements/Dropdown.tsx
+++ b/libs/shared/src/Components/FloatingElements/Dropdown.tsx
@@ -58,14 +58,19 @@ export const MenuItem = forwardRef<
{
label: string
icon: MIcon
+ tabIndex?: number
+ role?: string
+ className?: string
count?: number
+ fontSize?: 'small' | 'regular'
multiSelect?: boolean
+ isActive?: boolean
selected?: boolean
disabled?: boolean
color?: string
onClick?: (e: React.MouseEvent) => void
}
->(({ label, disabled, count, color, icon, multiSelect, selected, ...props }, ref) => {
+>(({ label, disabled, count, color, fontSize, icon, multiSelect, selected, ...props }, ref) => {
return (
@@ -75,7 +80,7 @@ export const MenuItem = forwardRef<
)}
- {label}
+ {label}
{count && {count}}
@@ -199,7 +204,7 @@ export const MenuComponent = forwardRef void
- // isHidden: boolean
- // setIsHidden: (isHidden: boolean) => void
- // isFocused: boolean
- // setIsFocused: (isFocused: boolean) => void
toolbarState: ToolbarState
setToolbarState: (state: ToolbarState) => void
}
@@ -35,10 +31,6 @@ interface BalloonToolbarStore {
export const useBalloonToolbarStore = create((set, get) => ({
open: false,
setOpen: (open) => set({ open }),
- // isHidden: true,
- // setIsHidden: (isHidden) => set({ isHidden }),
- // isFocused: false,
- // setIsFocused: (isFocused) => set({ isFocused }),
toolbarState: 'normal',
setToolbarState: (state) => set({ toolbarState: state })
}))
diff --git a/libs/shared/src/Style/BalloonToolbar.styles.ts b/libs/shared/src/Style/BalloonToolbar.styles.ts
index 2db662a41..9d8963ac0 100644
--- a/libs/shared/src/Style/BalloonToolbar.styles.ts
+++ b/libs/shared/src/Style/BalloonToolbar.styles.ts
@@ -119,13 +119,13 @@ export const BalloonToolbarBase = styled(ToolbarBase)`
position: absolute;
white-space: nowrap;
- opacity: 100;
- transition: opacity 0.2s ease-in-out;
+ opacity: 1;
+ transition: width 0.5s ease, opacity 0.2s ease-in-out;
color: ${({ theme }) => theme.tokens.text.default};
${({ theme }) => generateStyle(theme.editor.toolbar.balloonToolbar.wrapper)}
z-index: 500;
border: 1px solid transparent;
- border-radius: ${({ theme }) => theme.borderRadius.tiny};
+ border-radius: ${({ theme }) => theme.borderRadius.small};
padding: ${({ theme }) => theme.spacing.tiny};
box-shadow: ${({ theme }) => theme.tokens.shadow.medium};
@@ -142,8 +142,10 @@ export const BalloonToolbarBase = styled(ToolbarBase)`
}
.slate-ToolbarButton-active {
- color: ${({ theme }) => theme.tokens.colors.primary.default};
- background: rgba(${({ theme }) => theme.rgbTokens.colors.primary.default}, 0.1);
+ svg {
+ color: ${({ theme }) => theme.tokens.colors.primary.default};
+ }
+ background: rgba(${({ theme }) => theme.rgbTokens.colors.primary.default}, 0.2);
}
`
@@ -151,10 +153,9 @@ export const BalloonToolbarInputWrapper = styled.div`
display: flex;
align-items: center;
gap: ${({ theme }) => theme.spacing.small};
- padding: ${({ theme }) => theme.spacing.tiny};
svg {
- width: 1.2rem;
- height: 1.2rem;
+ width: 1rem;
+ height: 1rem;
}
`
diff --git a/libs/shared/src/Style/Editor.tsx b/libs/shared/src/Style/Editor.tsx
index 5a48468bf..7aae7cfff 100644
--- a/libs/shared/src/Style/Editor.tsx
+++ b/libs/shared/src/Style/Editor.tsx
@@ -420,6 +420,11 @@ export const EditorStyles = styled.div<{ readOnly?: boolean; withShadow?: boolea
/* Slate Code Block */
+ .highlight {
+ color: ${({ theme }) => theme.tokens.text.heading};
+ background: ${({ theme }) => `rgba(${theme.rgbTokens.colors.primary.default}, 0.4)`};
+ }
+
.slate-code_block {
select {
font-size: 0.8rem;
diff --git a/libs/shared/src/Style/Mentions.tsx b/libs/shared/src/Style/Mentions.tsx
index 63f2f510b..8b99b6b4f 100644
--- a/libs/shared/src/Style/Mentions.tsx
+++ b/libs/shared/src/Style/Mentions.tsx
@@ -3,8 +3,7 @@ import styled, { css } from 'styled-components'
import { CardShadow } from './Helpers'
import { BodyFont } from './Search'
-export const SMentionRoot = styled.div<{ type?: 'mentionable' | 'invite' | 'self' }>`
- display: inline-block;
+export const SMentionRoot = styled.span<{ type?: 'mentionable' | 'invite' | 'self' }>`
line-height: 1.2;
background-color: ${({ theme }) => theme.tokens.surfaces.s[2]};
border-radius: ${({ theme }) => theme.borderRadius.tiny};
diff --git a/libs/shared/src/Style/QuickLinkElement.styles.ts b/libs/shared/src/Style/QuickLinkElement.styles.ts
index 51182affa..cd335e179 100644
--- a/libs/shared/src/Style/QuickLinkElement.styles.ts
+++ b/libs/shared/src/Style/QuickLinkElement.styles.ts
@@ -1,8 +1,7 @@
import { Icon } from '@iconify/react'
import styled, { css } from 'styled-components'
-export const SILinkRoot = styled.div`
- display: inline-block;
+export const SILinkRoot = styled.span`
line-height: 1.2;
vertical-align: middle;
`
@@ -16,7 +15,7 @@ export const StyledIcon = styled(Icon)`
margin-right: 4px;
`
-export const SILink = styled.div`
+export const SILink = styled.span`
display: inline-flex;
justify-content: center;
align-items: center;
diff --git a/libs/shared/src/Style/TagElement.styles.tsx b/libs/shared/src/Style/TagElement.styles.tsx
index d60af301c..0ad99f937 100644
--- a/libs/shared/src/Style/TagElement.styles.tsx
+++ b/libs/shared/src/Style/TagElement.styles.tsx
@@ -1,14 +1,13 @@
import { transparentize } from 'polished'
import styled, { css } from 'styled-components'
-export const STagRoot = styled.div`
- display: inline-block;
+export const STagRoot = styled.span`
line-height: 1.2;
/* outline: selectedFocused ? rgb(0, 120, 212) auto 1px : undefined, */
`
-export const STag = styled.div<{ selected: boolean }>`
+export const STag = styled.span<{ selected: boolean }>`
color: ${({ theme }) => theme.colors.secondary};
${({ selected, theme }) =>
selected &&
diff --git a/yarn.lock b/yarn.lock
index ad12b8272..31bcff51e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1962,6 +1962,15 @@
dependencies:
"@floating-ui/core" "^1.2.4"
+"@floating-ui/react-dom-interactions@^0.13.3":
+ version "0.13.3"
+ resolved "https://registry.yarnpkg.com/@floating-ui/react-dom-interactions/-/react-dom-interactions-0.13.3.tgz#6c49dda9e16fff64d188603c1efc139588ce925d"
+ integrity sha512-AnCW06eIZxzD/Hl1Qbi2JkQRU5KpY7Dn81k3xRfbvs+HylhB+t3x88/GNKLK39mMTlJ/ylxm5prUpiLrTWvifQ==
+ dependencies:
+ "@floating-ui/react-dom" "^1.0.1"
+ aria-hidden "^1.1.3"
+ tabbable "^6.0.1"
+
"@floating-ui/react-dom-interactions@^0.6.6":
version "0.6.6"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom-interactions/-/react-dom-interactions-0.6.6.tgz#8542e8c4bcbee2cd0d512de676c6a493e0a2d168"
@@ -1971,14 +1980,6 @@
aria-hidden "^1.1.3"
use-isomorphic-layout-effect "^1.1.1"
-"@floating-ui/react-dom-interactions@^0.9.3":
- version "0.9.3"
- resolved "https://registry.yarnpkg.com/@floating-ui/react-dom-interactions/-/react-dom-interactions-0.9.3.tgz#4d4d81664066ac36980e50691aa90b1d40667949"
- integrity sha512-oHwFLxySRtmhgwg7ZdWswvDDi+ld4mEtxu6ngOd7mRC5L1Rk6adjSfOBOHDxea+ItAWmds8m6A725sn1HQtUyQ==
- dependencies:
- "@floating-ui/react-dom" "^1.0.0"
- aria-hidden "^1.1.3"
-
"@floating-ui/react-dom@^0.7.2":
version "0.7.2"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-0.7.2.tgz#0bf4ceccb777a140fc535c87eb5d6241c8e89864"
@@ -1987,19 +1988,19 @@
"@floating-ui/dom" "^0.5.3"
use-isomorphic-layout-effect "^1.1.1"
-"@floating-ui/react-dom@^1.0.0", "@floating-ui/react-dom@^1.2.1":
+"@floating-ui/react-dom@^1.0.1", "@floating-ui/react-dom@^1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-1.3.0.tgz#4d35d416eb19811c2b0e9271100a6aa18c1579b3"
integrity sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==
dependencies:
"@floating-ui/dom" "^1.2.1"
-"@floating-ui/react@^0.18.0":
- version "0.18.1"
- resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.18.1.tgz#3a5ea19d22239f6c8d0250121488969680435473"
- integrity sha512-Uqntjem19/3ghAwKSaMU/719P/riiox13rkAzMhCthmAAhTzvvmNka52L5s9Gi/cjAfNNR5/RjkD0YKqTecWzQ==
+"@floating-ui/react@^0.22.2":
+ version "0.22.2"
+ resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.22.2.tgz#57d3e32dd82790be5dfcf1a42a44b4a3d0366ca1"
+ integrity sha512-7u5JqNfcbUCY9WNGJvcbaoChTx5fbFlW2Mpo/6B5DzB+pPWRBbFknALRUTcXj599Sm7vCZ2HdJS9hID22QKriQ==
dependencies:
- "@floating-ui/react-dom" "^1.2.1"
+ "@floating-ui/react-dom" "^1.3.0"
aria-hidden "^1.1.3"
tabbable "^6.0.1"