commit all

This commit is contained in:
moris 2024-11-29 18:54:42 +09:00
commit e61627fd7f
51 changed files with 4608 additions and 0 deletions

8
.env.example Normal file
View File

@ -0,0 +1,8 @@
# Hostname
PUBLIC_HOSTNAME = 'moris.day'
# Post
PUBLIC_POST_REPO = 'https://git.moris.day/moris/Posts'
# Access token (optional)
TOKEN = ''

21
.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
node_modules
# Output
.output
.vercel
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

1
.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=true

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2024 moris
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

15
README.md Normal file
View File

@ -0,0 +1,15 @@
# moris.day blog source code
## Develop
```
git clone https://git.moris.day/moris/blog.git
cd blog
npm install
npm run dev -- --open
```
## Build
```
npm run build
```
You can preview the production build with npm run preview

2858
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

30
package.json Normal file
View File

@ -0,0 +1,30 @@
{
"name": "blog",
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"@sveltejs/adapter-static": "^3.0.6",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"svelte": "^5.0.0",
"vite": "^5.0.3"
},
"dependencies": {
"@twemoji/parser": "^15.1.1",
"github-slugger": "^2.0.0",
"rehype-external-links": "^3.0.0",
"rehype-highlight": "^7.0.1",
"rehype-slug": "^6.0.0",
"rehype-stringify": "^10.0.1",
"remark": "^15.0.1",
"remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.0",
"remark-rehype": "^11.1.1",
"yaml": "^2.6.0"
}
}

87
src/app.css Normal file
View File

@ -0,0 +1,87 @@
/* theme color */
body, body.light {
--color-scheme: light;
--white-black: white;
--black-white: black;
--theme-color: hsl(85, 50%, 60%);
--back-color: hsl(60, 100%, 98%);
--font-color: #222;
--grid-color: #fff;
--code-color: #ddd;
--code-block: #30303a;
--frame-shadow: #999;
}
body.dark {
--color-scheme: dark;
--white-black: black;
--black-white: white;
--theme-color: teal;
--back-color: hsl(210, 7%, 18%);
--font-color: #f5f5f5;
--grid-color: #333;
--code-color: #445;
--code-block: #23232a;
--frame-shadow: #111;
}
@media(prefers-color-scheme: dark){
body {
--color-scheme: dark;
--white-black: black;
--black-white: white;
--theme-color: teal;
--back-color: hsl(210, 7%, 18%);
--font-color: #fafafa;
--grid-color: #333;
--code-color: #445;
--code-block: #23232a;
--frame-shadow: #111;
}
}
/* general */
body {
margin: 0;
color: var(--font-color);
}
img {
-webkit-user-drag: none;
user-select: none;
}
@media (scripting: none) {
.js {display: none;}
}
/* font */
body {
font-family: "-apple-system", "BlinkMacSystemFont", "Hiragino Kaku Gothic ProN", "Noto Sans CJK JP", "Segoe UI", "BIZ UDPGothic", sans-serif;
/* Apple | Linux | win-en win-jp */
}
code {
font-family: "Source Code Pro", "Monaco", "Consolas", "BIZ UDGothic", monospace;
/* Linux | Apple | windows | 和文 */
}
em {
font-family: serif;
}
/* link style */
a {
text-decoration: none;
color: var(--theme-color);
&:hover {
text-decoration: underline;
}
&:visited {
color: hsl(290, 40%, 50%);
}
}

15
src/app.html Normal file
View File

@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#a1cc66" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#" media="(prefers-color-scheme: dark)">
<meta property="og:site_name" content="moris.day">
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@ -0,0 +1,148 @@
.markdown {
width: 100%;
& h1 {
font-size: 2em;
@media(width>999px){padding-top:50px;margin-top:0;}
}
& h1::before {
display: inline-block;
content: '';
margin-right: 6px;
width: 4px;
height: 1.2em;
vertical-align: bottom;
background-color: var(--theme-color);
}
& h2 {
padding-top: 25px;
@media(width>999px){padding-top:50px;margin-top:0;}
}
& h2::before {
display: inline-block;
content: '';
margin-right: 4px;
width: 3px;
height: 1.2em;
vertical-align: text-bottom;
background-color: var(--theme-color);
}
/*
& h3 {
padding-left: 2px;
border-left: 2px solid var(--header-line);
}
*/
& p {
margin: 0 1.5em;
line-height: 1.8em;
@media (width<480px) {
margin: 0 .5em;
}
}
& li {
line-height: 1.5em;
}
& img {
display: block;
width: 90%;
margin: 1em auto;
}
& code {
font-size: 1rem;
margin: 0 3px;
padding: 0 3px;
border-radius: 3px;
background-color: var(--code-color);
}
& pre {
margin: 16px;
padding: 16px;
box-sizing: border-box;
border-radius: 8px;
background-color: var(--code-block);
color: #d1d9e1;
overflow-x: scroll;
@media (width<480px) {
margin: 0;
}
& code {
margin: 0;
padding: 0;
border-radius: 0;
background-color: #fff0;
}
}
& blockquote {
position: relative;
font-style: italic;
width: fit-content;
padding: .5em 0;
border-top: 1px solid var(--font-color);
border-bottom: 1px solid var(--font-color);
& p::before {
content: '';
mask-image: url('data:image/svg+xml;utf-8,<svg version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg"><path d="m57.2 36.6-4.3 20.4h-20.5l3.8-17.9q3.3-16 10.4-23.5 7.2-7.5 16.8-8.6l-1.6 7.9q-11.7 3.2-15.7 21.7zm-31.8 0-4.3 20.4h-20.5l3.8-17.9q3.3-16 10.4-23.5 7.2-7.5 16.8-8.6l-1.6 7.9q-11.7 3.2-15.7 21.7z"/></svg>');
width: .8em;
height: .8em;
position: absolute;
background-color: var(--font-color);
top: -.3em;
left: -.9em;
}
& p::after {
content: '';
mask-image: url('data:image/svg+xml;utf-8,<svg version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg"><path d="m6.8 27.4 4.3-20.4h20.5l-3.8 17.9q-3.3 16-10.4 23.5-7.17 7.5-16.8 8.6l1.6-7.9q11.7-3.2 15.7-21.7zm31.8 0 4.3-20.4h20.5l-3.8 17.9q-3.3 16-10.4 23.5-7.17 7.5-16.8 8.6l1.6-7.9q11.7-3.2 15.7-21.7z"/></svg>');
width: .8em;
height: .8em;
position: absolute;
background-color: var(--font-color);
bottom: -.3em;
right: -.9em;
}
}
& iframe {
display: block;
margin: 30px auto;
border: none;
width: 80%;
aspect-ratio: 16/9;
border-radius: 12px;
box-shadow: 0px 6px 30px var(--frame-shadow);
max-height: 75vh;
transition: max-height .3s ease-in-out;
&.hide {
max-height: 30px;
}
@media(width<480px) {
width: 100%;
aspect-ratio: 4/3;
box-shadow: 0px 4px 16px var(--frame-shadow);
}
@media(width>1280px) {
width: 60%;
}
&[src*="youtube.com"]{
aspect-ratio: 16/9;
}
}
& video {
display: block;
margin: 30px auto;
width: 80%;
border-radius: 15px;
box-shadow: 0px 6px 30px var(--frame-shadow);
@media(width<480px) {
width: 100%;
}
@media(width>1280px) {
width: 60%;
}
}
}

