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
995ec34b
Commit
995ec34b
authored
May 02, 2022
by
boojack
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: pin/unpin memo
parent
fcb5e2ee
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
101 additions
and
44 deletions
+101
-44
10001__schema.sql
store/migration/10001__schema.sql
+2
-2
add.svg
web/public/icons/add.svg
+1
-0
Memo.tsx
web/src/components/Memo.tsx
+26
-0
MemoFilter.tsx
web/src/components/MemoFilter.tsx
+1
-1
MemoList.tsx
web/src/components/MemoList.tsx
+7
-3
ShortcutList.tsx
web/src/components/ShortcutList.tsx
+18
-14
UsageHeatMap.tsx
web/src/components/UsageHeatMap.tsx
+1
-1
api.ts
web/src/helpers/api.ts
+21
-1
memo-filter.less
web/src/less/memo-filter.less
+2
-6
memo-list.less
web/src/less/memo-list.less
+1
-1
memo.less
web/src/less/memo.less
+2
-2
shortcut-list.less
web/src/less/shortcut-list.less
+8
-10
locationService.ts
web/src/services/locationService.ts
+1
-1
memoService.ts
web/src/services/memoService.ts
+9
-1
models.d.ts
web/src/types/models.d.ts
+1
-1
No files found.
store/migration/10001__schema.sql
View file @
995ec34b
...
...
@@ -31,7 +31,7 @@ CREATE TABLE memo (
id
INTEGER
PRIMARY
KEY
AUTOINCREMENT
,
created_ts
BIGINT
NOT
NULL
DEFAULT
(
strftime
(
'%s'
,
'now'
)),
updated_ts
BIGINT
NOT
NULL
DEFAULT
(
strftime
(
'%s'
,
'now'
)),
-- allowed row status are 'NORMAL', '
PINN
ED', 'HIDDEN'.
-- allowed row status are 'NORMAL', '
ARCHIV
ED', 'HIDDEN'.
row_status
TEXT
NOT
NULL
DEFAULT
'NORMAL'
,
content
TEXT
NOT
NULL
DEFAULT
''
,
creator_id
INTEGER
NOT
NULL
,
...
...
@@ -64,7 +64,7 @@ CREATE TABLE shortcut (
title
TEXT
NOT
NULL
DEFAULT
''
,
payload
TEXT
NOT
NULL
DEFAULT
''
,
creator_id
INTEGER
NOT
NULL
,
-- allowed row status are 'NORMAL', '
PINN
ED'.
-- allowed row status are 'NORMAL', '
ARCHIV
ED'.
row_status
TEXT
NOT
NULL
DEFAULT
'NORMAL'
,
FOREIGN
KEY
(
creator_id
)
REFERENCES
users
(
id
)
);
...
...
web/public/icons/add.svg
0 → 100644
View file @
995ec34b
<svg
xmlns=
"http://www.w3.org/2000/svg"
height=
"48"
width=
"48"
><path
d=
"M22.5 38V25.5H10V22.5H22.5V10H25.5V22.5H38V25.5H25.5V38Z"
/></svg>
\ No newline at end of file
web/src/components/Memo.tsx
View file @
995ec34b
...
...
@@ -29,6 +29,26 @@ const Memo: React.FC<Props> = (props: Props) => {
showMemoCardDialog
(
memo
);
};
const
handleTogglePinMemoBtnClick
=
async
()
=>
{
try
{
if
(
memo
.
rowStatus
===
"ARCHIVED"
)
{
await
memoService
.
unpinMemo
(
memo
.
id
);
memoService
.
editMemo
({
...
memo
,
rowStatus
:
"NORMAL"
,
});
}
else
{
await
memoService
.
pinMemo
(
memo
.
id
);
memoService
.
editMemo
({
...
memo
,
rowStatus
:
"ARCHIVED"
,
});
}
}
catch
(
error
)
{
// do nth
}
};
const
handleMarkMemoClick
=
()
=>
{
globalStateService
.
setMarkMemoId
(
memo
.
id
);
};
...
...
@@ -86,6 +106,9 @@ const Memo: React.FC<Props> = (props: Props) => {
<
div
className=
"memo-top-wrapper"
>
<
span
className=
"time-text"
onClick=
{
handleShowMemoStoryDialog
}
>
{
memo
.
createdAtStr
}
<
Only
when=
{
memo
.
rowStatus
===
"ARCHIVED"
}
>
<
span
className=
"ml-2"
>
PINNED
</
span
>
</
Only
>
</
span
>
<
div
className=
"btns-container"
>
<
span
className=
"btn more-action-btn"
>
...
...
@@ -96,6 +119,9 @@ const Memo: React.FC<Props> = (props: Props) => {
<
span
className=
"btn"
onClick=
{
handleShowMemoStoryDialog
}
>
View Story
</
span
>
<
span
className=
"btn"
onClick=
{
handleTogglePinMemoBtnClick
}
>
{
memo
.
rowStatus
===
"NORMAL"
?
"Pin"
:
"Unpin"
}
</
span
>
<
span
className=
"btn"
onClick=
{
handleMarkMemoClick
}
>
Mark
</
span
>
...
...
web/src/components/MemoFilter.tsx
View file @
995ec34b
...
...
@@ -50,7 +50,7 @@ const MemoFilter: React.FC<FilterProps> = () => {
locationService
.
setFromAndToQuery
(
0
,
0
);
}
}
>
<
span
className=
"icon-text"
>
🗓️
</
span
>
{
utils
.
getDateString
(
duration
.
from
)
}
至
{
utils
.
getDateString
(
duration
.
to
)
}
<
span
className=
"icon-text"
>
🗓️
</
span
>
{
utils
.
getDateString
(
duration
.
from
)
}
to
{
utils
.
getDateString
(
duration
.
to
)
}
</
div
>
)
:
null
}
<
div
...
...
web/src/components/MemoList.tsx
View file @
995ec34b
...
...
@@ -76,6 +76,10 @@ const MemoList: React.FC<Props> = () => {
}
)
: memos;
const pinnedMemos = shownMemos.filter((m) =
>
m.rowStatus === "ARCHIVED");
const unpinnedMemos = shownMemos.filter((m) =
>
m.rowStatus === "NORMAL");
const sortedMemos = pinnedMemos.concat(unpinnedMemos);
useEffect(() =
>
{
memoService
.
fetchAllMemos
()
...
...
@@ -84,7 +88,7 @@ const MemoList: React.FC<Props> = () => {
memoService
.
updateTagsState
();
})
.
catch
(()
=>
{
toastHelper
.
error
(
"😭
Refresh
failed, please try again later."
);
toastHelper
.
error
(
"😭
Fetching
failed, please try again later."
);
});
}
, []);
...
...
@@ -107,14 +111,14 @@ const MemoList: React.FC<Props> = () => {
return (
<
div
className=
{
`memo-list-container ${isFetching ? "" : "completed"}`
}
onClick=
{
handleMemoListClick
}
ref=
{
wrapperElement
}
>
{
s
hown
Memos
.
map
((
memo
)
=>
(
{
s
orted
Memos
.
map
((
memo
)
=>
(
<
Memo
key=
{
`${memo.id}-${memo.updatedAt}`
}
memo=
{
memo
}
/>
))
}
<
div
className=
"status-text-container"
>
<
p
className=
"status-text"
>
{
isFetching
?
"Fetching data..."
:
s
hown
Memos
.
length
===
0
:
s
orted
Memos
.
length
===
0
?
"Oops, there is nothing"
:
showMemoFilter
?
""
...
...
web/src/components/ShortcutList.tsx
View file @
995ec34b
import
{
useContext
,
useEffect
}
from
"react"
;
import
{
locationService
,
shortcutService
}
from
"../services"
;
import
appContext
from
"../stores/appContext"
;
import
useToggle
from
"../hooks/useToggle"
;
import
useLoading
from
"../hooks/useLoading"
;
import
Only
from
"./common/OnlyWhen"
;
import
utils
from
"../helpers/utils"
;
import
Only
from
"./common/OnlyWhen"
;
import
toastHelper
from
"./Toast"
;
import
{
locationService
,
shortcutService
}
from
"../services"
;
import
showCreateQueryDialog
from
"./CreateShortcutDialog"
;
import
showCreateShortcutDialog
from
"./CreateShortcutDialog"
;
import
"../less/shortcut-list.less"
;
interface
Props
{}
...
...
@@ -19,9 +19,13 @@ const ShortcutList: React.FC<Props> = () => {
},
}
=
useContext
(
appContext
);
const
loadingState
=
useLoading
();
const
sortedShortcuts
=
shortcuts
.
sort
((
a
,
b
)
=>
utils
.
getTimeStampByDate
(
b
.
createdAt
)
-
utils
.
getTimeStampByDate
(
a
.
createdAt
))
.
sort
((
a
,
b
)
=>
utils
.
getTimeStampByDate
(
b
.
updatedAt
)
-
utils
.
getTimeStampByDate
(
a
.
updatedAt
));
const
pinnedShortcuts
=
shortcuts
.
filter
((
s
)
=>
s
.
rowStatus
===
"ARCHIVED"
)
.
sort
((
a
,
b
)
=>
utils
.
getTimeStampByDate
(
b
.
createdAt
)
-
utils
.
getTimeStampByDate
(
a
.
createdAt
));
const
unpinnedShortcuts
=
shortcuts
.
filter
((
s
)
=>
s
.
rowStatus
===
"NORMAL"
)
.
sort
((
a
,
b
)
=>
utils
.
getTimeStampByDate
(
b
.
createdAt
)
-
utils
.
getTimeStampByDate
(
a
.
createdAt
));
const
sortedShortcuts
=
pinnedShortcuts
.
concat
(
unpinnedShortcuts
);
useEffect
(()
=>
{
shortcutService
...
...
@@ -38,13 +42,13 @@ const ShortcutList: React.FC<Props> = () => {
<
div
className=
"shortcuts-wrapper"
>
<
p
className=
"title-text"
>
<
span
className=
"normal-text"
>
Shortcuts
</
span
>
<
span
className=
"btn"
onClick=
{
()
=>
showCreate
Query
Dialog
()
}
>
+
<
span
className=
"btn"
onClick=
{
()
=>
showCreate
Shortcut
Dialog
()
}
>
<
img
src=
"/icons/add.svg"
alt=
"add shortcut"
/>
</
span
>
</
p
>
<
Only
when=
{
loadingState
.
isSucceed
&&
sortedShortcuts
.
length
===
0
}
>
<
div
className=
"create-shortcut-btn-container"
>
<
span
className=
"btn"
onClick=
{
()
=>
showCreate
Query
Dialog
()
}
>
<
span
className=
"btn"
onClick=
{
()
=>
showCreate
Shortcut
Dialog
()
}
>
New shortcut
</
span
>
</
div
>
...
...
@@ -92,12 +96,12 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
}
};
const
handleEdit
Query
BtnClick
=
(
event
:
React
.
MouseEvent
)
=>
{
const
handleEdit
Shortcut
BtnClick
=
(
event
:
React
.
MouseEvent
)
=>
{
event
.
stopPropagation
();
showCreate
Query
Dialog
(
shortcut
.
id
);
showCreate
Shortcut
Dialog
(
shortcut
.
id
);
};
const
handlePin
Query
BtnClick
=
async
(
event
:
React
.
MouseEvent
)
=>
{
const
handlePin
Shortcut
BtnClick
=
async
(
event
:
React
.
MouseEvent
)
=>
{
event
.
stopPropagation
();
try
{
...
...
@@ -136,10 +140,10 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
</
span
>
<
div
className=
"action-btns-wrapper"
>
<
div
className=
"action-btns-container"
>
<
span
className=
"btn"
onClick=
{
handlePin
Query
BtnClick
}
>
<
span
className=
"btn"
onClick=
{
handlePin
Shortcut
BtnClick
}
>
{
shortcut
.
rowStatus
===
"ARCHIVED"
?
"Unpin"
:
"Pin"
}
</
span
>
<
span
className=
"btn"
onClick=
{
handleEdit
Query
BtnClick
}
>
<
span
className=
"btn"
onClick=
{
handleEdit
Shortcut
BtnClick
}
>
Edit
</
span
>
<
span
...
...
web/src/components/UsageHeatMap.tsx
View file @
995ec34b
...
...
@@ -125,7 +125,7 @@ const UsageHeatMap: React.FC<Props> = () => {
></
span
>
);
})
}
{
nullCell
.
map
((
v
,
i
)
=>
(
{
nullCell
.
map
((
_
,
i
)
=>
(
<
span
className=
"stat-container null"
key=
{
i
}
></
span
>
))
}
</
div
>
...
...
web/src/helpers/api.ts
View file @
995ec34b
...
...
@@ -113,7 +113,7 @@ namespace api {
export
function
getMyMemos
()
{
return
request
<
Model
.
Memo
[]
>
({
method
:
"GET"
,
url
:
"/api/memo
?rowStatus=NORMAL
"
,
url
:
"/api/memo"
,
});
}
...
...
@@ -144,6 +144,26 @@ namespace api {
});
}
export
function
pinMemo
(
memoId
:
string
)
{
return
request
({
method
:
"PATCH"
,
url
:
`/api/memo/
${
memoId
}
`
,
data
:
{
rowStatus
:
"ARCHIVED"
,
},
});
}
export
function
unpinMemo
(
shortcutId
:
string
)
{
return
request
({
method
:
"PATCH"
,
url
:
`/api/memo/
${
shortcutId
}
`
,
data
:
{
rowStatus
:
"NORMAL"
,
},
});
}
export
function
hideMemo
(
memoId
:
string
)
{
return
request
({
method
:
"PATCH"
,
...
...
web/src/less/memo-filter.less
View file @
995ec34b
...
...
@@ -2,7 +2,7 @@
.filter-query-container {
.flex(row, flex-start, flex-start);
@apply w-full flex-wrap p-2 pb-1 text-sm leading-7;
@apply w-full flex-wrap p-2 pb-1 text-sm
font-mono
leading-7;
> .tip-text {
@apply mr-2;
...
...
@@ -10,10 +10,6 @@
> .filter-item-container {
@apply px-2 mr-2 cursor-pointer bg-gray-200 rounded whitespace-nowrap truncate hover:line-through;
max-width: 200px;
> .icon-text {
letter-spacing: 2px;
}
max-width: 256px;
}
}
web/src/less/memo-list.less
View file @
995ec34b
...
...
@@ -28,6 +28,6 @@
}
&.completed {
@apply pb-
28
;
@apply pb-
40
;
}
}
web/src/less/memo.less
View file @
995ec34b
...
...
@@ -3,7 +3,7 @@
.memo-wrapper {
.flex(column, flex-start, flex-start);
@apply w-full max-w-full p-4 p
x-6 mt-2 first:
mt-2 bg-white rounded-lg border border-transparent hover:border-gray-200;
@apply w-full max-w-full p-4 p
b-3
mt-2 bg-white rounded-lg border border-transparent hover:border-gray-200;
&.deleted-memo {
@apply border-gray-200;
...
...
@@ -11,7 +11,7 @@
> .memo-top-wrapper {
.flex(row, space-between, center);
@apply w-full h-6 mb-
1
;
@apply w-full h-6 mb-
2
;
> .time-text {
@apply text-xs text-gray-400 cursor-pointer;
...
...
web/src/less/shortcut-list.less
View file @
995ec34b
...
...
@@ -6,28 +6,26 @@
.hide-scroll-bar();
> .title-text {
.flex(row,
space-between
, center);
.flex(row,
flex-start
, center);
@apply w-full px-4;
> .normal-text {
@apply text-xs leading-6 font-
bold text-black opacity-5
0;
@apply text-xs leading-6 font-
mono text-gray-40
0;
}
> .btn {
@apply hidden px-1 text-lg leading-6
;
}
.flex(column, center, center)
;
@apply w-5 h-5 bg-gray-200 rounded ml-2 hover:opacity-80;
&:hover,
&:active {
> .btn {
@apply block;
> img {
@apply w-4 h-4 opacity-80;
}
}
}
> .create-shortcut-btn-container {
.flex(row,
center
, center);
@apply w-full mt-4 mb-2;
.flex(row,
flex-start
, center);
@apply w-full mt-4 mb-2
ml-4
;
> .btn {
@apply flex p-2 px-4 rounded-lg text-sm border border-dashed border-blue-600;
...
...
web/src/services/locationService.ts
View file @
995ec34b
...
...
@@ -42,7 +42,7 @@ class LocationService {
state
.
query
.
tag
=
urlParams
.
get
(
"tag"
)
??
""
;
state
.
query
.
type
=
(
urlParams
.
get
(
"type"
)
??
""
)
as
MemoSpecType
;
state
.
query
.
text
=
urlParams
.
get
(
"text"
)
??
""
;
state
.
query
.
shortcutId
=
urlParams
.
get
(
"
filter
"
)
??
""
;
state
.
query
.
shortcutId
=
urlParams
.
get
(
"
shortcutId
"
)
??
""
;
const
from
=
parseInt
(
urlParams
.
get
(
"from"
)
??
"0"
);
const
to
=
parseInt
(
urlParams
.
get
(
"to"
)
??
"0"
);
if
(
to
>
from
&&
to
!==
0
)
{
...
...
web/src/services/memoService.ts
View file @
995ec34b
...
...
@@ -17,7 +17,7 @@ class MemoService {
}
const
data
=
await
api
.
getMyMemos
();
const
memos
:
Model
.
Memo
[]
=
data
.
map
((
m
)
=>
this
.
convertResponseModelMemo
(
m
));
const
memos
:
Model
.
Memo
[]
=
data
.
filter
((
m
)
=>
m
.
rowStatus
!==
"HIDDEN"
).
map
((
m
)
=>
this
.
convertResponseModelMemo
(
m
));
appStore
.
dispatch
({
type
:
"SET_MEMOS"
,
payload
:
{
...
...
@@ -133,6 +133,14 @@ class MemoService {
return
this
.
convertResponseModelMemo
(
memo
);
}
public
async
pinMemo
(
memoId
:
string
)
{
await
api
.
pinMemo
(
memoId
);
}
public
async
unpinMemo
(
memoId
:
string
)
{
await
api
.
unpinMemo
(
memoId
);
}
private
convertResponseModelMemo
(
memo
:
Model
.
Memo
):
Model
.
Memo
{
return
{
...
memo
,
...
...
web/src/types/models.d.ts
View file @
995ec34b
...
...
@@ -15,7 +15,7 @@ declare namespace Model {
interface
Memo
extends
BaseModel
{
content
:
string
;
rowStatus
:
"NORMAL"
|
"HIDDEN"
;
rowStatus
:
"NORMAL"
|
"
ARCHIVED"
|
"
HIDDEN"
;
}
interface
Shortcut
extends
BaseModel
{
...
...
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