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
b770042a
Commit
b770042a
authored
Mar 31, 2025
by
Johnny
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor: migrate eslint
parent
d649d326
Changes
18
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
809 additions
and
885 deletions
+809
-885
.eslintrc.json
web/.eslintrc.json
+0
-50
eslint.config.mjs
web/eslint.config.mjs
+34
-0
package.json
web/package.json
+5
-5
pnpm-lock.yaml
web/pnpm-lock.yaml
+261
-321
CreateAccessTokenDialog.tsx
web/src/components/CreateAccessTokenDialog.tsx
+147
-147
CreateWebhookDialog.tsx
web/src/components/CreateWebhookDialog.tsx
+160
-160
LocaleSelect.tsx
web/src/components/LocaleSelect.tsx
+1
-1
MemoActionMenu.tsx
web/src/components/MemoActionMenu.tsx
+2
-2
CodeBlock.tsx
web/src/components/MemoContent/CodeBlock.tsx
+1
-1
LineBreak.tsx
web/src/components/MemoContent/LineBreak.tsx
+1
-5
Link.tsx
web/src/components/MemoContent/Link.tsx
+1
-1
AddMemoRelationPopover.tsx
...onents/MemoEditor/ActionButton/AddMemoRelationPopover.tsx
+187
-187
TagSuggestions.tsx
web/src/components/MemoEditor/Editor/TagSuggestions.tsx
+5
-1
types.ts
web/src/components/MemoRelationForceGraph/types.ts
+1
-0
ReactionSelector.tsx
web/src/components/ReactionSelector.tsx
+1
-1
ReactionView.tsx
web/src/components/ReactionView.tsx
+1
-1
Popover.tsx
web/src/components/ui/Popover.tsx
+0
-1
utils.ts
web/src/helpers/utils.ts
+1
-1
No files found.
web/.eslintrc.json
deleted
100644 → 0
View file @
d649d326
{
"env"
:
{
"browser"
:
true
,
"es2021"
:
true
},
"extends"
:
[
"eslint:recommended"
,
"plugin:react/recommended"
,
"plugin:@typescript-eslint/recommended"
,
"plugin:prettier/recommended"
],
"parser"
:
"@typescript-eslint/parser"
,
"parserOptions"
:
{
"ecmaFeatures"
:
{
"jsx"
:
true
},
"ecmaVersion"
:
"latest"
,
"sourceType"
:
"module"
},
"plugins"
:
[
"react"
,
"@typescript-eslint"
,
"prettier"
],
"ignorePatterns"
:
[
"node_modules"
,
"dist"
,
"public"
,
"src/assets"
],
"rules"
:
{
"prettier/prettier"
:
[
"error"
,
{
"endOfLine"
:
"auto"
}
],
"no-unused-vars"
:
"off"
,
"@typescript-eslint/no-unused-vars"
:
"error"
,
"@typescript-eslint/no-explicit-any"
:
[
"off"
],
"react/react-in-jsx-scope"
:
"off"
,
"react/jsx-no-target-blank"
:
"off"
,
"no-restricted-syntax"
:
[
"error"
,
{
"selector"
:
"VariableDeclarator[init.callee.name='useTranslation'] > ObjectPattern > Property[key.name='t']:not([parent.declarations.0.init.callee.object.name='i18n'])"
,
"message"
:
"Destructuring 't' from useTranslation is not allowed. Please use the 'useTranslate' hook from '@/utils/i18n'."
}
]
},
"settings"
:
{
"react"
:
{
"version"
:
"detect"
}
},
"overrides"
:
[
{
"files"
:
[
"src/utils/i18n.ts"
],
"rules"
:
{
"no-restricted-syntax"
:
"off"
}
}
]
}
web/eslint.config.mjs
0 → 100644
View file @
b770042a
import
eslint
from
"@eslint/js"
;
import
eslintPluginPrettierRecommended
from
"eslint-plugin-prettier/recommended"
;
import
tseslint
from
"typescript-eslint"
;
export
default
[
...
tseslint
.
config
(
eslint
.
configs
.
recommended
,
tseslint
.
configs
.
recommended
),
eslintPluginPrettierRecommended
,
{
ignores
:
[
"**/dist/**"
,
"**/node_modules/**"
,
"**/proto/**"
],
},
{
rules
:
{
"no-unused-vars"
:
"off"
,
"@typescript-eslint/no-unused-vars"
:
"error"
,
"@typescript-eslint/no-explicit-any"
:
[
"off"
],
"react/react-in-jsx-scope"
:
"off"
,
"react/jsx-no-target-blank"
:
"off"
,
"no-restricted-syntax"
:
[
"error"
,
{
selector
:
"VariableDeclarator[init.callee.name='useTranslation'] > ObjectPattern > Property[key.name='t']:not([parent.declarations.0.init.callee.object.name='i18n'])"
,
message
:
"Destructuring 't' from useTranslation is not allowed. Please use the 'useTranslate' hook from '@/utils/i18n'."
,
},
],
},
},
{
files
:
[
"src/utils/i18n.ts"
],
rules
:
{
"no-restricted-syntax"
:
"off"
,
},
},
];
web/package.json
View file @
b770042a
...
...
@@ -52,6 +52,7 @@
"devDependencies"
:
{
"@bufbuild/buf"
:
"^1.50.1"
,
"@bufbuild/protobuf"
:
"^2.2.3"
,
"@eslint/js"
:
"^9.23.0"
,
"@trivago/prettier-plugin-sort-imports"
:
"^4.3.0"
,
"@types/d3"
:
"^7.4.3"
,
"@types/katex"
:
"^0.16.7"
,
...
...
@@ -63,15 +64,13 @@
"@types/react-dom"
:
"^18.3.5"
,
"@types/textarea-caret"
:
"^3.0.3"
,
"@types/uuid"
:
"^10.0.0"
,
"@typescript-eslint/eslint-plugin"
:
"^7.18.0"
,
"@typescript-eslint/parser"
:
"^7.18.0"
,
"@vitejs/plugin-legacy"
:
"^6.0.2"
,
"@vitejs/plugin-react"
:
"^4.3.4"
,
"autoprefixer"
:
"^10.4.21"
,
"code-inspector-plugin"
:
"^0.18.3"
,
"eslint"
:
"^
8.57.1
"
,
"eslint-config-prettier"
:
"^
9.1.0
"
,
"eslint-plugin-prettier"
:
"^5.2.
3
"
,
"eslint"
:
"^
9.23.0
"
,
"eslint-config-prettier"
:
"^
10.1.1
"
,
"eslint-plugin-prettier"
:
"^5.2.
5
"
,
"eslint-plugin-react"
:
"^7.37.4"
,
"long"
:
"^5.3.1"
,
"nice-grpc-web"
:
"^3.3.6"
,
...
...
@@ -80,6 +79,7 @@
"protobufjs"
:
"^7.4.0"
,
"terser"
:
"^5.39.0"
,
"typescript"
:
"^5.8.2"
,
"typescript-eslint"
:
"^8.28.0"
,
"vite"
:
"^6.2.1"
},
"pnpm"
:
{
...
...
web/pnpm-lock.yaml
View file @
b770042a
This diff is collapsed.
Click to expand it.
web/src/components/CreateAccessTokenDialog.tsx
View file @
b770042a
import
{
Radio
,
RadioGroup
}
from
"@mui/joy"
;
import
{
Button
,
Input
}
from
"@usememos/mui"
;
import
{
XIcon
}
from
"lucide-react"
;
import
React
,
{
useState
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
userServiceClient
}
from
"@/grpcweb"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
useLoading
from
"@/hooks/useLoading"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
generateDialog
}
from
"./Dialog"
;
interface
Props
extends
DialogProps
{
onConfirm
:
()
=>
void
;
}
interface
State
{
description
:
string
;
expiration
:
number
;
}
const
CreateAccessTokenDialog
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
destroy
,
onConfirm
}
=
props
;
const
t
=
useTranslate
();
const
currentUser
=
useCurrentUser
();
const
[
state
,
setState
]
=
useState
({
description
:
""
,
expiration
:
3600
*
8
,
});
const
requestState
=
useLoading
(
false
);
const
expirationOptions
=
[
{
label
:
t
(
"setting.access-token-section.create-dialog.duration-8h"
),
value
:
3600
*
8
,
},
{
label
:
t
(
"setting.access-token-section.create-dialog.duration-1m"
),
value
:
3600
*
24
*
30
,
},
{
label
:
t
(
"setting.access-token-section.create-dialog.duration-never"
),
value
:
0
,
},
];
const
setPartialState
=
(
partialState
:
Partial
<
State
>
)
=>
{
setState
({
...
state
,
...
partialState
,
});
};
const
handleDescriptionInputChange
=
(
e
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
setPartialState
({
description
:
e
.
target
.
value
,
});
};
const
handleRoleInputChange
=
(
e
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
setPartialState
({
expiration
:
Number
(
e
.
target
.
value
),
});
};
const
handleSaveBtnClick
=
async
()
=>
{
if
(
!
state
.
description
)
{
toast
.
error
(
t
(
"message.description-is-required"
));
return
;
}
try
{
await
userServiceClient
.
createUserAccessToken
({
name
:
currentUser
.
name
,
description
:
state
.
description
,
expiresAt
:
state
.
expiration
?
new
Date
(
Date
.
now
()
+
state
.
expiration
*
1000
)
:
undefined
,
});
onConfirm
();
destroy
();
}
catch
(
error
:
any
)
{
toast
.
error
(
error
.
details
);
console
.
error
(
error
);
}
};
return
(
<
div
className=
"max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg"
>
<
div
className=
"flex flex-row justify-between items-center w-full mb-4 gap-2"
>
<
p
>
{
t
(
"setting.access-token-section.create-dialog.create-access-token"
)
}
</
p
>
<
Button
size=
"sm"
variant=
"plain"
onClick=
{
()
=>
destroy
()
}
>
<
XIcon
className=
"w-5 h-auto"
/>
</
Button
>
</
div
>
<
div
className=
"flex flex-col justify-start items-start !w-80"
>
<
div
className=
"w-full flex flex-col justify-start items-start mb-3"
>
<
span
className=
"mb-2"
>
{
t
(
"setting.access-token-section.create-dialog.description"
)
}
<
span
className=
"text-red-600"
>
*
</
span
>
</
span
>
<
div
className=
"relative w-full"
>
<
Input
className=
"w-full"
type=
"text"
placeholder=
{
t
(
"setting.access-token-section.create-dialog.some-description"
)
}
value=
{
state
.
description
}
onChange=
{
handleDescriptionInputChange
}
/>
</
div
>
</
div
>
<
div
className=
"w-full flex flex-col justify-start items-start mb-3"
>
<
span
className=
"mb-2"
>
{
t
(
"setting.access-token-section.create-dialog.expiration"
)
}
<
span
className=
"text-red-600"
>
*
</
span
>
</
span
>
<
div
className=
"w-full flex flex-row justify-start items-center text-base"
>
<
RadioGroup
orientation=
"horizontal"
value=
{
state
.
expiration
}
onChange=
{
handleRoleInputChange
}
>
{
expirationOptions
.
map
((
option
)
=>
(
<
Radio
key=
{
option
.
value
}
value=
{
option
.
value
}
checked=
{
state
.
expiration
===
option
.
value
}
label=
{
option
.
label
}
/>
))
}
</
RadioGroup
>
</
div
>
</
div
>
<
div
className=
"w-full flex flex-row justify-end items-center mt-4 space-x-2"
>
<
Button
variant=
"plain"
disabled=
{
requestState
.
isLoading
}
onClick=
{
destroy
}
>
{
t
(
"common.cancel"
)
}
</
Button
>
<
Button
color=
"primary"
disabled=
{
requestState
.
isLoading
}
onClick=
{
handleSaveBtnClick
}
>
{
t
(
"common.create"
)
}
</
Button
>
</
div
>
</
div
>
</
div
>
);
};
function
showCreateAccessTokenDialog
(
onConfirm
:
()
=>
void
)
{
generateDialog
(
{
className
:
"create-access-token-dialog"
,
dialogName
:
"create-access-token-dialog"
,
},
CreateAccessTokenDialog
,
{
onConfirm
,
},
);
}
export
default
showCreateAccessTokenDialog
;
import
{
Radio
,
RadioGroup
}
from
"@mui/joy"
;
import
{
Button
,
Input
}
from
"@usememos/mui"
;
import
{
XIcon
}
from
"lucide-react"
;
import
React
,
{
useState
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
userServiceClient
}
from
"@/grpcweb"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
useLoading
from
"@/hooks/useLoading"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
generateDialog
}
from
"./Dialog"
;
interface
Props
extends
DialogProps
{
onConfirm
:
()
=>
void
;
}
interface
State
{
description
:
string
;
expiration
:
number
;
}
const
CreateAccessTokenDialog
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
destroy
,
onConfirm
}
=
props
;
const
t
=
useTranslate
();
const
currentUser
=
useCurrentUser
();
const
[
state
,
setState
]
=
useState
({
description
:
""
,
expiration
:
3600
*
8
,
});
const
requestState
=
useLoading
(
false
);
const
expirationOptions
=
[
{
label
:
t
(
"setting.access-token-section.create-dialog.duration-8h"
),
value
:
3600
*
8
,
},
{
label
:
t
(
"setting.access-token-section.create-dialog.duration-1m"
),
value
:
3600
*
24
*
30
,
},
{
label
:
t
(
"setting.access-token-section.create-dialog.duration-never"
),
value
:
0
,
},
];
const
setPartialState
=
(
partialState
:
Partial
<
State
>
)
=>
{
setState
({
...
state
,
...
partialState
,
});
};
const
handleDescriptionInputChange
=
(
e
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
setPartialState
({
description
:
e
.
target
.
value
,
});
};
const
handleRoleInputChange
=
(
e
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
setPartialState
({
expiration
:
Number
(
e
.
target
.
value
),
});
};
const
handleSaveBtnClick
=
async
()
=>
{
if
(
!
state
.
description
)
{
toast
.
error
(
t
(
"message.description-is-required"
));
return
;
}
try
{
await
userServiceClient
.
createUserAccessToken
({
name
:
currentUser
.
name
,
description
:
state
.
description
,
expiresAt
:
state
.
expiration
?
new
Date
(
Date
.
now
()
+
state
.
expiration
*
1000
)
:
undefined
,
});
onConfirm
();
destroy
();
}
catch
(
error
:
any
)
{
toast
.
error
(
error
.
details
);
console
.
error
(
error
);
}
};
return
(
<
div
className=
"max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg"
>
<
div
className=
"flex flex-row justify-between items-center w-full mb-4 gap-2"
>
<
p
>
{
t
(
"setting.access-token-section.create-dialog.create-access-token"
)
}
</
p
>
<
Button
size=
"sm"
variant=
"plain"
onClick=
{
()
=>
destroy
()
}
>
<
XIcon
className=
"w-5 h-auto"
/>
</
Button
>
</
div
>
<
div
className=
"flex flex-col justify-start items-start !w-80"
>
<
div
className=
"w-full flex flex-col justify-start items-start mb-3"
>
<
span
className=
"mb-2"
>
{
t
(
"setting.access-token-section.create-dialog.description"
)
}
<
span
className=
"text-red-600"
>
*
</
span
>
</
span
>
<
div
className=
"relative w-full"
>
<
Input
className=
"w-full"
type=
"text"
placeholder=
{
t
(
"setting.access-token-section.create-dialog.some-description"
)
}
value=
{
state
.
description
}
onChange=
{
handleDescriptionInputChange
}
/>
</
div
>
</
div
>
<
div
className=
"w-full flex flex-col justify-start items-start mb-3"
>
<
span
className=
"mb-2"
>
{
t
(
"setting.access-token-section.create-dialog.expiration"
)
}
<
span
className=
"text-red-600"
>
*
</
span
>
</
span
>
<
div
className=
"w-full flex flex-row justify-start items-center text-base"
>
<
RadioGroup
orientation=
"horizontal"
value=
{
state
.
expiration
}
onChange=
{
handleRoleInputChange
}
>
{
expirationOptions
.
map
((
option
)
=>
(
<
Radio
key=
{
option
.
value
}
value=
{
option
.
value
}
checked=
{
state
.
expiration
===
option
.
value
}
label=
{
option
.
label
}
/>
))
}
</
RadioGroup
>
</
div
>
</
div
>
<
div
className=
"w-full flex flex-row justify-end items-center mt-4 space-x-2"
>
<
Button
variant=
"plain"
disabled=
{
requestState
.
isLoading
}
onClick=
{
destroy
}
>
{
t
(
"common.cancel"
)
}
</
Button
>
<
Button
color=
"primary"
disabled=
{
requestState
.
isLoading
}
onClick=
{
handleSaveBtnClick
}
>
{
t
(
"common.create"
)
}
</
Button
>
</
div
>
</
div
>
</
div
>
);
};
function
showCreateAccessTokenDialog
(
onConfirm
:
()
=>
void
)
{
generateDialog
(
{
className
:
"create-access-token-dialog"
,
dialogName
:
"create-access-token-dialog"
,
},
CreateAccessTokenDialog
,
{
onConfirm
,
},
);
}
export
default
showCreateAccessTokenDialog
;
web/src/components/CreateWebhookDialog.tsx
View file @
b770042a
import
{
Button
,
Input
}
from
"@usememos/mui"
;
import
{
XIcon
}
from
"lucide-react"
;
import
React
,
{
useEffect
,
useState
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
webhookServiceClient
}
from
"@/grpcweb"
;
import
useLoading
from
"@/hooks/useLoading"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
generateDialog
}
from
"./Dialog"
;
interface
Props
extends
DialogProps
{
webhookId
?:
number
;
onConfirm
:
()
=>
void
;
}
interface
State
{
name
:
string
;
url
:
string
;
}
const
CreateWebhookDialog
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
webhookId
,
destroy
,
onConfirm
}
=
props
;
const
t
=
useTranslate
();
const
[
state
,
setState
]
=
useState
({
name
:
""
,
url
:
""
,
});
const
requestState
=
useLoading
(
false
);
const
isCreating
=
webhookId
===
undefined
;
useEffect
(()
=>
{
if
(
webhookId
)
{
webhookServiceClient
.
getWebhook
({
id
:
webhookId
,
})
.
then
((
webhook
)
=>
{
setState
({
name
:
webhook
.
name
,
url
:
webhook
.
url
,
});
});
}
},
[]);
const
setPartialState
=
(
partialState
:
Partial
<
State
>
)
=>
{
setState
({
...
state
,
...
partialState
,
});
};
const
handleTitleInputChange
=
(
e
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
setPartialState
({
name
:
e
.
target
.
value
,
});
};
const
handleUrlInputChange
=
(
e
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
setPartialState
({
url
:
e
.
target
.
value
,
});
};
const
handleSaveBtnClick
=
async
()
=>
{
if
(
!
state
.
name
||
!
state
.
url
)
{
toast
.
error
(
t
(
"message.fill-all-required-fields"
));
return
;
}
try
{
if
(
isCreating
)
{
await
webhookServiceClient
.
createWebhook
({
name
:
state
.
name
,
url
:
state
.
url
,
});
}
else
{
await
webhookServiceClient
.
updateWebhook
({
webhook
:
{
id
:
webhookId
,
name
:
state
.
name
,
url
:
state
.
url
,
},
updateMask
:
[
"name"
,
"url"
],
});
}
onConfirm
();
destroy
();
}
catch
(
error
:
any
)
{
console
.
error
(
error
);
toast
.
error
(
error
.
details
);
}
};
return
(
<
div
className=
"max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg"
>
<
div
className=
"flex flex-row justify-between items-center mb-4 gap-2 w-full"
>
<
p
className=
"title-text"
>
{
isCreating
?
t
(
"setting.webhook-section.create-dialog.create-webhook"
)
:
t
(
"setting.webhook-section.create-dialog.edit-webhook"
)
}
</
p
>
<
Button
size=
"sm"
variant=
"plain"
onClick=
{
()
=>
destroy
()
}
>
<
XIcon
className=
"w-5 h-auto"
/>
</
Button
>
</
div
>
<
div
className=
"flex flex-col justify-start items-start !w-80"
>
<
div
className=
"w-full flex flex-col justify-start items-start mb-3"
>
<
span
className=
"mb-2"
>
{
t
(
"setting.webhook-section.create-dialog.title"
)
}
<
span
className=
"text-red-600"
>
*
</
span
>
</
span
>
<
div
className=
"relative w-full"
>
<
Input
className=
"w-full"
type=
"text"
placeholder=
{
t
(
"setting.webhook-section.create-dialog.an-easy-to-remember-name"
)
}
value=
{
state
.
name
}
onChange=
{
handleTitleInputChange
}
/>
</
div
>
</
div
>
<
div
className=
"w-full flex flex-col justify-start items-start mb-3"
>
<
span
className=
"mb-2"
>
{
t
(
"setting.webhook-section.create-dialog.payload-url"
)
}
<
span
className=
"text-red-600"
>
*
</
span
>
</
span
>
<
div
className=
"relative w-full"
>
<
Input
className=
"w-full"
type=
"text"
placeholder=
{
t
(
"setting.webhook-section.create-dialog.url-example-post-receive"
)
}
value=
{
state
.
url
}
onChange=
{
handleUrlInputChange
}
/>
</
div
>
</
div
>
<
div
className=
"w-full flex flex-row justify-end items-center mt-2 space-x-2"
>
<
Button
variant=
"plain"
disabled=
{
requestState
.
isLoading
}
onClick=
{
destroy
}
>
{
t
(
"common.cancel"
)
}
</
Button
>
<
Button
color=
"primary"
disabled=
{
requestState
.
isLoading
}
onClick=
{
handleSaveBtnClick
}
>
{
t
(
"common.create"
)
}
</
Button
>
</
div
>
</
div
>
</
div
>
);
};
function
showCreateWebhookDialog
(
onConfirm
:
()
=>
void
)
{
generateDialog
(
{
className
:
"create-webhook-dialog"
,
dialogName
:
"create-webhook-dialog"
,
},
CreateWebhookDialog
,
{
onConfirm
,
},
);
}
export
default
showCreateWebhookDialog
;
import
{
Button
,
Input
}
from
"@usememos/mui"
;
import
{
XIcon
}
from
"lucide-react"
;
import
React
,
{
useEffect
,
useState
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
webhookServiceClient
}
from
"@/grpcweb"
;
import
useLoading
from
"@/hooks/useLoading"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
generateDialog
}
from
"./Dialog"
;
interface
Props
extends
DialogProps
{
webhookId
?:
number
;
onConfirm
:
()
=>
void
;
}
interface
State
{
name
:
string
;
url
:
string
;
}
const
CreateWebhookDialog
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
webhookId
,
destroy
,
onConfirm
}
=
props
;
const
t
=
useTranslate
();
const
[
state
,
setState
]
=
useState
({
name
:
""
,
url
:
""
,
});
const
requestState
=
useLoading
(
false
);
const
isCreating
=
webhookId
===
undefined
;
useEffect
(()
=>
{
if
(
webhookId
)
{
webhookServiceClient
.
getWebhook
({
id
:
webhookId
,
})
.
then
((
webhook
)
=>
{
setState
({
name
:
webhook
.
name
,
url
:
webhook
.
url
,
});
});
}
},
[]);
const
setPartialState
=
(
partialState
:
Partial
<
State
>
)
=>
{
setState
({
...
state
,
...
partialState
,
});
};
const
handleTitleInputChange
=
(
e
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
setPartialState
({
name
:
e
.
target
.
value
,
});
};
const
handleUrlInputChange
=
(
e
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
setPartialState
({
url
:
e
.
target
.
value
,
});
};
const
handleSaveBtnClick
=
async
()
=>
{
if
(
!
state
.
name
||
!
state
.
url
)
{
toast
.
error
(
t
(
"message.fill-all-required-fields"
));
return
;
}
try
{
if
(
isCreating
)
{
await
webhookServiceClient
.
createWebhook
({
name
:
state
.
name
,
url
:
state
.
url
,
});
}
else
{
await
webhookServiceClient
.
updateWebhook
({
webhook
:
{
id
:
webhookId
,
name
:
state
.
name
,
url
:
state
.
url
,
},
updateMask
:
[
"name"
,
"url"
],
});
}
onConfirm
();
destroy
();
}
catch
(
error
:
any
)
{
console
.
error
(
error
);
toast
.
error
(
error
.
details
);
}
};
return
(
<
div
className=
"max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg"
>
<
div
className=
"flex flex-row justify-between items-center mb-4 gap-2 w-full"
>
<
p
className=
"title-text"
>
{
isCreating
?
t
(
"setting.webhook-section.create-dialog.create-webhook"
)
:
t
(
"setting.webhook-section.create-dialog.edit-webhook"
)
}
</
p
>
<
Button
size=
"sm"
variant=
"plain"
onClick=
{
()
=>
destroy
()
}
>
<
XIcon
className=
"w-5 h-auto"
/>
</
Button
>
</
div
>
<
div
className=
"flex flex-col justify-start items-start !w-80"
>
<
div
className=
"w-full flex flex-col justify-start items-start mb-3"
>
<
span
className=
"mb-2"
>
{
t
(
"setting.webhook-section.create-dialog.title"
)
}
<
span
className=
"text-red-600"
>
*
</
span
>
</
span
>
<
div
className=
"relative w-full"
>
<
Input
className=
"w-full"
type=
"text"
placeholder=
{
t
(
"setting.webhook-section.create-dialog.an-easy-to-remember-name"
)
}
value=
{
state
.
name
}
onChange=
{
handleTitleInputChange
}
/>
</
div
>
</
div
>
<
div
className=
"w-full flex flex-col justify-start items-start mb-3"
>
<
span
className=
"mb-2"
>
{
t
(
"setting.webhook-section.create-dialog.payload-url"
)
}
<
span
className=
"text-red-600"
>
*
</
span
>
</
span
>
<
div
className=
"relative w-full"
>
<
Input
className=
"w-full"
type=
"text"
placeholder=
{
t
(
"setting.webhook-section.create-dialog.url-example-post-receive"
)
}
value=
{
state
.
url
}
onChange=
{
handleUrlInputChange
}
/>
</
div
>
</
div
>
<
div
className=
"w-full flex flex-row justify-end items-center mt-2 space-x-2"
>
<
Button
variant=
"plain"
disabled=
{
requestState
.
isLoading
}
onClick=
{
destroy
}
>
{
t
(
"common.cancel"
)
}
</
Button
>
<
Button
color=
"primary"
disabled=
{
requestState
.
isLoading
}
onClick=
{
handleSaveBtnClick
}
>
{
t
(
"common.create"
)
}
</
Button
>
</
div
>
</
div
>
</
div
>
);
};
function
showCreateWebhookDialog
(
onConfirm
:
()
=>
void
)
{
generateDialog
(
{
className
:
"create-webhook-dialog"
,
dialogName
:
"create-webhook-dialog"
,
},
CreateWebhookDialog
,
{
onConfirm
,
},
);
}
export
default
showCreateWebhookDialog
;
web/src/components/LocaleSelect.tsx
View file @
b770042a
...
...
@@ -33,7 +33,7 @@ const LocaleSelect: FC<Props> = (props: Props) => {
</
Option
>
);
}
}
catch
(
error
)
{
}
catch
{
// do nth
}
...
...
web/src/components/MemoActionMenu.tsx
View file @
b770042a
...
...
@@ -77,7 +77,7 @@ const MemoActionMenu = (props: Props) => {
[
"pinned"
],
);
}
}
catch
(
error
)
{
}
catch
{
// do nth
}
};
...
...
@@ -108,7 +108,7 @@ const MemoActionMenu = (props: Props) => {
}
if
(
isInMemoDetailPage
)
{
memo
.
state
===
State
.
ARCHIVED
?
navigateTo
(
"/"
)
:
navigateTo
(
"/archived"
);
navigateTo
(
memo
.
state
===
State
.
ARCHIVED
?
"/"
:
"/archived"
);
}
memoUpdatedCallback
();
};
...
...
web/src/components/MemoContent/CodeBlock.tsx
View file @
b770042a
...
...
@@ -45,7 +45,7 @@ const CodeBlock: React.FC<Props> = ({ language, content }: Props) => {
language
:
formatedLanguage
,
}).
value
;
}
}
catch
(
error
)
{
}
catch
{
// Skip error and use default highlighted code.
}
...
...
web/src/components/MemoContent/LineBreak.tsx
View file @
b770042a
import
{
BaseProps
}
from
"./types"
;
interface
Props
extends
BaseProps
{}
const
LineBreak
:
React
.
FC
<
Props
>
=
()
=>
{
const
LineBreak
=
()
=>
{
return
<
br
/>;
};
...
...
web/src/components/MemoContent/Link.tsx
View file @
b770042a
...
...
@@ -14,7 +14,7 @@ const getFaviconWithGoogleS2 = (url: string) => {
try
{
const
urlObject
=
new
URL
(
url
);
return
`https://www.google.com/s2/favicons?sz=128&domain=
${
urlObject
.
hostname
}
`
;
}
catch
(
error
)
{
}
catch
{
return
undefined
;
}
};
...
...
web/src/components/MemoEditor/ActionButton/AddMemoRelationPopover.tsx
View file @
b770042a
This diff is collapsed.
Click to expand it.
web/src/components/MemoEditor/Editor/TagSuggestions.tsx
View file @
b770042a
...
...
@@ -86,7 +86,11 @@ const TagSuggestions = observer(({ editorRef, editorActions }: Props) => {
const
caretCordinates
=
getCaretCoordinates
(
editor
,
index
);
caretCordinates
.
top
-=
editor
.
scrollTop
;
isActive
?
setPosition
(
caretCordinates
)
:
hide
();
if
(
isActive
)
{
setPosition
(
caretCordinates
);
}
else
{
hide
();
}
};
const
listenersAreRegisteredRef
=
useRef
(
false
);
...
...
web/src/components/MemoRelationForceGraph/types.ts
View file @
b770042a
...
...
@@ -4,6 +4,7 @@ export interface NodeType {
memo
:
MemoRelation_Memo
;
}
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export
interface
LinkType
{
// ...add more additional properties relevant to the link here.
}
web/src/components/ReactionSelector.tsx
View file @
b770042a
...
...
@@ -49,7 +49,7 @@ const ReactionSelector = (props: Props) => {
});
}
await
memoStore
.
getOrFetchMemoByName
(
memo
.
name
,
{
skipCache
:
true
});
}
catch
(
error
)
{
}
catch
{
// skip error.
}
setOpen
(
false
);
...
...
web/src/components/ReactionView.tsx
View file @
b770042a
...
...
@@ -58,7 +58,7 @@ const ReactionView = (props: Props) => {
await
memoServiceClient
.
deleteMemoReaction
({
id
:
reaction
.
id
});
}
}
}
catch
(
error
)
{
}
catch
{
// Skip error.
}
await
memoStore
.
getOrFetchMemoByName
(
memo
.
name
,
{
skipCache
:
true
});
...
...
web/src/components/ui/Popover.tsx
View file @
b770042a
...
...
@@ -9,7 +9,6 @@ const PopoverTrigger = PopoverPrimitive.Trigger;
const
PopoverContent
=
React
.
forwardRef
<
React
.
ElementRef
<
typeof
PopoverPrimitive
.
Content
>
,
React
.
ComponentPropsWithoutRef
<
typeof
PopoverPrimitive
.
Content
>
// eslint-disable-next-line react/prop-types
>
(({
className
,
align
=
"center"
,
sideOffset
=
4
,
...
props
},
ref
)
=>
(
<
PopoverPrimitive
.
Portal
>
<
PopoverPrimitive
.
Content
...
...
web/src/helpers/utils.ts
View file @
b770042a
...
...
@@ -25,7 +25,7 @@ export const isValidUrl = (url: string): boolean => {
try
{
new
URL
(
url
);
return
true
;
}
catch
(
err
)
{
}
catch
{
return
false
;
}
};
...
...
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