View File

@ -0,0 +1,31 @@
<script>
export let mdtext;
import { remark } from 'remark'
import remarkgfm from 'remark-gfm'
import remarkBreaks from "remark-breaks"
import remarkRehype from 'remark-rehype'
import rehypeslug from 'rehype-slug'
import rehypeHighlight from 'rehype-highlight'
import rehypeExternalLinks from 'rehype-external-links'
import rehypeStringify from 'rehype-stringify'
import '$lib/components/rainbow.css';
import '$lib/components/Markdown.css';
let md = remark()
.use(remarkgfm) // Github Markdown
.use(remarkBreaks) // 改行
.use(remarkRehype, {allowDangerousHtml: true})
.use(rehypeslug) // headingにidを設定
.use(rehypeHighlight)// Syntax highlight
.use(rehypeExternalLinks, {target:'_blank', rel:['noreferrer','noopener']})// 外部サイトを新規タブで開く
.use(rehypeStringify, {allowDangerousHtml: true})
.processSync(mdtext)
</script>
<div class='markdown'>
{@html md}
</div>

View File

@ -0,0 +1,120 @@
/*
BSD 3-Clause License
Copyright (c) 2006, Ivan Sagalaev.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
Style with support for rainbow parens
*/
.hljs {
background: #474949;
color: #d1d9e1;
}
.hljs-comment,
.hljs-quote {
color: #969896;
font-style: italic;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-literal,
.hljs-type {
color: #cc99cc;
}
.hljs-number,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #f99157;
}
.hljs-string,
.hljs-doctag,
.hljs-regexp {
color: #8abeb7;
}
.hljs-title,
.hljs-name,
.hljs-section,
.hljs-built_in {
color: #b5bd68;
}
.hljs-variable,
.hljs-template-variable,
.hljs-selector-id,
.hljs-title.class_,
.hljs-class .hljs-title {
color: #ffcc66;
}
.hljs-section,
.hljs-strong {
font-weight: bold;
}
.hljs-symbol,
.hljs-bullet,
.hljs-subst,
.hljs-meta,
.hljs-link {
color: #f99157;
}
.hljs-deletion {
color: #dc322f;
}
.hljs-addition {
color: #68dc2e
}
.hljs-formula {
background: #eee8d5;
}
.hljs-attr,
.hljs-attribute {
color: #81a2be;
}
.hljs-emphasis {
font-style: italic;
}

View File

@ -0,0 +1,26 @@
export default getpost;
import { POST_DIR } from '$env/static/private';
import Parser from './MetaParser';
import fs from 'node:fs';
async function getpost(id) {
console.log(id)
const md = fs.readFileSync(`${POST_DIR}/Posts/${id}.md`, 'utf8');
let fm_line = md.split('\n');
let line_no = []
fm_line.forEach((i,n)=>{
if(i=='---'){line_no.push(n)}
})
let md_text = fm_line.slice(line_no[1]+1).join('\n');
let fm_text = fm_line.slice(line_no[0]+1, line_no[1]).join('\n');
let metadata = Parser(fm_text);
return {metadata: metadata, post: md_text}
}

View File

@ -0,0 +1,28 @@
export default parser;
import yaml from "yaml"
import { parse } from '@twemoji/parser';
function parser(fm_text) {
let fm = yaml.parse(fm_text)
let title = 'title' in fm && typeof fm.title === 'string' ? fm.title : ''
let description = 'description' in fm && typeof fm.description === 'string'? fm.description : ''
let thumbnail = 'thumbnail' in fm && typeof fm.thumbnail === 'string' ? fm.thumbnail : ''
let emoji = 'emoji' in fm && typeof fm.emoji === 'string' ? fm.emoji : ''
let date = 'date' in fm && typeof fm.date === 'string' ? fm.date : ''
let category = 'category' in fm && typeof fm.category === 'string' ? fm.category : 'other'
let tags = 'tags' in fm && Array.isArray(fm.tags) ? fm.tags : []
let index = 'index' in fm && typeof fm.index === 'boolean' ? fm.index : true
let published = 'published' in fm && typeof fm.published === 'boolean' ? fm.published : false
if (emoji){
emoji = parse(emoji)[0].url
}
if (date) {
date = new Date(date)
}
return {title, description,thumbnail,emoji,date,category,tags,index,published}
}

View File

@ -0,0 +1,48 @@
export default Metadatas;
import fs from 'node:fs';
import path from 'node:path';
import Posts from './PostList';
import Load from './LoadPost';
async function Metadatas() {
const cache_dir = '/tmp/day.moris.blog/';
const cache_file = `${cache_dir}metadata.json`;
async function build() {
const posts = await Posts();
const metadataList = await Promise.all(posts.map(async (file)=>{
const postId = path.basename(file, '.md')
const metadata = (await Load(postId)).metadata
return {postId, metadata}
}))
const sorted = metadataList.sort((a,b)=>{
return b.metadata.date.getTime() - a.metadata.date.getTime()
})
return sorted
}
if (fs.existsSync(cache_file)) {
const metas = JSON.parse(fs.readFileSync(cache_file, 'utf8'), (key,value)=>{
if(key=='date'){
return (new Date(value));
}
return value
})
return metas
} else {
const metas = await build()
if(!fs.existsSync(cache_dir)){fs.mkdirSync(cache_dir), {recursive: true}}
fs.writeFileSync(cache_file, JSON.stringify(metas), 'utf8')
return metas
}
}

View File

@ -0,0 +1,15 @@
export default postList;
import fs from 'node:fs';
import Path from 'node:path';
import { POST_DIR } from '$env/static/private';
async function postList() {
const files = fs.readdirSync(`${POST_DIR}/Posts`);
const posts = files.filter((content)=> Path.extname(content)=='.md' && content!='README.md')
return posts
}

View File

