Skip to content

Instantly share code, notes, and snippets.

@pacochi
Created August 9, 2021 08:42
Show Gist options
  • Save pacochi/41ed4cbdd6cbf2b5ca36e79ac6a34a58 to your computer and use it in GitHub Desktop.
Save pacochi/41ed4cbdd6cbf2b5ca36e79ac6a34a58 to your computer and use it in GitHub Desktop.
Mastodon のデータのエクスポートでもらったログを簡易的に見るもの
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>ログ見るだけのやつ</title>
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="referrer" content="no-referrer">
<meta name="robots" content="none">
<style type="text/css">
body {
background-color: #fafafa;
display: flex;
flex-wrap: wrap;
}
h1 {
font-size: medium;
color: #777;
width: 100%;
}
nav {
width: 10em;
}
ul {
list-style-type: none;
margin: 5px;
padding: 5px;
}
li {
margin: 0.5em;
color: #555;
cursor: pointer;
}
main {
width: calc(100% - 20em) !important;
}
article {
margin: 5px;
border-radius: 5px;
box-shadow: 0 2px 5px #ccc;
background-color: white;
}
img {
width: 256px;
border-radius: 5px 5px 0 0;
max-width: 100%;
max-height: 100%;
height: auto;
cursor: zoom-in;
transition: all 100ms 0s ease;
}
img.zoom {
width: auto;
border-radius: 5px;
position: fixed;
top: 50%;
left: 50%;
transform: translateY(-50%) translateX(-50%);
z-index: 2;
cursor: zoom-out;
}
img.zoom~span::before {
content: ' ';
position: fixed;
top:0px;
left:0px;
z-index: 1;
background-color: black;
opacity: 0.5;
width: 100%;
height: 100%;
}
video {
max-width: 100%;
max-height: 100%;
}
article {
color: #777;
}
article div {
padding: 20px;
}
article p {
color: #555;
font-size: 16px;
line-height: 1.5;
}
aside {
padding: 0 20px;
}
footer {
display: block;
text-align: right;
padding: 5px;
}
time a {
color: #333;
}
em {
font-style: normal;
}
p.dropme {
position: absolute;
top: 50%;
width: 100%;
text-align:center;
color: #555;
font-size: 20px;
}
</style>
</head>
<body>
<h1>ログ見るだけのやつ</h1>
<nav><ul>
</ul></nav>
<main>
</main>
<script type="text/javascript">
(() => {
const E = (name, attr = {}, ...children) => {
const element = Object.assign(document.createElement(name), attr);
children.forEach(child => element.appendChild(child));
return element;
};
const T = str => (str === null) ? '' : str;
if (location.host == 'hen.acho.co') {
document.body.appendChild(E('a', {
href: location.href,
download: 'view.htm'
}, new Text('この HTML ファイルをダウンロードして archive-* フォルダに入れて開いてね。')));
return;
}
const list = outbox => {
const followers = outbox.orderedItems[0].actor + '/followers';
const aPublic = 'https://www.w3.org/ns/activitystreams#Public';
outbox.orderedItems.forEach(toot => {
const published = new Date(toot.published);
const month = published.getFullYear() + '_' + (published.getMonth() + 1);
const privacy = (toot.type == 'Announce') ? '🔃'
: (toot.to[0] == aPublic && toot.cc[0] == followers) ? '🌎'
: (toot.to[0] == followers && toot.cc[0] == aPublic) ? '🔓'
: (toot.to[0] == followers) ? '🔒' : '✉';
if (typeof(toot.object) == 'string') toot.object = Object.assign(toot, { content: '🔃: ' + toot.object });
else if (toot.type == 'Announce') toot.object.content = '🔃: ' + toot.object.url + '<br>' + toot.object.content;
toot.object.date = published.toLocaleString();
toot.object.privacy = privacy;
if (!toots[month]) toots[month] = [];
toots[month].push(toot.object);
});
(Object.entries(toots)).forEach(([month, toot]) => nav.appendChild(
E('li', { onclick: view }, new Text(month))
));
};
const view = e => {
const month = event.target.textContent;
main.textContent = '';
toots[month].forEach(toot => main.appendChild(E('article', {},
E('div', { innerHTML: ((typeof(toot.summary) == 'string') ? toot.summary + '<br>' : '') + toot.content }),
addAttachments(toot.attachment),
E('footer', {},
E('time', { datetime: toot.published },
E('a', { href: toot.url, target: '_blank' }, new Text(T(toot.date)))
),
E('em', {}, new Text(T(toot.privacy)))
)
)));
};
const addAttachments = attachments => {
return attachments?.length ? E('aside', {}, ...attachments.map(attachment => {
const url = '.' + attachment.url;
switch (attachment.mediaType) {
case 'image/png':
case 'image/jpeg':
return(E('img', { src: url, onclick() { this.className = this.className ? '' : 'zoom'; } }));
case 'audio/mpeg':
return(E('audio', { src: url, controls: 'controls' }));
case 'video/mp4':
return(E('video', { src: url, controls: 'controls' }));
default:
return(E('a', { href: url, target: '_blank' }, new Text(T(url))));
}
}), E('span')) : new Text('');
};
const toots = {};
const nav = document.querySelector('nav').firstChild;
const main = document.querySelector('main');
fetch('./outbox.json').then(responce => responce.json()).then(outbox => list(outbox)).catch(e => {
const reader = new FileReader();
reader.addEventListener('load', e => list(JSON.parse(e.target.result)), false);
const guard = e => { e.stopPropagation(); e.preventDefault(); };
main.addEventListener('drop', e => {
main.textContent = '';
main.style.height = 'auto';
reader.readAsText(e.dataTransfer.files[0]);
e.stopPropagation();
e.preventDefault();
}, { once: true });
main.addEventListener('dragenter', guard, false);
main.addEventListener('dragover', guard, false);
main.style.width = '100%';
main.style.height = Math.trunc(window.innerHeight - main.getBoundingClientRect().top) + 'px';
main.appendChild(E('p', { className: 'dropme' }, new Text('ここら辺に outbox.json をドラッグアンドドロップしてね')));
});
})();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment