Skip to content

Commit

Permalink
New Add form with suggested collections
Browse files Browse the repository at this point in the history
  • Loading branch information
Rustem Mussabekov committed May 6, 2024
1 parent cf37500 commit 70ad621
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 36 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "app",
"version": "5.6.26",
"version": "5.6.27",
"description": "",
"author": "",
"license": "ISC",
Expand Down
178 changes: 148 additions & 30 deletions src/co/bookmarks/add/fallback/link.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,157 @@
import React, { useCallback, useState, useRef } from 'react'
import { useDispatch } from 'react-redux'
import s from './link.module.css'
import React, { useCallback, useState, useRef, useEffect, useMemo } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import t from '~t'
import links from '~config/links'
import useDebounce from '~modules/format/callback/use-debounce'
import isURL from 'validator/es/lib/isURL'
import { oneCreate } from '~data/actions/bookmarks'
import { suggestFields } from '~data/actions/bookmarks'
import { makeSuggestedFields } from '~data/selectors/bookmarks'
import { makeCollectionPath } from '~data/selectors/collections'
import { isPro } from '~data/selectors/user'

import { Error } from '~co/overlay/dialog'
import Popover from '~co/overlay/popover'
import { Layout } from '~co/common/form'
import Button from '~co/common/button'
import Icon from '~co/common/icon'
import PickerLink from '~co/picker/link'
import ItemLink from '~co/bookmarks/edit/form/link'

export default function BookmarksAddFallbackLink({ spaceId, autoFocus, onEdit }) {
function Suggestion({ id, primary, disabled, onClick }) {
const getCollectionPath = useMemo(()=>makeCollectionPath(), [])
const path = useSelector(state=>getCollectionPath(state, id, { self: true }))
const shortPath = useMemo(()=>path.map((p)=>p.title).slice(-2).join(' / '), [path])
const fullPath = useMemo(()=>path.map((p)=>p.title).join(' / '), [path])
const collection = useMemo(()=>path?.[path.length-1], [path])
const onSelfClick = useCallback(e=>onClick(parseInt(e.currentTarget.getAttribute('data-id'))), [onClick])

if (!collection?.title)
return null

return (
<Button
data-id={id}
variant={primary ? 'primary' : 'dotted'}
disabled={disabled}
data-shape='pill'
tabIndex='-1'
title={fullPath}
className={s.suggestion}
onClick={onSelfClick}>
{primary && `${t.s('add')} ${t.s('to')}`} {shortPath}
</Button>
)
}

function Suggestions({ item, loading, onClick }) {
const dispatch = useDispatch()

//load suggestions
const debounced = useDebounce(item, 300)
useEffect(()=>dispatch(suggestFields(debounced)), [debounced.link])

//get suggestions
const enabled = useSelector(state=>state.config.ai_suggestions)
const pro = useSelector(state=>isPro(state))
const getSuggestedFields = useMemo(()=>makeSuggestedFields(), [])
const { collections=[] } = useSelector(state=>getSuggestedFields(state, item))

return (
<div className={s.suggestions}>
<Suggestion
id={item.collectionId}
disabled={loading || !item.link}
primary={true}
onClick={onClick} />

{enabled && pro && collections.length ? (<>
<span className={s.or}>{t.s('or')} {t.s('to')}</span>

{collections.map(id=>(
<Suggestion
key={id}
id={id}
disabled={loading}
onClick={onClick} />
))}
</>) : null}
</div>
)
}

function AddForm({ spaceId, onEdit, pin, onClose }) {
const dispatch = useDispatch()

const [item, setItem] = useState(()=>({ collectionId: parseInt(spaceId)||-1 }))
const [loading, setLoading] = useState(true)

const onChangeField = useCallback((changed)=>
setItem(item=>({...item, ...changed})),
[setItem]
)

const onAddTo = useCallback((collectionId)=>{
setLoading(true)

dispatch(oneCreate({ ...item, collectionId }, (items)=>{
onEdit && onEdit(items)
onClose()
}, e=>{
Error(e)
setLoading(false)
}))
}, [item, onEdit, setLoading, onClose, dispatch])

const onSubmitForm = useCallback(e=>{
e.preventDefault()
e.stopPropagation()
onAddTo(item.collectionId)
}, [onAddTo, item.collectionId])

//on load
useEffect(()=>{
async function getLink() {
//in safari readText works bad, so this line prevents run on safari
await navigator.permissions.query({name: 'clipboard-read'})
const link = await navigator.clipboard.readText()
if (isURL(link, { require_protocol: true })) return link
}

getLink()
.then(link=>onChangeField({ link }))
.finally(()=>setLoading(false))
}, [setLoading, onChangeField])

return (
<Popover
pin={pin}
closable={true}
className={s.modal}
onClose={onClose}>
<form onSubmit={onSubmitForm}>
<Layout>
<ItemLink
key={loading}
autoFocus={loading ? '' : 'link'}
disabled={loading}
selectAll={true}
item={item}
onChange={onChangeField} />

<Suggestions
item={item}
loading={loading}
onClick={onAddTo} />
</Layout>
</form>
</Popover>
)
}

export default function BookmarksAddFallbackLink({ spaceId, autoFocus, onEdit }) {
const [show, setShow] = useState(false)
const button = useRef(null)

const onLink = useCallback(link=>(
new Promise((res, rej)=>{
dispatch(
oneCreate({
collectionId: spaceId,
link
}, (items)=>{
onEdit && onEdit(items)
res(items)
}, rej)
)
})
), [spaceId, onEdit])

return (
<>
<Button
Expand All @@ -40,18 +165,11 @@ export default function BookmarksAddFallbackLink({ spaceId, autoFocus, onEdit })
</Button>

{show && (
<PickerLink
<AddForm
spaceId={spaceId}
onEdit={onEdit}
pin={button}
onClose={()=>setShow(false)}
buttons={
<Button
href={links.help['add-bookmark']}
target='_blank'>
<Icon name='help' />
{t.s('help')}
</Button>
}
onLink={onLink} />
onClose={()=>setShow(false)} />
)}
</>
)
Expand Down
15 changes: 15 additions & 0 deletions src/co/bookmarks/add/fallback/link.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.modal {
width: 11rem
}

.suggestions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--padding-small);
padding-bottom: var(--padding-small)
}

.or {
color: var(--secondary-text-color)
}
3 changes: 2 additions & 1 deletion src/co/bookmarks/edit/form/collection/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ export default function BookmarkEditFormCollection({ item, onChange, onSave }) {
const pickerEvents = useMemo(()=>({
onItemClick: ({ _id })=>{
onChange({ collectionId: _id })
onSave()
if (typeof onSave == 'function')
onSave()
onPickerClose()
}
}), [onChange, onSave, onPickerClose])
Expand Down
14 changes: 12 additions & 2 deletions src/co/bookmarks/edit/form/collection/suggested.module.styl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

&:empty {
opacity: 0
transform: translateY(5px)
transform: translateY(2px)

&[data-is-new='false'] {
min-height: 0
Expand All @@ -28,7 +28,7 @@
}

.path {
max-width: 150px
max-width: 15ch
overflow: hidden
overflow: clip
direction: rtl
Expand All @@ -44,4 +44,14 @@
&[data-tint]:not([data-tint='']) {
background: unquote('color-mix(in srgb, var(--tint) 12%, transparent)')
}
}

@media screen and (min-width: 600px) {
.path {
max-width: 30ch
}

.suggested {
max-height: 100% !important
}
}
3 changes: 2 additions & 1 deletion src/co/bookmarks/edit/form/link.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useState, useCallback } from 'react'

import { Text, Label } from '~co/common/form'

export default function BookmarkEditFormLink({ autoFocus, item: { link, fileType }, onCommit, onChange }) {
export default function BookmarkEditFormLink({ autoFocus, selectAll, item: { link, fileType }, onCommit, onChange }) {
const [maxRows, setMaxRows] = useState(1)

const onChangeField = useCallback(e=>
Expand All @@ -29,6 +29,7 @@ export default function BookmarkEditFormLink({ autoFocus, item: { link, fileType
placeholder='https://'
value={link}
autoSize={true}
selectAll={selectAll}
maxRows={maxRows}
onChange={onChangeField}
onFocus={onFocusField}
Expand Down
2 changes: 1 addition & 1 deletion src/co/common/button/index.module.styl
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@
.button[data-variant='dotted'] {
background: var(--background-color)
color: var(--primary-text-color)
border: var(--shadow-height) dashed unquote('color-mix(in srgb, var(--tint, var(--secondary-text-color)) 60%, transparent)')
border: var(--shadow-height) dashed unquote('color-mix(in srgb, var(--tint, var(--secondary-text-color)) 65%, transparent)')

&[disabled] {
box-shadow: none
Expand Down
20 changes: 20 additions & 0 deletions src/modules/format/callback/use-debounce.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React, { useState, useEffect } from 'react';

// Наш хук
export default function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);

useEffect(
() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
},
[value]
);

return debouncedValue;
}

0 comments on commit 70ad621

Please sign in to comment.