lbry-desktop/ui/util/remark-lbry.js

168 lines
4.3 KiB
JavaScript
Raw Permalink Normal View History

2019-05-30 22:51:23 +02:00
import { parseURI } from 'lbry-redux';
2019-06-10 08:20:40 +02:00
import visit from 'unist-util-visit';
2019-05-30 22:51:23 +02:00
const protocol = 'lbry://';
2020-01-03 04:40:45 +01:00
const uriRegex = /(lbry:\/\/)[^\s"]*[^)]/g;
2021-10-01 18:00:57 +02:00
export const punctuationMarks = [',', '.', '!', '?', ':', ';', '-', ']', ')', '}'];
2019-10-10 22:50:38 +02:00
const mentionToken = '@';
2020-12-01 18:56:59 +01:00
// const mentionTokenCode = 64; // @
const mentionRegex = /@[^\s()"]*/gm;
2019-10-10 22:50:38 +02:00
const invalidRegex = /[-_.+=?!@#$%^&*:;,{}<>\w/\\]/;
2019-10-10 22:50:38 +02:00
function handlePunctuation(value) {
const modifierIndex =
(value.indexOf(':') >= 0 && value.indexOf(':')) || (value.indexOf('#') >= 0 && value.indexOf('#'));
let punctuationIndex;
punctuationMarks.some((p) => {
if (modifierIndex) {
punctuationIndex = value.indexOf(p, modifierIndex + 1) >= 0 && value.indexOf(p, modifierIndex + 1);
}
return punctuationIndex;
});
return punctuationIndex ? value.substring(0, punctuationIndex) : value;
}
// Find channel mention
2019-10-10 22:50:38 +02:00
function locateMention(value, fromIndex) {
const index = value.indexOf(mentionToken, fromIndex);
2019-10-10 22:50:38 +02:00
2019-10-13 00:04:40 +02:00
// Skip invalid mention
if (index > 0 && invalidRegex.test(value.charAt(index - 1))) {
2019-10-10 22:50:38 +02:00
return locateMention(value, index + 1);
}
return index;
}
// Find claim url
2019-10-10 22:50:38 +02:00
function locateURI(value, fromIndex) {
var index = value.indexOf(protocol, fromIndex);
// Skip invalid uri
if (index > 0 && invalidRegex.test(value.charAt(index - 1))) {
2019-10-10 22:50:38 +02:00
return locateMention(value, index + 1);
}
return index;
}
2019-05-30 22:51:23 +02:00
// Generate a valid markdown link
2019-06-13 09:18:30 +02:00
const createURI = (text, uri, embed = false) => ({
2019-05-30 22:51:23 +02:00
type: 'link',
2019-05-31 22:09:54 +02:00
url: (uri.startsWith(protocol) ? '' : protocol) + uri,
2019-06-09 09:14:27 +02:00
data: {
// Custom attribute
2019-06-13 09:18:30 +02:00
hProperties: { embed },
2019-06-09 09:14:27 +02:00
},
2019-05-30 22:51:23 +02:00
children: [{ type: 'text', value: text }],
});
const validateURI = (match, eat) => {
2019-05-30 22:51:23 +02:00
if (match) {
try {
const text = match[0];
const newText = handlePunctuation(text);
const uri = parseURI(newText);
2019-06-13 09:18:30 +02:00
const isValid = uri && uri.claimName;
2019-10-10 22:50:38 +02:00
const isChannel = uri.isChannel && uri.path === uri.claimName;
2019-06-13 09:18:30 +02:00
if (isValid) {
// Create channel link
if (isChannel) {
return eat(newText)(createURI(uri.claimName, newText, false));
2019-06-13 09:18:30 +02:00
}
// Create claim link
return eat(newText)(createURI(newText, newText, true));
2019-05-30 22:51:23 +02:00
}
} catch (err) {
// Silent errors: console.error(err)
}
}
2019-06-02 22:26:33 +02:00
};
2019-05-30 22:51:23 +02:00
2019-05-31 22:09:54 +02:00
// Generate a markdown link from channel name
2019-10-10 22:50:38 +02:00
function tokenizeMention(eat, value, silent) {
if (silent) {
return true;
}
const match = value.match(mentionRegex);
return validateURI(match, eat, self);
2019-05-31 22:09:54 +02:00
}
// Generate a markdown link from lbry url
2019-10-10 22:50:38 +02:00
function tokenizeURI(eat, value, silent) {
if (silent) {
return true;
}
2020-01-03 04:40:45 +01:00
const match = value.match(uriRegex);
2019-10-10 22:50:38 +02:00
2019-06-02 22:26:33 +02:00
return validateURI(match, eat);
2019-05-31 22:09:54 +02:00
}
2019-05-30 22:51:23 +02:00
// Configure tokenizer for lbry urls
tokenizeURI.locator = locateURI;
2019-06-10 08:20:40 +02:00
tokenizeURI.notInList = true;
2019-05-30 22:51:23 +02:00
tokenizeURI.notInLink = true;
tokenizeURI.notInBlock = true;
// Configure tokenizer for lbry channels
tokenizeMention.locator = locateMention;
2019-06-10 08:20:40 +02:00
tokenizeMention.notInList = true;
2019-05-30 22:51:23 +02:00
tokenizeMention.notInLink = true;
tokenizeMention.notInBlock = true;
2019-06-10 08:20:40 +02:00
const visitor = (node, index, parent) => {
2019-06-10 22:16:01 +02:00
if (node.type === 'link' && parent && parent.type === 'paragraph') {
2019-06-10 08:20:40 +02:00
try {
2019-06-13 09:18:30 +02:00
const uri = parseURI(node.url);
const isValid = uri && uri.claimName;
2019-10-10 22:50:38 +02:00
const isChannel = uri.isChannel && uri.path === uri.claimName;
2019-06-13 09:18:30 +02:00
if (isValid && !isChannel) {
if (!node.data || !node.data.hProperties) {
// Create new node data
2019-06-10 08:20:40 +02:00
node.data = {
2019-06-13 09:18:30 +02:00
hProperties: { embed: true },
};
} else if (node.data.hProperties) {
// Don't overwrite current attributes
node.data.hProperties = {
embed: true,
...node.data.hProperties,
2019-06-10 08:20:40 +02:00
};
}
}
} catch (err) {
// Silent errors: console.error(err)
}
}
};
// transform
const transform = (tree) => {
2019-06-10 08:20:40 +02:00
visit(tree, ['link'], visitor);
};
export const formatedLinks = () => transform;
2019-05-30 22:51:23 +02:00
// Main module
2019-06-10 08:20:40 +02:00
export function inlineLinks() {
2019-05-30 22:51:23 +02:00
const Parser = this.Parser;
const tokenizers = Parser.prototype.inlineTokenizers;
const methods = Parser.prototype.inlineMethods;
// Add an inline tokenizer (defined in the following example).
tokenizers.uri = tokenizeURI;
tokenizers.mention = tokenizeMention;
// Run it just before `text`.
methods.splice(methods.indexOf('text'), 0, 'uri');
methods.splice(methods.indexOf('text'), 0, 'mention');
}