Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/client/src/components/channels/Channel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@
{/if}
</div>

<MessageList channelId={selectedChannelId} bind:replyingTo />
<MessageList {organizationId} channelId={selectedChannelId} bind:replyingTo />
<MessageInput channelId={selectedChannelId} {organizationId} bind:replyingTo />
64 changes: 62 additions & 2 deletions packages/client/src/components/chat/MessageInput.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
<script lang="ts">
import { api, type Id } from "@packages/convex";
import type { Doc } from "@packages/convex/src/convex/_generated/dataModel";
import { useQuery } from "convex-svelte";
import { useConvexClient, useQuery } from "convex-svelte";
import FilePreview from "~/features/files/upload/FilePreview.svelte";
import FileSelector from "~/features/files/upload/Selector.svelte";
import { FileUploader } from "~/features/files/upload/uploader.svelte";
import MdiClose from "~/icons/mdi-close.svelte";
import { useMutation } from "~/lib/useMutation.svelte.ts";
import EmojiPalette from "./EmojiPalette.svelte";
import VoteMaker from "./VoteMaker.svelte";

interface Props {
organizationId: Id<"organizations">;
Expand All @@ -17,18 +18,45 @@

let { channelId, organizationId, replyingTo = $bindable() }: Props = $props();

type Vote = {
title: string;
maxVotes: number;
voteOptions: Array<string>;
voters: Array<{
userId: Id<"users">;
votedOptions: Array<number>;
}>;
};

const sendMessageMutation = useMutation(api.messages.send);
const identity = useQuery(api.users.me, {});
const convex = useConvexClient();

let messageContent = $state("");
let authorName = $state("");
let showEmojiPalette = $state(false);
let showFileSelector = $state(false);
let attachedFiles = $state<File[]>([]);

let showVoteMaker = $state(false);
let vote = $state<Vote>({
title: "",
maxVotes: 1,
voteOptions: [],
voters: [],
});

const personalization = useQuery(api.personalization.getPersonalization, {
organizationId: organizationId,
});

$effect(() => {
if (identity?.data && !authorName) {
authorName = identity.data.name ?? identity.data.email ?? "匿名";
authorName =
personalization.data?.nickname ??
identity.data.name ??
identity.data.email ??
"匿名";
}
});

Expand All @@ -43,18 +71,34 @@
(it) => it.id,
);

let voteId: Id<"votes"> | undefined;
if (vote.title.trim() && vote.voteOptions.length !== 0) {
voteId = await convex.mutation(api.vote.addVote, {
title: vote.title,
maxVotes: vote.maxVotes,
voteOptions: vote.voteOptions,
});
}

await sendMessageMutation.run({
channelId,
content: messageContent.trim() || "",
author: authorName.trim() || "匿名",
parentId: replyingTo?._id ?? undefined,
attachments,
vote: voteId,
});

messageContent = "";
attachedFiles = [];
replyingTo = null;
showFileSelector = false;
vote = {
title: "",
maxVotes: 1,
voteOptions: [],
voters: [],
};
}