@ -0,0 +1,101 @@
<script>
import "../../app.css";
import Header from "./header.svelte";
import Footer from "./footer.svelte";
import { onMount } from 'svelte';
let { children } = $props();
let scrollObserver = $state(true);
onMount(()=>{
let observer = new IntersectionObserver(
(entries)=>{scrollObserver = entries[0].isIntersecting},
{rootMargin: "25px",threshold: 0}
);
observer.observe(document.querySelector("#scroll"))
});
</script>
<div id="root">
<div id="scroll"></div>
<div class="headerContainer">
<header class:hide={!scrollObserver}>
<Header />
</header>
</div>
<div class="mainContainer">
<main>
{@render children()}
</main>
<Footer />
</div>
<div class="u320"></div>
</div>
<style>
#root {
display: flex;
flex-direction: column;
background-color: var(--back-color);
min-height: 100vh;
}
.headerContainer {
position: sticky;
top: 0;
height: 100px;
z-index: 1;
@media(width<1000px) {
height: 50px;
}
& header {
background-color: var(--theme-color);
height: 100%;
transition: height .4s, transform .4s;
&.hide {
height: 50px;
@media(width<1000px) {
transform: translate(0px,-50px);
}
}
}
}
.mainContainer {
display: flex;
flex-direction: column;
flex-grow: 1;
}
main {
flex-grow: 1;
}
@media(width<320px) {
.u320 {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 2;
background: no-repeat center/25% url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAgCAAAAADC94zaAAAAiklEQVQoz62RQQ7EMAgDxxH/f3LdQ9MmNGQPq3JBwsLYRkYUZQIZv+dCFgUAQgFIKxcEoJXN0NhVG8SpD8Sd0g/1s2OlPu107cNDmz2kOGIyoeQ5Xv5KRBmN5BtUKOjaFtXrtbhEbbI+XCUqXEet/Rfitp+u6/4pHEpS/eedb5GdABwbTPzMQDVyAtNNK05J5lfeAAAAAElFTkSuQmCC);
background-color: white;
image-rendering: pixelated;
color: black;
&::after {
content: 'This site supports screen sizes larger than 320px';
}
}
}
@media (scripting: none) {
header{height:50px !important;}
}
</style>

View File

@ -0,0 +1,5 @@
<svelte:head>
<meta http-equiv="Refresh" content="0; URL=0" />
</svelte:head>
To show posts, click <a href="/blog/0">here</a>

View File

@ -0,0 +1,11 @@
import Metas from '$lib/server/Metadatas';
export async function load({params}){
const pageNo = params.slug ?? 0;
const postList = await Metas()
const posts = postList.slice(pageNo*12, (pageNo+1)*12);
const lastPage = Math.trunc(postList.length/12);
return {posts, pageNo, lastPage}
}

View File

