简化版的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.

428 lines
12 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  6. <link rel="icon" type="image/png" href="/img/ord/icon-128.png" />
  7. <meta property="og:title" content="闭社简化版" />
  8. <meta property="og:description" content="一个提供更简洁界面的闭社web client" />
  9. <link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
  10. <title>闭社简化版</title>
  11. <style>
  12. body {
  13. background: linear-gradient(-45deg, #fff calc(50% - 1px), #d0d0d0 calc(50%), #fff calc(50% + 1px) );
  14. background-size: 6px 5px;
  15. font-family: 'Noto Sans SC', sans-serif;
  16. }
  17. body.grey {
  18. background: #707070;
  19. background-size: 6px 5px;
  20. }
  21. body,
  22. pre {
  23. font-family: 'Noto Sans SC', sans-serif;
  24. }
  25. h1,
  26. h2,
  27. h3,
  28. h4,
  29. h5,
  30. h6 {
  31. font-family: 'Noto Serif SC', serif;
  32. font-weight: 300;
  33. }
  34. a,
  35. a:hover,
  36. .btn-link,
  37. .btn-link:hover {
  38. color: inherit;
  39. text-decoration: underline;
  40. }
  41. .part1 {
  42. min-width: 300px;
  43. float: left;
  44. padding-right: 10px;
  45. position: relative;
  46. }
  47. .part2 {
  48. min-width: 200px;
  49. overflow: hidden;
  50. padding-left: 25px;
  51. }
  52. .qbox {
  53. border: 2px black solid;
  54. background: white;
  55. padding: 5px;
  56. color: black;
  57. margin: 5px 5px 20px;
  58. }
  59. body.grey #statuses-list .qbox {
  60. background: #b0b0b0;
  61. }
  62. .new .qbox {
  63. background: black;
  64. color: white;
  65. }
  66. .new .qbox textarea {
  67. background: black;
  68. color: white;
  69. }
  70. .qbox textarea {
  71. border: none;
  72. border-bottom: 1px solid;
  73. border-radius: 0;
  74. }
  75. body.grey #statuses-list .qbox textarea {
  76. background: #b0b0b0;
  77. }
  78. .qbox .content {
  79. margin: 15px 5px;
  80. }
  81. .liked, .reblogged {
  82. font-weight: bold;
  83. }
  84. .timeago {
  85. font-size: 0.5em;
  86. margin-right: 10px;
  87. }
  88. .display_name {
  89. margin: 0;
  90. }
  91. .behind {
  92. z-index: 98;
  93. cursor: pointer;
  94. transform: translateY(5px) scale(0.98);
  95. transform-origin: top;
  96. transition-property: transform;
  97. transition-duration: 0.5s;
  98. }
  99. .front {
  100. z-index: 99;
  101. transition-property: transform;
  102. transition-duration: 0.5s;
  103. }
  104. .judge {
  105. position: absolute;
  106. top: 0;
  107. right: 0;
  108. margin: 0 0 30px 20px;
  109. width: 90%;
  110. }
  111. .new {
  112. position: relative;
  113. margin: 30px 20px 30px 0;
  114. }
  115. a.hashtag {
  116. font-weight: bold;
  117. }
  118. .comment-list-wrapper {
  119. background: #3333;
  120. padding-left: 7%;
  121. font-size: 90%;
  122. }
  123. .status-media {
  124. max-height: 500px;
  125. max-width: 100%;
  126. margin: 10px auto;
  127. }
  128. #statuses-list .invisible {
  129. font-size: 0;
  130. line-height: 0;
  131. display: inline-block;
  132. width: 0;
  133. height: 0;
  134. visibility: visible !important;
  135. }
  136. #statuses-list .ellipsis::after {
  137. content: "...";
  138. }
  139. </style>
  140. </head>
  141. <body>
  142. <div class="container" style="overflow: hidden;min-height: 100vh">
  143. <div style='padding:15px'>
  144. <h1> 闭社简化版 </h1>
  145. </div>
  146. <div class="part1">
  147. <div class="new">
  148. <form action="" onsubmit="return post_status(event, '')">
  149. <div class="form-group qbox">
  150. <textarea class="form-control" name="text" rows="5" maxlength="5000" placeholder="啥?" required="required"></textarea>
  151. <div class="form-check mt-3 mb-3">
  152. <input class="form-check-input" type="checkbox" value="" id="post-checkbox-an">
  153. <label class="form-check-label" for="post-checkbox-an">
  154. 匿名
  155. </label>
  156. </div>
  157. <button type="submit" class="btn btn-link btn-lg">发布</button>
  158. </div>
  159. </form>
  160. </div>
  161. </div>
  162. <div class="part2" id="part2">
  163. <h1>本站</h1>
  164. <button class="btn btn-link grey_theme">切换亮度</button>
  165. <div id="statuses-list">
  166. </div>
  167. <span id="loading-span">加载中..</span>
  168. </div>
  169. </div>
  170. </body>
  171. <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
  172. <script src="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script>
  173. <script src="https://cdn.bootcdn.net/ajax/libs/jquery-timeago/1.6.7/jquery.timeago.min.js"></script>
  174. <script src="https://cdn.bootcdn.net/ajax/libs/jquery-timeago/1.6.7/locales/jquery.timeago.zh-CN.js"></script>
  175. <script>
  176. var token;
  177. var max_id;
  178. var loading_statues;
  179. var base_api_url = "https://thu.closed.social/";
  180. function get_token() {
  181. (document.cookie || "").split("; ").forEach( (c) => {
  182. let cc = c.split("=");
  183. if (cc.shift() === "mast_token") {
  184. token = cc.join("=");
  185. }
  186. });
  187. }
  188. function get_more_statuses() {
  189. loading_statues = true;
  190. $.getJSON(
  191. `${base_api_url}api/v1/timelines/public?limit=20&local=true` + (
  192. max_id ? `&max_id=${max_id}` : ''
  193. ),
  194. function (data) {
  195. data.forEach((status) => {
  196. $('#statuses-list').append(make_status_box_html(status, true));
  197. });
  198. $('.timeago').timeago();
  199. if (data.length) {
  200. max_id = data[data.length - 1].id;
  201. loading_statues = false;
  202. }
  203. }
  204. ).fail((e) => {
  205. alert(e.responseText);
  206. });
  207. }
  208. function make_status_box_html(status) {
  209. return (`
  210. <div class="qbox" id="status-${status.id.toString()}">
  211. <img class="avatar" width="24" src="${status.account.avatar}">
  212. <small>
  213. ${status.account.acct === "mask_bot" ?
  214. "匿名用户" + /^<p>(\[[^\]]*\]):/.exec(status.content)[1] : (
  215. status.account.display_name + ' @' +status.account.acct)}
  216. </small>
  217. ${status.reblog && (status = status.reblog) &&
  218. `<small> 转发 @${status.account.acct}</small>` || ''}
  219. <div class="content">
  220. ${status.account.acct === "mask_bot" ?
  221. status.content.replace(/^<p>\[([^\]]*)\]:<br \/>/, '<p>') : status.content}
  222. </div>
  223. ${status.media_attachments.map((media) => {
  224. switch (media.type) {
  225. case 'image':
  226. return `<image class="status-media" src=${media.url}>`;
  227. case 'video':
  228. return `<video class="status-media" src=${media.url} controls></video>`;
  229. default:
  230. return '';
  231. }
  232. }).join('\n')}
  233. <div style="text-align:right;margin: 0px 0 -5px">
  234. <time class="timeago" datetime="${status.created_at}"
  235. title="${status.created_at}"></time>
  236. <a href="###" class="mr-3 like-btn ${status.favourited? 'liked' : ''}"
  237. onclick="like_status(event, '${status.id.toString()}')">
  238. 点赞<span class="num">${status.favourites_count}</span>
  239. </a>
  240. <a href="###" class="mr-3 reblog-btn ${status.reblogged? 'reblogged' : ''}"
  241. onclick="reblog_status(event, '${status.id.toString()}')">
  242. 转发<span class="num">${status.reblogs_count}</span>
  243. </a>
  244. <a class="mr-3 reply-btn" href="###"
  245. onclick="get_comments('${status.id.toString()}')">
  246. 回复${status.replies_count}
  247. </a>
  248. </div>
  249. <div class="collapse comment-list-wrapper" id="collapse-${status.id.toString()}">
  250. <div class="comment-list">
  251. </div>
  252. <form action="" onsubmit="return post_status(event, '${status.id}')">
  253. <div class="form-group qbox">
  254. <textarea class="form-control" rows="2" maxlength="5000" required="required" >@${status.account.acct} </textarea>
  255. <div class="form-check mt-1 mb-1">
  256. <input class="form-check-input" type="checkbox" value="" id="reply-${status.id}-checkbox-an">
  257. <label class="form-check-label" for="reply-${status.id}-checkbox-an">
  258. 匿名
  259. </label>
  260. </div>
  261. <button type="submit" class="btn btn-link">添加回复</button>
  262. </div>
  263. </form>
  264. </div>
  265. </div>
  266. `)
  267. }
  268. function get_comments(sid) {
  269. let coll = $(`#collapse-${sid}`);
  270. if (coll.hasClass('show')) {
  271. coll.collapse('hide');
  272. } else {
  273. coll.collapse('show');
  274. coll.find('> .comment-list').append("加载中..");
  275. $.getJSON(
  276. `${base_api_url}api/v1/statuses/${sid}/context`,
  277. function (data) {
  278. coll.find('> .comment-list').empty();
  279. data.descendants.forEach( (rp) => {
  280. let parent_coll = $(`#collapse-${rp.in_reply_to_id}`);
  281. parent_coll.collapse('show');
  282. parent_coll.find('> .comment-list').append(make_status_box_html(rp));
  283. });
  284. $('.timeago').timeago();
  285. }
  286. );
  287. }
  288. }
  289. function like_status(e, sid) {
  290. let target = $(e.target);
  291. if (!target.hasClass('liked')) {
  292. $.post(
  293. `${base_api_url}api/v1/statuses/${sid}/favourite`,
  294. (status) => {
  295. target.addClass('liked');
  296. target.find('.num').text(status.favourites_count)
  297. }
  298. );
  299. } else {
  300. $.post(
  301. `${base_api_url}api/v1/statuses/${sid}/unfavourite`,
  302. (status) => {
  303. target.removeClass('liked');
  304. target.find('.num').text(
  305. target.find('.num').text() - 1
  306. )
  307. }
  308. );
  309. }
  310. }
  311. function reblog_status(e, sid) {
  312. let target = $(e.target);
  313. if (!target.hasClass('reblogged')) {
  314. $.post(
  315. `${base_api_url}api/v1/statuses/${sid}/reblog`,
  316. (status) => {
  317. target.addClass('reblogged');
  318. target.find('.num').text(
  319. target.find('.num').text() - (-1)
  320. )
  321. }
  322. );
  323. } else {
  324. $.post(
  325. `${base_api_url}api/v1/statuses/${sid}/unreblog`,
  326. (status) => {
  327. target.removeClass('reblogged');
  328. target.find('.num').text(
  329. target.find('.num').text() - 1
  330. )
  331. }
  332. );
  333. }
  334. }
  335. function post_status(e, sid) {
  336. e.preventDefault();
  337. let form = $(e.target);
  338. let text = form.find('textarea').val();
  339. if (form.find('input[type=checkbox]').is(':checked')) {
  340. text += "\n匿了";
  341. }
  342. $.post(
  343. `${base_api_url}api/v1/statuses`,
  344. {'status': text, 'in_reply_to_id': sid || null, 'visibility': 'public'},
  345. (status) => {
  346. form.find('textarea').val('');
  347. if (sid) {
  348. form.prev().append(make_status_box_html(status));
  349. } else {
  350. $('#statuses-list').prepend(make_status_box_html(status));
  351. }
  352. },
  353. 'json'
  354. ).fail((e) => {
  355. alert(e.responseText);
  356. });
  357. return false;
  358. }
  359. $(document).ready(function(){
  360. get_token();
  361. console.log(token);
  362. if (!token) {
  363. 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`
  364. } else {
  365. $.ajaxSetup({
  366. headers : {
  367. 'Authorization' : 'Bearer ' + token
  368. }
  369. });
  370. get_more_statuses();
  371. $(window).scroll(() => {
  372. if($('#loading-span').offset().top < $(window).scrollTop() + $(window).innerHeight() * 1.5 && !loading_statues) {
  373. get_more_statuses();
  374. }
  375. });
  376. }
  377. $('.grey_theme').click((e) => {
  378. $('body').toggleClass("grey");
  379. });
  380. });
  381. </script>
  382. </html>