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

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