svelte-email-tailwind
Version:
Build emails with Svelte 5 and Tailwind
170 lines (169 loc) • 7.1 kB
JavaScript
export function matchMultiKeyBracket(input) {
const splitInit = input.split(/([{}])/);
let totals = 0;
const brackets = { open: 0, close: 0 };
const split = [];
splitInit.forEach((item, i) => {
split[i] = {
text: item,
length: item.length,
index: 0,
};
totals = totals + item.length;
split[i].index = totals;
if (item === "{") {
brackets.open = brackets.open + 1;
}
else if (item === "}") {
brackets.close = brackets.close + 1;
}
});
// This is the bracket we care about!
const firstOpen = split.findIndex(item => item.text === "{");
// This is the bracket we're currently trying to match (-1 if currently none)
let currentOpen = -1;
const unmatched = [];
let prevUnmatched;
const foundMatch = split.some((item, i) => {
if (item.text === "{") {
if (currentOpen >= 0) {
// after finding a match ("}"), currentOpen = -1, meaning we restart the search
// otherwise, we've run into consecutive "{"
// console.log(`[${i}] It's an open again... Prev open (${currentOpen}) is unmatched. New open is ${i}.`)
// so we set the currentOpen to not-matched
// split2[currentOpen].matched = false
// and push it into an array of non-matched open-brackets
unmatched.push(currentOpen);
// and the most recent not-matched is the last item in that array
prevUnmatched = unmatched[unmatched.length - 1];
}
// set current item as the new open bracket we're trying to match
currentOpen = i;
}
if (item.text === "}") {
if (currentOpen === -1) {
// console.log(`[${i}] It's a close again... Match ${i} to Prev unmatched (${prevUnmatched}).`)
// if we find 2 consecutive "}"...
// this one can be matched to the previously non-matched "{"
split[prevUnmatched].matched = i;
split[i].matched = prevUnmatched;
// and it can be taken off the unmatched array...
unmatched.pop();
// ...so that the most recent not-matched is updated to the new last item.
prevUnmatched = unmatched[unmatched.length - 1];
}
else {
// else if it's the first "}" we encounter since matching a pair...
// ...it's matched to the current "{" that we're trying to match
split[currentOpen].matched = i;
split[i].matched = currentOpen;
}
// console.log(`[${i}] Close found at i=${i}. Match: ${currentOpen}`)
currentOpen = -1;
}
// and finally, we've found the matching closing bracket of the first opening bracket!
return typeof split[firstOpen].matched === 'number';
});
if (foundMatch) {
// find first opening bracket's match, and return the index thereof.
return split[split[firstOpen].matched].index;
}
else {
return -1;
}
}
export function matchSingleKeyChar(char, input) {
if (!char)
return 0;
// values that start with a letter or number,
// then end at ", " or " }"
if ((/^[a-zA-Z]+$/).test(char)
// @ts-ignore
|| !isNaN(char)) {
// KV ends either with a comma if more KVs, or just the object's closing bracket.
return input.search(",") > 0 ? input.search(",") - 1 : input.search(" }") - 1;
}
// (???) else it can only be { [ ` ' " (???)
const charMatch = {
char: '',
regexp: /''/g
};
switch (char) {
case "{":
charMatch.char = "}";
charMatch.regexp = /\}/g;
break;
case "[":
charMatch.char = "]";
charMatch.regexp = /\]/g;
break;
case "'":
charMatch.char = "'";
charMatch.regexp = new RegExp("\\'", 'g');
break;
case "`":
charMatch.char = "`";
charMatch.regexp = new RegExp("\\`", 'g');
break;
case `"`:
charMatch.char = `"`;
charMatch.regexp = new RegExp(`\\"`, 'g');
break;
}
const firstClose = input.indexOf(charMatch.char);
const openCount = input.substring(0, firstClose + 1).match(charMatch.regexp)?.length;
if (!openCount) {
// Would be odd, but if no openining char... match value up to next key or end of obj
return input.search(",") > 0 ? input.search(",") - 1 : input.search(" }") - 1;
}
const closingBracketMatches = input.matchAll(charMatch.regexp);
let match = -1;
const closingBrackets = Array.from(closingBracketMatches, (m) => {
return {
start: m.index,
//@ts-ignore
end: m.index + m[0].length,
length: m[0].length
};
});
if (openCount >= 2) {
// for every additional '{' inbetween, find the next '}'
closingBrackets.forEach((bracket, i) => {
// find matching closing bracket
if (i === openCount - 1) {
match = bracket.end;
// return bracket.end
}
});
}
else if (openCount === 1) {
if (char === '`' || char === '"' || char === "'") {
// find second occurrence of quotation mark
const afterMatch = input.substring(input.indexOf(charMatch.char, input.indexOf(charMatch.char) + 1) + 1);
if (afterMatch.substring(0, 2) === ', ' || afterMatch.substring(0, 2) === ' }') {
// find 2nd occurrence of quotation mark
match = input.indexOf(charMatch.char, input.indexOf(charMatch.char) + 1);
}
else {
// end at next occurrence of ', ' or ' }'
// because in svelte, href="mailto:{email}" -> href: "mailto:" + {email}
// and mailto:" will be falsely identified as a key,
// which you can tell by the closing quote not being followed by a comma (next key) or closing bracket (end of obj)
const shift = afterMatch.indexOf(', ') >= 0
? afterMatch.indexOf(', ')
: afterMatch.indexOf(' }');
match = input.indexOf(charMatch.char, input.indexOf(charMatch.char) + 1) + (shift + 1);
}
}
else if (char === '[' || char === '{') {
// find 1st occurrence of closing bracket
match = input.indexOf(charMatch.char);
}
}
return match;
}
export const substituteText = (text, start, oldPart, newPart) => {
return text.substring(0, start)
+ newPart
+ text.substring(start + oldPart.length);
};