mirror of
https://github.com/docmost/docmost.git
synced 2026-05-08 15:23:07 +08:00
Compare commits
5 Commits
react-email
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c9fa6e20b3 | |||
| ec51ca7815 | |||
| 2b63137217 | |||
| 3227bc6059 | |||
| 73dc62bca3 |
@@ -870,6 +870,8 @@
|
||||
"Previous 7 days": "Previous 7 days",
|
||||
"Previous 30 days": "Previous 30 days",
|
||||
"Search chats...": "Search chats...",
|
||||
"Search chats": "Search chats",
|
||||
"Ask anything... Use @ to mention pages": "Ask anything... Use @ to mention pages",
|
||||
"Start a new chat to see it here.": "Start a new chat to see it here.",
|
||||
"Summarize this page": "Summarize this page",
|
||||
"Toggle AI Chat": "Toggle AI Chat",
|
||||
|
||||
@@ -137,7 +137,8 @@ export default function AiChatSidebar() {
|
||||
|
||||
<TextInput
|
||||
className={classes.searchInput}
|
||||
placeholder="Search chats..."
|
||||
placeholder={t("Search chats...")}
|
||||
aria-label={t("Search chats")}
|
||||
leftSection={<IconSearch size={14} />}
|
||||
size="xs"
|
||||
value={search}
|
||||
|
||||
@@ -178,6 +178,7 @@ export default function AsideChatPanel() {
|
||||
href="/ai"
|
||||
variant="subtle"
|
||||
color="dark"
|
||||
aria-label={t("New chat")}
|
||||
onClick={handleNewChat}
|
||||
>
|
||||
<IconPlus size={20} stroke={1.75} />
|
||||
@@ -185,13 +186,23 @@ export default function AsideChatPanel() {
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label={t("Open full page")} openDelay={250}>
|
||||
<ActionIcon variant="subtle" color="dark" onClick={handleExpand}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="dark"
|
||||
aria-label={t("Open full page")}
|
||||
onClick={handleExpand}
|
||||
>
|
||||
<IconArrowsDiagonal size={18} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label={t("Close")} openDelay={250}>
|
||||
<ActionIcon variant="subtle" color="dark" onClick={handleClose}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="dark"
|
||||
aria-label={t("Close")}
|
||||
onClick={handleClose}
|
||||
>
|
||||
<IconX size={20} stroke={1.75} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
@@ -225,6 +225,10 @@ export default function ChatInput({
|
||||
}),
|
||||
],
|
||||
editorProps: {
|
||||
attributes: {
|
||||
"aria-label": placeholder || t("Ask anything... Use @ to mention pages"),
|
||||
"aria-multiline": "true",
|
||||
},
|
||||
handleDOMEvents: {
|
||||
keydown: (_view, event) => {
|
||||
if (
|
||||
@@ -275,6 +279,8 @@ export default function ChatInput({
|
||||
type="file"
|
||||
accept={ACCEPTED_FILE_TYPES}
|
||||
multiple
|
||||
aria-label={t("Add files")}
|
||||
tabIndex={-1}
|
||||
style={{ display: "none" }}
|
||||
onChange={(e) => handleFileSelect(e.target.files)}
|
||||
/>
|
||||
|
||||
@@ -31,7 +31,16 @@ export default function ChatToolGroup({ toolCalls, isStreaming }: Props) {
|
||||
<div className={classes.toolGroup}>
|
||||
<div
|
||||
className={classes.toolGroupHeader}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-expanded={expanded}
|
||||
onClick={() => setExpanded((prev) => !prev)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
setExpanded((prev) => !prev);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{activeLabel ? (
|
||||
<IconLoader2 size={12} className={classes.processingSpinner} />
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
|
||||
color: var(--mantine-color-dimmed);
|
||||
margin-bottom: var(--mantine-spacing-xs);
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
.suggestionsLabel {
|
||||
font-size: var(--mantine-font-size-xs);
|
||||
font-weight: 500;
|
||||
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));
|
||||
color: var(--mantine-color-dimmed);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: var(--mantine-spacing-sm);
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
margin-top: 6px;
|
||||
text-align: center;
|
||||
font-size: var(--mantine-font-size-xs);
|
||||
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));
|
||||
color: var(--mantine-color-dimmed);
|
||||
}
|
||||
|
||||
.attachmentChips {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
padding: 4px var(--mantine-spacing-xs);
|
||||
font-size: var(--mantine-font-size-xs);
|
||||
font-weight: 600;
|
||||
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
|
||||
color: var(--mantine-color-dimmed);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
|
||||
.chatItemDate {
|
||||
font-size: var(--mantine-font-size-xs);
|
||||
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));
|
||||
color: var(--mantine-color-dimmed);
|
||||
white-space: nowrap;
|
||||
transition: opacity 150ms;
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
||||
{
|
||||
title: "Numbered list",
|
||||
description: "Create a list with numbering.",
|
||||
searchTerms: ["numbered", "ordered", "list"],
|
||||
searchTerms: ["numbered", "ordered", "list", "ol"],
|
||||
icon: IconListNumbers,
|
||||
command: ({ editor, range }: CommandProps) => {
|
||||
editor.chain().focus().deleteRange(range).toggleOrderedList().run();
|
||||
@@ -471,7 +471,14 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
||||
{
|
||||
title: "Subpages (Child pages)",
|
||||
description: "List all subpages of the current page",
|
||||
searchTerms: ["subpages", "child", "children", "nested", "hierarchy"],
|
||||
searchTerms: [
|
||||
"subpages",
|
||||
"child",
|
||||
"children",
|
||||
"nested",
|
||||
"hierarchy",
|
||||
"toc",
|
||||
],
|
||||
icon: IconSitemap,
|
||||
command: ({ editor, range }: CommandProps) => {
|
||||
editor.chain().focus().deleteRange(range).insertSubpages().run();
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
.subtitle {
|
||||
font-size: var(--mantine-font-size-sm);
|
||||
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
|
||||
color: var(--mantine-color-dimmed);
|
||||
text-align: center;
|
||||
margin-top: 6px;
|
||||
margin-bottom: var(--mantine-spacing-lg);
|
||||
|
||||
@@ -134,6 +134,17 @@ export class EnvironmentService {
|
||||
return this.configService.get<string>('MAIL_FROM_NAME', 'Docmost');
|
||||
}
|
||||
|
||||
getMailBlockedRecipientDomains(): string[] {
|
||||
const raw = this.configService.get<string>(
|
||||
'MAIL_BLOCKED_RECIPIENT_DOMAINS',
|
||||
'',
|
||||
);
|
||||
return raw
|
||||
.split(',')
|
||||
.map((d) => d.trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
getSmtpHost(): string {
|
||||
return this.configService.get<string>('SMTP_HOST');
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ export class MailService {
|
||||
) {}
|
||||
|
||||
async sendEmail(message: MailMessage): Promise<void> {
|
||||
if (this.isRecipientBlocked(message.to)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.template) {
|
||||
// in case this method is used directly. we do not send the tsx template from queue
|
||||
message.html = await render(message.template, {
|
||||
@@ -35,6 +39,10 @@ export class MailService {
|
||||
}
|
||||
|
||||
async sendToQueue(message: MailMessage): Promise<void> {
|
||||
if (this.isRecipientBlocked(message.to)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.template) {
|
||||
// transform the React object because it gets lost when sent via the queue
|
||||
message.html = await render(message.template, {
|
||||
@@ -47,4 +55,11 @@ export class MailService {
|
||||
}
|
||||
await this.emailQueue.add(QueueJob.SEND_EMAIL, message);
|
||||
}
|
||||
|
||||
private isRecipientBlocked(to: string): boolean {
|
||||
const blocked = this.environmentService.getMailBlockedRecipientDomains();
|
||||
if (blocked.length === 0) return false;
|
||||
const domain = to?.split('@')[1]?.toLowerCase();
|
||||
return !!domain && blocked.includes(domain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,14 @@ async function bootstrap() {
|
||||
await app.register(fastifyMultipart);
|
||||
await app.register(fastifyCookie);
|
||||
|
||||
app
|
||||
.getHttpAdapter()
|
||||
.getInstance()
|
||||
.addHook('onRequest', (request, _reply, done) => {
|
||||
(request.raw as any).ip = request.ip;
|
||||
done();
|
||||
});
|
||||
|
||||
app
|
||||
.getHttpAdapter()
|
||||
.getInstance()
|
||||
|
||||
Reference in New Issue
Block a user