function intersect(a, b) { if (b.length > a.length) { [a, b] = [b, a]; } // loop over shorter array return a.filter(item => (b.indexOf(item) !== -1)); } function insertAfter(newNode, referenceNode) { referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); } function moveAll(fromNode, toNode) { for (let c; (c = fromNode.firstChild) != null; ) { toNode.appendChild(c); } } function removeAllFrom(fromNode) { for (let c; (c = fromNode.lastChild) != null; ) { fromNode.removeChild(c); } } function turnIntoCts(node, makeNodeCallback) { node.onclick = function(e){ e.preventDefault(); let newNode = makeNodeCallback(); if(newNode !== node) { removeAllFrom(node); moveAll(newNode, node); node.className = newNode.className; } else { node.onclick = ''; node.classList.remove('cts'); } }; } function makeCts(makeNodeCallback, title) { let ctsNode = document.createElement('div'); let placeholder = document.createElement('div'); placeholder.className = 'placeholder'; placeholder.innerHTML = title; ctsNode.className = 'cts'; ctsNode.appendChild(placeholder); turnIntoCts(ctsNode, makeNodeCallback); return ctsNode; } function makeIframe(src, w, h, scrolling='no') { let iframe = document.createElement('iframe'); iframe.width = w; iframe.height = h; iframe.frameBorder = 0; iframe.scrolling = scrolling; iframe.setAttribute('allowFullScreen', ''); iframe.src = src; return iframe; } function naiveEllipsis(str, len, ellStr='...') { let ellLen = ellStr.length; if (str.length <= len) { return str; } let half = Math.floor((len - ellLen) / 2); let left = str.substring(0, half); let right = str.substring(str.length - (len - half - ellLen)); return '' + left + ellStr + right; } function wrapIntoTag(node, tagName, className) { let tag = document.createElement(tagName); if (className !== undefined) { tag.className = className; } tag.appendChild(node); return tag; } function getEmbedableLinkTypes() { return [ { name: 'Jpeg and png images', id: 'embed_jpeg_and_png_images', ctsDefault: false, re: /\.(jpeg|jpg|png|svg)(:[a-zA-Z]+)?(?:\?[\w&;\?=]*)?$/i, makeNode: function(aNode) { let aNode2 = document.createElement('a'); let imgNode = document.createElement('img'); imgNode.src = aNode.href; aNode2.href = aNode.href; aNode2.appendChild(imgNode); return wrapIntoTag(aNode2, 'div'); } }, { name: 'Gif images', id: 'embed_gif_images', ctsDefault: true, re: /\.gif(:[a-zA-Z]+)?(?:\?[\w&;\?=]*)?$/i, makeNode: function(aNode) { let aNode2 = document.createElement('a'); let imgNode = document.createElement('img'); imgNode.src = aNode.href; aNode2.href = aNode.href; aNode2.appendChild(imgNode); return wrapIntoTag(aNode2, 'div'); } }, { name: 'Webm and mp4 video', id: 'embed_webm_and_mp4_videos', ctsDefault: false, re: /\.(webm|mp4)(?:\?[\w&;\?=]*)?$/i, makeNode: function(aNode) { let video = document.createElement('video'); video.src = aNode.href; video.setAttribute('controls', ''); return wrapIntoTag(video, 'div', 'video'); } }, { name: 'Mp3 and ogg audio', id: 'embed_sound_files', ctsDefault: false, re: /\.(mp3|ogg)(?:\?[\w&;\?=]*)?$/i, makeNode: function(aNode) { let audio = document.createElement('audio'); audio.src = aNode.href; audio.setAttribute('controls', ''); return wrapIntoTag(audio, 'div', 'audio'); } }, { name: 'YouTube videos (and playlists)', id: 'embed_youtube_videos', ctsDefault: false, re: /^(?:https?:)?\/\/(?:www\.|m\.)?(?:youtu(?:(?:\.be\/|be\.com\/(?:v|embed)\/)([-\w]+)|be\.com\/watch)((?:(?:\?|&(?:amp;)?)(?:\w+=[-\.\w]*[-\w]))*)|youtube\.com\/playlist\?list=([-\w]*)(&(amp;)?[-\w\?=]*)?)/i, makeNode: function(aNode, reResult) { let [url, v, args, plist] = reResult; let iframeUrl = url; if (plist !== undefined) { iframeUrl = '//www.youtube-nocookie.com/embed/videoseries?list=' + plist; } else { args = args.replace(/^\?/, ''); let arr = args.split('&').map(s => s.split('=')); let pp = {}; arr.forEach(z => pp[z[0]] = z[1]); let embedArgs = { rel: '0' }; if (pp.t != undefined) { const tre = /^(?:(\d+)|(?:(\d+)h)?(?:(\d+)m)?(\d+)s|(?:(\d+)h)?(\d+)m|(\d+)h)$/i; let [, t, h, m, s, h1, m1, h2] = tre.exec(pp.t); embedArgs['start'] = (+t) || ((+(h || h1 || h2 || 0))*60*60 + (+(m || m1 || 0))*60 + (+(s || 0))); } if (pp.list !== undefined) { embedArgs['list'] = pp.list; } v = v || pp.v; iframeUrl = '//www.youtube-nocookie.com/embed/' + v + '?' + Object.keys(embedArgs).map(k => `${k}=${embedArgs[k]}`).join('&'); } return wrapIntoTag(makeIframe(iframeUrl, 640, 360), 'div', 'youtube'); } }, { name: 'Vimeo videos', id: 'embed_vimeo_videos', ctsDefault: false, //re: /^(?:https?:)?\/\/(?:www\.)?(?:player\.)?vimeo\.com\/(?:(?:video\/|album\/[\d]+\/video\/)?([\d]+)|([\w-]+)\/(?!videos)([\w-]+))/i, re: /^(?:https?:)?\/\/(?:www\.)?(?:player\.)?vimeo\.com\/(?:video\/|album\/[\d]+\/video\/)?([\d]+)/i, makeNode: function(aNode, reResult) { return wrapIntoTag(makeIframe('//player.vimeo.com/video/' + reResult[1], 640, 360), 'div', 'vimeo'); } } ] } function embedLink(aNode, linkTypes, container, alwaysCts, afterNode) { let anyEmbed = false; let linkId = (aNode.href.replace(/^https?:/i, '').replace(/\'/i,'')); let sameEmbed = container.querySelector('*[data-linkid=\'' + linkId + '\']'); // do not embed the same thing twice if (sameEmbed === null) { anyEmbed = [].some.call(linkTypes, function(linkType) { /* if (GM_getValue(linkType.id, true)) { TODO user embed particular link type setting */ let reResult = linkType.re.exec(aNode.href); if (reResult !== null) { if ((linkType.match !== undefined) && (linkType.match(aNode, reResult) === false)) { return false; } let newNode; let isCts = alwaysCts /* || TODO user click-to-show setting */; if (isCts) { let linkTitle = (linkType.makeTitle !== undefined) ? linkType.makeTitle(aNode, reResult) : naiveEllipsis(aNode.href, 55); newNode = makeCts(() => linkType.makeNode(aNode, reResult, newNode), 'Click to show: ' + linkTitle); } else { newNode = linkType.makeNode(aNode, reResult); } if (!newNode) { return false; } aNode.classList.add('embedLink'); /* if (GM_getValue('enable_link_text_update', true) && (linkType.linkTextUpdate !== undefined)) { linkType.linkTextUpdate(aNode, reResult); } */ newNode.setAttribute('data-linkid', linkId); if (afterNode !== undefined) { insertAfter(newNode, afterNode); } else { container.appendChild(newNode); } //setHighlightOnHover(aNode, newNode); return true; } /* } */ }); } return anyEmbed; } function embedLinks(aNodes, container, alwaysCts, afterNode) { let anyEmbed = false; let embedableLinkTypes = getEmbedableLinkTypes(); Array.from(aNodes).forEach(aNode => { let isEmbedded = embedLink(aNode, embedableLinkTypes, container, alwaysCts, afterNode); anyEmbed = anyEmbed || isEmbedded; }); return anyEmbed; } function articleInfo(article) { let userId = article.querySelector('div.msg-avatar > a > img').alt; let tagNodes = article.querySelectorAll('.msg-tags > *'); let tags = Array.from(tagNodes).map(d => d.textContent.toLowerCase()); return { userId: userId, tags: tags }; } function isFilteredX(x, filteredUsers, filteredTags) { let {userId, tags} = articleInfo(x); return (filteredUsers !== undefined && filteredUsers.indexOf(userId.toLowerCase()) !== -1) || (intersect(tags, filteredTags).length > 0); } function embedLinksToX(x, beforeNodeSelector, allLinksSelector, ctsUsers, ctsTags) { let isCtsPost = isFilteredX(x, ctsUsers, ctsTags); let allLinks = x.querySelectorAll(allLinksSelector); let embedContainer = document.createElement('div'); embedContainer.className = 'embedContainer'; let anyEmbed = embedLinks(allLinks, embedContainer, isCtsPost); if (anyEmbed) { let beforeNode = x.querySelector(beforeNodeSelector); x.insertBefore(embedContainer, beforeNode); } } function embedLinksToArticles() { let ctsUsers = [], ctsTags = [] // TODO click-to-show users and tags let beforeNodeSelector = 'nav.l'; let allLinksSelector = 'p:not(.ir) a, pre a'; Array.from(document.querySelectorAll('#content > article')).forEach(article => { embedLinksToX(article, beforeNodeSelector, allLinksSelector, ctsUsers, ctsTags); }); } function embedLinksToPost() { let ctsUsers = [], ctsTags = [] // TODO click-to-show users and tags let beforeNodeSelector = '.msg-txt + *'; let allLinksSelector = '.msg-txt a'; Array.from(document.querySelectorAll('#content .msg-cont')).forEach(msg => { embedLinksToX(msg, beforeNodeSelector, allLinksSelector, ctsUsers, ctsTags); }); } exports.embed = function() { if (document.querySelectorAll('#content > article').length) { embedLinksToArticles(); } else { embedLinksToPost(); } }