mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-07 20:42:44 +00:00
Merge branch 'master' into experimentalGoogleServiceAccountChange
This commit is contained in:
@@ -12,7 +12,7 @@ const MetadataEditor = require('./metadataEditor/metadataEditor.jsx');
|
|||||||
|
|
||||||
const EDITOR_THEME_KEY = 'HOMEBREWERY-EDITOR-THEME';
|
const EDITOR_THEME_KEY = 'HOMEBREWERY-EDITOR-THEME';
|
||||||
|
|
||||||
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
|
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n]*})?$)/m;
|
||||||
const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/;
|
const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/;
|
||||||
const DEFAULT_STYLE_TEXT = dedent`
|
const DEFAULT_STYLE_TEXT = dedent`
|
||||||
/*=======--- Example CSS styling ---=======*/
|
/*=======--- Example CSS styling ---=======*/
|
||||||
|
|||||||
@@ -5,33 +5,45 @@ const { splitTextStyleAndMetadata } = require('../../../shared/helpers.js'); //
|
|||||||
|
|
||||||
const BREWKEY = 'homebrewery-new';
|
const BREWKEY = 'homebrewery-new';
|
||||||
const STYLEKEY = 'homebrewery-new-style';
|
const STYLEKEY = 'homebrewery-new-style';
|
||||||
const METAKEY = 'homebrewery-new-meta';
|
const METAKEY = 'homebrewery-new-meta';
|
||||||
|
|
||||||
const NewBrew = ()=>{
|
const NewBrew = ()=>{
|
||||||
const handleFileChange = (e)=>{
|
const handleFileChange = (e)=>{
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if(file) {
|
if(!file) return;
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e)=>{
|
const currentNew = localStorage.getItem(BREWKEY);
|
||||||
const fileContent = e.target.result;
|
if(currentNew && !confirm(
|
||||||
const newBrew = {
|
`You have some text in the new brew space, if you load a file that text will be lost, are you sure you want to load the file?`
|
||||||
text : fileContent,
|
)) return;
|
||||||
style : ''
|
|
||||||
};
|
const reader = new FileReader();
|
||||||
if(fileContent.startsWith('```metadata')) {
|
reader.onload = (e)=>{
|
||||||
splitTextStyleAndMetadata(newBrew); // Modify newBrew directly
|
const fileContent = e.target.result;
|
||||||
localStorage.setItem(BREWKEY, newBrew.text);
|
const newBrew = { text: fileContent, style: '' };
|
||||||
localStorage.setItem(STYLEKEY, newBrew.style);
|
|
||||||
localStorage.setItem(METAKEY, JSON.stringify(_.pick(newBrew, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang'])));
|
if(fileContent.startsWith('```metadata')) {
|
||||||
window.location.href = '/new';
|
splitTextStyleAndMetadata(newBrew);
|
||||||
} else {
|
localStorage.setItem(BREWKEY, newBrew.text);
|
||||||
alert('This file is invalid, please, enter a valid file');
|
localStorage.setItem(STYLEKEY, newBrew.style);
|
||||||
}
|
localStorage.setItem(METAKEY, JSON.stringify(
|
||||||
};
|
_.pick(newBrew, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang'])
|
||||||
reader.readAsText(file);
|
));
|
||||||
}
|
window.location.href = '/new';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = file.name.split('.').pop().toLowerCase();
|
||||||
|
|
||||||
|
alert(`This file is invalid: ${!type ? "Missing file extension" :`.${type} files are not supported`}. Only .txt files exported from the Homebrewery are allowed.`);
|
||||||
|
|
||||||
|
|
||||||
|
console.log(file);
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Nav.dropdown>
|
<Nav.dropdown>
|
||||||
<Nav.item
|
<Nav.item
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
&::before {
|
&::before {
|
||||||
margin-right : 5px;
|
margin-right : 5px;
|
||||||
font-family : 'Font Awesome 6 Free';
|
font-family : 'Font Awesome 6 Free';
|
||||||
|
font-weight : 900;
|
||||||
content : '\f00c';
|
content : '\f00c';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const Account = require('../../navbar/account.navitem.jsx');
|
|||||||
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||||
const VaultNavItem = require('../../navbar/vault.navitem.jsx');
|
const VaultNavItem = require('../../navbar/vault.navitem.jsx');
|
||||||
|
|
||||||
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
const SplitPane = require('client/components/splitPane/splitPane.jsx');
|
||||||
const Editor = require('../../editor/editor.jsx');
|
const Editor = require('../../editor/editor.jsx');
|
||||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const AccountNavItem = require('../../navbar/account.navitem.jsx');
|
|||||||
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
|
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
|
||||||
const { fetchThemeBundle } = require('../../../../shared/helpers.js');
|
const { fetchThemeBundle } = require('../../../../shared/helpers.js');
|
||||||
|
|
||||||
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
const SplitPane = require('client/components/splitPane/splitPane.jsx');
|
||||||
const Editor = require('../../editor/editor.jsx');
|
const Editor = require('../../editor/editor.jsx');
|
||||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const ErrorNavItem = require('../../navbar/error-navitem.jsx');
|
|||||||
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||||
const HelpNavItem = require('../../navbar/help.navitem.jsx');
|
const HelpNavItem = require('../../navbar/help.navitem.jsx');
|
||||||
|
|
||||||
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
const SplitPane = require('client/components/splitPane/splitPane.jsx');
|
||||||
const Editor = require('../../editor/editor.jsx');
|
const Editor = require('../../editor/editor.jsx');
|
||||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const Account = require('../../navbar/account.navitem.jsx');
|
|||||||
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
|
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
|
||||||
const HelpNavItem = require('../../navbar/help.navitem.jsx');
|
const HelpNavItem = require('../../navbar/help.navitem.jsx');
|
||||||
const BrewItem = require('../basePages/listPage/brewItem/brewItem.jsx');
|
const BrewItem = require('../basePages/listPage/brewItem/brewItem.jsx');
|
||||||
const SplitPane = require('../../../../shared/naturalcrit/splitPane/splitPane.jsx');
|
const SplitPane = require('client/components/splitPane/splitPane.jsx');
|
||||||
const ErrorIndex = require('../errorPage/errors/errorIndex.js');
|
const ErrorIndex = require('../errorPage/errors/errorIndex.js');
|
||||||
|
|
||||||
import request from '../../utils/request-middleware.js';
|
import request from '../../utils/request-middleware.js';
|
||||||
|
|||||||
74
client/homebrew/utils/request-middleware.spec.js
Normal file
74
client/homebrew/utils/request-middleware.spec.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import requestMiddleware from './request-middleware';
|
||||||
|
|
||||||
|
jest.mock('superagent');
|
||||||
|
import request from 'superagent';
|
||||||
|
|
||||||
|
describe('request-middleware', ()=>{
|
||||||
|
let version;
|
||||||
|
|
||||||
|
let setFn;
|
||||||
|
let testFn;
|
||||||
|
|
||||||
|
beforeEach(()=>{
|
||||||
|
jest.resetAllMocks();
|
||||||
|
version = global.version;
|
||||||
|
|
||||||
|
global.version = '999';
|
||||||
|
|
||||||
|
setFn = jest.fn();
|
||||||
|
testFn = jest.fn(()=>{ return { set: setFn }; });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(()=>{
|
||||||
|
global.version = version;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add header to get', ()=>{
|
||||||
|
// Ensure tests functions have been reset
|
||||||
|
expect(testFn).not.toHaveBeenCalled();
|
||||||
|
expect(setFn).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
request.get = testFn;
|
||||||
|
|
||||||
|
requestMiddleware.get('path');
|
||||||
|
|
||||||
|
expect(testFn).toHaveBeenCalledWith('path');
|
||||||
|
expect(setFn).toHaveBeenCalledWith('Homebrewery-Version', '999');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add header to put', ()=>{
|
||||||
|
expect(testFn).not.toHaveBeenCalled();
|
||||||
|
expect(setFn).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
request.put = testFn;
|
||||||
|
|
||||||
|
requestMiddleware.put('path');
|
||||||
|
|
||||||
|
expect(testFn).toHaveBeenCalledWith('path');
|
||||||
|
expect(setFn).toHaveBeenCalledWith('Homebrewery-Version', '999');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add header to post', ()=>{
|
||||||
|
expect(testFn).not.toHaveBeenCalled();
|
||||||
|
expect(setFn).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
request.post = testFn;
|
||||||
|
|
||||||
|
requestMiddleware.post('path');
|
||||||
|
|
||||||
|
expect(testFn).toHaveBeenCalledWith('path');
|
||||||
|
expect(setFn).toHaveBeenCalledWith('Homebrewery-Version', '999');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add header to delete', ()=>{
|
||||||
|
expect(testFn).not.toHaveBeenCalled();
|
||||||
|
expect(setFn).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
request.delete = testFn;
|
||||||
|
|
||||||
|
requestMiddleware.delete('path');
|
||||||
|
|
||||||
|
expect(testFn).toHaveBeenCalledWith('path');
|
||||||
|
expect(setFn).toHaveBeenCalledWith('Homebrewery-Version', '999');
|
||||||
|
});
|
||||||
|
});
|
||||||
1364
package-lock.json
generated
1364
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -136,19 +136,19 @@
|
|||||||
"written-number": "^0.11.1"
|
"written-number": "^0.11.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@stylistic/stylelint-plugin": "^3.1.3",
|
"@stylistic/stylelint-plugin": "^4.0.0",
|
||||||
"babel-plugin-transform-import-meta": "^2.3.3",
|
"babel-plugin-transform-import-meta": "^2.3.3",
|
||||||
"eslint": "^9.31.0",
|
"eslint": "^9.34.0",
|
||||||
"eslint-plugin-jest": "^29.0.1",
|
"eslint-plugin-jest": "^29.0.1",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"globals": "^16.3.0",
|
"globals": "^16.3.0",
|
||||||
"jest": "^30.0.4",
|
"jest": "^30.0.5",
|
||||||
"jest-expect-message": "^1.1.3",
|
"jest-expect-message": "^1.1.3",
|
||||||
"jsdom-global": "^3.0.2",
|
"jsdom-global": "^3.0.2",
|
||||||
"postcss-less": "^6.0.0",
|
"postcss-less": "^6.0.0",
|
||||||
"stylelint": "^16.21.1",
|
"stylelint": "^16.23.1",
|
||||||
"stylelint-config-recess-order": "^7.1.0",
|
"stylelint-config-recess-order": "^7.2.0",
|
||||||
"stylelint-config-recommended": "^16.0.0",
|
"stylelint-config-recommended": "^17.0.0",
|
||||||
"supertest": "^7.1.3"
|
"supertest": "^7.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -490,8 +490,8 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
|
|||||||
const query = { authors: req.account.username, googleId: { $exists: false } };
|
const query = { authors: req.account.username, googleId: { $exists: false } };
|
||||||
const mongoCount = await HomebrewModel.countDocuments(query)
|
const mongoCount = await HomebrewModel.countDocuments(query)
|
||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
mongoCount = 0;
|
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
data.accountDetails = {
|
data.accountDetails = {
|
||||||
|
|||||||
@@ -52,13 +52,13 @@ const api = {
|
|||||||
// ID Validation Checks
|
// ID Validation Checks
|
||||||
// Homebrewery ID
|
// Homebrewery ID
|
||||||
// Typically 12 characters, but the DB shows a range of 7 to 14 characters
|
// Typically 12 characters, but the DB shows a range of 7 to 14 characters
|
||||||
if(!id.match(/^[A-Za-z0-9_-]{7,14}$/)){
|
if(!id.match(/^[a-zA-Z0-9-_]{7,14}$/)){
|
||||||
throw { name: 'ID Error', message: 'Invalid ID', status: 404, HBErrorCode: '11', brewId: id };
|
throw { name: 'ID Error', message: 'Invalid ID', status: 404, HBErrorCode: '11', brewId: id };
|
||||||
}
|
}
|
||||||
// Google ID
|
// Google ID
|
||||||
// Typically 33 characters, old format is 44 - always starts with a 1
|
// Typically 33 characters, old format is 44 - always starts with a 1
|
||||||
// Managed by Google, may change outside of our control, so any length between 33 and 44 is acceptable
|
// Managed by Google, may change outside of our control, so any length between 33 and 44 is acceptable
|
||||||
if(googleId && !googleId.match(/^1(?:[A-Za-z0-9+\/]{32,43})$/)){
|
if(googleId && !googleId.match(/^1(?:[a-zA-Z0-9-_]{32,43})$/)){
|
||||||
throw { name: 'Google ID Error', message: 'Invalid ID', status: 404, HBErrorCode: '12', brewId: id };
|
throw { name: 'Google ID Error', message: 'Invalid ID', status: 404, HBErrorCode: '12', brewId: id };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,7 +382,7 @@ const api = {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
//debugTextMismatch(brewFromClient.text, brewFromServer.text, `edit/${brewFromClient.editId}`);
|
//debugTextMismatch(brewFromClient.text, brewFromServer.text, `edit/${brewFromClient.editId}`);
|
||||||
console.error('Failed to apply patches:', {
|
console.error('Failed to apply patches:', {
|
||||||
patches : brewFromClient.patches,
|
//patches : brewFromClient.patches,
|
||||||
brewId : brewFromClient.editId || 'unknown',
|
brewId : brewFromClient.editId || 'unknown',
|
||||||
error : err
|
error : err
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ const mustacheSpans = {
|
|||||||
start(src) { return src.match(/{{[^{]/)?.index; }, // Hint to Marked.js to stop and check for a match
|
start(src) { return src.match(/{{[^{]/)?.index; }, // Hint to Marked.js to stop and check for a match
|
||||||
tokenizer(src, tokens) {
|
tokenizer(src, tokens) {
|
||||||
const completeSpan = /^{{[^\n]*}}/; // Regex for the complete token
|
const completeSpan = /^{{[^\n]*}}/; // Regex for the complete token
|
||||||
const inlineRegex = /{{(?=((?:[:=](?:"['\w,\-+*/()#%=?. ]*"|[\w\-+*/()#%.]*)|[^"=':{}\s]*)*))\1 *|}}/g;
|
const inlineRegex = /{{(?=((?:[:=](?:"['\w,\-()#%=?. \&\:\!\@\$\^\*\<\>\;\:\[\]\{\}\-\_\+\=]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1 *|}}/g;
|
||||||
const match = completeSpan.exec(src);
|
const match = completeSpan.exec(src);
|
||||||
if(match) {
|
if(match) {
|
||||||
//Find closing delimiter
|
//Find closing delimiter
|
||||||
@@ -241,8 +241,8 @@ const mustacheDivs = {
|
|||||||
level : 'block',
|
level : 'block',
|
||||||
start(src) { return src.match(/\n *{{[^{]/m)?.index; }, // Hint to Marked.js to stop and check for a match
|
start(src) { return src.match(/\n *{{[^{]/m)?.index; }, // Hint to Marked.js to stop and check for a match
|
||||||
tokenizer(src, tokens) {
|
tokenizer(src, tokens) {
|
||||||
const completeBlock = /^ *{{[^\n}]* *\n.*\n *}}/s; // Regex for the complete token
|
const completeBlock = /^ *{{[^\n]* *\n.*\n *}}/s; // Regex for the complete token
|
||||||
const blockRegex = /^ *{{(?=((?:[:=](?:"['\w,\-+*/()#%=?. ]*"|[\w\-+*/()#%.]*)|[^"=':{}\s]*)*))\1 *$|^ *}}$/gm;
|
const blockRegex = /^ *{{(?=((?:[:=](?:"['\w,\-()#%=?.\&\:\!\@\$\^\*\<\>\;\:\[\]\{\}\-\_\+\= ]*"|[\w\-()#%. ]*)|[^"=':{}\s]*)*))\1 *$|^ *}}$/gm;
|
||||||
const match = completeBlock.exec(src);
|
const match = completeBlock.exec(src);
|
||||||
if(match) {
|
if(match) {
|
||||||
//Find closing delimiter
|
//Find closing delimiter
|
||||||
@@ -297,7 +297,7 @@ const mustacheInjectInline = {
|
|||||||
level : 'inline',
|
level : 'inline',
|
||||||
start(src) { return src.match(/ *{[^{\n]/)?.index; }, // Hint to Marked.js to stop and check for a match
|
start(src) { return src.match(/ *{[^{\n]/)?.index; }, // Hint to Marked.js to stop and check for a match
|
||||||
tokenizer(src, tokens) {
|
tokenizer(src, tokens) {
|
||||||
const inlineRegex = /^ *{(?=((?:[:=](?:"['\w,\-+*/()#%=?. ]*"|[\w\-+*/()#%.]*)|[^"=':{}\s]*)*))\1}/g;
|
const inlineRegex = /^ *{(?=((?:[:=](?:"['\w,\-()#%=?.\&\:\!\@\$\^\*\<\>\;\:\[\]\{\}\-\_\+\= ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1}/g;
|
||||||
const match = inlineRegex.exec(src);
|
const match = inlineRegex.exec(src);
|
||||||
if(match) {
|
if(match) {
|
||||||
const lastToken = tokens[tokens.length - 1];
|
const lastToken = tokens[tokens.length - 1];
|
||||||
@@ -343,7 +343,7 @@ const mustacheInjectBlock = {
|
|||||||
level : 'block',
|
level : 'block',
|
||||||
start(src) { return src.match(/\n *{[^{\n]/m)?.index; }, // Hint to Marked.js to stop and check for a match
|
start(src) { return src.match(/\n *{[^{\n]/m)?.index; }, // Hint to Marked.js to stop and check for a match
|
||||||
tokenizer(src, tokens) {
|
tokenizer(src, tokens) {
|
||||||
const inlineRegex = /^ *{(?=((?:[:=](?:"['\w,\-+*/()#%=?. ]*"|[\w\-+*/()#%.]*)|[^"=':{}\s]*)*))\1}/ym;
|
const inlineRegex = /^ *{(?=((?:[:=](?:"['\w,\-()#%=?.& ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1}/ym;
|
||||||
const match = inlineRegex.exec(src);
|
const match = inlineRegex.exec(src);
|
||||||
if(match) {
|
if(match) {
|
||||||
const lastToken = tokens[tokens.length - 1];
|
const lastToken = tokens[tokens.length - 1];
|
||||||
@@ -735,6 +735,17 @@ const voidTags = new Set([
|
|||||||
'input', 'keygen', 'link', 'meta', 'param', 'source'
|
'input', 'keygen', 'link', 'meta', 'param', 'source'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const notInside = (string, stringMatch1, stringMatch2)=> {
|
||||||
|
const pos1 = string.indexOf(stringMatch1);
|
||||||
|
const pos2 = string.indexOf(stringMatch2);
|
||||||
|
|
||||||
|
if(((pos1 > 0) && (pos2 == -1)) ||
|
||||||
|
((pos1 > 0) && (pos2 > 0) && (pos2 > pos1))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
const processStyleTags = (string)=>{
|
const processStyleTags = (string)=>{
|
||||||
//split tags up. quotes can only occur right after : or =.
|
//split tags up. quotes can only occur right after : or =.
|
||||||
//TODO: can we simplify to just split on commas?
|
//TODO: can we simplify to just split on commas?
|
||||||
@@ -742,7 +753,7 @@ const processStyleTags = (string)=>{
|
|||||||
|
|
||||||
const id = _.remove(tags, (tag)=>tag.startsWith('#')).map((tag)=>tag.slice(1))[0] || null;
|
const id = _.remove(tags, (tag)=>tag.startsWith('#')).map((tag)=>tag.slice(1))[0] || null;
|
||||||
const classes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('='))).join(' ') || null;
|
const classes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('='))).join(' ') || null;
|
||||||
const attributes = _.remove(tags, (tag)=>(tag.includes('='))).map((tag)=>tag.replace(/="?([^"]*)"?/g, '="$1"'))
|
const attributes = _.remove(tags, (tag)=>(notInside(tag, '=', ':'))).map((tag)=>tag.replace(/="?([^"]*)"?/g, '="$1"'))
|
||||||
?.filter((attr)=>!attr.startsWith('class="') && !attr.startsWith('style="') && !attr.startsWith('id="'))
|
?.filter((attr)=>!attr.startsWith('class="') && !attr.startsWith('style="') && !attr.startsWith('id="'))
|
||||||
.reduce((obj, attr)=>{
|
.reduce((obj, attr)=>{
|
||||||
const index = attr.indexOf('=');
|
const index = attr.indexOf('=');
|
||||||
|
|||||||
Reference in New Issue
Block a user