[js] 限制数据库换行文本最大行数

效果是将开启了换行的字段文本限制为仅显示前 3 行(这个数值可以改),如果文本超出 3 行,鼠标悬浮在单元格上时可以在悬浮提示中看到完整的文本

image.png

2025.02.01:重构逻辑,修复浮窗不生效的问题,实现了更通用且性能更高的方案

// 限制数据库换行文本最大行数 JS片段 - author: JeffreyChen // https://ld246.com/article/1716034408735 (function() { // TODO 1 需要与目标元素间隔 1px(跟原生的一起改) const showTooltip = (message, target) => { const targetRect = target.getBoundingClientRect(); if (targetRect.height === 0) { hideTooltip(); return; } const clonedTooltip = tooltipElement.cloneNode(true); clonedTooltip.id = "clonedAvCellTooltip"; clonedTooltip.removeAttribute("style"); clonedTooltip.className = "tooltip"; clonedTooltip.innerHTML = message; document.body.append(clonedTooltip); let top = targetRect.bottom; let left = targetRect.left; const topHeight = targetRect.top; const bottomHeight = window.innerHeight - top; clonedTooltip.style.maxHeight = Math.max(topHeight, bottomHeight) + "px"; if (top + clonedTooltip.clientHeight > window.innerHeight && topHeight > bottomHeight) { clonedTooltip.style.top = (targetRect.top - clonedTooltip.clientHeight) + "px"; } else { clonedTooltip.style.top = top + "px"; } if (left + clonedTooltip.clientWidth > window.innerWidth) { clonedTooltip.style.left = (window.innerWidth - 1 - clonedTooltip.clientWidth) + "px"; } else { clonedTooltip.style.left = Math.max(0, left) + "px"; } const cloneStyle = clonedTooltip.getAttribute("style"); if (tooltipElement.getAttribute("style") !== cloneStyle) { tooltipElement.setAttribute("style", cloneStyle); } if (tooltipElement.innerHTML !== clonedTooltip.innerHTML) { tooltipElement.innerHTML = clonedTooltip.innerHTML; } tooltipElement.classList.remove("fn__none"); clonedTooltip.remove(); }; const hideTooltip = () => { if (!tooltipElement.classList.contains("fn__none")) { tooltipElement.classList.add("fn__none"); } }; // 检查内容是否超出了单元格 const isCellOverflow = (cell) => { // 获取单元格的边界信息 const cellRect = cell.getBoundingClientRect(); // 获取单元格的所有子元素 const children = cell.querySelectorAll('.av__celltext'); // 遍历所有子元素 // 倒序,从最后一个元素开始检查 for (let i = children.length - 1; i >= 0; i--) { const child = children[i]; const childRect = child.getBoundingClientRect(); // 检查子元素是否超出单元格的边界(如果没有特殊情况,只检查底部就够了) if (childRect.bottom > cellRect.bottom) { return true; // 如果有任意一个子元素超出边界,返回 true } // if (childRect.top < cellRect.top || // childRect.bottom > cellRect.bottom || // childRect.left < cellRect.left || // childRect.right > cellRect.right) { // return true; // } } return false; // 如果没有子元素超出边界,返回 false }; const getMessage = (cell) => { // 将所有文本组合成一个字符串以用于显示 tooltip const textElements = Array.from(cell.querySelectorAll('.av__celltext')); // 特殊处理 .av__cell[data-dtype="relation"] 和 .av__cell[data-dtype="rollup"] 元素 if (cell.dataset.dtype === 'relation' || cell.dataset.dtype === 'rollup') { // 获取文本、去除每个子项内部的换行符、每个子项之间添加换行符 return textElements.map(textElement => textElement.textContent.replace(/\n+/g, ' ').trim()).join(',\n'); } else { // 获取文本,子项之间加上逗号分隔 return textElements.map(textElement => textElement.textContent.trim()).join(', '); } }; const showAvCellTooltip = (event) => { let cell = event.target; if (!cell || cell.nodeType === 9) return false; if (cell.classList.contains('av__cell')) { // 继续执行 } else if (cell.parentElement?.classList.contains('av__cell')) { cell = cell.parentElement; } else if (cell.parentElement?.parentElement?.classList.contains('av__cell')) { // 关联、汇总字段有三层元素 cell = cell.parentElement.parentElement; } else { // 不存在 return false; } // 不是换行 | 是字段标题 if (cell.getAttribute("data-wrap") !== "true" || cell.classList.contains('av__cell--header')) return false; // 内容超出了单元格才需要显示悬浮提示 if (!isCellOverflow(cell)) return false; // 获取 tooltip 显示的内容 const message = getMessage(cell); if (message) { showTooltip(message, cell); return true; } }; let tooltipElement = Object.assign(document.createElement("div"), { id: "avCellTooltip", className: "tooltip fn__none" }); (async () => { if (!!document.getElementById("sidebar")) return; // 手机界面不运行 document.body.insertAdjacentElement("beforeend", tooltipElement); const tryShowAvCellTooltip = (event) => { if (!showAvCellTooltip(event)) hideTooltip(); }; document.addEventListener('mouseover', tryShowAvCellTooltip); // 查找代码片段自身 let script; const keyword = "限制数据库换行文本最大行数-b6fb408a-d400-4874-b357-06fcdce67ca6"; // 根据这个关键词来查找 const foundScript = Array.from(document.head.querySelectorAll("script[id^=snippetJS]")).find(script => script.textContent.includes(keyword)); if (foundScript) { const scriptId = foundScript.id; script = document.getElementById(scriptId); } if (script) { // 监听 head,检查代码片段是否被移除 const observer = new MutationObserver(function (mutationsList, observer) { for (const mutation of mutationsList) { mutation.removedNodes.forEach(function (node) { if (node === script) { observer.disconnect(); // 移除监听 document.removeEventListener('mouseover', tryShowAvCellTooltip); // 移除监听 document.querySelector('#avCellStyle').remove(); // 移除样式 document.querySelector('#avCellTooltip').remove(); // 移除元素 } }); } }); observer.observe(document.head, { childList: true, subtree: true }); } })(); // 创建并添加 CSS 代码 const style = document.createElement('style'); style.id = 'avCellStyle'; style.textContent = `/* 限制数据库换行文本最大行数 CSS片段 */ .av__row:not(.av__row--header) .av__cell[data-wrap="true"]:not([data-dtype="mSelect"]):not([data-dtype="mAsset"]) { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; /* 最多3行 */ overflow: hidden; } /* 针对关联字段、汇总字段 */ .av__row:not(.av__row--header) .av__cell[data-wrap="true"][data-dtype="relation"], .av__row:not(.av__row--header) .av__cell[data-wrap="true"][data-dtype="rollup"] { white-space: normal; } /* 多选、资源字段变为滚动容器 */ .av__row:not(.av__row--header) .av__cell[data-wrap="true"][data-dtype="mSelect"], .av__row:not(.av__row--header) .av__cell[data-wrap="true"][data-dtype="mAsset"] { max-height: calc(1.625em * 3 + 10px); /* 3行文本的高度 */ overflow-y: auto; } #clonedAvCellTooltip { animation: unset; opacity: 0; pointer-events: none; }`; document.head.appendChild(style); })();
  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    25709 引用 • 106350 回帖
  • 代码片段

    代码片段分为 CSS 与 JS 两种代码,添加在 [设置 - 外观 - 代码片段] 中,这些代码会在思源笔记加载时自动执行,用于改善笔记的样式或功能。

    用户在该标签下分享代码片段时需在帖子标题前添加 [css] [js] 用于区分代码片段类型。

    173 引用 • 1202 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • 如果能改思源本身的话,只需要增加 3 行 JS 代码,用代码片段的话就得用几十行,性能还差 😭

    该回帖因已过时而被折叠
    1 操作
    JeffreyChen 在 2024-09-22 01:28:44 折叠了该回帖
  • 纯 CSS:

    .av__row:not(.av__row--header) .av__cell[data-wrap="true"]:not([data-block-id]) .av__celltext { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; /* 最多3行 */ overflow: hidden; }
    该回帖因已过时而被折叠
    1 操作
    JeffreyChen 在 2024-09-22 01:28:39 折叠了该回帖
  • 我自己用的版本:

    // 限制数据库换行文本最大行数 JS片段 - author: JeffreyChen var animationFrameRequestId = null; // 用于存储 requestAnimationFrame 的 ID function updateAriaLabels() { // 数据库渲染后所有 aria-label 属性都会丢失,所以直接全部添加即可 // 如果已经有一个动画帧请求在等待,取消它 if (animationFrameRequestId !== null) { cancelAnimationFrame(animationFrameRequestId); } animationFrameRequestId = requestAnimationFrame(function() { document.querySelectorAll('.av__row:not(.av__row--header) .av__cell[data-wrap="true"]').forEach(cell => { const textElement = cell.querySelector('.av__celltext'); // 查找包含文本的子元素 // 检查父元素是否已有 aria-label 属性、是否是有效的 DOM 元素、文本是否被截断 if (!cell.getAttribute('aria-label') && textElement && textElement.scrollHeight > textElement.clientHeight) { const text = textElement.textContent.trim(); // 提取文本 cell.setAttribute('aria-label', text); // 为单元格添加 aria-label 属性 } }); // 重置 animationFrameRequestId,以便下次调用 updateAriaLabels 时可以检查 animationFrameRequestId = null; }); } function updateAriaLabels2() { // 调整列(宽)后 aria-label 仍然保留,需要逐个判断移除或者添加;调整列(宽)后切换到镜像数据库时也需要逐个判断移除或者添加 // 如果已经有一个动画帧请求在等待,取消它 if (animationFrameRequestId !== null) { cancelAnimationFrame(animationFrameRequestId); } animationFrameRequestId = requestAnimationFrame(function() { document.querySelectorAll('.av__row:not(.av__row--header) .av__cell[data-wrap="true"]').forEach(cell => { const textElement = cell.querySelector('.av__celltext'); // 查找包含文本的子元素 // 检查父元素是否已有 aria-label 属性、是否是有效的 DOM 元素、文本是否被截断 if (cell.getAttribute('aria-label') && textElement && !(textElement.scrollHeight > textElement.clientHeight)) { cell.removeAttribute('aria-label'); // 为无截断文本的单元格移除 aria-label 属性 } else if (!cell.getAttribute('aria-label') && textElement && textElement.scrollHeight > textElement.clientHeight) { const text = textElement.textContent.trim(); // 提取文本 cell.setAttribute('aria-label', text); // 为有截断文本单元格添加 aria-label 属性 } }); // 重置 animationFrameRequestId,以便下次调用 updateAriaLabels 时可以检查 animationFrameRequestId = null; }); } var timeoutId = null; // 创建一个新的 MutationObserver 实例,并提供一个回调函数 const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'attributes') { // 数据库渲染:检查被修改的节点是否是数据库 av 类型并且已经渲染完成 if (mutation.target.classList.contains('av') && mutation.target.getAttribute('data-render') === 'true') { clearTimeout(timeoutId); timeoutId = setTimeout(function() { updateAriaLabels() }, 500); // 500 毫秒延时。用以避免短时间内重复执行 // 调整列(宽):检查被修改的节点是否是数据库列头并且开启了换行;或者是否是切换页签 } else if (mutation.target.classList.contains('av__cell--header') && mutation.target.getAttribute('data-wrap') === 'true' || mutation.target.classList.contains('item--focus')) { clearTimeout(timeoutId); timeoutId = setTimeout(function() { updateAriaLabels2() }, 500); // 500 毫秒延时。拖拽的过程中属性会高频变化,此时不继续运行 } } }); }); // 配置MutationObserver以观察DOM树的变化 const config = { attributes: true, childList: false, subtree: true }; // 开始观察 observer.observe(document.body, config); // 创建一个新的style元素 var style = document.createElement('style'); // 添加CSS代码 style.textContent = ` /* 限制数据库换行文本最大行数 CSS片段 */ .av__row:not(.av__row--header) .av__cell[data-wrap="true"] .av__celltext { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; /* 最多3行 */ overflow: hidden; } .av__celltext--ref { border-bottom: 0px; text-decoration: underline; /* 下划线 */ text-decoration-color: rgb(0 202 255 / 85%); /* 浅蓝色 */ text-decoration-thickness: 2px; } `; // 将style元素添加到文档的head中 document.head.appendChild(style);
    该回帖因已过时而被折叠
    1 操作
    JeffreyChen 在 2024-09-22 01:28:30 折叠了该回帖