@ -0,0 +1,64 @@
<script>
const { data } = $props();
import Postgrid from './grid.svelte'
</script>
<div class='contain'>
<div class="posts">
{#each data.posts as post}
{#if post.metadata.published}
<div class="post">
<a style='text-decoration: none;' href="./post/{post.postId}" tabindex="0">
<Postgrid id={post.postId} {...post.metadata}></Postgrid>
</a>
</div>
{/if}
{/each}
{#if data.posts.length%4}
{#each Array(4-data.posts.length%4) as i}
<div class="blank"></div>
{/each}
{/if}
</div>
<div class='paging'>
<a href='{data.pageNo==0? "":Number(data.pageNo)-1}' style:visibility={data.pageNo==0? 'hidden':''}></a>
<span>{data.pageNo}</span><span>/</span><span>{data.lastPage}</span>
<a href='{data.pageNo==data.lastPage? "":Number(data.pageNo)+1}' style:visibility={data.pageNo==data.lastPage? 'hidden':''}></a>
</div>
</div>
<style>
.contain {
margin: 3vw;
}
.posts {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
grid-template-rows: max-content;
gap: 25px;
justify-content: center;
margin: 0 auto;
max-width: 1599px;
}
.post {
border-radius: 8px;
overflow: hidden;
box-shadow: 0 0 6px #0001;
}
.paging {
display: flex;
justify-content: center;
column-gap: 8px;
margin: 20px;
font-size: 1.5em;
font-weight: 550;
& a {
color: var(--theme-color);
}
}
</style>

View File

@ -0,0 +1,104 @@
<script>
export let title;
export let description;
export let thumbnail;
export let emoji;
export let date;
export let category;
export let id;
</script>
<div class='grid'>
<div class='thumbnail'>
{#if thumbnail }
<img src="{thumbnail}" alt='thumbnail' style='view-transition-name: {id}'/>
{:else if emoji}
<div class='emoji'><img class='svg' src="{emoji}" alt="thumbnail" style='view-transition-name: {id}'/></div>
{:else}
<img src='data:image/svg+xml,{encodeURIComponent('<svg fill="#aaa" version="1.1" viewBox="0 -960 96 96" xmlns="http://www.w3.org/2000/svg"><path d="m41-903q-0.8 0-1.4-0.6t-0.6-1.4v-14q0-0.8 0.6-1.4t1.4-0.6h14q0.8 0 1.4 0.6t0.6 1.4v14q0 0.8-0.6 1.4t-1.4 0.6zm0-2h14v-14h-14zm1-2h12l-3.8-5-3 4-2.25-3zm-1 2v-14z"/></svg>')}' alt="fallback"/>
{/if}
<div class="tag date">{date.toLocaleDateString('sv-SE')}</div>
<div class="tag category">{category}</div>
</div>
<div class='label'>
<div class='title'>{title}</div>
<div class='description'>{description}</div>
</div>
</div>
<style>
.grid {
display: flex;
height: 100%;
flex-direction: column;
position: relative;
background-color: var(--grid-color);
&:hover {
img {
transform: scale(1.03);
}
.title{
text-decoration-line: underline;
text-decoration-color: var(--font-color);
}
}
}
.thumbnail {
display: flex;
align-items: center;
justify-content: center;
aspect-ratio: 1/0.6;
width: 100%;
max-height: 200px;
& img {
display: block;
max-width: 100%;
max-height: 100%;
object-fit: contain;
margin: 0 auto;
transition: transform .3s ease-out;
}
& div.emoji {
height: 70%;
aspect-ratio: 1/1;
}
& .tag{
position: absolute;
top: 5px;
padding:2px;
color: var(--font-color);
background-color: var(--back-color);
border-radius: 3px;
font-size: 0.85em;
}
& .date {
right:5px;
}
& .category {
left: 5px;
}
}
.label {
flex-grow: 1;
height: 100%;
width: 100%;
color: var(--font-color);
& .title{
font-size: 1.25em;
font-weight: bold;
margin: 4px 8px;
}
& .description {
margin:8px;
font-size: .9em;
text-decoration: none;
}
}
</style>

View File

@ -0,0 +1,17 @@
import Metadatas from "$lib/server/Metadatas";
export async function load() {
let metalist = await Metadatas()
let cats = []
for (let meta of metalist){
let cat = meta.metadata.category;
if (!cats.includes(cat)) {
cats.push(cat)
}
}
return {cats}
}

View File

@ -0,0 +1,9 @@
<script>
const { data } = $props();
</script>
{#each data.cats as cat}
<div>
<a href="./category/{cat}">{cat}</a>
</div>
{/each}

View File

@ -0,0 +1,77 @@
<script>
import { base } from '$app/paths'
</script>
<footer>
<div id="footercontents">
<div id="ftitle">
<svg class="title" version="1.1" viewBox="0 0 300 64" xmlns="http://www.w3.org/2000/svg" fill="white"><path d="m10 45.6v-26.3h3.8v6c1.2-2.56 2.3-4.3 3.34-5.3 2.57-2.4 5-1.5 6.2 0 1.24 1.64 1.17 3.7 1.17 5.65 0.723-2.5 2.13-4.6 4.1-6.26 1.2-0.92 3.72-1.66 5.56 0.54 0.8 0.96 1.2 2.86 1.2 5.72v20h-3.8v-17.9c0-2.8-0.137-3.7-0.7-4.26-1.26-1.27-2.9 0.6-3.5 1.46-0.92 1.24-1.86 3.3-2.8 6.2v14.6h-3.65v-18c0-2.3-0.225-3.7-0.68-4.1-2.2-2-4.5 1.84-6.4 6.85v15.2zm41.4 0.73c-3.7 0-6.7-1.3-9-3.9-2.24-2.6-3.4-5.95-3.4-10s1.1-7.31 3.4-9.9 5.2-3.9 9-3.9c3.7 0 6.7 1.3 9 3.9s3.4 5.9 3.4 9.9-1.1 7.3-3.4 10c-2.24 2.6-5.2 3.9-9 3.9zm0-3.65c2.3 0 4.2-0.9 5.53-2.73 1.4-1.8 2.1-4.32 2.1-7.5 0-3.2-0.685-5.7-2.1-7.5-1.37-1.8-3.2-2.73-5.53-2.73-2.33 0-4.2 0.9-5.6 2.73-1.37 1.8-2.1 4.3-2.1 7.5 0 3.2 0.69 5.7 2.1 7.5 1.37 1.8 3.2 2.7 5.6 2.7zm20 2.9v-26.3h4.35v5.74c2.44-4 5.8-6.5 10.5-6.5 1.6 0 3.15 0.243 4.7 0.73v9.5h-4.2v-6c-5.3-1-8.7 3.5-11 7.53v15.3zm34 0v-22.6h-7.24v-3.65h11.6v22.6h7.27v3.65zm-0.724-36c0-1 0.34-1.73 1-2.2s1.3-0.73 1.9-0.73c0.6 0 1.24 0.24 1.92 0.73 0.67 0.49 1 1.2 1 2.2 0 0.97-0.335 1.7-1 2.2-0.67 0.49-1.3 0.73-1.9 0.73-0.6 0-1.22-0.24-1.9-0.73-0.675-0.49-1-1.2-1-2.2zm22.4 34.4v-4.6c4.5 2.9 9.3 3.1 10.5 3.1 2.35 0 6.2-0.5 6.2-3.74-0.22-2.7-3.4-3.4-5.5-4.3l-3-1.2c-3.24-1.3-5.3-2.45-6.1-3.5-1.75-2.2-2.1-6.5 1.2-9.24 3.26-2.7 10.8-2.6 16-0.9v4c-2.9-1.04-5.6-1.55-8.1-1.55-0.5 0-5.9 0.078-5.9 3.4 0 1.5 0.81 2.4 5.7 4.2l2.94 1.1c2.7 1 4.6 2.1 5.7 3.2 2.3 2.4 2.44 7.1-0.98 10-3 2.5-10.4 3.34-18.6 0.07zm39.1 2.5c-1 0-1.94-0.38-2.7-1.13-0.75-0.75-1.12-1.65-1.1-2.7 0-1.1 0.37-2 1.12-2.7 0.76-0.75 1.66-1.13 2.7-1.13s1.92 0.376 2.66 1.13c0.763 0.737 1.14 1.64 1.14 2.7 0 1.05-0.373 1.95-1.12 2.7s-1.64 1.13-2.7 1.13zm35.5-7c-3.63 6-10.7 9.3-15.8 3.8-3.37-3.7-3.3-11.8-0.864-16.8 2.9-6.1 7.53-7.76 11.8-7.76 1.63 0.016 3.25 0.215 4.86 0.376v-11.7h4.37v38h-4.37zm0-5.2v-11.3c-1.54-0.41-3-0.61-4.3-0.61-4.67 0-9.37 3.6-9.4 11.6 0 7.36 4 7.9 4.8 7.9 2.5 0 6-2.6 8.9-7.65zm28.4 5.2c-3.5 5.8-8.4 6.6-9.9 6.6-0.7 0-8.5-0.253-8.5-11.5 0-2.65 0.58-9.68 6.36-13.8 3.55-2.6 7.93-2.14 12-1.72h4.37v18.6c0 3.17 0.327 5.74 1 7.7h-4.53c-0.343-1.44-0.615-3.4-0.82-5.9zm0-5.2v-11.3c-5.57-1.5-8.75-0.11-11 2.5-1.8 2.1-2.66 4.9-2.66 8.5 0 7.36 4 7.9 4.8 7.9 2.5 0 6-2.6 8.9-7.65zm21.2 10-12-25.2h4.8l9.6 19.7 8-19.7h4.4l-10.1 24.4c-2.14 5.16-4.16 8.6-6.1 10.3-4 3.53-9.8 2.46-11 2.2v-3.86c1.62 0.5 4.53 1 7.17-0.26 2.8-1.6 4.16-4.7 5.3-7.55z"/><line class="underline" x1="268" y1="50" x2="290" y2="50" stroke="white" stroke-width="3"/><style>.underline {animation: 1.5s step-end infinite underline;}@keyframes underline {0% {visibility: visible;}50% {visibility: hidden;}}</style></svg>
</div>
<div id='footlink'>
<dl>
<dt>Site Map</dt>
<dd><a href="{base}">Top Page</a></dd>
<dd><a href="{base}/about">About me</a></dd>
<dd><a href="{base}/post">Blog</a></dd>
</dl>
<dl>
<dt>SNS</dt>
<dd><a target="_blank" href="https://mi.moris.day/@moris">Misskey</a></dd>
<dd><a target="_blank" href="https://github.com/cocoyayann">Github</a></dd>
<dd><a target="_blank" href="https://twitter.com/cocoyayan">Twitter</a></dd>
</dl>
</div>
<div id="coffee"><a href="https://www.amazon.jp/hz/wishlist/ls/1OA6N018YLSF0?ref_=wl_share" target="_blank">☕️ Buy me a coffee</a></div>
</div>
<div style="margin:8px auto;width:fit-content;font-size:0.8em;">
Copyright ©2024 moris All Rights Reserved.
</div>
</footer>
<style>
footer {
font-family: "Source Code Pro", "Monaco", "Consolas", monospace;
height: fit-content;
background-color: var(--theme-color);
color: white;
& a {
color: white;
}
& dd {
margin: 12px 0 0 24px;
}
& dt {
font-size: 1.25em;
}
& #footercontents {
display: flex;
align-items: center;
justify-content: space-around;
height: 85%;
margin: 25px;
@media(width < 720px) {
flex-direction: column;
row-gap: 20px;
}
}
& #ftitle {
margin-bottom: 20px;
}
& .title {
height: 50px;
}
& #footlink {
width:80%;
max-width: 600px;
display: flex;
justify-content: space-around;
}
& #coffee{display:none;}
}
</style>

View File

@ -0,0 +1,23 @@
<div class="title">
<a href="/blog" aria-label="blog home">
<svg version="1.1" viewBox="0 0 256 64" xmlns="http://www.w3.org/2000/svg"><path fill="white" d="m0 45.6v-26.3h3.8v6c1.2-2.56 2.3-4.3 3.34-5.3 2.57-2.4 5-1.5 6.2 0 1.24 1.64 1.17 3.7 1.17 5.65 0.723-2.5 2.13-4.6 4.1-6.26 1.2-0.92 3.72-1.66 5.56 0.54 0.8 0.96 1.2 2.86 1.2 5.72v20h-3.8v-17.9c0-2.8-0.137-3.7-0.7-4.26-1.26-1.27-2.9 0.6-3.5 1.46-0.92 1.24-1.86 3.3-2.8 6.2v14.6h-3.65v-18c0-2.3-0.225-3.7-0.68-4.1-2.2-2-4.5 1.84-6.4 6.85v15.2zm41.4 0.73c-3.7 0-6.7-1.3-9-3.9-2.24-2.6-3.4-5.95-3.4-10s1.1-7.31 3.4-9.9 5.2-3.9 9-3.9c3.7 0 6.7 1.3 9 3.9s3.4 5.9 3.4 9.9-1.1 7.3-3.4 10c-2.24 2.6-5.2 3.9-9 3.9zm0-3.65c2.3 0 4.2-0.9 5.53-2.73 1.4-1.8 2.1-4.32 2.1-7.5 0-3.2-0.685-5.7-2.1-7.5-1.37-1.8-3.2-2.73-5.53-2.73-2.33 0-4.2 0.9-5.6 2.73-1.37 1.8-2.1 4.3-2.1 7.5 0 3.2 0.69 5.7 2.1 7.5 1.37 1.8 3.2 2.7 5.6 2.7zm20 2.9v-26.3h4.35v5.74c2.44-4 5.8-6.5 10.5-6.5 1.6 0 3.15 0.243 4.7 0.73v9.5h-4.2v-6c-5.3-1-8.7 3.5-11 7.53v15.3zm34 0v-22.6h-7.24v-3.65h11.6v22.6h7.27v3.65zm-0.724-36c0-1 0.34-1.73 1-2.2s1.3-0.73 1.9-0.73c0.6 0 1.24 0.24 1.92 0.73 0.67 0.49 1 1.2 1 2.2 0 0.97-0.335 1.7-1 2.2-0.67 0.49-1.3 0.73-1.9 0.73-0.6 0-1.22-0.24-1.9-0.73-0.675-0.49-1-1.2-1-2.2zm22.4 34.4v-4.6c4.5 2.9 9.3 3.1 10.5 3.1 2.35 0 6.2-0.5 6.2-3.74-0.22-2.7-3.4-3.4-5.5-4.3l-3-1.2c-3.24-1.3-5.3-2.45-6.1-3.5-1.75-2.2-2.1-6.5 1.2-9.24 3.26-2.7 10.8-2.6 16-0.9v4c-2.9-1.04-5.6-1.55-8.1-1.55-0.5 0-5.9 0.078-5.9 3.4 0 1.5 0.81 2.4 5.7 4.2l2.94 1.1c2.7 1 4.6 2.1 5.7 3.2 2.3 2.4 2.44 7.1-0.98 10-3 2.5-10.4 3.34-18.6 0.07zm39.1 2.5c-1 0-1.94-0.38-2.7-1.13-0.75-0.75-1.12-1.65-1.1-2.7 0-1.1 0.37-2 1.12-2.7 0.76-0.75 1.66-1.13 2.7-1.13s1.92 0.376 2.66 1.13c0.763 0.737 1.14 1.64 1.14 2.7 0 1.05-0.373 1.95-1.12 2.7s-1.64 1.13-2.7 1.13zm35.5-7c-3.63 6-10.7 9.3-15.8 3.8-3.37-3.7-3.3-11.8-0.864-16.8 2.9-6.1 7.53-7.76 11.8-7.76 1.63 0.016 3.25 0.215 4.86 0.376v-11.7h4.37v38h-4.37zm0-5.2v-11.3c-1.54-0.41-3-0.61-4.3-0.61-4.67 0-9.37 3.6-9.4 11.6 0 7.36 4 7.9 4.8 7.9 2.5 0 6-2.6 8.9-7.65zm28.4 5.2c-3.5 5.8-8.4 6.6-9.9 6.6-0.7 0-8.5-0.253-8.5-11.5 0-2.65 0.58-9.68 6.36-13.8 3.55-2.6 7.93-2.14 12-1.72h4.37v18.6c0 3.17 0.327 5.74 1 7.7h-4.53c-0.343-1.44-0.615-3.4-0.82-5.9zm0-5.2v-11.3c-5.57-1.5-8.75-0.11-11 2.5-1.8 2.1-2.66 4.9-2.66 8.5 0 7.36 4 7.9 4.8 7.9 2.5 0 6-2.6 8.9-7.65zm21.2 10-12-25.2h4.8l9.6 19.7 8-19.7h4.4l-10.1 24.4c-2.14 5.16-4.16 8.6-6.1 10.3-4 3.53-9.8 2.46-11 2.2v-3.86c1.62 0.5 4.53 1 7.17-0.26 2.8-1.6 4.16-4.7 5.3-7.55z"/></svg>
</a>
</div>
<style>
.title {
display: flex;
height: 100%;
align-items: center;
justify-content: center;
a {
display: block;
height: 75%;
}
svg {
height: 100%
}
}
</style>

View File

@ -0,0 +1,20 @@
import Post from '$lib/server/LoadPost';
import { remark } from 'remark'
export async function load({params}) {
const md = await Post(params.slug);
let mdast = remark().parse(md.post)
let header = mdast.children.filter((i) => i.type=='heading').map((i)=>{
let title = i.children[0].value
return {"depth": i.depth, "title": title}
})
return {
id: params.slug,
metadata: md.metadata,
post: md.post,
heading: header,
}
}

View File

@ -0,0 +1,268 @@
<script>
const { data } = $props();
import Markdown from '$lib/components/Markdown.svelte';
import Toc from './toc.svelte'
import Share from './share.svelte'
import Profile from './profile.svelte'
import { base } from '$app/paths';
import { PUBLIC_HOSTNAME } from '$env/static/public';
const baseURL = `https://${PUBLIC_HOSTNAME}${base}`
</script>
<svelte:head>
<title>{data.metadata.title} | moris.day Blog</title>
<meta property="og:type" content="article">
<meta property="og:title" content="{data.metadata.title}">
<meta property="og:url" content="{baseURL}/{data.id}">
<meta property="og:description" content="{data.metadata.description}">
{#if data.metadata.thumbnail}
<meta property="og:image" content="https://moris.day{data.metadata.thumbnail}">
{/if}
{#if !data.metadata.index}
<meta name="robots" content="noindex">
{/if}
</svelte:head>
<div id='blog'>
<article>
<h1 class="title">{data.metadata.title}</h1>
<div class="meta">
<div class="category">
<span class="txt">Category:</span>
<a class="tag" href='/blog/category/{data.metadata.category}'>{data.metadata.category}</a>
</div>
{#if data.metadata.tags.length }
<div class="divider"></div>
<div class="tags">
<span class="txt">Tags:</span>
{#each data.metadata.tags as tag}
<a class='tag' href='/blog/tag/{tag}'>{tag}</a>
{/each}
</div>
{/if}
<div class="date">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M480-120q-138 0-240.5-91.5T122-440h82q14 104 92.5 172T480-200q117 0 198.5-81.5T760-480q0-117-81.5-198.5T480-760q-69 0-129 32t-101 88h110v80H120v-240h80v94q51-64 124.5-99T480-840q75 0 140.5 28.5t114 77q48.5 48.5 77 114T840-480q0 75-28.5 140.5t-77 114q-48.5 48.5-114 77T480-120Zm112-192L440-464v-216h80v184l128 128-56 56Z"/></svg>
<span>{data.metadata.date.toLocaleDateString('sv-SE')}</span>
</div>
</div>
{#if data.metadata.thumbnail}
<img class="thumbnail" alt="thumbnail" src="{data.metadata.thumbnail}" style='view-transition-name: {data.id}'>
{:else if data.metadata.emoji}
<div class='thumbnail emoji'><img class="emoji" alt="thumbnail" src="{data.metadata.emoji}" style='view-transition-name: {data.id}'></div>
{/if}
<div class="md">
<Markdown mdtext={data.post} />
</div>
<div class="data">
<div><a target="_blank" href="https://git.moris.day/moris/test-sub/commits/branch/main/Posts/{data.id}.md">History</a></div>
<div class="spacer"></div>
<div><a target="_blank" href="{data.id}/raw">Raw file</a></div>
</div>
</article>
<aside>
<div id='side'>
<div>
<Profile></Profile>
</div>
<div>
<Share share={{url:`${baseURL}/${data.id}`, title:data.metadata.title}} />
</div>
<div id='toc'>
<Toc toclist={data.heading}></Toc>
</div>
<div class="mi-posts">
<!--
<iframe src="https://mi.moris.day/embed/user-timeline/9w7bhjzt2b5z0001?maxHeight=300&rounded=false&border=false" title="moris's posts" data-misskey-embed-id="v1_fe3a992c-ac63-42f9-9339-d657fe56462f" loading="lazy" referrerpolicy="strict-origin-when-cross-origin" style="width:100%; height:300px; border:solid #8884 1px; border-radius:8px; box-sizing:border-box"></iframe>
-->
</div>
</div>
</aside>
</div>
<style>
#blog {
display: flex;
margin: 2.5%;
gap: 20px;
}
aside {
@media (width<1000px) {
display: none;
}
& #side {
width: 280px;
display: flex;
flex-direction: column;
row-gap: 20px;
position: sticky;
top: 70px;
}
& #side>* {
background-color: var(--grid-color);
border-radius: 8px;
box-shadow: 0 0 6px #1111;
overflow: hidden;
}
& #toc {
max-height: 50vh;
overflow: scroll;
}
}
article {
min-width: 0;
flex-grow: 1;
padding: 0 20px;
border-radius: 8px;
@media(width<480px){
padding: 0;
}
& .title {
font-size: 2em;
margin: 0;
padding: 5px;
border-bottom: 1px solid;
}
& .thumbnail {
display: block;
margin: 0 auto;
max-height: 50vh;
}
& img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
& .thumbnail.emoji {
width: 30%;
max-width: 30vh;
aspect-ratio: 1;
@media(width<480px) {
width: 50%;
}
& img {
margin: 15%;
}
}
& .md {
margin: 25px 0;
@media (width<480px) {
margin: 8px;
}
}
& .data {
display: flex;
padding: 8px;
border-top: solid 1px;
font-weight: 200;
font-size: 1.2em;
& .spacer {
width: 1px;
background-color: var(--font-color);
box-sizing: border-box;
margin: 2px 6px;
}
& a {
margin: 4px;
color: var(--font-color);
text-decoration-thickness: 1px;
}
}
}
.meta {
display: grid;
grid-template-rows: auto auto;
grid-template-columns: auto auto 1fr auto;
gap: 8px;
font-size: 0.95em;
margin: 8px;
white-space: nowrap;
@media(width<1000px) {
flex-direction: column;
}
& .category {
grid-row: 1;
grid-column: 1;
}
& .tags {
grid-row: 1;
grid-column: 3;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@media(width<1000px) {
grid-row: 2;
grid-column: 1 / 5;
}
@media(width<480px){
display: inline-block;
grid-row: 1;
grid-column: 3;
}
}
& .tag {
display: inline-block;
background-color: var(--theme-color);
color: var(--font-color);
border-radius: 3px;
padding: 0 4px 1px 4px;
margin: 3px;
}
& .divider {
display: inline-block;
width: 1px;
margin: 3px 0;
background-color: var(--font-color);
grid-row: 1;
grid-column: 2;
@media(480px<width<1000px){
display: none;
}
}
& .date{
display: flex;
grid-row: 1;
grid-column: 4;
vertical-align: middle;
align-items: center;
@media(width<480px){
display: inline-block;
grid-row: 2;
grid-column: 1 / 5;
}
}
& .date svg{
height: 1rem;
fill: var(--font-color);
vertical-align: middle;
}
@media(width<480px) {
& .txt {display: none;}
}
}
</style>

View File

@ -0,0 +1,49 @@
<div class="profile" aria-label="プロフィール">
<div>
<div class="space topspace"></div>
<div class="icon">
<img src="/blog/icon_192.webp" width="192" height="192" alt="moris icon">
</div>
<div class="space bottomspace"></div>
<div style="position:relative; text-align:center; font-family:monospace;font-size:1.2em; font-weight:350; width:fit-content; margin:auto;"><span>moris</span><img src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/svg/1f9ea.svg" alt="test tube" style="position:absolute; left:100%; aspect-ratio:1; height:100%; padding:3%; box-sizing:border-box;"></div>
</div>
<div class="discription">
<span>有機化学好きの大学生<br>電子工作やプログラムもいじります<br>ご連絡は<a href="https://mi.moris.day/@moris" target="_blank">Misskey</a>から<br>誤字などは<a href="https://github.com/cocoyayann" target="_blank">Github</a>のissueへ</span>
</div>
</div>
<style>
.profile {
position: relative;
}
.topspace {
height: 55px;
background-color: color-mix(in srgb, var(--theme-color) 25%, var(--back-color));
}
.bottomspace {
height: 38px;
}
.icon {
position: absolute;
padding: 4px;
border-radius: 50%;
width: 70px;
height: 70px;
top: 14px; /*calc(55px - 35px - 6px);*/
left: calc(50% - 41px); /*calc(50% - 35px - 6px);*/
background-color: var(--grid-color);
& img {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
.discription {
padding: 8px;
font-size: 0.9em;
text-align: center;
line-height: 1.8;
}
</style>

View File

@ -0,0 +1,11 @@
export const prerender = true
import fs from 'node:fs';
import { POST_DIR } from '$env/static/private';
/** @type {import('./$types').RequestHandler} */
export async function GET({params}) {
const raw = fs.readFileSync(`${POST_DIR}/Posts/${params.slug}.md`)
return new Response(raw);
};

View File

@ -0,0 +1,85 @@
<script>
export let share;
let copyed = false;
function copylink() {
navigator.clipboard.writeText(share.url);
copyed = true;
setTimeout(() => (copyed = false), 1000);
}
</script>
<div style="margin:4px 0 0 8px;color:#aaaa;font-size:0.85em">Share</div>
<div id="share">
<button class="link" type="button" on:click={copylink} aria-label="urlをコピー" title="copy url">
{#if copyed}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M620-163 450-333l56-56 114 114 226-226 56 56-282 282Zm220-397h-80v-200h-80v120H280v-120h-80v560h240v80H200q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h167q11-35 43-57.5t70-22.5q40 0 71.5 22.5T594-840h166q33 0 56.5 23.5T840-760v200ZM480-760q17 0 28.5-11.5T520-800q0-17-11.5-28.5T480-840q-17 0-28.5 11.5T440-800q0 17 11.5 28.5T480-760Z"/></svg>
{:else}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h167q11-35 43-57.5t70-22.5q40 0 71.5 22.5T594-840h166q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560h-80v120H280v-120h-80v560Zm280-560q17 0 28.5-11.5T520-800q0-17-11.5-28.5T480-840q-17 0-28.5 11.5T440-800q0 17 11.5 28.5T480-760Z"/></svg>
{/if}
</button>
<div class="misskey">
<a href="https://misskey-hub.net/share?url={share.url}&text={share.title}+-+moris+Blog" target="_blank" aria-label="misskeyで共有">
<svg version="1.1" viewBox="0 0 136 136" xmlns="http://www.w3.org/2000/svg"><path d="m16.5 20c-9 0-16.5 7.5-16.5 16.5v62c0 9 7.5 16.5 16.5 16.5 9 0 16.5-7.5 16.5-16.5v-12c0-3 2.6-2 4 0 2.4 4 7.4 7.6 13.3 7.5s10.9-3.5 13.3-7.5c1.4-2 4-3 4 0v12c0 9 7.5 16.5 16.5 16.5 9 0 16.5-9 16.5-16.5v-62c0-9-7.5-16.5-16.5-16.5-5 0-9.4 2-13 6l-16.5 19.5c-2.45 3.13-5.96 2.92-8.4 0l-16.5-19.5c-3.4-4-7.7-6-13-6zm105.5 0c-8 0-14 6-14 14 0 8 6 14 14 14 8 0 14-6 14-14 0-8-6-14-14-14m0 31c-8 0-14 6 -14 14v35c0 8 6 14 14 14 8 0 14-6 14-14v-35c0-8-6-14-14-14"/></svg>
</a>
</div>
<div class="threads">
<a href="https://www.threads.net/intent/post?text={share.title}+-+moris+Blog%0A{share.url}" target="_blank" aria-label="threadsで共有">
<svg version="1.1" viewBox="0 0 1100 1100" xmlns="http://www.w3.org/2000/svg"><path d="m558 1050c-229 0-444-129-446-499v-0.7c1.4-172 56-496 446-499h0.6c231 1.6 377 122 430 315l-85 23.7c-46-165-162-249-346-251-284 2.1-356 208-358 411 2.8 325 179 410 358 411 109-0.8 182-26 242-85 83.6-82 81-212-22.7-273-34.2 240-253 237-344 177-92-60-97-184-17.6-252 82-71 221-57 275-48-25-150-195-129-244-55l-73-49c93-138 393-152 409 130 196 83 203 303 80 432-82 88-172 112-304 112zm11-486c-173 9.7-151 171-6 163 51-2.7 117-22.6 129-155-41-3.7-82-10-123-8z"/></svg>
</a>
</div>
<div class="twitter">
<a href="http://twitter.com/share?url={share.url}&text={share.title}+-+moris+Blog%0A" target="_blank" aria-label="twitterで共有">
<svg viewBox="0 0 248 248" xmlns="http://www.w3.org/2000/svg"><path d="M 222,73 C 222,205 87,257 1,201 28,204 54,196 76,180 54,180 35,166 29,145 37,147 44,146 52,144 28,139 11,118 11,94 18,98 26,100 34,100 12,85 5,56 18,33 44,65 81,84 122,86 111,38 172,2 208,40 219,38 230,34 240,28 236,40 228,50 218,56 227,56 238,52 247,48Z"/></svg>
</a>
</div>
<div class="rss">
<a href="/blog/feed.rss" target="_blank" aria-label="rss配信" type="application/rss+xml">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200q0-33 23.5-56.5T200-280q33 0 56.5 23.5T280-200q0 33-23.5 56.5T200-120Zm480 0q0-117-44-218.5T516-516q-76-76-177.5-120T120-680v-120q142 0 265 53t216 146q93 93 146 216t53 265H680Zm-240 0q0-67-25-124.5T346-346q-44-44-101.5-69T120-440v-120q92 0 171.5 34.5T431-431q60 60 94.5 139.5T560-120H440Z"/></svg>
</a>
</div>
</div>
<style>
#share {
display: flex;
gap: 4px;
height: 36px;
padding: 5px;
& > * {
height: 100%;
flex: 1;
border-radius: 3px;
&:hover {
background-color: #0001;
}
}
}
button {
all: unset;
}
svg {
height: 100%;
width: 100%;
fill: #aaaa;
transition: fill 0.2s;
}
.link svg:hover {
fill: hsl(0, 25%, 30%);
}
.misskey svg:hover {
fill: hsl(75, 99%, 40%);
}
.threads svg:hover {
fill: black;
}
.twitter svg:hover {
fill: hsl(203, 100%, 65%);
}
.rss svg:hover {
fill: orange;
}
</style>

View File

@ -0,0 +1,48 @@
<script>
export let toclist;
import GithubSlugger from 'github-slugger';
const slugs = new GithubSlugger()
</script>
<nav>
<div style="margin:4px 0 0 8px;color:#aaaa;font-size:0.85em;">目次</div>
<ul>
{#each toclist as toc}
{#if toc.depth==2}
<li><a href='#{slugs.slug(toc.title)}'>{toc.title}</a></li>
{/if}
{/each}
</ul>
</nav>
<style>
ul {
margin: 0;
padding: 5px;
font-size: .9rem;
}
li {
padding: 4px;
border-radius: 3px;
}
li:hover {
background-color: #0002;
}
a {
display: block;
text-decoration: none;
color: var(--font-color);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&::before {
content: '・';
color: var(--font-color);
}
}
</style>

View File

@ -0,0 +1,17 @@
import Metadatas from "$lib/server/Metadatas";
export async function load() {
let metalist = await Metadatas()
let tags = []
for (let meta of metalist){
for (let tag of meta.metadata.tags) {
if (!tags.includes(tag)) {
tags.push(tag)
}
}
}
return {tags}
}

View File

@ -0,0 +1,9 @@
<script>
const { data } = $props();
</script>
{#each data.tags as tag}
<div>
<a href="./tag/{tag}">{tag}</a>
</div>
{/each}

View File

@ -0,0 +1,11 @@
import Metadatas from "$lib/server/Metadatas";
export async function load({params}) {
const tag = params.slug
const metalist = await Metadatas()
const result = metalist.filter((meta)=>meta.metadata.tags.includes(tag)).map((meta)=>meta.postId)
return {posts: result}
}

View File

@ -0,0 +1,7 @@
<script>
const { data } = $props();
</script>
{#each data.posts as post}
<a href="{post}}">{post}</a>
{/each}

1
src/routes/+layout.js Normal file
View File

@ -0,0 +1 @@
export const prerender = true;

View File

@ -0,0 +1,12 @@
import { POST_DIR } from '$env/static/private';
import Path from 'node:path';
import fs from 'node:fs';
export async function load({}) {
const files = fs.readdirSync(`${POST_DIR}/Codes`);
const codes = files.filter((content)=> Path.extname(content)=='.html')
return {codes}
}

View File

@ -0,0 +1,10 @@
<script>
const { data } = $props();
import { base } from '$app/paths';
</script>
<ul>
{#each data.codes as code}
<li><a href="{base}/code/{code}">{code}</a></li>
{/each}
</ul>

View File

@ -0,0 +1,71 @@
<script>
import './app.css'
function toggleFullScreen() {
console.log('hello')
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
console.log('full')
document.querySelector("body").requestFullscreen();
}
}
function hide() {
let uuid = crypto.randomUUID()
window.name = uuid
window.parent.postMessage({"id": uuid, 'message': 'toggleHide'}, '*');
}
</script>
<div id='window'>
<div class="menubar">
<button style="background-color: rgb(255 95 87)" aria-label="reload" onclick={()=>{location.reload()}}></button>
<button style="background-color: rgb(255 188 46)" aria-label="close" onclick={hide}></button>
<button style="background-color: rgb(40 200 64)" aria-label="fullscreen" onclick={toggleFullScreen}></button>
</div>
<div class="code">
<slot />
</div>
</div>
<style>
#window {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
box-sizing: border-box;
border: solid var(--border-color) 1px;
border-radius: 12px;
box-shadow: 0px 8px 40px var(--shadow-color);
background-color: var(--back-color);
font-family: sans-serif;
overflow: hidden;
}
.menubar {
display: flex;
height: 28px;
padding: 0 4px;
border-radius: 12px 12px 0 0;
background-color: var(--head-color);
border-bottom: solid var(--divider-color) 1px;
& >* {
all: unset;
width: 12px;
height: 12px;
margin: 8px 4px;
border-radius: 50%;
background-color: gray;
cursor: pointer;
}
}
.code {
height: calc(100% - 28px);
overflow-x: hidden;
overflow-y: scroll;
}
</style>

View File

@ -0,0 +1,10 @@
import { POST_DIR } from '$env/static/private';
import fs from 'node:fs';
export async function load({params}) {
const code = params.slug.replace(/\.html$/, "");
const html = fs.readFileSync(`${POST_DIR}/Codes/${code}.html`);
return {html}
}

View File

@ -0,0 +1,5 @@
<script>
const { data } = $props();
</script>
<div>{@html data.html}</div>

View File

@ -0,0 +1,35 @@
:root {
height: 100%;
background-color: var(--white-black);
--back-color: white; /*rgb(240 238 245)*/
--head-color: rgb(245, 245, 245); /*rgb(244 243 250)*/
--divider-color: rgb(220 220 220);
--border-color: #bbb;
--shadow-color: #0009;
--white-black: #fff;
--font-color: #222;
@media(prefers-color-scheme: dark){
--back-color: rgb(43 48 55);
--head-color: rgb(58 60 66);
--divider-color: #222;
--border-color: #555;
--shadow-color: #000;
--white-black: #000;
--font-color: #f5f5f5;
}
}
body {
margin: 0;
height: 100%;
background-color: var(--white-black);
color: var(--font-color);
}
body:fullscreen {
padding: 10%;
background-image: url(/img/BigSur.avif);
background-size: cover;
}

View File

@ -0,0 +1,48 @@
export const prerender = true
import path from 'node:path';
import Metadatas from '$lib/server/Metadatas';
import { PUBLIC_HOSTNAME } from '$env/static/public';
/** @type {import('./$types').RequestHandler} */
export async function GET() {
let items = ""
let i = 0
const metalist = await Metadatas()
for (let post of metalist){
let meta = post.metadata
let link = `https://${PUBLIC_HOSTNAME}/blog/post/${path.basename(post.postId, '.md')}`
if (meta.published) {
i++
items += `
<item>
<title>${meta.title}</title>
<description>${meta.description}</description>
<link>${link}</link>
<pubDate>${meta.date.toUTCString()}</pubDate>
<guid>${link}</guid>
</item>`
}
if (i==15) {break}
}
let rss = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>moris.day blog RSSfeed</title>
<description>工作とか化学とかプログラミングとか</description>
<link>https://moris.day/blog</link>
<pubDate>${(new Date()).toUTCString()}</pubDate>
${items}
</channel>
</rss>`
return new Response(rss);
};

BIN
static/icon_192.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
static/img/BigSur.avif Normal file

Binary file not shown.

1
static/img/image.svg Normal file
View File

@ -0,0 +1 @@
<svg fill="#aaa" version="1.1" viewBox="0 -960 96 96" xmlns="http://www.w3.org/2000/svg"><path d="m41-903q-0.8 0-1.4-0.6t-0.6-1.4v-14q0-0.8 0.6-1.4t1.4-0.6h14q0.8 0 1.4 0.6t0.6 1.4v14q0 0.8-0.6 1.4t-1.4 0.6zm0-2h14v-14h-14zm1-2h12l-3.8-5-3 4-2.25-3zm-1 2v-14z"/></svg>

After

Width:  |  Height:  |  Size: 268 B

13
svelte.config.js Normal file
View File

@ -0,0 +1,13 @@
import adapter from '@sveltejs/adapter-static';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter(),
paths: {
base: "/blog"
}
}
};
export default config;

6
vite.config.js Normal file
View File

@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
});