-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
怎么限制数据库换行文本的最大行数? #11365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
这个需要修改一下 js, 下划线的话估计只能用 text-decoration: underline; |
我目前选择的方案是使用下面两个代码片段: /* 最多3行 */
.av__row:not(.av__row--header) .av__cell[data-wrap="true"] .av__celltext {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
/* 主键下划线 */
.av__celltext--url, .av__celltext--ref {
border-bottom: 0px;
text-decoration: underline;
text-decoration-color: var(--b3-border-color);
} document.querySelectorAll('.av__row:not(.av__row--header) .av__cell[data-wrap="true"] .av__celltext').forEach(element => {
// 提取文本
const text = element.textContent.trim();
// 添加 ariaLabel 类
element.classList.add('ariaLabel');
// 添加 aria-label 属性
element.setAttribute('aria-label', text);
}); 但我不知道怎么让这个 JS 在数据库 DOM 变化后重复运行,我最多只懂得让它隔几秒运行一次: function updateAriaLabels() {
document.querySelectorAll('.av__row:not(.av__row--header) .av__cell[data-wrap="true"] .av__celltext').forEach(element => {
// 提取文本
const text = element.textContent.trim();
// 添加 ariaLabel 类
element.classList.add('ariaLabel');
// 添加 aria-label 属性
element.setAttribute('aria-label', text);
});
}
// 每3秒执行一次updateAriaLabels函数
setInterval(updateAriaLabels, 3000); 除此之外还有个问题是没被截断的文本也会显示提示,我不知道怎么判断文本是否有被截断: |
然后我就没什么头绪了,需要 @Vanessa219 指点一下 |
js 要在 siyuan/app/src/block/popover.ts Line 26 in 50d2562
多一行的问题我这里没有。 |
现在用这两个代码片段,只有被截断的文本才会产生悬浮提示了: /* 限制数据库换行文本最大行数 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--url, .av__celltext--ref {
border-bottom: 0px;
text-decoration: underline;
text-decoration-color: var(--b3-border-color);
} /* 限制数据库换行文本最大行数 JS片段 */
document.querySelectorAll('.av__row:not(.av__row--header) .av__cell[data-wrap="true"]').forEach(cell => {
const textElement = cell.querySelector('.av__celltext'); // 查找包含文本的子元素
if (textElement && textElement.scrollHeight > textElement.clientHeight) { // 检查文本是否被截断
const text = textElement.textContent.trim(); // 提取文本
cell.setAttribute('aria-label', text); // 为单元格添加 aria-label 属性
}
});
|
这个 JS 片段不知道怎么整,只能改思源本身了 #11373 这样就只剩下主键多一行的问题了 /* 限制数据库换行文本最大行数 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--url, .av__celltext--ref {
border-bottom: 0px;
text-decoration: underline;
text-decoration-color: var(--b3-border-color);
} |
给文本加了 default.webm |
但不能直接展开看到全部,折行的目的是为了方便看全部,不是为了截断缩减展示。 |
我是想在 |
这个怕是没法折中,两个需求,否则的话设计就麻烦了,还要设置一个参数给用户:保留 n 行显示 |
所以只用自定义的 CSS 片段就可以了,但没法显示悬浮提示 |
没有问题的话我们关闭 issue 了哦,谢谢。 |
还没解决呢,这个 CSS 有两个问题:
|
@Vanessa219 有空的话再看看 |
|
回复 01
@Vanessa219 如下: /* 限制数据库换行文本最大行数 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(72,255,32);
} |
回复 02
@Vanessa219 js 或许可以改成这样:(仅供参考) if (!tip && event.target.dataset.type !== "block-more" && !hasClosestByClassName(event.target, "block__icon")) {
if (aElement.dataset.wrap !== "true") {
aElement.style.overflow = "auto";
if (aElement.scrollWidth > aElement.clientWidth + 2) {
tip = getCellText(aElement);
}
aElement.style.overflow = "";
}
else {
const textElement = aElement.querySelector(".av__celltext");
if (textElement.scrollHeight > textElement.clientHeight) {
tip = getCellText(aElement);
}
}
} app/src/block/popover.ts |
如果不改思源本身的话,我想到了一个复杂的方法:(只不过 // 限制数据库换行文本最大行数 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') {
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 = `
.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("72,255,32");
}
`;
// 将style元素添加到文档的head中
document.head.appendChild(style); p.s. 这种用多了感觉性能不好 |
使用最新代码再试试 |
现在发现还是代码片段容易处理一些特殊情况。[js] 限制数据库换行文本最大行数 - 链滴 // 限制数据库换行文本最大行数 JS片段 - author: JeffreyChen
(function() {
var animationFrameRequestId = null; // 用于存储 requestAnimationFrame 的 ID
const rowsSelector = '.av__row:not(.av__row--header) ';
// 获取当前单元格的函数
function getCurrentCells() {
return Array.from(document.querySelectorAll(rowsSelector + '.av__cell[data-wrap="true"]'));
}
// 计算单元格内所有文本元素的总高度的函数
function calculateTotalHeight(cell) {
const textElements = Array.from(cell.querySelectorAll('.av__celltext'));
const tempSpan = document.createElement('span');
tempSpan.style.visibility = 'hidden';
tempSpan.style.display = 'block';
// 根据 data-dtype 属性设置 whiteSpace 样式
if (cell.dataset.dtype === 'relation' || cell.dataset.dtype === 'rollup') {
tempSpan.style.whiteSpace = 'normal'; // 对于 relation 和 rollup
} else {
tempSpan.style.whiteSpace = 'pre-wrap'; // 其他情况使用 pre-wrap
}
cell.appendChild(tempSpan); // 将其附加到单元格以便准确测量
// 将所有文本内容与分隔符 ", " 组合
const combinedText = textElements.map(textElement => textElement.textContent.trim()).join(', ');
tempSpan.textContent = combinedText; // 设置组合文本以进行高度计算
const totalHeight = tempSpan.scrollHeight; // 获取总高度
// 清理临时 span
cell.removeChild(tempSpan);
return totalHeight;
}
function updateAriaLabels(cells) {
if (animationFrameRequestId !== null) {
cancelAnimationFrame(animationFrameRequestId);
}
animationFrameRequestId = requestAnimationFrame(function() {
cells.forEach(cell => {
const currentLabel = cell.getAttribute('aria-label');
const totalHeight = calculateTotalHeight(cell);
const isTruncated = totalHeight > cell.clientHeight;
// 将所有文本组合成一个字符串以用于 aria-label
const textElements = cell.querySelectorAll('.av__celltext');
let combinedText;
// 特殊处理 .av__cell[data-dtype="relation"] 和 .av__cell[data-dtype="rollup"] 元素
if (cell.dataset.dtype === 'relation' || cell.dataset.dtype === 'rollup') {
combinedText = Array.from(textElements).map(textElement => {
// 获取文本并替换换行符
const cleanedText = textElement.textContent.replace(/\n+/g, ' ').trim();
return cleanedText;
}).join(',\n');
} else {
combinedText = Array.from(textElements).map(textElement => textElement.textContent.trim()).join(', ');
}
// 根据组合文本长度更新 aria-label
if (isTruncated && !currentLabel) {
cell.setAttribute('aria-label', combinedText);
} else if (!isTruncated && currentLabel) {
cell.removeAttribute('aria-label');
}
});
animationFrameRequestId = null;
});
}
function deferredUpdateAriaLabels() {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
const cells = getCurrentCells();
updateAriaLabels(cells);
}, 500); // 500 毫秒延时。
}
// 判断脚本是否开启 https://ld246.com/article/1726930314271
// 通过唯一标志符判断是否启用此脚本,注释中的uuid不要删除,也可以改成其他全局唯一字符串
// 也可以通过/api/snippet/getSnippet来判断脚本开启状态,这里采用判断脚本是否存在的方式
// 调用方式 isEnabled()
let scriptId = '';
function isEnabled(keyword = '限制数据库换行文本最大行数-b6fb408a-d400-4874-b357-06fcdce67ca6') {
if(!siyuan.config.snippet.enabledJS) return false;
const script = scriptId ? document.getElementById(scriptId) : null;
if(script) return true;
const scripts = document.head.querySelectorAll("script[id^=snippetJS]");
for (var i = 0; i < scripts.length; i++) {
// 限制数据库换行文本最大行数-b6fb408a-d400-4874-b357-06fcdce67ca6
if (scripts[i].textContent.indexOf('// ' + keyword) !== -1) {
scriptId = scripts[i].id;
return true;
}
}
return false;
}
var timeoutId = null;
// 定期检查 .layout__center 是否存在于 DOM 中
function checkForLayoutCenter() {
const targetNode = document.querySelector('.layout__center');
if (targetNode) {
startObserving(targetNode);
} else {
// 如果未找到,则每 200 毫秒重试
setTimeout(checkForLayoutCenter, 200);
}
}
function startObserving(targetNode) {
// 创建一个新的 MutationObserver 实例,观察 .layout__center 元素
const observer = new MutationObserver((mutations) => {
if(!isEnabled()) { // 判断脚本是否开启
if(observer) observer.disconnect();
if(timeoutId) clearTimeout(timeoutId);
if(animationFrameRequestId) cancelAnimationFrame(animationFrameRequestId);
return;
}
for (let mutation of mutations) {
if (mutation.type === 'attributes') {
const target = mutation.target;
// 数据库渲染检查
if (target.classList.contains('av') && target.getAttribute('data-render') === 'true') {
deferredUpdateAriaLabels();
// 列头调整或切换页签检查
} else if (target.classList.contains('av__cell--header') && target.getAttribute('data-wrap') === 'true' || target.classList.contains('item--focus')) {
deferredUpdateAriaLabels();
}
}
}
});
// 配置并开始观察
const config = { attributes: true, childList: false, subtree: true };
observer.observe(targetNode, config);
// 脚本启用后立即对当前 DOM 进行一次操作
const cells = getCurrentCells();
updateAriaLabels(cells);
}
checkForLayoutCenter(); // 开始检查 .layout__center 是否存在
// 创建并添加 CSS 代码
const style = document.createElement('style');
style.textContent = `
/* 限制数据库换行文本最大行数 CSS片段 */
.av__row:not(.av__row--header) .av__cell[data-wrap="true"]:not([data-dtype="relation"]):not([data-dtype="rollup"]):not([data-dtype="mAsset"]) {
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;
}
/* 针对关联字段、汇总字段 */
.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"] {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3; /* 最多3行 */
overflow: hidden;
white-space: normal;
}
/* 资源字段变为滚动容器 */
.av__row:not(.av__row--header) .av__cell[data-wrap="true"][data-dtype="mAsset"] {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3; /* 最多3行 */
text-overflow: clip; /* 用于去掉多余的 "..." ,但不起效,要等 CSS4 再看有没有合适的 CSS 属性*/
overflow: auto;
overflow-x: hidden;
}
`;
document.head.appendChild(style);
})(); |
更新:我换逻辑重写了,实现了更通用且性能更高的方案 // 限制数据库换行文本最大行数 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);
})(); |
Uh oh!
There was an error while loading. Please reload this page.
https://ld246.com/article/1715443199417
怎么限制数据库换行文本的最大行数?
目前通过下面的代码片段可以将换行文本限制为3行:
但是鼠标悬浮在上面时无法显示全部文本:
不换行的可以显示:
想知道有什么办法能让文本被截断的单元格在鼠标悬浮时显示全部文本?
这个代码片段还有个问题是,会导致绑定块的主键下方的那条线延长:
The text was updated successfully, but these errors were encountered: