自动展开全文(永久 beta 版)

自动展开全文的 beta 版,大概永远不会正式。使用前请务必认真阅读发布页说明,安装即表示知悉、理解并同意说明中全部内容。此版本不包含任何 match 信息,请自行添加。

Verze ze dne 06. 08. 2020. Zobrazit nejnovější verzi.

// ==UserScript==
// @name        自动展开全文(永久 beta 版)
// @namespace   Expand the article for vip.
// @match       https://meta.appinn.net/t/beta/17991
// @grant       GM_addStyle
// @version     0.0.5
// @supportURL  https://meta.appinn.net/t/17991
// @homepageURL https://meta.appinn.net/t/17991
// @inject-into content
// @author      稻米鼠
// @created     2020-07-24 07:04:35
// @updated     2020-08-06 09:04:41
// @description 自动展开全文的 beta 版,大概永远不会正式。使用前请务必认真阅读发布页说明,安装即表示知悉、理解并同意说明中全部内容。此版本不包含任何 match 信息,请自行添加。
// ==/UserScript==

(function(){
// 闭包 Start
// 设定控制阈值,当元素的子元素是特定元素的数量超过此值,当作正文处理。
const passagesMinCount = 3
// 设定控制阈值,当元素内文字字数超过此值,当作正文处理。
const contentMinCount = 200
// 重写添加 style 功能,以兼容无接口的运行环境。
GM_addStyle = GM_addStyle ? GM_addStyle : (css)=>{
  const style = document.createElement('style')
  // 添加额外 ID 便于识别
  style.id = 'expand-the-article-for-me'
  style.innerHTML = css
  document.head.appendChild(style)
}
// 加入基础样式信息,后面通过为元素添加相应类来实现展开
GM_addStyle(`
.expand-the-article-no-limit {
  max-height: none !important;
  height: auto !important;
}
.expand-the-article-display-none { display: none !important; }
`)
/**
 * 元素处理器,对每个进行分析,是否为(疑似)正文元素
 * @param {*} el 待判断元素
 * @returns 如果是类正文元素,则返回真,否则返回假
 */
const elFilter = el=>{
  try {
    // 统计元素中特定子元素的个数,判断是否属于正文
    let passages = 0
    const children = el.children
    for(let i=0; i<children.length; i++){
      if(/^p|br|h1|h2|h3|h4|h5|h6$/i.test(children[i].tagName)) passages++
    }
    if(passages >= passagesMinCount)  return true
    // 如果有文字内容,并且字数大于阈值
    if(el.innerText && el.innerText.length >= contentMinCount) return true
  } catch (error) {}
  return false
}
/**
 * 移除可能的 阅读更多 按钮
 * @param {*} el 待处理元素
 * @param {*} index 当前处理层级,超出一定深度则跳出
 */
const removeReadMoreButton = (el, index=0)=>{
  if(index>=3) return // 只处理两层深度
  const e = el.lastElementChild  // 只看最后一个子元素(一般没差吧
  if(!e) return // 没有子元素就跳出
  const eStyle = window.getComputedStyle(e)
  if(/^(fixed|absolute)$/i.test(eStyle.position) || Number(eStyle.zIndex)>=1){
    e.classList.add('expand-the-article-display-none')
  }
  // 递归
  removeReadMoreButton(e, ++index)
}
/**
 * 移除元素高度限制,会尝试处理正文元素的所有祖先元素
 * @param {*} el 待处理元素
 */
const removeHeightLimit = el=>{
  // 如果元素标签是 html 则返回
  if(/html/i.test(el.tagName)) return
  // 如果包含特定类名(表示已处理过),则返回
  if(el.classList.contains('expand-the-article-no-limit')) return
  // 获取元素高度属性,如有限制,则去除
  const elStyle = window.getComputedStyle(el)
  // 如果存在高度限制,或者隐藏内容,则去除
  if (
    elStyle.maxHeight !== 'none' ||
    elStyle.height !== 'auto' ||
    elStyle.overflowY !== 'hidden'
  ) {
    el.classList.add('expand-the-article-no-limit');
  }
  // 寻找并移除 阅读更多 按钮
  removeReadMoreButton(el)
  // 递归处理祖先元素
  removeHeightLimit(el.parentElement)
}

document.querySelectorAll('*').forEach((el)=>{
  if(elFilter(el)) removeHeightLimit(el)
})
const obOptions = {
  childList: true,
  subtree: true,
  attributes: true,
  characterData: true,
  attributeOldValue: false,
  characterDataOldValue: false,
  attributeFilter: [],
};
const observer = new MutationObserver(async (records, observer) => {
  observer.disconnect();
  const els = records
    // 改变的类型为 characterData,并且不是 body 元素的话
    .filter( (el) => /^characterData$/i.test(el.type) && elFilter(el) )
    .map((el) => el.target); // 把发生改变的元素放入合集
  // 改变的类型为 childList,则把新增的元素放入合集
  records
    .filter((el) => /^childList$/i.test(el.type))
    .forEach((el) => {
      if(!elFilter(el)) return
      el.addedNodes.forEach((node) => els.push(node));
    });
  // 遍历合集中所有元素
  for await (el of els) {
    removeHeightLimit(el);
  }
  // 页面处理完成之后重新监控页面变化
  observer.observe(document.body, obOptions);
});
observer.observe(document.body, obOptions);
// 闭包 End
})()
长期地址
遇到问题?请前往 GitHub 提 Issues。