Last active
April 27, 2026 11:41
-
-
Save shyamzzp/7a6a352e948ea6c74e22d22c1568ebeb to your computer and use it in GitHub Desktop.
Tampermonkey userscript: show Workflowy notes up to two lines
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Workflowy Two-Line Notes | |
| // @namespace https://github.com/shyamzzp/workflowy-cli | |
| // @version 0.3.1 | |
| // @description Keep Workflowy notes visible for up to two lines, then truncate with an ellipsis. | |
| // @match https://workflowy.com | |
| // @match https://workflowy.com/* | |
| // @match https://*.workflowy.com/* | |
| // @include https://workflowy.com/#/* | |
| // @include https://*.workflowy.com/#/* | |
| // @run-at document-idle | |
| // @grant none | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| const STYLE_ID = 'workflowy-two-line-notes-style'; | |
| const CLASS_NAME = 'wf-two-line-note'; | |
| const PROJECT_CLASS_NAME = 'wf-two-line-note-project'; | |
| // Workflowy changes class names over time, so this intentionally uses a few | |
| // likely note selectors plus a text/role fallback applied by the observer. | |
| const noteSelectors = [ | |
| '.content .note', | |
| '.content .notes', | |
| '.project .note', | |
| '.project .notes', | |
| '.project .content .note', | |
| '.project .content .notes', | |
| '.nameAndNote .note', | |
| '.nameAndNote .notes', | |
| '.nameAndNotes .note', | |
| '.nameAndNotes .notes', | |
| '.notes', | |
| '[data-testid*="note" i]', | |
| '[class*="note" i]', | |
| ]; | |
| const projectSelectors = [ | |
| '.project', | |
| '[class*="project" i]', | |
| ]; | |
| function installStyle() { | |
| if (document.getElementById(STYLE_ID)) return; | |
| const style = document.createElement('style'); | |
| style.id = STYLE_ID; | |
| style.textContent = ` | |
| .${CLASS_NAME} { | |
| display: -webkit-box !important; | |
| -webkit-box-orient: vertical !important; | |
| -webkit-line-clamp: 2 !important; | |
| overflow: hidden !important; | |
| text-overflow: ellipsis !important; | |
| white-space: normal !important; | |
| line-height: 1.35 !important; | |
| max-height: calc(1.35em * 2) !important; | |
| min-height: min(calc(1.35em * 2), 100%) !important; | |
| height: auto !important; | |
| } | |
| .${CLASS_NAME}:focus, | |
| .${CLASS_NAME}:focus-within, | |
| .${CLASS_NAME}[contenteditable="true"]:focus { | |
| -webkit-line-clamp: unset !important; | |
| max-height: none !important; | |
| overflow: visible !important; | |
| } | |
| .${PROJECT_CLASS_NAME} .note, | |
| .${PROJECT_CLASS_NAME} .notes, | |
| .${PROJECT_CLASS_NAME} [class*="note" i], | |
| .${PROJECT_CLASS_NAME} [data-testid*="note" i] { | |
| display: -webkit-box !important; | |
| -webkit-box-orient: vertical !important; | |
| -webkit-line-clamp: 2 !important; | |
| overflow: hidden !important; | |
| text-overflow: ellipsis !important; | |
| white-space: normal !important; | |
| line-height: 1.35 !important; | |
| max-height: calc(1.35em * 2) !important; | |
| height: auto !important; | |
| min-height: 0 !important; | |
| } | |
| .${PROJECT_CLASS_NAME} .note:focus, | |
| .${PROJECT_CLASS_NAME} .notes:focus, | |
| .${PROJECT_CLASS_NAME} [class*="note" i]:focus, | |
| .${PROJECT_CLASS_NAME} [data-testid*="note" i]:focus { | |
| -webkit-line-clamp: unset !important; | |
| max-height: none !important; | |
| overflow: visible !important; | |
| } | |
| `; | |
| document.head.appendChild(style); | |
| } | |
| function looksLikeEditableNote(element) { | |
| if (!(element instanceof HTMLElement)) return false; | |
| const className = String(element.className || '').toLowerCase(); | |
| const testId = String(element.getAttribute('data-testid') || '').toLowerCase(); | |
| const aria = String(element.getAttribute('aria-label') || '').toLowerCase(); | |
| if (className.includes('note') || testId.includes('note') || aria.includes('note')) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| function applyClamp(root = document) { | |
| for (const selector of projectSelectors) { | |
| root.querySelectorAll?.(selector).forEach((element) => { | |
| if (element instanceof HTMLElement) { | |
| element.classList.add(PROJECT_CLASS_NAME); | |
| } | |
| }); | |
| } | |
| for (const selector of noteSelectors) { | |
| root.querySelectorAll?.(selector).forEach((element) => { | |
| if (element instanceof HTMLElement) { | |
| element.classList.add(CLASS_NAME); | |
| element.style.height = 'auto'; | |
| element.style.maxHeight = 'calc(1.35em * 2)'; | |
| element.style.webkitLineClamp = '2'; | |
| } | |
| }); | |
| } | |
| root.querySelectorAll?.('[contenteditable="true"], textarea').forEach((element) => { | |
| if (looksLikeEditableNote(element)) { | |
| element.classList.add(CLASS_NAME); | |
| } | |
| }); | |
| } | |
| installStyle(); | |
| applyClamp(); | |
| const observer = new MutationObserver((mutations) => { | |
| for (const mutation of mutations) { | |
| for (const node of mutation.addedNodes) { | |
| if (node instanceof HTMLElement) { | |
| if (looksLikeEditableNote(node)) node.classList.add(CLASS_NAME); | |
| for (const selector of projectSelectors) { | |
| if (node.matches?.(selector)) node.classList.add(PROJECT_CLASS_NAME); | |
| } | |
| applyClamp(node); | |
| } | |
| } | |
| } | |
| }); | |
| observer.observe(document.documentElement, { | |
| childList: true, | |
| subtree: true, | |
| }); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment