Skip to content
Open
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
13 changes: 10 additions & 3 deletions application/client/babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,23 @@ module.exports = {
[
"@babel/preset-env",
{
targets: "ie 11",
corejs: "3",
targets: {"chrome": "145"},
// corejs: "3",
modules: "commonjs",
useBuiltIns: false,
},
// {
// targets: "ie 11",
// corejs: "3",
// modules: "commonjs",
// useBuiltIns: false,
// },
],
[
"@babel/preset-react",
{
development: true,
development: false,
// development: true,
runtime: "automatic",
},
],
Expand Down
3 changes: 2 additions & 1 deletion application/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"license": "MPL-2.0",
"author": "CyberAgent, Inc.",
"scripts": {
"build": "NODE_ENV=development webpack",
"build": "NODE_ENV=production webpack",
"typecheck": "tsc"
},
"dependencies": {
Expand All @@ -20,6 +20,7 @@
"classnames": "2.5.1",
"common-tags": "1.8.2",
"core-js": "3.45.1",
"dayjs": "1.11.20",
"encoding-japanese": "2.2.0",
"fast-average-color": "9.5.0",
"gifler": "github:themadcreator/gifler#v0.3.0",
Expand Down
5 changes: 4 additions & 1 deletion application/client/src/auth/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ export const validate = (values: AuthFormData): FormErrors<AuthFormData> => {
errors.name = "名前を入力してください";
}

if (/^(?:[^\P{Letter}&&\P{Number}]*){16,}$/v.test(normalizedPassword)) {
// if (/^(?:[^\P{Letter}&&\P{Number}]*){16,}$/v.test(normalizedPassword)) {
// errors.password = "パスワードには記号を含める必要があります";
// }
if (/^(?:[^\P{Letter}&&\P{Number}]*)$/v.test(normalizedPassword)) {
errors.password = "パスワードには記号を含める必要があります";
}
if (normalizedPassword.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import classNames from "classnames";
import { useLocation } from "react-router";
import { Link, useLocation } from "react-router";

import { Link } from "@web-speed-hackathon-2026/client/src/components/foundation/Link";
// import { Link } from "@web-speed-hackathon-2026/client/src/components/foundation/Link";

interface Props {
badge?: React.ReactNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { Field, formValueSelector, InjectedFormProps, reduxForm } from "redux-fo
import { AuthFormData } from "@web-speed-hackathon-2026/client/src/auth/types";
import { validate } from "@web-speed-hackathon-2026/client/src/auth/validation";
import { FormInputField } from "@web-speed-hackathon-2026/client/src/components/foundation/FormInputField";
import { Link } from "@web-speed-hackathon-2026/client/src/components/foundation/Link";
// import { Link } from "@web-speed-hackathon-2026/client/src/components/foundation/Link";
import { ModalErrorMessage } from "@web-speed-hackathon-2026/client/src/components/modal/ModalErrorMessage";
import { ModalSubmitButton } from "@web-speed-hackathon-2026/client/src/components/modal/ModalSubmitButton";
import { Link } from "react-router";

interface Props {
onRequestCloseModal: () => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import moment from "moment";
import { useCallback, useEffect, useState } from "react";
// import moment from "moment";
import 'dayjs/locale/ja';
import dayjs, { locale, extend } from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';import { useCallback, useEffect, useState } from "react";

import { Button } from "@web-speed-hackathon-2026/client/src/components/foundation/Button";
import { FontAwesomeIcon } from "@web-speed-hackathon-2026/client/src/components/foundation/FontAwesomeIcon";
import { Link } from "@web-speed-hackathon-2026/client/src/components/foundation/Link";
// import { Link } from "@web-speed-hackathon-2026/client/src/components/foundation/Link";
import { useWs } from "@web-speed-hackathon-2026/client/src/hooks/use_ws";
import { fetchJSON } from "@web-speed-hackathon-2026/client/src/utils/fetchers";
import { getProfileImagePath } from "@web-speed-hackathon-2026/client/src/utils/get_path";
import { Link } from 'react-router';

interface Props {
activeUser: Models.User;
Expand Down Expand Up @@ -45,6 +48,9 @@ export const DirectMessageListPage = ({ activeUser, newDmModalId }: Props) => {
return null;
}

locale('ja');
extend(relativeTime);

return (
<section>
<header className="border-cax-border flex flex-col gap-4 border-b px-4 pt-6 pb-4">
Expand Down Expand Up @@ -100,7 +106,8 @@ export const DirectMessageListPage = ({ activeUser, newDmModalId }: Props) => {
className="text-cax-text-subtle text-xs"
dateTime={lastMessage.createdAt}
>
{moment(lastMessage.createdAt).locale("ja").fromNow()}
{/* {moment(lastMessage.createdAt).locale("ja").fromNow()} */}
{dayjs(lastMessage.createdAt).locale("ja").fromNow()}
</time>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import classNames from "classnames";
import moment from "moment";
// import moment from "moment";
import {
ChangeEvent,
useCallback,
Expand All @@ -14,6 +14,7 @@ import {
import { FontAwesomeIcon } from "@web-speed-hackathon-2026/client/src/components/foundation/FontAwesomeIcon";
import { DirectMessageFormData } from "@web-speed-hackathon-2026/client/src/direct_message/types";
import { getProfileImagePath } from "@web-speed-hackathon-2026/client/src/utils/get_path";
import dayjs from "dayjs";

interface Props {
conversationError: Error | null;
Expand Down Expand Up @@ -44,13 +45,17 @@ export const DirectMessagePage = ({
const textAreaRows = Math.min((text || "").split("\n").length, 5);
const isInvalid = text.trim().length === 0;
const scrollHeightRef = useRef(0);
const [isTyping, setIsTyping] = useState(false);

const handleChange = useCallback(
(event: ChangeEvent<HTMLTextAreaElement>) => {
setText(event.target.value);
onTyping();
if(!isTyping) {
onTyping();
setIsTyping(true)
}
},
[onTyping],
[isTyping],
);

const handleKeyDown = useCallback(
Expand Down Expand Up @@ -80,7 +85,8 @@ export const DirectMessagePage = ({
scrollHeightRef.current = height;
window.scrollTo(0, height);
}
}, 1);
setIsTyping(false);
}, 5000);

return () => clearInterval(id);
}, []);
Expand Down Expand Up @@ -141,7 +147,8 @@ export const DirectMessagePage = ({
</p>
<div className="flex gap-1 text-xs">
<time dateTime={message.createdAt}>
{moment(message.createdAt).locale("ja").format("HH:mm")}
{/* {moment(message.createdAt).locale("ja").format("HH:mm")} */}
{dayjs(message.createdAt).locale("ja").format("HH:mm")}
</time>
{isActiveUserSend && message.isRead && (
<span className="text-cax-text-muted">既読</span>
Expand Down
28 changes: 14 additions & 14 deletions application/client/src/components/foundation/SoundWaveSVG.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import _ from "lodash";
import { chunk, map, mean, zip, max } from 'es-toolkit/compat';
import { useEffect, useRef, useState } from "react";

interface ParsedData {
max: number;
maxNum: number;
peaks: number[];
}

Expand All @@ -12,20 +12,20 @@ async function calculate(data: ArrayBuffer): Promise<ParsedData> {
// 音声をデコードする
const buffer = await audioCtx.decodeAudioData(data.slice(0));
// 左の音声データの絶対値を取る
const leftData = _.map(buffer.getChannelData(0), Math.abs);
const leftData = map(buffer.getChannelData(0), Math.abs);
// 右の音声データの絶対値を取る
const rightData = _.map(buffer.getChannelData(1), Math.abs);
const rightData = map(buffer.getChannelData(1), Math.abs);

// 左右の音声データの平均を取る
const normalized = _.map(_.zip(leftData, rightData), _.mean);
const normalized = map(zip(leftData, rightData), mean);
// 100 個の chunk に分ける
const chunks = _.chunk(normalized, Math.ceil(normalized.length / 100));
const chunks = chunk(normalized, Math.ceil(normalized.length / 100));
// chunk ごとに平均を取る
const peaks = _.map(chunks, _.mean);
const peaks = map(chunks, mean);
// chunk の平均の中から最大値を取る
const max = _.max(peaks) ?? 0;
const maxNum = max(peaks) ?? 0;

return { max, peaks };
return { maxNum, peaks };
}

interface Props {
Expand All @@ -34,21 +34,21 @@ interface Props {

export const SoundWaveSVG = ({ soundData }: Props) => {
const uniqueIdRef = useRef(Math.random().toString(16));
const [{ max, peaks }, setPeaks] = useState<ParsedData>({
max: 0,
const [{ maxNum, peaks }, setPeaks] = useState<ParsedData>({
maxNum: 0,
peaks: [],
});

useEffect(() => {
calculate(soundData).then(({ max, peaks }) => {
setPeaks({ max, peaks });
calculate(soundData).then(({ maxNum, peaks }) => {
setPeaks({ maxNum, peaks });
});
}, [soundData]);

return (
<svg className="h-full w-full" preserveAspectRatio="none" viewBox="0 0 100 1">
{peaks.map((peak, idx) => {
const ratio = peak / max;
const ratio = peak / maxNum;
return (
<rect
key={`${uniqueIdRef.current}#${idx}`}
Expand Down
11 changes: 8 additions & 3 deletions application/client/src/components/post/CommentItem.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import moment from "moment";
// import moment from "moment";

import { Link } from "@web-speed-hackathon-2026/client/src/components/foundation/Link";
// import { Link } from "@web-speed-hackathon-2026/client/src/components/foundation/Link";
import { TranslatableText } from "@web-speed-hackathon-2026/client/src/components/post/TranslatableText";
import { getProfileImagePath } from "@web-speed-hackathon-2026/client/src/utils/get_path";
import dayjs from "dayjs";
import { Link } from "react-router";

interface Props {
comment: Models.Comment;
Expand Down Expand Up @@ -42,8 +44,11 @@ export const CommentItem = ({ comment }: Props) => {
<TranslatableText text={comment.text} />
</div>
<p className="text-cax-text-muted pt-1 text-xs">
<time dateTime={moment(comment.createdAt).toISOString()}>
{/* <time dateTime={moment(comment.createdAt).toISOString()}>
{moment(comment.createdAt).locale("ja").format("LL")}
</time> */}
<time dateTime={dayjs(comment.createdAt).toISOString()}>
{dayjs(comment.createdAt).locale("ja").format('YYYY年MM月DD日')}
</time>
</p>
</div>
Expand Down
11 changes: 8 additions & 3 deletions application/client/src/components/post/PostItem.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import moment from "moment";
// import moment from "moment";

import { Link } from "@web-speed-hackathon-2026/client/src/components/foundation/Link";
// import { Link } from "@web-speed-hackathon-2026/client/src/components/foundation/Link";
import { ImageArea } from "@web-speed-hackathon-2026/client/src/components/post/ImageArea";
import { MovieArea } from "@web-speed-hackathon-2026/client/src/components/post/MovieArea";
import { SoundArea } from "@web-speed-hackathon-2026/client/src/components/post/SoundArea";
import { TranslatableText } from "@web-speed-hackathon-2026/client/src/components/post/TranslatableText";
import { getProfileImagePath } from "@web-speed-hackathon-2026/client/src/utils/get_path";
import dayjs from "dayjs";
import { Link } from "react-router";

interface Props {
post: Models.Post;
Expand Down Expand Up @@ -67,8 +69,11 @@ export const PostItem = ({ post }: Props) => {
) : null}
<p className="mt-2 text-sm sm:mt-4">
<Link className="text-cax-text-muted hover:underline" to={`/posts/${post.id}`}>
<time dateTime={moment(post.createdAt).toISOString()}>
{/* <time dateTime={moment(post.createdAt).toISOString()}>
{moment(post.createdAt).locale("ja").format("LL")}
</time> */}
<time dateTime={dayjs(post.createdAt).toISOString()}>
{dayjs(post.createdAt).locale("ja").format('YYYY年MM月DD日')}
</time>
</Link>
</p>
Expand Down
8 changes: 6 additions & 2 deletions application/client/src/components/timeline/TimelineItem.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import moment from "moment";
// import moment from "moment";
import { MouseEventHandler, useCallback } from "react";
import { Link, useNavigate } from "react-router";

Expand All @@ -7,6 +7,7 @@ import { MovieArea } from "@web-speed-hackathon-2026/client/src/components/post/
import { SoundArea } from "@web-speed-hackathon-2026/client/src/components/post/SoundArea";
import { TranslatableText } from "@web-speed-hackathon-2026/client/src/components/post/TranslatableText";
import { getProfileImagePath } from "@web-speed-hackathon-2026/client/src/utils/get_path";
import dayjs from "dayjs";

const isClickedAnchorOrButton = (target: EventTarget | null, currentTarget: Element): boolean => {
while (target !== null && target instanceof Element) {
Expand Down Expand Up @@ -76,8 +77,11 @@ export const TimelineItem = ({ post }: Props) => {
</Link>
<span className="text-cax-text-muted pr-1">-</span>
<Link className="text-cax-text-muted pr-1 hover:underline" to={`/posts/${post.id}`}>
<time dateTime={moment(post.createdAt).toISOString()}>
{/* <time dateTime={moment(post.createdAt).toISOString()}>
{moment(post.createdAt).locale("ja").format("LL")}
</time> */}
<time dateTime={dayjs(post.createdAt).toISOString()}>
{dayjs(post.createdAt).locale("ja").format('YYYY年MM月DD日')}
</time>
</Link>
</p>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { FastAverageColor } from "fast-average-color";
import moment from "moment";
// import moment from "moment";
import { ReactEventHandler, useCallback, useState } from "react";

import { FontAwesomeIcon } from "@web-speed-hackathon-2026/client/src/components/foundation/FontAwesomeIcon";
import { getProfileImagePath } from "@web-speed-hackathon-2026/client/src/utils/get_path";
import dayjs from "dayjs";

interface Props {
user: Models.User;
Expand Down Expand Up @@ -43,8 +44,11 @@ export const UserProfileHeader = ({ user }: Props) => {
<FontAwesomeIcon iconType="calendar-alt" styleType="regular" />
</span>
<span>
<time dateTime={moment(user.createdAt).toISOString()}>
{/* <time dateTime={moment(user.createdAt).toISOString()}>
{moment(user.createdAt).locale("ja").format("LL")}
</time> */}
<time dateTime={dayjs(user.createdAt).toISOString()}>
{dayjs(user.createdAt).locale("ja").format('YYYY年MM月DD日')}
</time>
からサービスを利用しています
</span>
Expand Down
9 changes: 4 additions & 5 deletions application/client/src/utils/bm25_search.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BM25 } from "bayesian-bm25";
import { zipWith } from "es-toolkit/compat";
import type { Tokenizer, IpadicFeatures } from "kuromoji";
import _ from "lodash";

const STOP_POS = new Set(["助詞", "助動詞", "記号"]);

Expand Down Expand Up @@ -28,15 +28,14 @@ export function filterSuggestionsBM25(
const tokenizedCandidates = candidates.map((c) => extractTokens(tokenizer.tokenize(c)));
bm25.index(tokenizedCandidates);

const results = _.zipWith(candidates, bm25.getScores(queryTokens), (text, score) => {
const results = zipWith(candidates, bm25.getScores(queryTokens), (text, score) => {
return { text, score };
});

// スコアが高い(=類似度が高い)ものが下に来るように、上位10件を取得する
return _(results)
return results
.filter((s) => s.score > 0)
.sortBy(["score"])
.sort((a,b) => b.score - a.score)
.slice(-10)
.map((s) => s.text)
.value();
}
5 changes: 3 additions & 2 deletions application/client/src/utils/convert_image.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { initializeImageMagick, ImageMagick, MagickFormat } from "@imagemagick/magick-wasm";
import magickWasm from "@imagemagick/magick-wasm/magick.wasm?binary";
// import magickWasm from "@imagemagick/magick-wasm/magick.wasm?binary";
import { dump, insert, ImageIFD } from "piexifjs";

interface Options {
extension: MagickFormat;
}

export async function convertImage(file: File, options: Options): Promise<Blob> {
await initializeImageMagick(magickWasm);
await initializeImageMagick(new ImageMagick());
// await initializeImageMagick(magickWasm);

const byteArray = new Uint8Array(await file.arrayBuffer());

Expand Down
Loading
Loading