browndust2.com news viewer

custom news viewer for sucking browndust2.com

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

  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.2.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 list
  28. </label>
  29. <style>
  30. *, *::before, *::after {
  31. box-sizing: border-box;
  32. }
  33. body {
  34. max-width: 1200px;
  35. margin: 0 auto;
  36. background-color: #e5cc9c;
  37. }
  38.  
  39. img {
  40. max-width: 100%;
  41. }
  42.  
  43. h2 {
  44. display: inline;
  45. position: relative;
  46. font-size: inherit;
  47. margin: 0;
  48.  
  49. & span {
  50. font-weight: 100;
  51. font-size: smaller;
  52. vertical-align: middle;
  53. opacity: .5;
  54. }
  55. }
  56.  
  57. .ctx {
  58. white-space: pre-wrap;
  59. background-color: #fff9;
  60. padding: 1em;
  61.  
  62. & [style*="font-size"] {
  63. font-size: inherit !important;
  64. }
  65. & [style*="font-family"] {
  66. font-family: inherit !important;
  67. }
  68. }
  69.  
  70. .list {
  71. list-style: none;
  72. margin: 2em 0;
  73. padding-left: 50px;
  74. }
  75.  
  76. summary {
  77. position: relative;
  78. top: 0;
  79. background-color: #dfb991;
  80. padding: 1em 1em .75em;
  81. min-height: 50px;
  82. cursor: pointer;
  83.  
  84. &::before {
  85. content: '';
  86. position: absolute;
  87. inset: 0;
  88. background-color: #fff1;
  89. pointer-events: none;
  90. opacity: 0;
  91. transition: opacity .1s;
  92. }
  93.  
  94. :target & {
  95. box-shadow: inset 0 -.5em #0003;
  96. }
  97.  
  98. &:hover::before {
  99. opacity: 1;
  100. }
  101.  
  102. & > img {
  103. position: absolute;
  104. top: 0;
  105. right: 100%;
  106. width: 50px;
  107. height: 50px;
  108. }
  109. }
  110.  
  111. details {
  112. margin-block-start: 1em;
  113.  
  114. &[open] summary {
  115. position: sticky;
  116. background-color: #ceac71;
  117. box-shadow: inset 0 -.5em #0003;
  118. }
  119. }
  120.  
  121. #filterform {
  122. position: fixed;
  123. top: 0;
  124. left: 0;
  125. transition: opacity .2s;
  126. opacity: .1;
  127.  
  128. &:hover,
  129. &:focus-within {
  130. opacity: .75;
  131. }
  132. }
  133.  
  134. body:not(:has(.showall:checked))
  135. .list[data-query=""]
  136. details:nth-child(n + 20) {
  137. display: none;
  138. }
  139.  
  140. .showall-label {
  141. position: sticky;
  142. bottom: 0;
  143. display: block;
  144. width: fit-content;
  145. margin: 0 1em 0 auto;
  146. padding: .25em 1em .25em .5em;
  147. background-color: #0002;
  148. border-radius: 1em 1em 0 0;
  149. cursor: pointer;
  150. }
  151. </style>
  152. `;
  153.  
  154. let data = [];
  155. let news_map = new Map();
  156. let query_arr = [];
  157. let id_arr = [];
  158.  
  159. function render(id = 34) {
  160. list.innerHTML = data.map(i => {
  161. let info = i.attributes;
  162. let ctx = info.NewContent || info.content;
  163. let time = format_time(info.publishedAt);
  164. return `
  165. <details name="item" data-id="${i.id}" id="news-${i.id}">
  166. <summary>
  167. <img src="https://www.browndust2.com/img/newsDetail/tag-${info.tag}.png" width="36" height="36" alt="${info.tag}" title="#${info.tag}">
  168. <h2>
  169. <span>
  170. #${i.id} -
  171. <time datetime="${info.publishedAt}" title="${info.publishedAt}">${time}</time>
  172. </span>
  173. ${info.subject}
  174. </h2>
  175. </summary>
  176. <article class="ctx"></article>
  177. </details>
  178. `;
  179. }).join('');
  180.  
  181. list.querySelectorAll('details').forEach(d => {
  182. d.addEventListener('toggle', (e) => {
  183. if (e.target.open) {
  184. show(e.target, e.target.dataset.id);
  185. }
  186. });
  187. })
  188.  
  189. if (id) {
  190. taget_id(id);
  191. }
  192. }
  193.  
  194. function taget_id(id) {
  195. let target = list.querySelector(`details[data-id="${id}"]`);
  196. if (target) {
  197. target.open = true;
  198. show(target, id);
  199. }
  200. }
  201.  
  202. function show(target, id) {
  203. let ctx = target.querySelector(':scope > article.ctx');
  204. location.hash = `news-${id}`;
  205. if (!(ctx?.dataset?.init === '1')) {
  206. ctx.dataset.init = '1';
  207. let info = news_map.get(+id)?.attributes;
  208. let ori_link = `<a href="https://www.browndust2.com/zh-tw/news/view?id=${id}" target="_bd2news" title="official link">#</a>`;
  209. if (!info) {
  210. ctx.innerHTML = ori_link;
  211. return;
  212. }
  213. let content = (info.content || info.NewContent);
  214. content = content.replace(/\<img\s/g, '<img loading="lazy" ');
  215.  
  216. ctx.innerHTML = content + ori_link;
  217. }
  218. }
  219.  
  220. function format_time(time) {
  221. let _time = time ? new Date(time) : new Date();
  222. return _time.toLocaleString('zh-TW', {
  223. weekday: 'narrow',
  224. year: 'numeric',
  225. month: '2-digit',
  226. day: '2-digit',
  227. });
  228. }
  229.  
  230. function query() {
  231. let value = searchinput.value.trim();
  232. // console.log('query', value);
  233. if (!value) {
  234. filter_style.textContent = '';
  235. list.dataset.query = '';
  236. return;
  237. }
  238.  
  239. let matched_ids = query_arr.map((i, index) => {
  240. // if (!i.includes(value)) {
  241. // if (i.indexOf(value) === -1) {
  242. let regex = new RegExp(value, 'i');
  243. if (!regex.test(i)) {
  244. return;
  245. }
  246. return id_arr[index];
  247. })
  248. .filter(Boolean);
  249.  
  250. if (!matched_ids.length) {
  251. list.dataset.query = '';
  252. } else {
  253. list.dataset.query = value;
  254. }
  255.  
  256. let selectors = matched_ids.map(i => `[data-id="${i}"]`).join();
  257. filter_style.textContent = `
  258. details {display:none;}
  259. details:is(${selectors}) { display: block; }
  260. `;
  261. }
  262.  
  263. function debounce(func, wait, immediate) {
  264. var timeout;
  265. return function() {
  266. var context = this, args = arguments;
  267. clearTimeout(timeout);
  268. if (immediate && !timeout) func.apply(context, args);
  269. timeout = setTimeout(function() {
  270. timeout = null;
  271. if (!immediate) func.apply(context, args);
  272. }, wait);
  273. };
  274. }
  275.  
  276. let data_url = window.test_data_url || 'https://www.browndust2.com/api/newsData_tw.json';
  277. fetch(data_url)
  278. .then(r => r.json())
  279. .then(d => {
  280. data = d.data.reverse();
  281. tags = [...new Set(data.map(i => {
  282. let info = i.attributes;
  283. news_map.set(i.id, i);
  284. id_arr.push(i.id);
  285. query_arr.push([
  286. i.id,
  287. info.content,
  288. info.NewContent,
  289. `#${info.tag}`,
  290. info.subject,
  291. ].join());
  292. return i.attributes.tag;
  293. }))];
  294.  
  295. // console.log(data[900], tags);
  296. let id = new URL(location.href)?.searchParams?.get('id') || data[data.length - 1].id || 34;
  297. render(id);
  298. });
  299.  
  300. filterform.addEventListener('submit', e => e.preventDefault());
  301. searchinput.addEventListener('input', debounce(query, 300));