JeffreyChen
思源是支持 Markdown 语法输入的块编辑器,不是 Markdown 文件编辑器; 思源笔记同步教程:https://ld246.com/article/1692089679062 爱发电:https://afdian.com/a/JeffreyChen

推荐标签 标签

  • IDEA

    IDEA 全称 IntelliJ IDEA,是一款 Java 语言开发的集成环境,在业界被公认为最好的 Java 开发工具之一。IDEA 是 JetBrains 公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主。

    181 引用 • 400 回帖
  • 996
    13 引用 • 200 回帖 • 4 关注
  • 黑曜石

    黑曜石是一款强大的知识库工具,支持本地 Markdown 文件编辑,支持双向链接和关系图。

    A second brain, for you, forever.

    24 引用 • 242 回帖
  • 大疆创新

    深圳市大疆创新科技有限公司(DJI-Innovations,简称 DJI),成立于 2006 年,是全球领先的无人飞行器控制系统及无人机解决方案的研发和生产商,客户遍布全球 100 多个国家。通过持续的创新,大疆致力于为无人机工业、行业用户以及专业航拍应用提供性能最强、体验最佳的革命性智能飞控产品和解决方案。

    2 引用 • 14 回帖
  • Postman

    Postman 是一款简单好用的 HTTP API 调试工具。

    4 引用 • 3 回帖 • 6 关注
  • SendCloud

    SendCloud 由搜狐武汉研发中心孵化的项目,是致力于为开发者提供高质量的触发邮件服务的云端邮件发送平台,为开发者提供便利的 API 接口来调用服务,让邮件准确迅速到达用户收件箱并获得强大的追踪数据。

    2 引用 • 8 回帖 • 503 关注
  • RabbitMQ

    RabbitMQ 是一个开源的 AMQP 实现,服务器端用 Erlang 语言编写,支持多种语言客户端,如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    49 引用 • 60 回帖 • 347 关注
  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    106 引用 • 275 回帖
  • GitHub

    GitHub 于 2008 年上线,目前,除了 Git 代码仓库托管及基本的 Web 管理界面以外,还提供了订阅、讨论组、文本渲染、在线文件编辑器、协作图谱(报表)、代码片段分享(Gist)等功能。正因为这些功能所提供的便利,又经过长期的积累,GitHub 的用户活跃度很高,在开源世界里享有深远的声望,并形成了社交化编程文化(Social Coding)。

    210 引用 • 2040 回帖 • 2 关注
  • Electron

    Electron 基于 Chromium 和 Node.js,让你可以使用 HTML、CSS 和 JavaScript 构建应用。它是一个由 GitHub 及众多贡献者组成的活跃社区共同维护的开源项目,兼容 Mac、Windows 和 Linux,它构建的应用可在这三个操作系统上面运行。

    15 引用 • 136 回帖 • 5 关注
  • Sillot

    Insights(注意当前设置 master 为默认分支)

    汐洛彖夲肜矩阵(Sillot T☳Converbenk Matrix),致力于服务智慧新彖乄,具有彖乄驱动、极致优雅、开发者友好的特点。其中汐洛绞架(Sillot-Gibbet)基于自思源笔记(siyuan-note),前身是思源笔记汐洛版(更早是思源笔记汐洛分支),是智慧新录乄终端(多端融合,移动端优先)。

    主仓库地址:Hi-Windom/Sillot

    文档地址:sillot.db.sc.cn

    注意事项:

    1. ⚠️ 汐洛仍在早期开发阶段,尚不稳定
    2. ⚠️ 汐洛并非面向普通用户设计,使用前请了解风险
    3. ⚠️ 汐洛绞架基于思源笔记,开发者尽最大努力与思源笔记保持兼容,但无法实现 100% 兼容
    29 引用 • 25 回帖 • 115 关注
  • Visio
    1 引用 • 2 回帖
  • Hibernate

    Hibernate 是一个开放源代码的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,使得 Java 程序员可以随心所欲的使用对象编程思维来操纵数据库。

    39 引用 • 103 回帖 • 726 关注
  • QQ

    1999 年 2 月腾讯正式推出“腾讯 QQ”,在线用户由 1999 年的 2 人(马化腾和张志东)到现在已经发展到上亿用户了,在线人数超过一亿,是目前使用最广泛的聊天软件之一。

    45 引用 • 557 回帖
  • 安装

    你若安好,便是晴天。

    132 引用 • 1184 回帖
  • etcd

    etcd 是一个分布式、高可用的 key-value 数据存储,专门用于在分布式系统中保存关键数据。

    6 引用 • 26 回帖 • 542 关注
  • ActiveMQ

    ActiveMQ 是 Apache 旗下的一款开源消息总线系统,它完整实现了 JMS 规范,是一个企业级的消息中间件。

    19 引用 • 13 回帖 • 675 关注
  • 面试

    面试造航母,上班拧螺丝。多面试,少加班。

    325 引用 • 1395 回帖
  • 快应用

    快应用 是基于手机硬件平台的新型应用形态;标准是由主流手机厂商组成的快应用联盟联合制定;快应用标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台;以平台化的生态模式对个人开发者和企业开发者全品类开放。

    15 引用 • 127 回帖 • 3 关注
  • 尊园地产

    昆明尊园房地产经纪有限公司,即:Kunming Zunyuan Property Agency Company Limited(简称“尊园地产”)于 2007 年 6 月开始筹备,2007 年 8 月 18 日正式成立,注册资本 200 万元,公司性质为股份经纪有限公司,主营业务为:代租、代售、代办产权过户、办理银行按揭、担保、抵押、评估等。

    1 引用 • 22 回帖 • 794 关注
  • JetBrains

    JetBrains 是一家捷克的软件开发公司,该公司位于捷克的布拉格,并在俄国的圣彼得堡及美国麻州波士顿都设有办公室,该公司最为人所熟知的产品是 Java 编程语言开发撰写时所用的集成开发环境:IntelliJ IDEA

    18 引用 • 54 回帖
  • Caddy

    Caddy 是一款默认自动启用 HTTPS 的 HTTP/2 Web 服务器。

    12 引用 • 54 回帖 • 177 关注
  • Kotlin

    Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,由 JetBrains 设计开发并开源。Kotlin 可以编译成 Java 字节码,也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。在 Google I/O 2017 中,Google 宣布 Kotlin 成为 Android 官方开发语言。

    19 引用 • 33 回帖 • 77 关注
  • SMTP

    SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。SMTP 协议属于 TCP/IP 协议簇,它帮助每台计算机在发送或中转信件时找到下一个目的地。

    4 引用 • 18 回帖 • 634 关注
  • Facebook

    Facebook 是一个联系朋友的社交工具。大家可以通过它和朋友、同事、同学以及周围的人保持互动交流,分享无限上传的图片,发布链接和视频,更可以增进对朋友的了解。

    4 引用 • 15 回帖 • 447 关注
  • HHKB

    HHKB 是富士通的 Happy Hacking 系列电容键盘。电容键盘即无接点静电电容式键盘(Capacitive Keyboard)。

    5 引用 • 74 回帖 • 513 关注
  • 前端

    前端技术一般分为前端设计和前端开发,前端设计可以理解为网站的视觉设计,前端开发则是网站的前台代码实现,包括 HTML、CSS 以及 JavaScript 等。

    246 引用 • 1338 回帖