From f43d2ebefadc406e44247ffea85601cfb2be1bdd Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 8 Oct 2024 13:24:04 -0500 Subject: [PATCH] WIP model --- package.json | 1 + src/storage/index.ts | 9 +++++- src/storage/schema.ts | 9 ++++++ .../com/composer/text-input/TextInput.web.tsx | 17 +++++----- .../text-input/tagsAutocompleteState.ts | 31 +++++++++++++++++++ .../com/composer/text-input/web/Tags/view.tsx | 28 ++++++++++------- yarn.lock | 5 +++ 7 files changed, 81 insertions(+), 19 deletions(-) create mode 100644 src/view/com/composer/text-input/tagsAutocompleteState.ts diff --git a/package.json b/package.json index 37f5fe272e..607bbcc130 100644 --- a/package.json +++ b/package.json @@ -143,6 +143,7 @@ "expo-updates": "~0.25.14", "expo-web-browser": "~13.0.3", "fast-text-encoding": "^1.0.6", + "fuse.js": "^7.0.0", "history": "^5.3.0", "hls.js": "^1.5.11", "js-sha256": "^0.9.0", diff --git a/src/storage/index.ts b/src/storage/index.ts index 7ef226d3aa..bc98341d4c 100644 --- a/src/storage/index.ts +++ b/src/storage/index.ts @@ -1,7 +1,7 @@ import {MMKV} from 'react-native-mmkv' import {IS_DEV} from '#/env' -import {Device} from '#/storage/schema' +import {Account} from '#/storage/schema' export * from '#/storage/schema' @@ -74,6 +74,13 @@ export class Storage { */ export const device = new Storage<[], Device>({id: 'bsky_device'}) +/** + * Account data that's specific to the account + * + * `account.set([did, key], true)` + */ +export const account = new Storage<[string], Account>({id: 'bsky_account'}) + if (IS_DEV && typeof window !== 'undefined') { // @ts-ignore window.bsky_storage = { diff --git a/src/storage/schema.ts b/src/storage/schema.ts index cf410c77de..69a379ae17 100644 --- a/src/storage/schema.ts +++ b/src/storage/schema.ts @@ -1,3 +1,5 @@ +import {Result} from '#/view/com/composer/text-input/tagsAutocompleteState' + /** * Device data that's specific to the device and does not vary based account */ @@ -9,3 +11,10 @@ export type Device = { countryCode: string | undefined } } + +/** + * Account data that's specific to the current account + */ +export type Account = { + recentTags: Result[] | undefined +} diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index 73fdbde941..118d5d8c10 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -13,15 +13,17 @@ import {Text as TiptapText} from '@tiptap/extension-text' import {generateJSON} from '@tiptap/html' import {EditorContent, JSONContent, useEditor} from '@tiptap/react' +import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle' import {usePalette} from '#/lib/hooks/usePalette' +import {blobToDataUri, isUriImage} from '#/lib/media/util' import {useActorAutocompleteFn} from '#/state/queries/actor-autocomplete' -import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' -import {blobToDataUri, isUriImage} from 'lib/media/util' -import {textInputWebEmitter} from '#/view/com/composer/text-input/textInputWebEmitter' +import {useSession} from '#/state/session' +import {tagAutocompleteModel} from '#/view/com/composer/text-input/tagsAutocompleteState' import { LinkFacetMatch, suggestLinkCardUri, -} from 'view/com/composer/text-input/text-input-util' +} from '#/view/com/composer/text-input/text-input-util' +import {textInputWebEmitter} from '#/view/com/composer/text-input/textInputWebEmitter' import {atoms as a, useAlf} from '#/alf' import {Portal} from '#/components/Portal' import {normalizeTextStyles} from '#/components/Typography' @@ -29,8 +31,7 @@ import {Text} from '../../util/text/Text' import {createSuggestion} from './web/Autocomplete' import {Emoji} from './web/EmojiPicker.web' import {LinkDecorator} from './web/LinkDecorator' -import {TagDecorator} from './web/TagDecorator' -import {Tags, createTagsAutocomplete} from './web/Tags' +import {createTagsAutocomplete,Tags} from './web/Tags' export interface TextInputRef { focus: () => void @@ -65,6 +66,7 @@ export const TextInput = React.forwardRef(function TextInputImpl( const autocomplete = useActorAutocompleteFn() const pal = usePalette('default') const modeClass = useColorSchemeStyle('ProseMirror-light', 'ProseMirror-dark') + const {currentAccount} = useSession() const [isDropping, setIsDropping] = React.useState(false) @@ -78,6 +80,7 @@ export const TextInput = React.forwardRef(function TextInputImpl( class: 'inline-tag', }, suggestion: createTagsAutocomplete({ + model: tagAutocompleteModel({currentDid: currentAccount?.did!}), }), }), Mention.configure({ @@ -94,7 +97,7 @@ export const TextInput = React.forwardRef(function TextInputImpl( History, Hardbreak, ], - [autocomplete, placeholder], + [autocomplete, placeholder, currentAccount], ) React.useEffect(() => { diff --git a/src/view/com/composer/text-input/tagsAutocompleteState.ts b/src/view/com/composer/text-input/tagsAutocompleteState.ts new file mode 100644 index 0000000000..e7f78238e3 --- /dev/null +++ b/src/view/com/composer/text-input/tagsAutocompleteState.ts @@ -0,0 +1,31 @@ +import {account} from '#/storage' + +export type Result = { + value: string +} + +export type Model = { + search(query: string): Promise + save(tag: string): void +} + +export function tagAutocompleteModel({ + currentDid, +}: { + currentDid: string +}): Model { + let recentTags = account.get([currentDid, 'recentTags']) || [] + + return { + async search(query: string) { + if (!query) return [{value: query}] + return [{value: query}] + }, + save(tag: string) { + recentTags = [ + {value: tag}, + ...recentTags.filter(t => t.value !== tag), + ].slice(0, 40) + }, + } +} diff --git a/src/view/com/composer/text-input/web/Tags/view.tsx b/src/view/com/composer/text-input/web/Tags/view.tsx index 6a1dd2d0f0..960ff9c909 100644 --- a/src/view/com/composer/text-input/web/Tags/view.tsx +++ b/src/view/com/composer/text-input/web/Tags/view.tsx @@ -9,28 +9,32 @@ import { import tippy, {Instance as TippyInstance} from 'tippy.js' // import {TagsAutocompleteModel} from 'state/models/ui/tags-autocomplete' -import {usePalette} from 'lib/hooks/usePalette' -import {Text} from 'view/com/util/text/Text' +import {usePalette} from '#/lib/hooks/usePalette' +import { + Model, + Result, +} from '#/view/com/composer/text-input/tagsAutocompleteState' +import {Text} from '#/view/com/util/text/Text' import {parsePunctuationFromTag} from './utils' -type AutocompleteResult = string -type ListProps = SuggestionProps & { - // autocompleteModel: TagsAutocompleteModel +type ListProps = SuggestionProps & { + model: Model } type AutocompleteRef = { onKeyDown: (props: SuggestionKeyDownProps) => boolean } -export function createTagsAutocomplete({}: // autocompleteModel, -{ - // autocompleteModel: TagsAutocompleteModel +export function createTagsAutocomplete({ + model, +}: { + model: Model }): Omit { return { /** * This `query` param comes from the result of `findSuggestionMatch` */ async items({query}) { - return [query, 'javascript'] + return await model.search(query) }, render() { let component: ReactRenderer | undefined @@ -41,6 +45,7 @@ export function createTagsAutocomplete({}: // autocompleteModel, component = new ReactRenderer(Autocomplete, { props: { ...props, + model, }, editor: props.editor, }) @@ -113,7 +118,7 @@ const Autocomplete = forwardRef( const selectItem = React.useCallback( (index: number) => { const item = items[index] - if (item) commit(item) + if (item) commit(item.value) }, [items, commit], ) @@ -157,7 +162,8 @@ const Autocomplete = forwardRef( return (
- {items.map((tag, index) => { + {items.map(({value}, index) => { + const {tag} = parsePunctuationFromTag(value) const isSelected = selectedIndex === index const isFirst = index === 0 const isLast = index === items.length - 1 diff --git a/yarn.lock b/yarn.lock index e38b5a7ccd..e448571571 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12153,6 +12153,11 @@ functions-have-names@^1.2.2, functions-have-names@^1.2.3: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +fuse.js@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-7.0.0.tgz#6573c9fcd4c8268e403b4fc7d7131ffcf99a9eb2" + integrity sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q== + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"