browndust2.com news viewer

custom news viewer for sucking browndust2.com

目前为 2024-10-17 提交的版本。查看 最新版本

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址