tangled
alpha
login
or
join now
linkat.blue
/
linkat
6
fork
atom
Create your Link in Bio for Bluesky
6
fork
atom
overview
issues
pulls
pipelines
Aboutページ追加 (#26)
authored by
mkizka.dev
and committed by
GitHub
1 year ago
a3e42bb5
9d52bcf5
+252
-8
8 changed files
expand all
collapse all
unified
split
app
i18n
locales
en.json
ja.json
routes
_index.tsx
about.tsx
utils
env.ts
package.json
pnpm-lock.yaml
tailwind.config.ts
+3
app/i18n/locales/en.json
···
86
86
"logout-button": {
87
87
"text": "Logout",
88
88
"confirm-message": "Are you sure you want to log out?"
89
89
+
},
90
90
+
"about": {
91
91
+
"back-to-top": "Back to top"
89
92
}
90
93
}
+3
app/i18n/locales/ja.json
···
87
87
"logout-button": {
88
88
"text": "ログアウト",
89
89
"confirm-message": "ログアウトしますか?"
90
90
+
},
91
91
+
"about": {
92
92
+
"back-to-top": "トップに戻る"
90
93
}
91
94
}
+2
-7
app/routes/_index.tsx
···
70
70
})}
71
71
>
72
72
<p>
73
73
-
<a
74
74
-
href="https://whtwnd.com/mkizka.dev/3l6sg24zov62s"
75
75
-
className="underline"
76
76
-
target="_blank"
77
77
-
rel="noreferrer"
78
78
-
>
73
73
+
<Link to="/about" className="underline">
79
74
{t("_index.notes-link")}
80
80
-
</a>
75
75
+
</Link>
81
76
</p>
82
77
</div>
83
78
</div>
+111
app/routes/about.tsx
···
1
1
+
import { AtUri } from "@atproto/api";
2
2
+
import { ChevronLeftIcon } from "@heroicons/react/24/outline";
3
3
+
import { LRUCache } from "lru-cache";
4
4
+
import markdownit from "markdown-it";
5
5
+
import linkAttributes from "markdown-it-link-attributes";
6
6
+
import { useTranslation } from "react-i18next";
7
7
+
import { Link } from "react-router";
8
8
+
import { z } from "zod";
9
9
+
10
10
+
import { Footer, Main } from "~/components/layout";
11
11
+
import { i18nServer } from "~/i18n/i18n";
12
12
+
import { LinkatAgent } from "~/libs/agent";
13
13
+
import { env } from "~/utils/env";
14
14
+
15
15
+
import type { Route } from "./+types/about";
16
16
+
17
17
+
type About = {
18
18
+
title: string;
19
19
+
content: string;
20
20
+
};
21
21
+
22
22
+
const cache = new LRUCache<string, About>({
23
23
+
max: 1,
24
24
+
ttl: 1000 * 60 * 10,
25
25
+
});
26
26
+
27
27
+
const whtwndSchema = z.object({
28
28
+
title: z.string(),
29
29
+
content: z.string(),
30
30
+
});
31
31
+
32
32
+
const md = markdownit().use(linkAttributes, {
33
33
+
attrs: {
34
34
+
target: "_blank",
35
35
+
rel: "noopener noreferrer",
36
36
+
},
37
37
+
});
38
38
+
39
39
+
const getRkey = (locale: string) => {
40
40
+
switch (locale) {
41
41
+
case "ja":
42
42
+
return env.ABOUT_WHTWND_RKEY_JA;
43
43
+
case "en":
44
44
+
return env.ABOUT_WHTWND_RKEY_EN;
45
45
+
default:
46
46
+
return env.ABOUT_WHTWND_RKEY_EN;
47
47
+
}
48
48
+
};
49
49
+
50
50
+
export const loader = async ({ request }: Route.LoaderArgs) => {
51
51
+
const locale = await i18nServer.getLocale(request);
52
52
+
const atUri = new AtUri(
53
53
+
`at://${env.ABOUT_WHTWND_REPO}/com.whtwnd.blog.entry/${getRkey(locale)}`,
54
54
+
);
55
55
+
const cachedAbout = cache.get(atUri.toString());
56
56
+
if (cachedAbout) {
57
57
+
return {
58
58
+
atUri: atUri.toString(),
59
59
+
about: cachedAbout,
60
60
+
};
61
61
+
}
62
62
+
const agent = LinkatAgent.credential(env.ABOUT_WHTWND_PDS_URL);
63
63
+
const response = await agent.com.atproto.repo.getRecord({
64
64
+
repo: atUri.host,
65
65
+
collection: atUri.collection,
66
66
+
rkey: atUri.rkey,
67
67
+
});
68
68
+
const whtwnd = whtwndSchema.parse(response.data.value);
69
69
+
const about = {
70
70
+
title: whtwnd.title,
71
71
+
content: md.render(whtwnd.content),
72
72
+
};
73
73
+
cache.set(atUri.toString(), about);
74
74
+
return {
75
75
+
atUri: atUri.toString(),
76
76
+
about,
77
77
+
};
78
78
+
};
79
79
+
80
80
+
export const meta: Route.MetaFunction = ({ data }) => {
81
81
+
const { about, atUri } = data;
82
82
+
83
83
+
return [
84
84
+
{ title: about.title },
85
85
+
{
86
86
+
tagName: "link",
87
87
+
rel: "alternate",
88
88
+
href: atUri,
89
89
+
},
90
90
+
];
91
91
+
};
92
92
+
93
93
+
export default function AboutPage({ loaderData }: Route.ComponentProps) {
94
94
+
const { t } = useTranslation();
95
95
+
const { about } = loaderData;
96
96
+
return (
97
97
+
<>
98
98
+
<Main className="py-2">
99
99
+
<Link to="/" className="flex h-8 items-center">
100
100
+
<ChevronLeftIcon className="size-6" />
101
101
+
{t("about.back-to-top")}
102
102
+
</Link>
103
103
+
<article className="prose py-6">
104
104
+
<h1 className="text-3xl">{about.title}</h1>
105
105
+
<div dangerouslySetInnerHTML={{ __html: about.content }} />
106
106
+
</article>
107
107
+
</Main>
108
108
+
<Footer />
109
109
+
</>
110
110
+
);
111
111
+
}
+7
app/utils/env.ts
···
59
59
),
60
60
UMAMI_SCRIPT_URL: z.string().optional(),
61
61
UMAMI_WEBSITE_ID: z.string().optional(),
62
62
+
// aboutページで使用するwhitewindの記事情報
63
63
+
ABOUT_WHTWND_PDS_URL: z
64
64
+
.string()
65
65
+
.default("https://enoki.us-east.host.bsky.network"),
66
66
+
ABOUT_WHTWND_REPO: z.string().default("did:plc:4gow62pk3vqpuwiwaslcwisa"),
67
67
+
ABOUT_WHTWND_RKEY_JA: z.string().default("3l6sg24zov62s"),
68
68
+
ABOUT_WHTWND_RKEY_EN: z.string().default("3lc5gjjo3sb2n"),
62
69
};
63
70
64
71
export const env = (() => {
+5
package.json
···
55
55
"isbot": "5.1.17",
56
56
"jotai": "2.10.3",
57
57
"lru-cache": "11.0.2",
58
58
+
"markdown-it": "14.1.0",
59
59
+
"markdown-it-link-attributes": "4.0.1",
58
60
"morgan": "1.10.0",
59
61
"prisma": "5.22.0",
60
62
"react": "18.3.1",
···
79
81
"@playwright/test": "1.49.0",
80
82
"@quramy/prisma-fabbrica": "2.2.1",
81
83
"@react-router/dev": "^7.0.1",
84
84
+
"@tailwindcss/typography": "0.5.15",
82
85
"@types/express": "5.0.0",
86
86
+
"@types/markdown-it": "14.1.2",
87
87
+
"@types/markdown-it-link-attributes": "3.0.5",
83
88
"@types/morgan": "1.9.9",
84
89
"@types/node": "22.10.0",
85
90
"@types/react": "18.3.12",
+119
pnpm-lock.yaml
···
95
95
lru-cache:
96
96
specifier: 11.0.2
97
97
version: 11.0.2
98
98
+
markdown-it:
99
99
+
specifier: 14.1.0
100
100
+
version: 14.1.0
101
101
+
markdown-it-link-attributes:
102
102
+
specifier: 4.0.1
103
103
+
version: 4.0.1
98
104
morgan:
99
105
specifier: 1.10.0
100
106
version: 1.10.0
···
162
168
'@react-router/dev':
163
169
specifier: ^7.0.1
164
170
version: 7.0.1(@types/node@22.10.0)(react-router@7.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.7.2)(vite@5.4.10(@types/node@22.10.0))
171
171
+
'@tailwindcss/typography':
172
172
+
specifier: 0.5.15
173
173
+
version: 0.5.15(tailwindcss@3.4.15)
165
174
'@types/express':
166
175
specifier: 5.0.0
167
176
version: 5.0.0
177
177
+
'@types/markdown-it':
178
178
+
specifier: 14.1.2
179
179
+
version: 14.1.2
180
180
+
'@types/markdown-it-link-attributes':
181
181
+
specifier: 3.0.5
182
182
+
version: 3.0.5
168
183
'@types/morgan':
169
184
specifier: 1.9.9
170
185
version: 1.9.9
···
1702
1717
typescript:
1703
1718
optional: true
1704
1719
1720
1720
+
'@tailwindcss/typography@0.5.15':
1721
1721
+
resolution: {integrity: sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==}
1722
1722
+
peerDependencies:
1723
1723
+
tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20'
1724
1724
+
1705
1725
'@ts-morph/common@0.17.0':
1706
1726
resolution: {integrity: sha512-RMSSvSfs9kb0VzkvQ2NWobwnj7TxCA9vI/IjR9bDHqgAyVbu2T0DN4wiKVqomyDWqO7dPr/tErSfq7urQ1Q37g==}
1707
1727
···
1753
1773
'@types/json-schema@7.0.15':
1754
1774
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
1755
1775
1776
1776
+
'@types/linkify-it@5.0.0':
1777
1777
+
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
1778
1778
+
1779
1779
+
'@types/markdown-it-link-attributes@3.0.5':
1780
1780
+
resolution: {integrity: sha512-VZ2BGN3ywUg7mBD8W6PwR8ChpOxaQSBDbLqPgvNI+uIra3zY2af1eG/3XzWTKjEraTWskMKnZqZd6m1fDF67Bg==}
1781
1781
+
1782
1782
+
'@types/markdown-it@14.1.2':
1783
1783
+
resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
1784
1784
+
1785
1785
+
'@types/mdurl@2.0.0':
1786
1786
+
resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
1787
1787
+
1756
1788
'@types/mime@1.3.5':
1757
1789
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
1758
1790
···
2530
2562
2531
2563
end-of-stream@1.4.4:
2532
2564
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
2565
2565
+
2566
2566
+
entities@4.5.0:
2567
2567
+
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
2568
2568
+
engines: {node: '>=0.12'}
2533
2569
2534
2570
environment@1.1.0:
2535
2571
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
···
3533
3569
lines-and-columns@1.2.4:
3534
3570
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
3535
3571
3572
3572
+
linkify-it@5.0.0:
3573
3573
+
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
3574
3574
+
3536
3575
lint-staged@15.2.10:
3537
3576
resolution: {integrity: sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg==}
3538
3577
engines: {node: '>=18.12.0'}
···
3557
3596
locate-path@6.0.0:
3558
3597
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
3559
3598
engines: {node: '>=10'}
3599
3599
+
3600
3600
+
lodash.castarray@4.4.0:
3601
3601
+
resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
3602
3602
+
3603
3603
+
lodash.isplainobject@4.0.6:
3604
3604
+
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
3560
3605
3561
3606
lodash.merge@4.6.2:
3562
3607
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
···
3602
3647
makeerror@1.0.12:
3603
3648
resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==}
3604
3649
3650
3650
+
markdown-it-link-attributes@4.0.1:
3651
3651
+
resolution: {integrity: sha512-pg5OK0jPLg62H4k7M9mRJLT61gUp9nvG0XveKYHMOOluASo9OEF13WlXrpAp2aj35LbedAy3QOCgQCw0tkLKAQ==}
3652
3652
+
3653
3653
+
markdown-it@14.1.0:
3654
3654
+
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
3655
3655
+
hasBin: true
3656
3656
+
3657
3657
+
mdurl@2.0.0:
3658
3658
+
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
3659
3659
+
3605
3660
media-typer@0.3.0:
3606
3661
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
3607
3662
engines: {node: '>= 0.6'}
···
4086
4141
peerDependencies:
4087
4142
postcss: ^8.2.14
4088
4143
4144
4144
+
postcss-selector-parser@6.0.10:
4145
4145
+
resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
4146
4146
+
engines: {node: '>=4'}
4147
4147
+
4089
4148
postcss-selector-parser@6.1.2:
4090
4149
resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
4091
4150
engines: {node: '>=4'}
···
4175
4234
pumpify@1.5.1:
4176
4235
resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==}
4177
4236
4237
4237
+
punycode.js@2.3.1:
4238
4238
+
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
4239
4239
+
engines: {node: '>=6'}
4240
4240
+
4178
4241
punycode@2.3.1:
4179
4242
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
4180
4243
engines: {node: '>=6'}
···
4869
4932
resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==}
4870
4933
engines: {node: '>=14.17'}
4871
4934
hasBin: true
4935
4935
+
4936
4936
+
uc.micro@2.1.0:
4937
4937
+
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
4872
4938
4873
4939
ufo@1.5.4:
4874
4940
resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
···
6739
6805
optionalDependencies:
6740
6806
typescript: 5.7.2
6741
6807
6808
6808
+
'@tailwindcss/typography@0.5.15(tailwindcss@3.4.15)':
6809
6809
+
dependencies:
6810
6810
+
lodash.castarray: 4.4.0
6811
6811
+
lodash.isplainobject: 4.0.6
6812
6812
+
lodash.merge: 4.6.2
6813
6813
+
postcss-selector-parser: 6.0.10
6814
6814
+
tailwindcss: 3.4.15
6815
6815
+
6742
6816
'@ts-morph/common@0.17.0':
6743
6817
dependencies:
6744
6818
fast-glob: 3.3.2
···
6812
6886
6813
6887
'@types/json-schema@7.0.15': {}
6814
6888
6889
6889
+
'@types/linkify-it@5.0.0': {}
6890
6890
+
6891
6891
+
'@types/markdown-it-link-attributes@3.0.5':
6892
6892
+
dependencies:
6893
6893
+
'@types/markdown-it': 14.1.2
6894
6894
+
6895
6895
+
'@types/markdown-it@14.1.2':
6896
6896
+
dependencies:
6897
6897
+
'@types/linkify-it': 5.0.0
6898
6898
+
'@types/mdurl': 2.0.0
6899
6899
+
6900
6900
+
'@types/mdurl@2.0.0': {}
6901
6901
+
6815
6902
'@types/mime@1.3.5': {}
6816
6903
6817
6904
'@types/morgan@1.9.9':
···
7660
7747
end-of-stream@1.4.4:
7661
7748
dependencies:
7662
7749
once: 1.4.0
7750
7750
+
7751
7751
+
entities@4.5.0: {}
7663
7752
7664
7753
environment@1.1.0: {}
7665
7754
···
9001
9090
9002
9091
lines-and-columns@1.2.4: {}
9003
9092
9093
9093
+
linkify-it@5.0.0:
9094
9094
+
dependencies:
9095
9095
+
uc.micro: 2.1.0
9096
9096
+
9004
9097
lint-staged@15.2.10:
9005
9098
dependencies:
9006
9099
chalk: 5.3.0
···
9045
9138
dependencies:
9046
9139
p-locate: 5.0.0
9047
9140
9141
9141
+
lodash.castarray@4.4.0: {}
9142
9142
+
9143
9143
+
lodash.isplainobject@4.0.6: {}
9144
9144
+
9048
9145
lodash.merge@4.6.2: {}
9049
9146
9050
9147
lodash@4.17.21: {}
···
9090
9187
makeerror@1.0.12:
9091
9188
dependencies:
9092
9189
tmpl: 1.0.5
9190
9190
+
9191
9191
+
markdown-it-link-attributes@4.0.1: {}
9192
9192
+
9193
9193
+
markdown-it@14.1.0:
9194
9194
+
dependencies:
9195
9195
+
argparse: 2.0.1
9196
9196
+
entities: 4.5.0
9197
9197
+
linkify-it: 5.0.0
9198
9198
+
mdurl: 2.0.0
9199
9199
+
punycode.js: 2.3.1
9200
9200
+
uc.micro: 2.1.0
9201
9201
+
9202
9202
+
mdurl@2.0.0: {}
9093
9203
9094
9204
media-typer@0.3.0: {}
9095
9205
···
9554
9664
postcss: 8.4.49
9555
9665
postcss-selector-parser: 6.1.2
9556
9666
9667
9667
+
postcss-selector-parser@6.0.10:
9668
9668
+
dependencies:
9669
9669
+
cssesc: 3.0.0
9670
9670
+
util-deprecate: 1.0.2
9671
9671
+
9557
9672
postcss-selector-parser@6.1.2:
9558
9673
dependencies:
9559
9674
cssesc: 3.0.0
···
9636
9751
duplexify: 3.7.1
9637
9752
inherits: 2.0.4
9638
9753
pump: 2.0.1
9754
9754
+
9755
9755
+
punycode.js@2.3.1: {}
9639
9756
9640
9757
punycode@2.3.1: {}
9641
9758
···
10378
10495
- supports-color
10379
10496
10380
10497
typescript@5.7.2: {}
10498
10498
+
10499
10499
+
uc.micro@2.1.0: {}
10381
10500
10382
10501
ufo@1.5.4: {}
10383
10502
+2
-1
tailwind.config.ts
···
1
1
+
import typography from "@tailwindcss/typography";
1
2
import daisyui from "daisyui";
2
3
import type { Config } from "tailwindcss";
3
4
import plugin from "tailwindcss/plugin";
···
20
21
murecho: ["murecho", "sans-serif"],
21
22
},
22
23
},
23
23
-
plugins: [daisyui, animate, lightSelectorPlugin],
24
24
+
plugins: [typography, daisyui, animate, lightSelectorPlugin],
24
25
// https://daisyui.com/docs/config/
25
26
daisyui: {
26
27
logs: false,