function handleKeyPress(event: KeyboardEvent) {
Expand All @@ -67,6 +111,10 @@
function toggleFileUploader() {
showFileSelector = !showFileSelector;
}

function toggleVoteMaker() {
showVoteMaker = !showVoteMaker;
}
</script>

<div class="border-base-300 bg-base-100 space-y-4 border-t p-4">
Expand Down Expand Up @@ -116,6 +164,10 @@
/>
{/if}

{#if showVoteMaker}
<VoteMaker bind:vote />
{/if}

<div class="flex gap-2">
<input
type="text"
Expand Down Expand Up @@ -158,6 +210,14 @@
</svg>
{showFileSelector ? "キャンセル" : "ファイル添付"}
</button>
<button
class="btn btn-ghost btn-sm"
onclick={toggleVoteMaker}
title="投票を作成"
type="button"
>
{showVoteMaker ? "キャンセル" : "投票を作成"}
</button>
</div>
</div>

Expand Down
9 changes: 7 additions & 2 deletions packages/client/src/components/chat/MessageList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
import MessageDropdown from "./MessageDropdown.svelte";
import ReactionButtons from "./ReactionButtons.svelte";
import ReactionList from "./ReactionList.svelte";
import VoteViewer from "./VoteViewer.svelte";

interface Props {
organizationId: Id<"organizations">;
channelId: Id<"channels">;
replyingTo: Doc<"messages"> | null;
}

let { channelId, replyingTo = $bindable() }: Props = $props();
let { organizationId, channelId, replyingTo = $bindable() }: Props = $props();

const messages = useQuery(api.messages.list, () => ({
channelId,
Expand Down Expand Up @@ -71,7 +73,7 @@
{#if messages.data}
{#each messages.data as message (message._id)}
{#snippet reactionListSnippet()}
<ReactionList messageId={message._id} />
<ReactionList {organizationId} messageId={message._id} />
{/snippet}

{#snippet dropdownContent()}
Expand Down Expand Up @@ -168,6 +170,9 @@
{/each}
</div>
{/if}
{#if message.vote}
<VoteViewer voteId={message.vote} />
{/if}
<div
class="bg-base-100 absolute top-4 right-4 -translate-y-1/2 rounded-md border opacity-0 group-hover:opacity-100"
>
Expand Down
10 changes: 8 additions & 2 deletions packages/client/src/components/chat/ReactionList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import { useQuery } from "convex-svelte";

interface Props {
organizationId: Id<"organizations">;
messageId: Id<"messages">;
}

let { messageId }: Props = $props();
let { organizationId, messageId }: Props = $props();

const reactions = useQuery(api.messages.getReactions, () => ({ messageId }));

Expand All @@ -30,8 +31,13 @@
reactions.data ? [...new Set(reactions.data.map((r) => r.userId))] : [],
);

const userNamesById = useQuery(api.users.getUserNames, () => ({
// const userNamesById = useQuery(api.users.getUserNames, () => ({
// userIds: allUserIdsInReactions,
// }));

const userNamesById = useQuery(api.users.getUserNicknames, () => ({
userIds: allUserIdsInReactions,
organizationId: organizationId,
}));

function toggleUserList(emoji: string) {
Expand Down
71 changes: 71 additions & 0 deletions packages/client/src/components/chat/VoteMaker.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<script lang="ts">
import { type Id } from "@packages/convex";

type Vote = {
title: string;
maxVotes: number;
voteOptions: Array<string>;
voters: Array<{
userId: Id<"users">;
votedOptions: Array<number>;
}>;
};
interface Props {
vote: Vote;
}
let { vote = $bindable() }: Props = $props();

let newOption = $state("");
</script>

<div class="m-1 flex items-center">
<p class="text">投票のタイトル:</p>
<input
type="text"
class="input input-sm input-bordered ml-2 w-128"
bind:value={vote.title}
/>
</div>

<div class="m-1 flex items-center">
<p class="text">一人が投票できる最大数:</p>
<input
type="number"
class="input input-sm input-bordered ml-2 w-32"
bind:value={vote.maxVotes}
onblur={() => {
vote.maxVotes = vote.maxVotes ?? 0;
}}
/>
</div>
<div class="max-h-32 overflow-auto">
{#each vote.voteOptions as option, i}
<div class="m-1 flex items-center">
<p class="text">{i}:{option}</p>
<button
class="btn btn-secondary ml-2"
onclick={() => {
vote.voteOptions.splice(i, 1);
}}>削除</button
>
</div>
{/each}
</div>

<div class="m-1 flex items-center">
<p class="text">選択肢を追加:</p>
<input
type="text"
class="input input-sm input-bordered ml-2 w-128"
bind:value={newOption}
/>
<button
class="btn btn-primary ml-2"
onclick={() => {
if (newOption.trim()) {
vote.voteOptions.push(newOption);
newOption = "";
}
}}>追加</button
>
</div>
Loading
Loading