diff --git a/package.json b/package.json index 67b8f87e..7ddd359b 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "app", - "version": "5.6.26", + "version": "5.6.27", "description": "", "author": "", "license": "ISC", diff --git a/src/co/bookmarks/add/fallback/link.js b/src/co/bookmarks/add/fallback/link.js index 6f12d026..79c303ad 100644 --- a/src/co/bookmarks/add/fallback/link.js +++ b/src/co/bookmarks/add/fallback/link.js @@ -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 ( + + ) +} + +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 ( +
+ + + {enabled && pro && collections.length ? (<> + {t.s('or')} {t.s('to')} + + {collections.map(id=>( + + ))} + ) : null} +
+ ) +} + +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 ( + +
+ + + + + +
+
+ ) +} + +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 ( <> - } - onLink={onLink} /> + onClose={()=>setShow(false)} /> )} ) diff --git a/src/co/bookmarks/add/fallback/link.module.css b/src/co/bookmarks/add/fallback/link.module.css new file mode 100644 index 00000000..74d022dd --- /dev/null +++ b/src/co/bookmarks/add/fallback/link.module.css @@ -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) +} \ No newline at end of file diff --git a/src/co/bookmarks/edit/form/collection/index.js b/src/co/bookmarks/edit/form/collection/index.js index 806ca6c3..fca0834b 100644 --- a/src/co/bookmarks/edit/form/collection/index.js +++ b/src/co/bookmarks/edit/form/collection/index.js @@ -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]) diff --git a/src/co/bookmarks/edit/form/collection/suggested.module.styl b/src/co/bookmarks/edit/form/collection/suggested.module.styl index 24c86d24..71aaecaa 100644 --- a/src/co/bookmarks/edit/form/collection/suggested.module.styl +++ b/src/co/bookmarks/edit/form/collection/suggested.module.styl @@ -13,7 +13,7 @@ &:empty { opacity: 0 - transform: translateY(5px) + transform: translateY(2px) &[data-is-new='false'] { min-height: 0 @@ -28,7 +28,7 @@ } .path { - max-width: 150px + max-width: 15ch overflow: hidden overflow: clip direction: rtl @@ -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 + } } \ No newline at end of file diff --git a/src/co/bookmarks/edit/form/link.js b/src/co/bookmarks/edit/form/link.js index fb66dd47..305826ca 100644 --- a/src/co/bookmarks/edit/form/link.js +++ b/src/co/bookmarks/edit/form/link.js @@ -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=> @@ -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} diff --git a/src/co/common/button/index.module.styl b/src/co/common/button/index.module.styl index d504ff5b..8ae02ecd 100644 --- a/src/co/common/button/index.module.styl +++ b/src/co/common/button/index.module.styl @@ -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 diff --git a/src/modules/format/callback/use-debounce.js b/src/modules/format/callback/use-debounce.js new file mode 100644 index 00000000..c1231166 --- /dev/null +++ b/src/modules/format/callback/use-debounce.js @@ -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; +} \ No newline at end of file