import {CE} from 'trans-render/lib/CE.js'; import {HypoLinkProps, HypoLinkActions} from './types.js'; import {TemplMgmtActions, TemplMgmtProps, TemplMgmt, beTransformed} from 'trans-render/lib/mixins/TemplMgmt.js'; export const mainTemplate = String.raw`
`; export class HypoLinkCore extends HTMLElement implements HypoLinkActions{ /** * @private */ self = this; processContent = processContent; parseText(self: this, s: string, excludeEmails: boolean | undefined, excludeUrls: boolean | undefined){ const {isUrl, isEmail} = self; const split = s.split(' '); split.forEach((token, idx) => { if(!excludeUrls && isUrl(token)){ split[idx] = `${token}`; }else if(!excludeEmails && isEmail(token)){ split[idx] = `${token}`; } }); return split.join(' '); } isUrl(s: string) { if (!rx_url.test(s)) return false; const sLC = s.toLowerCase(); for (let i=0, ii = prefixes.length; i < ii; i++) if (sLC.startsWith(prefixes[i])) return true; for (let i=0, ii = domains.length; i < ii; i++) if (sLC.endsWith('.'+ domains[i]) || sLC.includes('.'+ domains[i]+'\/') ||sLC.includes('.'+ domains[i]+'?')) return true; return false; } isEmail(s: string) { return rx_email.test(s); } handleSlotChange = (e: Event) => { const slot = e.target as HTMLSlotElement; const nodes = slot.assignedNodes(); let text = ''; nodes.forEach(node => { //const aNode = node as any; switch(node.nodeType){ case 1: const eNode = node as HTMLElement; text += eNode.innerText; break; case 3: text += node.nodeValue; break; } }); this.rawContent = text; } } const processContent = ({self, rawContent, excludeEmails, excludeUrls, parseText}: HypoLinkCore) => ({ processedContent: parseText(self, rawContent!, excludeEmails, excludeUrls) } as Partial); //https://stackoverflow.com/a/42659038/3320028 // taken from https://gist.github.com/dperini/729294 const rx_url = /^(?:(?:https?|ftp):\/\/)?(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/i; // valid prefixes const prefixes = ['http:\/\/', 'https:\/\/', 'ftp:\/\/', 'www.']; // taken from https://w3techs.com/technologies/overview/top_level_domain/all const domains = ['com','ru','net','org','de','jp','uk','br','pl','in','it','fr','au','info','nl','ir','cn','es','cz','kr','ua','ca','eu','biz','za','gr','co','ro','se','tw','mx','vn','tr','ch','hu','at','be','dk','tv','me','ar','no','us','sk','xyz','fi','id','cl','by','nz','il','ie','pt','kz','io','my','lt','hk','cc','sg','edu','pk','su','bg','th','top','lv','hr','pe','club','rs','ae','az','si','ph','pro','ng','tk','ee','asia','mobi']; // taken from http://stackoverflow.com/a/16016476/460084 const sQtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]'; const sDtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]'; const sAtom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+'; const sQuotedPair = '\\x5c[\\x00-\\x7f]'; const sDomainLiteral = '\\x5b(' + sDtext + '|' + sQuotedPair + ')*\\x5d'; const sQuotedString = '\\x22(' + sQtext + '|' + sQuotedPair + ')*\\x22'; const sDomain_ref = sAtom; const sSubDomain = '(' + sDomain_ref + '|' + sDomainLiteral + ')'; const sWord = '(' + sAtom + '|' + sQuotedString + ')'; const sDomain = sSubDomain + '(\\x2e' + sSubDomain + ')*'; const sLocalPart = sWord + '(\\x2e' + sWord + ')*'; const sAddrSpec = sLocalPart + '\\x40' + sDomain; // complete RFC822 email address spec const sValidEmail = '^' + sAddrSpec + '$'; // as whole string const rx_email = new RegExp(sValidEmail); export interface HypoLinkCore extends HypoLinkProps{} const ce = new CE({ config:{ tagName: 'hypo-link', propDefaults: { processedContent: '', rawContent: '', transform: [ { slotElements:[{},{slotchange:'handleSlotChange'}] }, { linkedTextParts: [{innerHTML: 'processedContent'}] } ], mainTemplate, }, actions:{ ...beTransformed, processContent:{ ifAllOf: ['rawContent'], ifKeyIn: ['excludeEmails', 'excludeUrls'] }, } }, mixins:[TemplMgmt], superclass: HypoLinkCore }); export const HypoLink = ce.classDef; declare global { interface HTMLElementTagNameMap { "hypo-link": HypoLinkCore } }