Skip to content

Commit 786bdfd

Browse files
author
Rustem Mussabekov
committed
- Show tags suggestions
- Improve Organize
1 parent 7556675 commit 786bdfd

File tree

24 files changed

+428
-182
lines changed

24 files changed

+428
-182
lines changed

package-lock.json

Lines changed: 12 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"react": "^17.0.2",
4141
"react-beautiful-dnd": "^13.0.0",
4242
"react-dom": "^17.0.2",
43+
"react-flip-move": "^3.0.5",
4344
"react-helmet": "^6.1.0",
4445
"react-portal": "^4.2.1",
4546
"react-router-dom": "^6.3.0",

src/assets/icons/micro_ai.svg

Lines changed: 7 additions & 0 deletions
Loading

src/co/bookmarks/edit/form/collection/suggested.js

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import s from './suggested.module.styl'
2-
import React, { useMemo, useEffect, useCallback } from 'react'
2+
import React, { useMemo, useCallback } from 'react'
33
import t from '~t'
4-
import { useSelector, useDispatch } from 'react-redux'
5-
import { suggestFields } from '~data/actions/bookmarks'
4+
import { useSelector } from 'react-redux'
65
import { makeSuggestedFields } from '~data/selectors/bookmarks'
76
import { makeCollection } from '~data/selectors/collections'
7+
import { isPro } from '~data/selectors/user'
88

99
import Button from '~co/common/button'
1010
import CollectionIcon from '~co/collections/item/icon'
@@ -33,21 +33,20 @@ function Suggestion({ id, onClick }) {
3333
}
3434

3535
export default function BookmarkEditFormCollectionSuggested({ item, events: { onItemClick } }) {
36-
const dispatch = useDispatch()
37-
3836
//get suggestions
37+
const pro = useSelector(state=>isPro(state))
3938
const getSuggestedFields = useMemo(()=>makeSuggestedFields(), [])
4039
const { collections=[] } = useSelector(state=>getSuggestedFields(state, item))
4140

42-
//load suggestions
43-
useEffect(()=>dispatch(suggestFields(item)), [item.link])
44-
4541
//click
4642
const onSuggestionClick = useCallback(e=>{
4743
const _id = parseInt(e.currentTarget.getAttribute('data-id'))
4844
onItemClick({ _id })
4945
}, [onItemClick])
5046

47+
if (!pro)
48+
return null
49+
5150
return (
5251
<div
5352
className={s.suggested}
Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import s from './index.module.styl'
2-
import React from 'react'
2+
import React, { useCallback, useEffect } from 'react'
3+
import { useDispatch } from 'react-redux'
4+
import { suggestFields } from '~data/actions/bookmarks'
35

46
import { Layout, Separator, Group } from '~co/common/form'
57
import Cover from './cover'
@@ -13,45 +15,48 @@ import Reminder from './reminder'
1315
import Important from './important'
1416
import Date from './date'
1517

16-
export default class BookmarkEditForm extends React.Component {
17-
onSubmitForm = e=>{
18+
export default function BookmarkEditForm(props) {
19+
const dispatch = useDispatch()
20+
21+
//load suggestions
22+
useEffect(()=>dispatch(suggestFields(props.item)), [props.item.link])
23+
24+
const onSubmitForm = useCallback(e=>{
1825
e.preventDefault()
1926
e.stopPropagation()
2027

21-
this.props.onSave().then(()=>{
22-
if (this.props.autoWindowClose)
28+
props.onSave().then(()=>{
29+
if (props.autoWindowClose)
2330
window.close()
2431
})
25-
}
32+
}, [props.onSave, props.autoWindowClose])
2633

27-
render() {
28-
return (
29-
<form
30-
className={s.form}
31-
data-status={this.props.status}
32-
onSubmit={this.onSubmitForm}>
33-
<Layout type='grid'>
34-
<Cover {...this.props} />
35-
<Title {...this.props} />
36-
<Note {...this.props} />
37-
38-
<Collection {...this.props} />
39-
<Tags {...this.props} />
40-
<Link {...this.props} />
34+
return (
35+
<form
36+
className={s.form}
37+
data-status={props.status}
38+
onSubmit={onSubmitForm}>
39+
<Layout type='grid'>
40+
<Cover {...props} />
41+
<Title {...props} />
42+
<Note {...props} />
43+
44+
<Collection {...props} />
45+
<Tags {...props} />
46+
<Link {...props} />
4147

42-
<div />
43-
<Group>
44-
<Important {...this.props} />
45-
<Reminder {...this.props} />
46-
</Group>
47-
48-
<Date {...this.props} />
48+
<div />
49+
<Group>
50+
<Important {...props} />
51+
<Reminder {...props} />
52+
</Group>
53+
54+
<Date {...props} />
4955

50-
<Separator variant='transparent' />
51-
52-
<Action {...this.props} />
53-
</Layout>
54-
</form>
55-
)
56-
}
56+
<Separator variant='transparent' />
57+
58+
<Action {...props} />
59+
</Layout>
60+
</form>
61+
)
5762
}
Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,33 @@
1-
import React from 'react'
1+
import React, { useCallback } from 'react'
22
import t from '~t'
33

44
import { Label } from '~co/common/form'
55
import TagsField from '~co/tags/field'
6+
import Suggested from './suggested'
67

7-
class BookmarkEditFormTags extends React.Component {
8-
onChange = (tags)=>{
9-
this.props.onChange({ tags })
10-
}
8+
export default function BookmarkEditFormTags({ autoFocus, item, onCommit, onChange, onSave }) {
9+
const onTagsFieldChange = useCallback((tags)=>onChange({ tags }), [onChange])
10+
const onSuggestionClick = useCallback((tag)=>{
11+
onChange({ tags: [...item.tags, tag] })
12+
onSave()
13+
}, [onChange, item.tags])
1114

12-
render() {
13-
const { autoFocus, item: { tags, collectionId }, onCommit } = this.props
15+
return (
16+
<>
17+
<Label>{t.s('tags')}</Label>
18+
<div>
19+
<TagsField
20+
value={item.tags}
21+
spaceId={item.collectionId}
22+
23+
autoFocus={autoFocus=='tags'}
24+
onChange={onTagsFieldChange}
25+
onBlur={onCommit} />
1426

15-
return (
16-
<>
17-
<Label>{t.s('tags')}</Label>
18-
<div>
19-
<TagsField
20-
value={tags}
21-
spaceId={collectionId}
22-
23-
autoFocus={autoFocus=='tags'}
24-
onChange={this.onChange}
25-
onBlur={onCommit} />
26-
</div>
27-
</>
28-
)
29-
}
30-
}
31-
32-
export default BookmarkEditFormTags
27+
<Suggested
28+
item={item}
29+
onTagClick={onSuggestionClick} />
30+
</div>
31+
</>
32+
)
33+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import s from './suggested.module.styl'
2+
import React, { useMemo, useCallback } from 'react'
3+
import t from '~t'
4+
import { useSelector } from 'react-redux'
5+
import { makeSuggestedFields } from '~data/selectors/bookmarks'
6+
import { isPro } from '~data/selectors/user'
7+
8+
import Button from '~co/common/button'
9+
10+
function Suggestion({ tag, onClick }) {
11+
return (
12+
<Button
13+
data-tag={tag}
14+
className={s.suggestion}
15+
variant='outline'
16+
size='small'
17+
tabIndex='-1'
18+
onClick={onClick}>
19+
+{tag}
20+
</Button>
21+
)
22+
}
23+
24+
export default function BookmarkEditFormTagsSuggested({ item, onTagClick }) {
25+
//get suggestions
26+
const pro = useSelector(state=>isPro(state))
27+
const getSuggestedFields = useMemo(()=>makeSuggestedFields(), [])
28+
const suggestions = useSelector(state=>getSuggestedFields(state, item))
29+
const tags = useMemo(()=>(suggestions.tags||[]).filter(tag=>!item.tags.includes(tag)), [suggestions.tags, item.tags])
30+
31+
//click
32+
const onSuggestionClick = useCallback(e=>{
33+
const tag = e.currentTarget.getAttribute('data-tag')
34+
onTagClick(tag)
35+
}, [onTagClick])
36+
37+
if (!pro)
38+
return null
39+
40+
return (
41+
<div
42+
className={s.suggested}
43+
title={t.s('suggested')+' '+t.s('tags').toLowerCase()}>
44+
{tags.map(tag=>(
45+
<Suggestion
46+
key={tag}
47+
tag={tag}
48+
onClick={onSuggestionClick} />
49+
))}
50+
</div>
51+
)
52+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
.suggested {
2+
display: flex
3+
flex-wrap: wrap
4+
overflow: hidden
5+
min-height: calc(var(--icon-size) + var(--padding-small))
6+
max-height: calc(var(--icon-size) + var(--padding-small))
7+
transition: opacity .2s ease-in-out, max-height .15s ease-in-out, min-height .15s ease-in-out
8+
9+
&:hover:not(:empty) {
10+
transition-delay: .2s
11+
max-height: calc( (var(--icon-size) + var(--padding-small)) * 3 )
12+
}
13+
14+
&:empty {
15+
opacity: 0
16+
min-height: 0
17+
max-height: 0
18+
}
19+
20+
> * {
21+
margin-top: var(--padding-small)
22+
margin-right: var(--padding-small)
23+
max-width: 300px
24+
overflow: hidden
25+
overflow: clip
26+
}
27+
}
28+
29+
.suggestion {
30+
--shadow-color: var(--shadow-light-color)
31+
* {
32+
--icon-size: var(--padding-medium)
33+
}
34+
35+
&[data-tint]:not([data-tint='']) {
36+
background: unquote('color-mix(in srgb, var(--tint) 12%, transparent)')
37+
}
38+
}

src/co/common/button/index.module.styl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@
5050
display: grid
5151
width: 100%
5252
}
53+
54+
&[data-shape='pill'] {
55+
border-radius: var(--button-height)
56+
padding: 0 var(--tertiary-font-size)
57+
}
5358
}
5459

5560
//default

src/data/actions/predictions.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as c from '../constants/predictions'
2+
import wrapFunc from '../utils/wrapFunc'
23

34
export const load = ()=>({
45
type: c.PREDICTIONS_LOAD_REQ
@@ -7,4 +8,11 @@ export const load = ()=>({
78
export const patch = (details)=>({
89
type: c.PREDICTION_PATCH,
910
...details
11+
})
12+
13+
export const apply = (_id, onSuccess, onFail)=>({
14+
type: c.PREDICTION_APPLY_REQ,
15+
_id,
16+
onSuccess: wrapFunc(onSuccess),
17+
onFail: wrapFunc(onFail)
1018
})

0 commit comments

Comments
 (0)