Last active
January 25, 2025 10:30
Hashtag Link Converter for Flatnotes
This file contains 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 Hashtag Link Converter | |
// @namespace http://tampermonkey.net/ | |
// @version 1.1 | |
// @description Converts hashtags in paragraphs to clickable links, works with dynamic content | |
// @author You | |
// @match *://192.168.0.2:32771* | |
// @include *://192.168.0.2:32771* | |
// @grant none | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
// Regular expression to match hashtags that can include letters, numbers, dots, and hyphens | |
const hashtagRegex = /#[a-zA-Z0-9\-\.]+/g; | |
// Function to check if an element has display:none | |
function isDisplayNone(element) { | |
return window.getComputedStyle(element).display === 'none'; | |
} | |
// Function to check if element is within valid container | |
function isInValidContainer(element) { | |
let current = element; | |
while (current && current !== document.body) { | |
// Check if element has ProseMirror class | |
if (current.classList.contains('ProseMirror')) { | |
return false; | |
} | |
// Check if element has display:none | |
if (isDisplayNone(current)) { | |
return false; | |
} | |
// Check if we found the toastui-editor-contents parent | |
if (current.classList.contains('toastui-editor-contents')) { | |
return true; | |
} | |
current = current.parentElement; | |
} | |
return false; | |
} | |
// Function to convert hashtags to links in a given text node | |
function convertHashtagsToLinks(textNode) { | |
const parent = textNode.parentNode; | |
if (!parent || parent.tagName === 'A' || !isInValidContainer(parent)) return; | |
const text = textNode.textContent; | |
const matches = text.match(hashtagRegex); | |
if (!matches) return; | |
const fragment = document.createDocumentFragment(); | |
let lastIndex = 0; | |
text.replace(hashtagRegex, (match, offset) => { | |
// Add text before the hashtag | |
if (offset > lastIndex) { | |
fragment.appendChild(document.createTextNode(text.slice(lastIndex, offset))); | |
} | |
// Create the link | |
const link = document.createElement('a'); | |
link.href = "/search?&sortBy=1&term=" + encodeURIComponent(match); | |
link.textContent = match; | |
link.style.color = '#1DA1F2'; | |
link.style.textDecoration = 'underline'; | |
fragment.appendChild(link); | |
lastIndex = offset + match.length; | |
}); | |
// Add any remaining text | |
if (lastIndex < text.length) { | |
fragment.appendChild(document.createTextNode(text.slice(lastIndex))); | |
} | |
parent.replaceChild(fragment, textNode); | |
} | |
// Function to process all text nodes in paragraphs | |
function processHashtags() { | |
// Find all toastui-editor-contents elements | |
const editorContainers = document.querySelectorAll('.toastui-editor-contents'); | |
editorContainers.forEach(container => { | |
if (isDisplayNone(container) || container.classList.contains('ProseMirror')) { | |
return; | |
} | |
const walker = document.createTreeWalker( | |
container, | |
NodeFilter.SHOW_TEXT, | |
{ | |
acceptNode: function(node) { | |
// Only process text nodes that are in paragraphs and contain hashtags | |
if (node.parentNode.tagName === 'P' && node.textContent.includes('#')) { | |
return NodeFilter.FILTER_ACCEPT; | |
} | |
return NodeFilter.FILTER_REJECT; | |
} | |
} | |
); | |
const textNodes = []; | |
while (walker.nextNode()) textNodes.push(walker.currentNode); | |
textNodes.forEach(convertHashtagsToLinks); | |
}); | |
} | |
// Set up MutationObserver to handle dynamic content | |
const observer = new MutationObserver((mutations) => { | |
mutations.forEach(mutation => { | |
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { | |
processHashtags(); | |
} | |
}); | |
}); | |
// Initial processing | |
processHashtags(); | |
// Start observing document for dynamic changes | |
observer.observe(document.body, { | |
childList: true, | |
subtree: true | |
}); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment