简化版的mastodon web client
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

472 lines
13 KiB

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" type="image/png" href="/img/ord/icon-128.png" />
<meta property="og:title" content="闭社简化版" />
<meta property="og:description" content="一个提供更简洁界面的闭社web client" />
<link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
<title>闭社简化版</title>
<style>
body {
background: linear-gradient(-45deg, #fff calc(50% - 1px), #d0d0d0 calc(50%), #fff calc(50% + 1px) );
background-size: 6px 5px;
font-family: 'Noto Sans SC', sans-serif;
}
body.grey {
background: #707070;
background-size: 6px 5px;
}
body,
pre {
font-family: 'Noto Sans SC', sans-serif;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: 'Noto Serif SC', serif;
font-weight: 300;
}
a,
a:hover,
.btn-link,
.btn-link:hover {
color: inherit;
text-decoration: underline;
display: inline-block;
}
.part1 {
min-width: 300px;
float: left;
padding-right: 10px;
position: relative;
}
.part2 {
min-width: 200px;
overflow: hidden;
padding-left: 25px;
}
.qbox {
border: 2px black solid;
background: white;
padding: 5px;
color: black;
margin: 5px 5px 20px;
}
body.grey #statuses-list .qbox {
background: #b0b0b0;
}
.new .qbox {
background: black;
color: white;
}
.new .qbox textarea {
background: black;
color: white;
}
.qbox textarea {
border: none;
border-bottom: 1px solid;
border-radius: 0;
}
body.grey #statuses-list .qbox textarea {
background: #b0b0b0;
}
.qbox .content {
margin: 15px 5px;
}
.liked, .reblogged {
font-weight: bold;
}
.super-small {
font-size: 0.5em;
}
.display_name {
margin: 0;
}
.behind {
z-index: 98;
cursor: pointer;
transform: translateY(5px) scale(0.98);
transform-origin: top;
transition-property: transform;
transition-duration: 0.5s;
}
.front {
z-index: 99;
transition-property: transform;
transition-duration: 0.5s;
}
.judge {
position: absolute;
top: 0;
right: 0;
margin: 0 0 30px 20px;
width: 90%;
}
.new {
position: relative;
margin: 30px 20px 30px 0;
}
a.hashtag {
font-weight: bold;
}
.comment-list-wrapper {
background: #3333;
padding-left: 3%;
font-size: 92%;
}
.status-media {
width: 80%;
margin: 10px 10%;
}
.emoji {
width: 22px;
vertical-align: text-bottom;
}
#statuses-list .invisible {
font-size: 0;
line-height: 0;
display: inline-block;
width: 0;
height: 0;
visibility: visible !important;
}
#statuses-list .ellipsis::after {
content: "...";
}
</style>
</head>
<body>
<div class="container" style="overflow: hidden;min-height: 100vh">
<div style='padding:15px'>
<h1> 闭社简化版 </h1>
</div>
<div class="part1">
<div class="new">
<form action="" onsubmit="return post_status(event, '')">
<div class="form-group qbox">
<textarea class="form-control" name="text" rows="5" maxlength="5000" placeholder="啥?" required="required"></textarea>
<div class="form-check mt-3 mb-3">
<input class="form-check-input" type="checkbox" value="" id="post-checkbox-an">
<label class="form-check-label" for="post-checkbox-an">
匿名
</label>
</div>
<button type="submit" class="btn btn-link btn-lg">发布</button>
</div>
</form>
</div>
</div>
<div class="part2" id="part2">
<h1>本站</h1>
<button class="btn btn-link grey_theme">切换亮度</button>
<div id="statuses-list">
</div>
<span id="loading-span">加载中..</span>
</div>
</div>
</body>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery-timeago/1.6.7/jquery.timeago.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery-timeago/1.6.7/locales/jquery.timeago.zh-CN.js"></script>
<script>
var token;
var max_id;
var loading_statues;
var base_api_url = "https://thu.closed.social/";
jQuery.timeago.settings.allowFuture = true;
function get_token() {
(document.cookie || "").split("; ").forEach( (c) => {
let cc = c.split("=");
if (cc.shift() === "mast_token") {
token = cc.join("=");
}
});
}
function get_more_statuses() {
loading_statues = true;
$.getJSON(
`${base_api_url}api/v1/timelines/public?limit=20&local=true` + (
max_id ? `&max_id=${max_id}` : ''
),
function (data) {
data.forEach((status) => {
$('#statuses-list').append(make_status_box_html(status, true));
});
$('.timeago').timeago();
if (data.length) {
max_id = data[data.length - 1].id;
loading_statues = false;
}
}
).fail((e) => {
alert(e.responseText);
});
}
function render_poll(poll) {
if (!poll)
return '';
let show_result = poll.expired || poll.voted;
return `
<div class="status-poll-box">
${poll.options.map((option, idx) => `
<small>
${option.title}
${show_result ? ` - ${option.votes_count} / ${poll.votes_count}` : ''}
${poll.own_votes.includes(idx) ? ' ✔️ ' : ''}
</small>
<div class="progress mb-2">
<div class="progress-bar progress-bar-striped" role="progressbar"
style="width: ${show_result ? option.votes_count / poll.votes_count * 100 : 0}%"
></div>
</div>
`).join('\n')}
<small>${poll.votes_count}人参与,${poll.expired ? '已结束' : `将结束于<time class="timeago" datetime="${poll.expires_at}"></timeago>`}</small>
</div>
`;
}
function render_media(media_attachments) {
return media_attachments.map((media) => {
switch (media.type) {
case 'image':
return `<image class="status-media" src=${media.url}>`;
case 'video':
return `<video class="status-media" src=${media.url} controls></video>`;
case 'gifv':
return `<video class="status-media" src=${media.url} autoplay loop></video>`;
default:
return '';
}
}).join('\n');
}
function render_content(text, is_mask_bot, emojis) {
if (is_mask_bot) {
text = text.replace(/^<p>\[([^\]]*)\]:<br \/>/, '<p>').replace(/匿了<\/p>$/, '</p>');
}
emojis.forEach((emoji) => {
text = text.replaceAll(`:${emoji.shortcode}:`, `<img class="emoji" src="${emoji.url}">`);
});
return text;
}
function make_status_box_html(status) {
return (`
<div class="qbox" id="status-${status.id.toString()}">
<img class="avatar" width="24" src="${status.account.avatar}">
<small>
${status.account.acct === "mask_bot" ?
"匿名用户" + /^<p>(\[[^\]]*\]):/.exec(status.content)[1] : (
status.account.display_name + ' @' +status.account.acct)}
</small>
${status.reblog && (status = status.reblog) &&
`<small> 转发 @${status.account.acct}</small>` || ''}
<div class="content">
${render_content(status.content, status.account.acct === "mask_bot", status.emojis)}
</div>
${render_poll(status.poll)}
${render_media(status.media_attachments)}
<div style="text-align:right;margin: 0px 0 -5px">
<time class="timeago mr-1 super-small" datetime="${status.created_at}"
title="${status.created_at}"></time>
<small class="mr-2 font-italic super-small">${status.application && status.application.name || ''}</small>
<a href="###" class="mr-3 like-btn ${status.favourited? 'liked' : ''}"
onclick="like_status(event, '${status.id.toString()}')">
点赞<span class="num">${status.favourites_count}</span>
</a>
<a href="###" class="mr-3 reblog-btn ${status.reblogged? 'reblogged' : ''}"
onclick="reblog_status(event, '${status.id.toString()}')">
转发<span class="num">${status.reblogs_count}</span>
</a>
<a class="mr-3 reply-btn" href="###"
onclick="get_comments('${status.id.toString()}')">
回复${status.replies_count}
</a>
</div>
<div class="collapse comment-list-wrapper" data-sid="${status.id.toString()}">
<div class="comment-list">
</div>
<form action="" onsubmit="return post_status(event, '${status.id}')">
<div class="form-group qbox">
<textarea class="form-control" rows="1" maxlength="5000" required="required" >@${status.account.acct} </textarea>
<div class="form-check mt-1 mr-2 float-right">
<input class="form-check-input" type="checkbox" value="" id="reply-${status.id}-checkbox-an">
<label class="form-check-label" for="reply-${status.id}-checkbox-an">
匿名
</label>
</div>
<button type="submit" class="btn btn-sm btn-link pb-0">添加回复</button>
</div>
</form>
</div>
</div>
`)
}
function get_comments(sid) {
let coll = $(`.comment-list-wrapper[data-sid="${sid}"`);
if (coll.hasClass('show')) {
coll.collapse('hide');
} else {
coll.collapse('show');
coll.find('> .comment-list').append("加载中..");
$.getJSON(
`${base_api_url}api/v1/statuses/${sid}/context`,
function (data) {
coll.find('> .comment-list').empty();
data.descendants.forEach( (rp) => {
let parent_coll = $(`.comment-list-wrapper[data-sid="${rp.in_reply_to_id}"`);
parent_coll.collapse('show');
parent_coll.find('> .comment-list').append(make_status_box_html(rp));
});
$('.timeago').timeago();
}
);
}
}
function like_status(e, sid) {
let target = $(e.target);
if (!target.hasClass('liked')) {
$.post(
`${base_api_url}api/v1/statuses/${sid}/favourite`,
(status) => {
target.addClass('liked');
target.find('.num').text(status.favourites_count)
}
);
} else {
$.post(
`${base_api_url}api/v1/statuses/${sid}/unfavourite`,
(status) => {
target.removeClass('liked');
target.find('.num').text(
target.find('.num').text() - 1
)
}
);
}
}
function reblog_status(e, sid) {
let target = $(e.target);
if (!target.hasClass('reblogged')) {
$.post(
`${base_api_url}api/v1/statuses/${sid}/reblog`,
(status) => {
target.addClass('reblogged');
target.find('.num').text(
target.find('.num').text() - (-1)
)
}
);
} else {
$.post(
`${base_api_url}api/v1/statuses/${sid}/unreblog`,
(status) => {
target.removeClass('reblogged');
target.find('.num').text(
target.find('.num').text() - 1
)
}
);
}
}
function post_status(e, sid) {
e.preventDefault();
let form = $(e.target);
let text = form.find('textarea').val();
if (form.find('input[type=checkbox]').is(':checked')) {
text += "\n匿了";
}
$.post(
`${base_api_url}api/v1/statuses`,
{'status': text, 'in_reply_to_id': sid || null, 'visibility': 'public'},
(status) => {
form.find('textarea').val('');
if (sid) {
form.prev().append(make_status_box_html(status));
} else {
$('#statuses-list').prepend(make_status_box_html(status));
}
},
'json'
).fail((e) => {
alert(e.responseText);
});
return false;
}
$(document).ready(function(){
get_token();
console.log(token);
if (!token) {
location.href = `${base_api_url}oauth/authorize?client_id=Wjf6ajif5kl6rIIt_TLu7SAAluGskaQiTXZoIr44jUc&response_type=code&redirect_uri=${encodeURIComponent(location.origin + "/auth")}&scope=read+write&force_login=False`
} else {
$.ajaxSetup({
headers : {
'Authorization' : 'Bearer ' + token
}
});
get_more_statuses();
$(window).scroll(() => {
if($('#loading-span').offset().top < $(window).scrollTop() + $(window).innerHeight() * 1.5 && !loading_statues) {
get_more_statuses();
}
});
}
$('.grey_theme').click((e) => {
$('body').toggleClass("grey");
});
});
</script>
</html>