Text to URL

Конвертирует текст в виде ссылок в реальные ссылки, на которые можно кликнуть.

  1. // ==UserScript==
  2. // @name Text to URL
  3. // @namespace https://github.com/T1mL3arn
  4. // @author T1mL3arn
  5. // @description:ru Конвертирует текст в виде ссылок в реальные ссылки, на которые можно кликнуть.
  6. // @description:en Converts url-like text into clickable url.
  7. // @match *://*/*
  8. // @version 1.1.1
  9. // @run-at document-end
  10. // @license GPLv3
  11. // @supportURL https://greasyfork.org/en/scripts/367955-text-to-url/feedback
  12. // @homepageURL https://greasyfork.org/en/scripts/367955-text-to-url
  13. // @description Конвертирует текст в виде ссылок в реальные ссылки, на которые можно кликнуть.
  14. // ==/UserScript==
  15.  
  16. ///TODO improve ereg to match URI syntax (https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Generic_syntax) ?
  17. let linkEreg = /(?:https|http|ftp|file):\/\/.+?(?=[,.]?(?:\s|$))/gi;
  18. let linkEregLocal = /(?:https|http|ftp|file):\/\/.+?(?=[,.]?(?:\s|$))/i;
  19. let obsOptions = { childList: true, subtree: true };
  20. let wrappedCount = 0;
  21.  
  22. function printWrappedCount() {
  23. if (wrappedCount > 0) {
  24. console.info(`[ ${GM_info.script.name} ] wrapped links count: ${wrappedCount}`);
  25. }
  26. }
  27.  
  28. let obs = new MutationObserver((changes, obs) => {
  29. wrappedCount = 0;
  30. obs.disconnect();
  31. changes.forEach((change) => change.addedNodes.forEach((node) => fixLinks(node)) );
  32. obs.observe(document.body, obsOptions);
  33. printWrappedCount();
  34. });
  35.  
  36. function fixLinks(node) {
  37. ///TODO consider not to run script for form and input elements!
  38. ///TODO also search syntax-highlith libraries and also exclude them
  39. if (node.tagName != 'A' && node.tagName != 'SCRIPT') {
  40. // this is a text node
  41. if (node.nodeType === 3) {
  42. let content = node.textContent;
  43. if (content && content != '') {
  44. if (linkEregLocal.test(content)) {
  45. wrapTextNode(node);
  46. }
  47. }
  48. } else if (node.childNodes && node.childNodes.length > 0) {
  49. node.childNodes.forEach(fixLinks);
  50. }
  51. }
  52. }
  53.  
  54. function wrapTextNode(node) {
  55. let match;
  56. let sibling = node;
  57. let content = node.textContent;
  58. linkEreg.lastIndex = 0;
  59. while ((match = linkEreg.exec(content)) != null) {
  60. let fullMatch = match[0];
  61. let anchor = document.createElement('a');
  62.  
  63. let range = document.createRange();
  64. range.setStart(sibling, linkEreg.lastIndex - match[0].length);
  65. range.setEnd(sibling, linkEreg.lastIndex);
  66. range.surroundContents(anchor);
  67.  
  68. wrappedCount++;
  69.  
  70. anchor.href = fullMatch;
  71. anchor.textContent = fullMatch;
  72. anchor.target = '_blank';
  73. anchor.title = 'open link in a new tab';
  74. anchor.setAttribute('ttu-wrapped', '1');
  75. linkEreg.lastIndex = 0;
  76. sibling = getNextTextSibling(anchor);
  77. if (sibling == null)
  78. break;
  79. else
  80. content = sibling.textContent;
  81. }
  82. }
  83.  
  84. function getNextTextSibling(node) {
  85. let next = node.nextSibling;
  86. while (next != null) {
  87. if (next.nodeType == 3)
  88. return next;
  89. else
  90. next = node.nextSibling;
  91. }
  92. return null;
  93. }
  94.  
  95. fixLinks(document.body);
  96. printWrappedCount();
  97. obs.observe(document.body, obsOptions);