Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
9a6e2d10
Commit
9a6e2d10
authored
Jan 10, 2022
by
email
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: tag selector popup
parent
48eb6189
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
145 additions
and
17 deletions
+145
-17
Editor.tsx
web/src/components/Editor/Editor.tsx
+1
-0
MemoEditor.tsx
web/src/components/MemoEditor.tsx
+115
-13
MemoList.tsx
web/src/components/MemoList.tsx
+1
-1
consts.ts
web/src/helpers/consts.ts
+1
-1
filter.ts
web/src/helpers/filter.ts
+1
-1
memo-editor.less
web/src/less/memo-editor.less
+25
-0
memoService.ts
web/src/services/memoService.ts
+1
-1
No files found.
web/src/components/Editor/Editor.tsx
View file @
9a6e2d10
...
...
@@ -120,6 +120,7 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRef
setContent
:
(
text
:
string
)
=>
{
if
(
editorRef
.
current
)
{
editorRef
.
current
.
value
=
text
;
handleContentChangeCallback
(
editorRef
.
current
.
value
);
refresh
();
}
},
...
...
web/src/components/MemoEditor.tsx
View file @
9a6e2d10
import
{
useCallback
,
useContext
,
useEffect
,
useMemo
,
useRef
}
from
"react"
;
import
React
,
{
useCallback
,
useContext
,
useEffect
,
useMemo
,
useRef
}
from
"react"
;
import
appContext
from
"../stores/appContext"
;
import
{
globalStateService
,
locationService
,
memoService
,
resourceService
}
from
"../services"
;
import
utils
from
"../helpers/utils"
;
import
{
storage
}
from
"../helpers/storage"
;
import
useToggle
from
"../hooks/useToggle"
;
import
toastHelper
from
"./Toast"
;
import
Editor
,
{
EditorProps
,
EditorRefActions
}
from
"./Editor/Editor"
;
import
"../less/memo-editor.less"
;
const
getCursorPostion
=
(
input
:
HTMLTextAreaElement
)
=>
{
const
{
offsetLeft
:
inputX
,
offsetTop
:
inputY
,
selectionEnd
:
selectionPoint
}
=
input
;
const
div
=
document
.
createElement
(
"div"
);
const
copyStyle
=
window
.
getComputedStyle
(
input
);
for
(
const
item
of
copyStyle
)
{
div
.
style
.
setProperty
(
item
,
copyStyle
.
getPropertyValue
(
item
));
}
div
.
style
.
position
=
"fixed"
;
div
.
style
.
visibility
=
"hidden"
;
div
.
style
.
whiteSpace
=
"pre-wrap"
;
// we need a character that will replace whitespace when filling our dummy element if it's a single line <input/>
const
swap
=
"."
;
const
inputValue
=
input
.
tagName
===
"INPUT"
?
input
.
value
.
replace
(
/ /g
,
swap
)
:
input
.
value
;
const
textContent
=
inputValue
.
substring
(
0
,
selectionPoint
||
0
);
div
.
textContent
=
textContent
;
if
(
input
.
tagName
===
"TEXTAREA"
)
{
div
.
style
.
height
=
"auto"
;
}
const
span
=
document
.
createElement
(
"span"
);
span
.
textContent
=
inputValue
.
substring
(
selectionPoint
||
0
)
||
"."
;
div
.
appendChild
(
span
);
document
.
body
.
appendChild
(
div
);
const
{
offsetLeft
:
spanX
,
offsetTop
:
spanY
}
=
span
;
document
.
body
.
removeChild
(
div
);
return
{
x
:
inputX
+
spanX
,
y
:
inputY
+
spanY
,
};
};
interface
Props
{}
const
MemoEditor
:
React
.
FC
<
Props
>
=
()
=>
{
const
{
globalState
}
=
useContext
(
appContext
);
const
{
globalState
,
memoState
:
{
tags
},
}
=
useContext
(
appContext
);
const
[
isTagSeletorShown
,
toggleTagSeletor
]
=
useToggle
(
false
);
const
editorRef
=
useRef
<
EditorRefActions
>
(
null
);
const
prevGlobalStateRef
=
useRef
(
globalState
);
const
tagSeletorRef
=
useRef
<
HTMLDivElement
>
(
null
);
useEffect
(()
=>
{
if
(
globalState
.
markMemoId
)
{
...
...
@@ -60,8 +99,22 @@ const MemoEditor: React.FC<Props> = () => {
}
};
const
handleClickEvent
=
()
=>
{
setTimeout
(()
=>
{
handleContentChange
(
editorRef
.
current
?.
element
.
value
??
""
);
});
};
const
handleKeyDownEvent
=
()
=>
{
setTimeout
(()
=>
{
handleContentChange
(
editorRef
.
current
?.
element
.
value
??
""
);
});
};
editorRef
.
current
.
element
.
addEventListener
(
"paste"
,
handlePasteEvent
);
editorRef
.
current
.
element
.
addEventListener
(
"drop"
,
handleDropEvent
);
editorRef
.
current
.
element
.
addEventListener
(
"click"
,
handleClickEvent
);
editorRef
.
current
.
element
.
addEventListener
(
"keydown"
,
handleKeyDownEvent
);
return
()
=>
{
editorRef
.
current
?.
element
.
removeEventListener
(
"paste"
,
handlePasteEvent
);
...
...
@@ -94,8 +147,6 @@ const MemoEditor: React.FC<Props> = () => {
const
{
editMemoId
}
=
globalStateService
.
getState
();
content
=
content
.
replaceAll
(
" "
,
" "
);
try
{
if
(
editMemoId
)
{
const
prevMemo
=
memoService
.
getMemoById
(
editMemoId
);
...
...
@@ -131,6 +182,23 @@ const MemoEditor: React.FC<Props> = () => {
content
=
""
;
}
setEditorContentCache
(
content
);
if
(
editorRef
.
current
)
{
const
currentValue
=
editorRef
.
current
.
getContent
();
const
selectionStart
=
editorRef
.
current
.
element
.
selectionStart
;
const
prevString
=
currentValue
.
slice
(
0
,
selectionStart
);
if
(
prevString
.
endsWith
(
"#"
))
{
toggleTagSeletor
(
true
);
updateTagSelectorPopupPosition
();
}
else
{
toggleTagSeletor
(
false
);
}
setTimeout
(()
=>
{
editorRef
.
current
?.
focus
();
});
}
},
[]);
const
handleTagTextBtnClick
=
useCallback
(()
=>
{
...
...
@@ -142,22 +210,41 @@ const MemoEditor: React.FC<Props> = () => {
const
selectionStart
=
editorRef
.
current
.
element
.
selectionStart
;
const
prevString
=
currentValue
.
slice
(
0
,
selectionStart
);
const
nextString
=
currentValue
.
slice
(
selectionStart
);
let
cursorIndex
=
prevString
.
length
;
if
(
prevString
.
endsWith
(
"# "
)
&&
nextString
.
startsWith
(
" "
))
{
editorRef
.
current
.
setContent
(
prevString
.
slice
(
0
,
prevString
.
length
-
2
)
+
nextString
.
slice
(
1
))
;
cursorIndex
=
prevString
.
length
-
2
;
}
else
{
editorRef
.
current
.
element
.
value
=
prevString
+
"# "
+
nextString
;
cursorIndex
=
prevString
.
length
+
2
;
let
nextValue
=
prevString
+
"# "
+
nextString
;
let
cursorIndex
=
prevString
.
length
+
1
;
if
(
prevString
.
endsWith
(
"#"
)
&&
nextString
.
startsWith
(
" "
))
{
nextValue
=
prevString
.
slice
(
0
,
prevString
.
length
-
1
)
+
nextString
.
slice
(
1
)
;
cursorIndex
=
prevString
.
length
-
1
;
}
setTimeout
(()
=>
{
editorRef
.
current
?.
element
.
setSelectionRange
(
cursorIndex
,
cursorIndex
);
editorRef
.
current
?.
focus
();
if
(
!
editorRef
.
current
)
{
return
;
}
editorRef
.
current
.
element
.
value
=
nextValue
;
editorRef
.
current
.
element
.
setSelectionRange
(
cursorIndex
,
cursorIndex
);
editorRef
.
current
.
focus
();
handleContentChange
(
editorRef
.
current
.
element
.
value
);
});
},
[]);
const
updateTagSelectorPopupPosition
=
useCallback
(()
=>
{
if
(
!
editorRef
.
current
||
!
tagSeletorRef
.
current
)
{
return
;
}
const
{
x
,
y
}
=
getCursorPostion
(
editorRef
.
current
.
element
);
if
(
x
+
128
+
16
>
editorRef
.
current
.
element
.
clientWidth
)
{
tagSeletorRef
.
current
.
style
.
left
=
`
${
editorRef
.
current
.
element
.
clientWidth
+
20
-
128
}
px`
;
}
else
{
tagSeletorRef
.
current
.
style
.
left
=
`
${
x
+
2
}
px`
;
}
tagSeletorRef
.
current
.
style
.
top
=
`
${
y
+
32
+
6
}
px`
;
},
[]);
const
handleUploadFileBtnClick
=
useCallback
(()
=>
{
const
inputEl
=
document
.
createElement
(
"input"
);
inputEl
.
type
=
"file"
;
...
...
@@ -177,6 +264,12 @@ const MemoEditor: React.FC<Props> = () => {
inputEl
.
click
();
},
[]);
const
handleTagSeletorClick
=
useCallback
((
event
:
React
.
MouseEvent
)
=>
{
if
(
tagSeletorRef
.
current
!==
event
.
target
&&
tagSeletorRef
.
current
?.
contains
(
event
.
target
as
Node
))
{
editorRef
.
current
?.
insertText
((
event
.
target
as
HTMLElement
).
textContent
??
""
);
}
},
[]);
const
showEditStatus
=
Boolean
(
globalState
.
editMemoId
);
const
editorConfig
:
EditorProps
=
useMemo
(
...
...
@@ -200,6 +293,15 @@ const MemoEditor: React.FC<Props> = () => {
<
div
className=
{
"memo-editor-wrapper "
+
(
showEditStatus
?
"edit-ing"
:
""
)
}
>
<
p
className=
{
"tip-text "
+
(
showEditStatus
?
""
:
"hidden"
)
}
>
正在修改中...
</
p
>
<
Editor
ref=
{
editorRef
}
{
...
editorConfig
}
/>
<
div
ref=
{
tagSeletorRef
}
className=
{
`tag-list ${isTagSeletorShown && tags.length > 0 ? "" : "hidden"}`
}
onClick=
{
handleTagSeletorClick
}
>
{
tags
.
map
((
t
)
=>
{
return
<
span
key=
{
t
}
>
{
t
}
</
span
>;
})
}
</
div
>
</
div
>
);
};
...
...
web/src/components/MemoList.tsx
View file @
9a6e2d10
...
...
@@ -34,7 +34,7 @@ const MemoList: React.FC<Props> = () => {
}
}
if
(
tagQuery
&&
!
memo
.
content
.
includes
(
`#
${tagQuery}
`
))
{
if
(
tagQuery
&&
!
memo
.
content
.
includes
(
`#
${tagQuery} `
)
&&
!
memo
.
content
.
includes
(
`# ${tagQuery}
`
))
{
shouldShow
=
false
;
}
if
(
...
...
web/src/helpers/consts.ts
View file @
9a6e2d10
...
...
@@ -11,7 +11,7 @@ export const TOAST_ANIMATION_DURATION = 400;
export
const
DAILY_TIMESTAMP
=
3600
*
24
*
1000
;
// 标签 正则
export
const
TAG_REG
=
/#
\s(
.+
?)\s
/g
;
export
const
TAG_REG
=
/#
\s
?
(
.+
?)\s
/g
;
// URL 正则
export
const
LINK_REG
=
/
(
https
?
:
\/\/[^\s
<
\\
*>'
]
+
)
/g
;
...
...
web/src/helpers/filter.ts
View file @
9a6e2d10
...
...
@@ -119,7 +119,7 @@ export const checkShouldShowMemo = (memo: Model.Memo, filter: Filter) => {
let
shouldShow
=
true
;
if
(
type
===
"TAG"
)
{
let
contained
=
memo
.
content
.
includes
(
`#
${
value
}
`
);
let
contained
=
memo
.
content
.
includes
(
`#
${
value
}
`
)
||
memo
.
content
.
includes
(
`#
${
value
}
`
);
if
(
operator
===
"NOT_CONTAIN"
)
{
contained
=
!
contained
;
}
...
...
web/src/less/memo-editor.less
View file @
9a6e2d10
...
...
@@ -27,6 +27,31 @@
height: auto;
background-color: white;
}
> .tag-list {
.flex(column, flex-start, flex-start);
position: absolute;
z-index: 10;
background-color: rgba(30, 30, 30, 0.9);
padding: 2px;
border-radius: 4px;
width: 128px;
max-height: 200px;
overflow: auto;
> span {
width: 100%;
padding: 2px 6px;
color: white;
cursor: pointer;
border-radius: 4px;
font-size: 13px;
&:hover {
background-color: #505050;
}
}
}
}
@media only screen and (max-width: 875px) {
...
...
web/src/services/memoService.ts
View file @
9a6e2d10
...
...
@@ -105,7 +105,7 @@ class MemoService {
appStore
.
dispatch
({
type
:
"SET_TAGS"
,
payload
:
{
tags
:
Array
.
from
(
tagsSet
),
tags
:
Array
.
from
(
tagsSet
)
.
filter
((
t
)
=>
Boolean
(
t
))
,
},
});
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment