mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-06 20:52:40 +00:00
Merge branch 'experimental-development' of https://github.com/5e-Cleric/homebrewery into experimental-development
This commit is contained in:
@@ -125,7 +125,7 @@ const BrewItem = createClass({
|
|||||||
<div className='info'>
|
<div className='info'>
|
||||||
|
|
||||||
{brew.tags?.length ? <>
|
{brew.tags?.length ? <>
|
||||||
<div className='brewTags' title={`Tags:\n${brew.tags.join('\n')}`}>
|
<div className='brewTags' title={`${brew.tags.length} tags:\n${brew.tags.join('\n')}`}>
|
||||||
<i className='fas fa-tags'/>
|
<i className='fas fa-tags'/>
|
||||||
{brew.tags.map((tag, idx)=>{
|
{brew.tags.map((tag, idx)=>{
|
||||||
const matches = tag.match(/^(?:([^:]+):)?([^:]+)$/);
|
const matches = tag.match(/^(?:([^:]+):)?([^:]+)$/);
|
||||||
@@ -135,7 +135,7 @@ const BrewItem = createClass({
|
|||||||
</> : <></>
|
</> : <></>
|
||||||
}
|
}
|
||||||
<span title={`Authors:\n${brew.authors?.join('\n')}`}>
|
<span title={`Authors:\n${brew.authors?.join('\n')}`}>
|
||||||
<i className='fas fa-user'/> {brew.authors?.join(', ')}
|
<i className='fas fa-user'/> {brew.authors.map((item) => <a href={`/user/${item}`}>{item}</a>)}
|
||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
<span title={`Last viewed: ${moment(brew.lastViewed).local().format(dateFormatString)}`}>
|
<span title={`Last viewed: ${moment(brew.lastViewed).local().format(dateFormatString)}`}>
|
||||||
|
|||||||
@@ -48,6 +48,10 @@
|
|||||||
&>span{
|
&>span{
|
||||||
margin-right : 12px;
|
margin-right : 12px;
|
||||||
line-height : 1.5em;
|
line-height : 1.5em;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color:inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.brewTags span {
|
.brewTags span {
|
||||||
|
|||||||
16
package-lock.json
generated
16
package-lock.json
generated
@@ -29,13 +29,13 @@
|
|||||||
"jwt-simple": "^0.5.6",
|
"jwt-simple": "^0.5.6",
|
||||||
"less": "^3.13.1",
|
"less": "^3.13.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"marked": "5.1.1",
|
"marked": "11.1.1",
|
||||||
"marked-extended-tables": "^1.0.8",
|
"marked-extended-tables": "^1.0.8",
|
||||||
"marked-gfm-heading-id": "^3.1.2",
|
"marked-gfm-heading-id": "^3.1.2",
|
||||||
"marked-smartypants-lite": "^1.0.2",
|
"marked-smartypants-lite": "^1.0.2",
|
||||||
"markedLegacy": "npm:marked@^0.3.19",
|
"markedLegacy": "npm:marked@^0.3.19",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"mongoose": "^8.1.0",
|
"mongoose": "^8.1.1",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"nconf": "^0.12.1",
|
"nconf": "^0.12.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@@ -10041,9 +10041,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/marked": {
|
"node_modules/marked": {
|
||||||
"version": "5.1.1",
|
"version": "11.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/marked/-/marked-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/marked/-/marked-11.1.1.tgz",
|
||||||
"integrity": "sha512-bTmmGdEINWmOMDjnPWDxGPQ4qkDLeYorpYbEtFOXzOruTwUE671q4Guiuchn4N8h/v6NGd7916kXsm3Iz4iUSg==",
|
"integrity": "sha512-EgxRjgK9axsQuUa/oKMx5DEY8oXpKJfk61rT5iY3aRlgU6QJtUcxU5OAymdhCvWvhYcd9FKmO5eQoX8m9VGJXg==",
|
||||||
"bin": {
|
"bin": {
|
||||||
"marked": "bin/marked.js"
|
"marked": "bin/marked.js"
|
||||||
},
|
},
|
||||||
@@ -10453,9 +10453,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mongoose": {
|
"node_modules/mongoose": {
|
||||||
"version": "8.1.0",
|
"version": "8.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.1.1.tgz",
|
||||||
"integrity": "sha512-kOA4Xnq2goqNpN9EmYElGNWfxA9H80fxcr7UdJKWi3UMflza0R7wpTihCpM67dE/0MNFljoa0sjQtlXVkkySAQ==",
|
"integrity": "sha512-DbLb0NsiEXmaqLOpEz+AtAsgwhRw6f25gwa1dF5R7jj6lS1D8X6uTdhBSC8GDVtOwe5Tfw2EL7nTn6hiJT3Bgg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bson": "^6.2.0",
|
"bson": "^6.2.0",
|
||||||
"kareem": "2.5.1",
|
"kareem": "2.5.1",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||||
"version": "3.10.0",
|
"version": "3.10.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"npm": "^10.2.x",
|
"npm": "^10.2.x",
|
||||||
"node": "^20.8.x"
|
"node": "^20.8.x"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -98,13 +98,13 @@
|
|||||||
"jwt-simple": "^0.5.6",
|
"jwt-simple": "^0.5.6",
|
||||||
"less": "^3.13.1",
|
"less": "^3.13.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"marked": "5.1.1",
|
"marked": "11.1.1",
|
||||||
"marked-extended-tables": "^1.0.8",
|
"marked-extended-tables": "^1.0.8",
|
||||||
"marked-gfm-heading-id": "^3.1.2",
|
"marked-gfm-heading-id": "^3.1.2",
|
||||||
"marked-smartypants-lite": "^1.0.2",
|
"marked-smartypants-lite": "^1.0.2",
|
||||||
"markedLegacy": "npm:marked@^0.3.19",
|
"markedLegacy": "npm:marked@^0.3.19",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"mongoose": "^8.1.0",
|
"mongoose": "^8.1.1",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"nconf": "^0.12.1",
|
"nconf": "^0.12.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
"codemirror/addon/edit/trailingspace.js",
|
"codemirror/addon/edit/trailingspace.js",
|
||||||
"codemirror/addon/selection/active-line.js",
|
"codemirror/addon/selection/active-line.js",
|
||||||
"moment",
|
"moment",
|
||||||
"superagent",
|
"superagent"
|
||||||
"marked"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,121 +26,124 @@ const mw = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const junkBrewPipeline = [
|
||||||
/* Search for brews that are older than 3 days and that are shorter than a tweet */
|
{ $match : {
|
||||||
const junkBrewQuery = HomebrewModel.find({
|
updatedAt : { $lt: Moment().subtract(30, 'days').toDate() },
|
||||||
'$where' : 'this.text.length < 140',
|
lastViewed : { $lt: Moment().subtract(30, 'days').toDate() }
|
||||||
createdAt : {
|
}},
|
||||||
$lt : Moment().subtract(30, 'days').toDate()
|
{ $project: { textBinSize: { $binarySize: '$textBin' } } },
|
||||||
}
|
{ $match: { textBinSize: { $lt: 140 } } },
|
||||||
}).limit(100).maxTime(60000);
|
{ $limit: 100 }
|
||||||
|
];
|
||||||
|
|
||||||
/* Search for brews that aren't compressed (missing the compressed text field) */
|
/* Search for brews that aren't compressed (missing the compressed text field) */
|
||||||
const uncompressedBrewQuery = HomebrewModel.find({
|
const uncompressedBrewQuery = HomebrewModel.find({
|
||||||
'text' : { '$exists': true }
|
'text' : { '$exists': true }
|
||||||
}).lean().limit(10000).select('_id');
|
}).lean().limit(10000).select('_id');
|
||||||
|
|
||||||
|
// Search for up to 100 brews that have not been viewed or updated in 30 days and are shorter than 140 bytes
|
||||||
router.get('/admin/cleanup', mw.adminOnly, (req, res)=>{
|
router.get('/admin/cleanup', mw.adminOnly, (req, res)=>{
|
||||||
junkBrewQuery.exec((err, objs)=>{
|
HomebrewModel.aggregate(junkBrewPipeline).option({ maxTimeMS: 60000 })
|
||||||
if(err) return res.status(500).send(err);
|
.then((objs)=>res.json({ count: objs.length }))
|
||||||
return res.json({ count: objs.length });
|
.catch((error)=>{
|
||||||
});
|
console.error(error);
|
||||||
|
res.status(500).json({ error: 'Internal Server Error' });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
/* Removes all empty brews that are older than 3 days and that are shorter than a tweet */
|
|
||||||
|
// Delete up to 100 brews that have not been viewed or updated in 30 days and are shorter than 140 bytes
|
||||||
router.post('/admin/cleanup', mw.adminOnly, (req, res)=>{
|
router.post('/admin/cleanup', mw.adminOnly, (req, res)=>{
|
||||||
junkBrewQuery.remove().exec((err, objs)=>{
|
HomebrewModel.aggregate(junkBrewPipeline).option({ maxTimeMS: 60000 })
|
||||||
if(err) return res.status(500).send(err);
|
.then((docs)=>{
|
||||||
return res.json({ count: objs.length });
|
const ids = docs.map((doc)=>doc._id);
|
||||||
});
|
return HomebrewModel.deleteMany({ _id: { $in: ids } });
|
||||||
|
}).then((result)=>{
|
||||||
|
res.json({ count: result.deletedCount });
|
||||||
|
}).catch((error)=>{
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).json({ error: 'Internal Server Error' });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/* Searches for matching edit or share id, also attempts to partial match */
|
/* Searches for matching edit or share id, also attempts to partial match */
|
||||||
|
router.get('/admin/lookup/:id', mw.adminOnly, async (req, res, next)=>{
|
||||||
router.get('/admin/lookup/:id', mw.adminOnly, async (req, res, next) => {
|
HomebrewModel.findOne({
|
||||||
try {
|
$or : [
|
||||||
const brew = await HomebrewModel.findOne({
|
{ editId: { $regex: req.params.id, $options: 'i' } },
|
||||||
$or: [
|
{ shareId: { $regex: req.params.id, $options: 'i' } },
|
||||||
{ editId: { $regex: req.params.id, $options: 'i' } },
|
|
||||||
{ shareId: { $regex: req.params.id, $options: 'i' } },
|
|
||||||
]
|
]
|
||||||
}).exec();
|
}).exec()
|
||||||
|
.then((brew)=>{
|
||||||
if (!brew) {
|
if(!brew) // No document found
|
||||||
// No document found
|
return res.status(404).json({ error: 'Document not found' });
|
||||||
return res.status(404).json({ error: 'Document not found' });
|
else
|
||||||
}
|
return res.json(brew);
|
||||||
|
})
|
||||||
return res.json(brew);
|
.catch((err)=>{
|
||||||
} catch (error) {
|
console.error(err);
|
||||||
console.error(error);
|
return res.status(500).json({ error: 'Internal Server Error' });
|
||||||
return res.status(500).json({ error: 'Internal Server Error' });
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/* Find 50 brews that aren't compressed yet */
|
/* Find 50 brews that aren't compressed yet */
|
||||||
router.get('/admin/finduncompressed', mw.adminOnly, (req, res)=>{
|
router.get('/admin/finduncompressed', mw.adminOnly, (req, res)=>{
|
||||||
uncompressedBrewQuery.exec((err, objs)=>{
|
const query = uncompressedBrewQuery.clone();
|
||||||
if(err) return res.status(500).send(err);
|
|
||||||
objs = objs.map((obj)=>{return obj._id;});
|
|
||||||
return res.json({ count: objs.length, ids: objs });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Compresses the "text" field of a brew to binary */
|
query.exec()
|
||||||
router.put('/admin/compress/:id', (req, res)=>{
|
.then((objs)=>{
|
||||||
HomebrewModel.get({ _id: req.params.id })
|
const ids = objs.map((obj)=>obj._id);
|
||||||
.then((brew)=>{
|
res.json({ count: ids.length, ids });
|
||||||
brew.textBin = zlib.deflateRawSync(brew.text); // Compress brew text to binary before saving
|
|
||||||
brew.text = undefined; // Delete the non-binary text field since it's not needed anymore
|
|
||||||
|
|
||||||
brew.save((err, obj)=>{
|
|
||||||
if(err) throw err;
|
|
||||||
return res.status(200).send(obj);
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
console.log(err);
|
console.error(err);
|
||||||
return res.status(500).send('Error while saving');
|
res.status(500).send(err.message || 'Internal Server Error');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/admin/stats', mw.adminOnly, async (req, res) => {
|
|
||||||
try {
|
/* Compresses the "text" field of a brew to binary */
|
||||||
const totalBrewsCount = await HomebrewModel.countDocuments({});
|
router.put('/admin/compress/:id', (req, res)=>{
|
||||||
const publishedBrewsCount = await HomebrewModel.countDocuments({ published: true });
|
HomebrewModel.findOne({ _id: req.params.id })
|
||||||
|
.then((brew)=>{
|
||||||
return res.json({
|
if(!brew)
|
||||||
totalBrews: totalBrewsCount,
|
return res.status(404).send('Brew not found');
|
||||||
totalPublishedBrews: publishedBrewsCount
|
|
||||||
});
|
if(brew.text) {
|
||||||
} catch (error) {
|
brew.textBin = brew.textBin || zlib.deflateRawSync(brew.text); //Don't overwrite textBin if exists
|
||||||
console.error(error);
|
brew.text = undefined;
|
||||||
return res.status(500).json({ error: 'Internal Server Error' });
|
}
|
||||||
}
|
|
||||||
|
return brew.save();
|
||||||
|
})
|
||||||
|
.then((obj)=>res.status(200).send(obj))
|
||||||
|
.catch((err)=>{
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).send('Error while saving');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
router.get('/admin/stats', mw.adminOnly, async (req, res)=>{
|
||||||
router.get('/admin/stats', mw.adminOnly, async (req, res) => {
|
|
||||||
try {
|
try {
|
||||||
const count = await HomebrewModel.countDocuments({});
|
const totalBrewsCount = await HomebrewModel.countDocuments({});
|
||||||
return res.json({
|
const publishedBrewsCount = await HomebrewModel.countDocuments({ published: true });
|
||||||
totalBrews: count
|
|
||||||
});
|
return res.json({
|
||||||
|
totalBrews : totalBrewsCount,
|
||||||
|
totalPublishedBrews : publishedBrewsCount
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return res.status(500).json({ error: 'Internal Server Error' });
|
return res.status(500).json({ error: 'Internal Server Error' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
|
|
||||||
router.get('/admin', mw.adminOnly, (req, res)=>{
|
router.get('/admin', mw.adminOnly, (req, res)=>{
|
||||||
templateFn('admin', {
|
templateFn('admin', {
|
||||||
url : req.originalUrl
|
url : req.originalUrl
|
||||||
})
|
})
|
||||||
.then((page)=>res.send(page))
|
.then((page)=>res.send(page))
|
||||||
.catch((err)=>res.sendStatus(500));
|
.catch((err)=>res.sendStatus(500));
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -28,6 +28,28 @@ renderer.paragraph = function(text){
|
|||||||
return `<p>${text}</p>\n`;
|
return `<p>${text}</p>\n`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//Fix local links in the Preview iFrame to link inside the frame
|
||||||
|
renderer.link = function (href, title, text) {
|
||||||
|
let self = false;
|
||||||
|
if(href[0] == '#') {
|
||||||
|
self = true;
|
||||||
|
}
|
||||||
|
href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
|
||||||
|
|
||||||
|
if(href === null) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
let out = `<a href="${escape(href)}"`;
|
||||||
|
if(title) {
|
||||||
|
out += ` title="${title}"`;
|
||||||
|
}
|
||||||
|
if(self) {
|
||||||
|
out += ' target="_self"';
|
||||||
|
}
|
||||||
|
out += `>${text}</a>`;
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
const mustacheSpans = {
|
const mustacheSpans = {
|
||||||
name : 'mustacheSpans',
|
name : 'mustacheSpans',
|
||||||
level : 'inline', // Is this a block-level or inline-level tokenizer?
|
level : 'inline', // Is this a block-level or inline-level tokenizer?
|
||||||
@@ -271,28 +293,6 @@ Marked.use(mustacheInjectBlock);
|
|||||||
Marked.use({ renderer: renderer, mangle: false });
|
Marked.use({ renderer: renderer, mangle: false });
|
||||||
Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite());
|
Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite());
|
||||||
|
|
||||||
//Fix local links in the Preview iFrame to link inside the frame
|
|
||||||
renderer.link = function (href, title, text) {
|
|
||||||
let self = false;
|
|
||||||
if(href[0] == '#') {
|
|
||||||
self = true;
|
|
||||||
}
|
|
||||||
href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
|
|
||||||
|
|
||||||
if(href === null) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
let out = `<a href="${escape(href)}"`;
|
|
||||||
if(title) {
|
|
||||||
out += ` title="${title}"`;
|
|
||||||
}
|
|
||||||
if(self) {
|
|
||||||
out += ' target="_self"';
|
|
||||||
}
|
|
||||||
out += `>${text}</a>`;
|
|
||||||
return out;
|
|
||||||
};
|
|
||||||
|
|
||||||
const nonWordAndColonTest = /[^\w:]/g;
|
const nonWordAndColonTest = /[^\w:]/g;
|
||||||
const cleanUrl = function (sanitize, base, href) {
|
const cleanUrl = function (sanitize, base, href) {
|
||||||
if(sanitize) {
|
if(sanitize) {
|
||||||
|
|||||||
Reference in New Issue
Block a user