mirror of
https://github.com/LalleSX/4chan-XZ.git
synced 2026-01-30 09:48:12 +01:00
247 lines
6.3 KiB
JavaScript
247 lines
6.3 KiB
JavaScript
import Callbacks from '../classes/Callbacks'
|
|
// import Test from "../General/Test";
|
|
import { g, Conf } from '../globals/globals'
|
|
import ImageHost from '../Images/ImageHost'
|
|
import ExpandComment from '../Miscellaneous/ExpandComment'
|
|
import $ from '../platform/$'
|
|
import $$ from '../platform/$$'
|
|
import Embedding from './Embedding'
|
|
|
|
/*
|
|
* decaffeinate suggestions:
|
|
* DS102: Remove unnecessary code created because of implicit returns
|
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
|
|
*/
|
|
var Linkify = {
|
|
init() {
|
|
if (!['index', 'thread', 'archive'].includes(g.VIEW) || !Conf['Linkify']) {
|
|
return
|
|
}
|
|
|
|
if (Conf['Comment Expansion']) {
|
|
ExpandComment.callbacks.push(this.node)
|
|
}
|
|
|
|
Callbacks.Post.push({
|
|
name: 'Linkify',
|
|
cb: this.node,
|
|
})
|
|
|
|
return Embedding.init()
|
|
},
|
|
|
|
node() {
|
|
let link
|
|
if (this.isClone) {
|
|
return Embedding.events(this)
|
|
}
|
|
if (!Linkify.regString.test(this.info.comment)) {
|
|
return
|
|
}
|
|
for (link of $$('a', this.nodes.comment)) {
|
|
if (g.SITE.isLinkified?.(link)) {
|
|
$.addClass(link, 'linkify')
|
|
if (ImageHost.useFaster) {
|
|
ImageHost.fixLinks([link])
|
|
}
|
|
Embedding.process(link, this)
|
|
}
|
|
}
|
|
const links = Linkify.process(this.nodes.comment)
|
|
if (ImageHost.useFaster) {
|
|
ImageHost.fixLinks(links)
|
|
}
|
|
for (link of links) {
|
|
Embedding.process(link, this)
|
|
}
|
|
},
|
|
|
|
process(node) {
|
|
let length
|
|
const test = /[^\s"]+/g
|
|
const space = /[\s"]/
|
|
const snapshot = $.X('.//br|.//text()', node)
|
|
let i = 0
|
|
const links = []
|
|
while ((node = snapshot.snapshotItem(i++))) {
|
|
var result
|
|
var { data } = node
|
|
if (!data || node.parentElement.nodeName === 'A') {
|
|
continue
|
|
}
|
|
|
|
while ((result = test.exec(data))) {
|
|
var { index } = result
|
|
var endNode = node
|
|
var word = result[0]
|
|
// End of node, not necessarily end of space-delimited string
|
|
if ((length = index + word.length) === data.length) {
|
|
var saved
|
|
test.lastIndex = 0
|
|
|
|
while ((saved = snapshot.snapshotItem(i++))) {
|
|
var end
|
|
if (
|
|
saved.nodeName === 'BR' ||
|
|
(saved.parentElement.nodeName === 'P' && !saved.previousSibling)
|
|
) {
|
|
var part1, part2
|
|
if (
|
|
// link deliberately split
|
|
(part1 = word.match(
|
|
/(https?:\/\/)?([a-z\d-]+\.)*[a-z\d-]+$/i,
|
|
)) &&
|
|
(part2 = snapshot
|
|
.snapshotItem(i)
|
|
?.data?.match(/^(\.[a-z\d-]+)*\//i)) &&
|
|
(part1[0] + part2[0]).search(Linkify.regString) === 0
|
|
) {
|
|
continue
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
if (
|
|
saved.parentElement.nodeName === 'A' &&
|
|
!Linkify.regString.test(word)
|
|
) {
|
|
break
|
|
}
|
|
|
|
endNode = saved
|
|
;({ data } = saved)
|
|
|
|
if ((end = space.exec(data))) {
|
|
// Set our snapshot and regex to start on this node at this position when the loop resumes
|
|
word += data.slice(0, end.index)
|
|
test.lastIndex = length = end.index
|
|
i--
|
|
break
|
|
} else {
|
|
;({ length } = data)
|
|
word += data
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Linkify.regString.test(word)) {
|
|
links.push(Linkify.makeRange(node, endNode, index, length))
|
|
|
|
// <% if (readJSON('/.tests_enabled')) { %>
|
|
// if (links.length) {
|
|
// Test.assert(() => word === links[links.length - 1]?.toString());
|
|
// }
|
|
// <% } %>
|
|
}
|
|
|
|
if (!test.lastIndex || node !== endNode) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
i = links.length
|
|
while (i--) {
|
|
links[i] = Linkify.makeLink(links[i])
|
|
}
|
|
return links
|
|
},
|
|
|
|
regString: new RegExp(
|
|
`(\
|
|
\
|
|
(https?|mailto|git|magnet|ftp|irc):(\
|
|
[a-z\\d%/?]\
|
|
)\
|
|
|\
|
|
([-a-z\\d]+[.])+(\
|
|
aero|asia|biz|cat|com|coop|dance|info|int|jobs|mobi|moe|museum|name|net|org|post|pro|tel|travel|xxx|xyz|edu|gov|mil|[a-z]{2}\
|
|
)([:/]|(?![^\\s"]))\
|
|
|\
|
|
[\\d]{1,3}\\.[\\d]{1,3}\\.[\\d]{1,3}\\.[\\d]{1,3}\
|
|
|\
|
|
[-\\w\\d.@]+@[a-z\\d.-]+\\.[a-z\\d]\
|
|
)`,
|
|
'i',
|
|
),
|
|
|
|
makeRange(startNode, endNode, startOffset, endOffset) {
|
|
const range = document.createRange()
|
|
range.setStart(startNode, startOffset)
|
|
range.setEnd(endNode, endOffset)
|
|
return range
|
|
},
|
|
|
|
makeLink(range) {
|
|
let t
|
|
let encodedDomain
|
|
let text = range.toString()
|
|
|
|
// Clean start of range
|
|
let i = text.search(Linkify.regString)
|
|
|
|
if (i > 0) {
|
|
text = text.slice(i)
|
|
while (range.startOffset + i >= range.startContainer.data.length) {
|
|
i--
|
|
}
|
|
|
|
if (i) {
|
|
range.setStart(range.startContainer, range.startOffset + i)
|
|
}
|
|
}
|
|
|
|
// Clean end of range
|
|
i = 0
|
|
while (/[)\]}>.,]/.test((t = text.charAt(text.length - (1 + i))))) {
|
|
if (!/[.,]/.test(t) && !(text.match(/[()\[\]{}<>]/g).length % 2)) {
|
|
break
|
|
}
|
|
i++
|
|
}
|
|
|
|
if (i) {
|
|
text = text.slice(0, -i)
|
|
while (range.endOffset - i < 0) {
|
|
i--
|
|
}
|
|
|
|
if (i) {
|
|
range.setEnd(range.endContainer, range.endOffset - i)
|
|
}
|
|
}
|
|
|
|
// Make our link 'valid' if it is formatted incorrectly.
|
|
if (!/((mailto|magnet):|.+:\/\/)/.test(text)) {
|
|
text = (/@/.test(text) ? 'mailto:' : 'http://') + text
|
|
}
|
|
|
|
// Decode percent-encoded characters in domain so that they behave consistently across browsers.
|
|
if ((encodedDomain = text.match(/^(https?:\/\/[^/]*%[0-9a-f]{2})(.*)$/i))) {
|
|
text =
|
|
encodedDomain[1].replace(/%([0-9a-f]{2})/gi, function (x, y) {
|
|
if (y === '25') {
|
|
return x
|
|
} else {
|
|
return String.fromCharCode(parseInt(y, 16))
|
|
}
|
|
}) + encodedDomain[2]
|
|
}
|
|
|
|
const a = $.el('a', {
|
|
className: 'linkify',
|
|
rel: 'noreferrer noopener',
|
|
target: '_blank',
|
|
href: text,
|
|
})
|
|
|
|
// Insert the range into the anchor, the anchor into the range's DOM location, and destroy the range.
|
|
$.add(a, range.extractContents())
|
|
range.insertNode(a)
|
|
|
|
return a
|
|
},
|
|
}
|
|
export default Linkify
|