browndust2.com news viewer

custom news viewer for sucking browndust2.com

当前为 2024-10-15 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name browndust2.com news viewer
  3. // @namespace Violentmonkey Scripts
  4. // @match https://www.browndust2.com/robots.txt
  5. // @grant none
  6. // @version 1.0.1
  7. // @author Rplus
  8. // @description custom news viewer for sucking browndust2.com
  9. // @license WTFPL
  10. // ==/UserScript==
  11.  
  12. document.head.innerHTML = `
  13. <link rel="icon" type="image/png" sizes="16x16" href="/img/seo/favicon.png">
  14. `;
  15.  
  16. document.body.innerHTML = `
  17. <form id="filterform">
  18. Filter
  19. <input type="search" name="q" tabindex="1" id="searchinput">
  20. <style id="filter_style"></style>
  21. </form>
  22.  
  23. <div class="list" id="list" data-query=""></div>
  24. <hr>
  25. <label class="showall-label">
  26. <input type="checkbox" class="showall" >
  27. show all
  28. </label>
  29. <style>
  30. body {
  31. max-width: 1200px;
  32. margin: 0 auto;
  33. background-color: #e5cc9c;
  34. }
  35. .ctx {
  36. white-space: pre-wrap;
  37. background-color: #fff9;
  38. padding: 1em;
  39. }
  40. img {
  41. max-width: 100%;
  42. }
  43. .list {
  44. list-style: none;
  45. margin: 0;
  46. padding-left: 50px;
  47. }
  48. li {
  49. margin-top: 1em;
  50. margin-bottom: 1em;
  51. }
  52. summary {
  53. position: sticky;
  54. top: 0;
  55. background-color: #dfb991;
  56. padding: 1em;
  57. cursor: pointer;
  58.  
  59. &::before {
  60. content: '';
  61. position: absolute;
  62. inset: 0;
  63. background-color: #0001;
  64. pointer-events: none;
  65. opacity: 0;
  66. transition: opacity .1s;
  67. }
  68. &:hover::before {
  69. opacity: 1;
  70. }
  71.  
  72. & img {
  73. position: absolute;
  74. top: 0;
  75. right: 100%;
  76. height: 50px;
  77. width: 50px;
  78. }
  79. }
  80. details {
  81. margin-bottom: 1em;
  82. &[open] summary {
  83. background-color: #ffc;
  84. background-color: #ceac71;
  85. box-shadow: inset 0 -.5em #0003;
  86. }
  87. }
  88.  
  89. #filterform {
  90. position: fixed;
  91. top: 0;
  92. right: 0;
  93. }
  94.  
  95. body:not(:has(.showall:checked)) .list[data-query=""] details:nth-child(n + 20) {
  96. display: none;
  97. }
  98.  
  99. .showall-label {
  100. display: block;
  101. margin: 0 auto;
  102. width: fit-content;
  103. padding: 1em;
  104. }
  105. </style>
  106. `;
  107.  
  108. let data = [];
  109. let news_map = new Map();
  110. let query_arr = [];
  111. let id_arr = [];
  112.  
  113. function render(id = 34) {
  114. list.innerHTML = data.map(i => {
  115. let info = i.attributes;
  116. let ctx = info.NewContent || info.content;
  117. let time = format_time(info.publishedAt);
  118. return `
  119. <details name="item" data-id="${i.id}">
  120. <summary>
  121. <img src="https://www.browndust2.com/img/newsDetail/tag-${info.tag}.png" width="36" height="36" alt="${info.tag}" title="#${info.tag}">
  122. #${i.id} - <time datetime="${info.publishedAt}" title="${info.publishedAt}">${time}</time>
  123. ${info.subject}
  124. </summary>
  125. <div class="ctx"></div>
  126. </details>
  127. `;
  128. }).join('');
  129.  
  130. list.querySelectorAll('details').forEach(d => {
  131. d.addEventListener('toggle', (e) => {
  132. show(e.target, e.target.dataset.id);
  133. });
  134. })
  135.  
  136. if (id) {
  137. taget_id(id);
  138. }
  139. }
  140.  
  141. function taget_id(id) {
  142. let target = list.querySelector(`details[data-id="${id}"]`)
  143. let event = new CustomEvent('toggle');
  144. target.open = true;
  145. target.dispatchEvent(event);
  146. target.scrollIntoView();
  147. }
  148.  
  149. function show(target, id) {
  150. let ctx = target.querySelector(':scope > div.ctx');
  151. if (!(ctx?.dataset?.init === '1')) {
  152. ctx.dataset.init = '1';
  153. let info = news_map.get(+id)?.attributes;
  154. let ori_link = `<a href="https://www.browndust2.com/zh-tw/news/view?id=${id}" target="_bd2news" title="official link">#</a>`;
  155. ctx.innerHTML = (info?.content || info?.NewContent) + ori_link;
  156.  
  157. setTimeout(() => {
  158. target.scrollIntoView({ behavior: 'smooth', });
  159. }, 350);
  160. }
  161. }
  162.  
  163. const time_format = {
  164. weekday: 'narrow',
  165. year: 'numeric',
  166. month: '2-digit',
  167. day: '2-digit',
  168. };
  169. function format_time(time) {
  170. let _time = time ? new Date(time) : new Date();
  171. return _time.toLocaleString('zh-TW', time_format);
  172. }
  173.  
  174. function query() {
  175. let value = searchinput.value;
  176. // console.log('query', value);
  177. if (!value) {
  178. filter_style.textContent = '';
  179. list.dataset.query = '';
  180. return;
  181. }
  182.  
  183. let op = query_arr.map((i, index) => {
  184. // if (!i.includes(value)) {
  185. // if (i.indexOf(value) === -1) {
  186. let regex = new RegExp(value, 'i');
  187. if (!regex.test(i)) {
  188. return;
  189. }
  190. return id_arr[index];
  191. })
  192. .filter(Boolean);
  193.  
  194. if (!op.length) {
  195. list.dataset.query = '';
  196. } else {
  197. list.dataset.query = value;
  198. }
  199.  
  200. let selectors = op.map(i => `[data-id="${i}"]`).join();
  201. filter_style.textContent = `
  202. details {display:none;}
  203. details:is(${selectors}) { display: block; }
  204. `;
  205. }
  206.  
  207. function debounce(func, wait, immediate) {
  208. var timeout;
  209. return function() {
  210. var context = this, args = arguments;
  211. clearTimeout(timeout);
  212. if (immediate && !timeout) func.apply(context, args);
  213. timeout = setTimeout(function() {
  214. timeout = null;
  215. if (!immediate) func.apply(context, args);
  216. }, wait);
  217. };
  218. }
  219.  
  220. fetch('https://www.browndust2.com/api/newsData_tw.json')
  221. .then(r => r.json())
  222. .then(d => {
  223. data = d.data.reverse();
  224. tags = [...new Set(data.map(i => {
  225. let info = i.attributes;
  226. news_map.set(i.id, i);
  227. id_arr.push(i.id);
  228. query_arr.push([
  229. i.id,
  230. info.content,
  231. info.NewContent,
  232. `#${info.tag}`,
  233. info.subject,
  234. ].join());
  235. return i.attributes.tag;
  236. }))];
  237.  
  238. // console.log(data[900], tags);
  239. let id = new URL(location.href)?.searchParams?.get('id') || data[data.length - 1].id || 34;
  240. render(id);
  241. });
  242.  
  243. filterform.addEventListener('submit', e => e.preventDefault());
  244. searchinput.addEventListener('input', debounce(query, 300));