WIP model
This commit is contained in:
@@ -143,6 +143,7 @@
|
|||||||
"expo-updates": "~0.25.14",
|
"expo-updates": "~0.25.14",
|
||||||
"expo-web-browser": "~13.0.3",
|
"expo-web-browser": "~13.0.3",
|
||||||
"fast-text-encoding": "^1.0.6",
|
"fast-text-encoding": "^1.0.6",
|
||||||
|
"fuse.js": "^7.0.0",
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
"hls.js": "^1.5.11",
|
"hls.js": "^1.5.11",
|
||||||
"js-sha256": "^0.9.0",
|
"js-sha256": "^0.9.0",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {MMKV} from 'react-native-mmkv'
|
import {MMKV} from 'react-native-mmkv'
|
||||||
|
|
||||||
import {IS_DEV} from '#/env'
|
import {IS_DEV} from '#/env'
|
||||||
import {Device} from '#/storage/schema'
|
import {Account} from '#/storage/schema'
|
||||||
|
|
||||||
export * from '#/storage/schema'
|
export * from '#/storage/schema'
|
||||||
|
|
||||||
@@ -74,6 +74,13 @@ export class Storage<Scopes extends unknown[], Schema> {
|
|||||||
*/
|
*/
|
||||||
export const device = new Storage<[], Device>({id: 'bsky_device'})
|
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') {
|
if (IS_DEV && typeof window !== 'undefined') {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.bsky_storage = {
|
window.bsky_storage = {
|
||||||
|
|||||||
@@ -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
|
* Device data that's specific to the device and does not vary based account
|
||||||
*/
|
*/
|
||||||
@@ -9,3 +11,10 @@ export type Device = {
|
|||||||
countryCode: string | undefined
|
countryCode: string | undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account data that's specific to the current account
|
||||||
|
*/
|
||||||
|
export type Account = {
|
||||||
|
recentTags: Result[] | undefined
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,15 +13,17 @@ import {Text as TiptapText} from '@tiptap/extension-text'
|
|||||||
import {generateJSON} from '@tiptap/html'
|
import {generateJSON} from '@tiptap/html'
|
||||||
import {EditorContent, JSONContent, useEditor} from '@tiptap/react'
|
import {EditorContent, JSONContent, useEditor} from '@tiptap/react'
|
||||||
|
|
||||||
|
import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
|
||||||
import {usePalette} from '#/lib/hooks/usePalette'
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
|
import {blobToDataUri, isUriImage} from '#/lib/media/util'
|
||||||
import {useActorAutocompleteFn} from '#/state/queries/actor-autocomplete'
|
import {useActorAutocompleteFn} from '#/state/queries/actor-autocomplete'
|
||||||
import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
|
import {useSession} from '#/state/session'
|
||||||
import {blobToDataUri, isUriImage} from 'lib/media/util'
|
import {tagAutocompleteModel} from '#/view/com/composer/text-input/tagsAutocompleteState'
|
||||||
import {textInputWebEmitter} from '#/view/com/composer/text-input/textInputWebEmitter'
|
|
||||||
import {
|
import {
|
||||||
LinkFacetMatch,
|
LinkFacetMatch,
|
||||||
suggestLinkCardUri,
|
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 {atoms as a, useAlf} from '#/alf'
|
||||||
import {Portal} from '#/components/Portal'
|
import {Portal} from '#/components/Portal'
|
||||||
import {normalizeTextStyles} from '#/components/Typography'
|
import {normalizeTextStyles} from '#/components/Typography'
|
||||||
@@ -29,8 +31,7 @@ import {Text} from '../../util/text/Text'
|
|||||||
import {createSuggestion} from './web/Autocomplete'
|
import {createSuggestion} from './web/Autocomplete'
|
||||||
import {Emoji} from './web/EmojiPicker.web'
|
import {Emoji} from './web/EmojiPicker.web'
|
||||||
import {LinkDecorator} from './web/LinkDecorator'
|
import {LinkDecorator} from './web/LinkDecorator'
|
||||||
import {TagDecorator} from './web/TagDecorator'
|
import {createTagsAutocomplete,Tags} from './web/Tags'
|
||||||
import {Tags, createTagsAutocomplete} from './web/Tags'
|
|
||||||
|
|
||||||
export interface TextInputRef {
|
export interface TextInputRef {
|
||||||
focus: () => void
|
focus: () => void
|
||||||
@@ -65,6 +66,7 @@ export const TextInput = React.forwardRef(function TextInputImpl(
|
|||||||
const autocomplete = useActorAutocompleteFn()
|
const autocomplete = useActorAutocompleteFn()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const modeClass = useColorSchemeStyle('ProseMirror-light', 'ProseMirror-dark')
|
const modeClass = useColorSchemeStyle('ProseMirror-light', 'ProseMirror-dark')
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
|
||||||
const [isDropping, setIsDropping] = React.useState(false)
|
const [isDropping, setIsDropping] = React.useState(false)
|
||||||
|
|
||||||
@@ -78,6 +80,7 @@ export const TextInput = React.forwardRef(function TextInputImpl(
|
|||||||
class: 'inline-tag',
|
class: 'inline-tag',
|
||||||
},
|
},
|
||||||
suggestion: createTagsAutocomplete({
|
suggestion: createTagsAutocomplete({
|
||||||
|
model: tagAutocompleteModel({currentDid: currentAccount?.did!}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
Mention.configure({
|
Mention.configure({
|
||||||
@@ -94,7 +97,7 @@ export const TextInput = React.forwardRef(function TextInputImpl(
|
|||||||
History,
|
History,
|
||||||
Hardbreak,
|
Hardbreak,
|
||||||
],
|
],
|
||||||
[autocomplete, placeholder],
|
[autocomplete, placeholder, currentAccount],
|
||||||
)
|
)
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import {account} from '#/storage'
|
||||||
|
|
||||||
|
export type Result = {
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Model = {
|
||||||
|
search(query: string): Promise<Result[]>
|
||||||
|
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)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,28 +9,32 @@ import {
|
|||||||
import tippy, {Instance as TippyInstance} from 'tippy.js'
|
import tippy, {Instance as TippyInstance} from 'tippy.js'
|
||||||
|
|
||||||
// import {TagsAutocompleteModel} from 'state/models/ui/tags-autocomplete'
|
// import {TagsAutocompleteModel} from 'state/models/ui/tags-autocomplete'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
import {Text} from 'view/com/util/text/Text'
|
import {
|
||||||
|
Model,
|
||||||
|
Result,
|
||||||
|
} from '#/view/com/composer/text-input/tagsAutocompleteState'
|
||||||
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
import {parsePunctuationFromTag} from './utils'
|
import {parsePunctuationFromTag} from './utils'
|
||||||
|
|
||||||
type AutocompleteResult = string
|
type ListProps = SuggestionProps<Result> & {
|
||||||
type ListProps = SuggestionProps<AutocompleteResult> & {
|
model: Model
|
||||||
// autocompleteModel: TagsAutocompleteModel
|
|
||||||
}
|
}
|
||||||
type AutocompleteRef = {
|
type AutocompleteRef = {
|
||||||
onKeyDown: (props: SuggestionKeyDownProps) => boolean
|
onKeyDown: (props: SuggestionKeyDownProps) => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTagsAutocomplete({}: // autocompleteModel,
|
export function createTagsAutocomplete({
|
||||||
{
|
model,
|
||||||
// autocompleteModel: TagsAutocompleteModel
|
}: {
|
||||||
|
model: Model
|
||||||
}): Omit<SuggestionOptions, 'editor'> {
|
}): Omit<SuggestionOptions, 'editor'> {
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
* This `query` param comes from the result of `findSuggestionMatch`
|
* This `query` param comes from the result of `findSuggestionMatch`
|
||||||
*/
|
*/
|
||||||
async items({query}) {
|
async items({query}) {
|
||||||
return [query, 'javascript']
|
return await model.search(query)
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
let component: ReactRenderer<AutocompleteRef> | undefined
|
let component: ReactRenderer<AutocompleteRef> | undefined
|
||||||
@@ -41,6 +45,7 @@ export function createTagsAutocomplete({}: // autocompleteModel,
|
|||||||
component = new ReactRenderer(Autocomplete, {
|
component = new ReactRenderer(Autocomplete, {
|
||||||
props: {
|
props: {
|
||||||
...props,
|
...props,
|
||||||
|
model,
|
||||||
},
|
},
|
||||||
editor: props.editor,
|
editor: props.editor,
|
||||||
})
|
})
|
||||||
@@ -113,7 +118,7 @@ const Autocomplete = forwardRef<AutocompleteRef, ListProps>(
|
|||||||
const selectItem = React.useCallback(
|
const selectItem = React.useCallback(
|
||||||
(index: number) => {
|
(index: number) => {
|
||||||
const item = items[index]
|
const item = items[index]
|
||||||
if (item) commit(item)
|
if (item) commit(item.value)
|
||||||
},
|
},
|
||||||
[items, commit],
|
[items, commit],
|
||||||
)
|
)
|
||||||
@@ -157,7 +162,8 @@ const Autocomplete = forwardRef<AutocompleteRef, ListProps>(
|
|||||||
return (
|
return (
|
||||||
<div className="items">
|
<div className="items">
|
||||||
<View style={[pal.borderDark, pal.view, styles.container]}>
|
<View style={[pal.borderDark, pal.view, styles.container]}>
|
||||||
{items.map((tag, index) => {
|
{items.map(({value}, index) => {
|
||||||
|
const {tag} = parsePunctuationFromTag(value)
|
||||||
const isSelected = selectedIndex === index
|
const isSelected = selectedIndex === index
|
||||||
const isFirst = index === 0
|
const isFirst = index === 0
|
||||||
const isLast = index === items.length - 1
|
const isLast = index === items.length - 1
|
||||||
|
|||||||
@@ -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"
|
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
|
||||||
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
|
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:
|
gensync@^1.0.0-beta.2:
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
|
||||||
|
|||||||
Reference in New Issue
Block a user