diff --git a/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx index 56c08e2af..b5e79e15a 100644 --- a/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx +++ b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx @@ -125,7 +125,7 @@ const BrewItem = createClass({
{brew.tags?.length ? <> -
+
{brew.tags.map((tag, idx)=>{ const matches = tag.match(/^(?:([^:]+):)?([^:]+)$/); @@ -135,7 +135,7 @@ const BrewItem = createClass({ : <> } - {brew.authors?.join(', ')} + {brew.authors.map((item) => {item})}
diff --git a/client/homebrew/pages/basePages/listPage/brewItem/brewItem.less b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.less index e8c7aa39a..5a1bb3d92 100644 --- a/client/homebrew/pages/basePages/listPage/brewItem/brewItem.less +++ b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.less @@ -48,6 +48,10 @@ &>span{ margin-right : 12px; line-height : 1.5em; + + a { + color:inherit; + } } } .brewTags span { diff --git a/package-lock.json b/package-lock.json index 00c5c0ec8..cb94b6e68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,13 +29,13 @@ "jwt-simple": "^0.5.6", "less": "^3.13.1", "lodash": "^4.17.21", - "marked": "5.1.1", + "marked": "11.1.1", "marked-extended-tables": "^1.0.8", "marked-gfm-heading-id": "^3.1.2", "marked-smartypants-lite": "^1.0.2", "markedLegacy": "npm:marked@^0.3.19", "moment": "^2.30.1", - "mongoose": "^8.1.0", + "mongoose": "^8.1.1", "nanoid": "3.3.4", "nconf": "^0.12.1", "react": "^18.2.0", @@ -10041,9 +10041,9 @@ } }, "node_modules/marked": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/marked/-/marked-5.1.1.tgz", - "integrity": "sha512-bTmmGdEINWmOMDjnPWDxGPQ4qkDLeYorpYbEtFOXzOruTwUE671q4Guiuchn4N8h/v6NGd7916kXsm3Iz4iUSg==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-11.1.1.tgz", + "integrity": "sha512-EgxRjgK9axsQuUa/oKMx5DEY8oXpKJfk61rT5iY3aRlgU6QJtUcxU5OAymdhCvWvhYcd9FKmO5eQoX8m9VGJXg==", "bin": { "marked": "bin/marked.js" }, @@ -10453,9 +10453,9 @@ } }, "node_modules/mongoose": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.1.0.tgz", - "integrity": "sha512-kOA4Xnq2goqNpN9EmYElGNWfxA9H80fxcr7UdJKWi3UMflza0R7wpTihCpM67dE/0MNFljoa0sjQtlXVkkySAQ==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.1.1.tgz", + "integrity": "sha512-DbLb0NsiEXmaqLOpEz+AtAsgwhRw6f25gwa1dF5R7jj6lS1D8X6uTdhBSC8GDVtOwe5Tfw2EL7nTn6hiJT3Bgg==", "dependencies": { "bson": "^6.2.0", "kareem": "2.5.1", diff --git a/package.json b/package.json index 50251c118..ae16e0b2d 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "Create authentic looking D&D homebrews using only markdown", "version": "3.10.0", "engines": { - "npm": "^10.2.x", + "npm": "^10.2.x", "node": "^20.8.x" }, "repository": { @@ -98,13 +98,13 @@ "jwt-simple": "^0.5.6", "less": "^3.13.1", "lodash": "^4.17.21", - "marked": "5.1.1", + "marked": "11.1.1", "marked-extended-tables": "^1.0.8", "marked-gfm-heading-id": "^3.1.2", "marked-smartypants-lite": "^1.0.2", "markedLegacy": "npm:marked@^0.3.19", "moment": "^2.30.1", - "mongoose": "^8.1.0", + "mongoose": "^8.1.1", "nanoid": "3.3.4", "nconf": "^0.12.1", "react": "^18.2.0", diff --git a/scripts/project.json b/scripts/project.json index 5a0289ad0..4c769660f 100644 --- a/scripts/project.json +++ b/scripts/project.json @@ -26,7 +26,6 @@ "codemirror/addon/edit/trailingspace.js", "codemirror/addon/selection/active-line.js", "moment", - "superagent", - "marked" + "superagent" ] } diff --git a/server/admin.api.js b/server/admin.api.js index 0884ca03f..626e63d68 100644 --- a/server/admin.api.js +++ b/server/admin.api.js @@ -26,121 +26,124 @@ const mw = { } }; - -/* Search for brews that are older than 3 days and that are shorter than a tweet */ -const junkBrewQuery = HomebrewModel.find({ - '$where' : 'this.text.length < 140', - createdAt : { - $lt : Moment().subtract(30, 'days').toDate() - } -}).limit(100).maxTime(60000); +const junkBrewPipeline = [ + { $match : { + updatedAt : { $lt: Moment().subtract(30, 'days').toDate() }, + lastViewed : { $lt: Moment().subtract(30, 'days').toDate() } + }}, + { $project: { textBinSize: { $binarySize: '$textBin' } } }, + { $match: { textBinSize: { $lt: 140 } } }, + { $limit: 100 } +]; /* Search for brews that aren't compressed (missing the compressed text field) */ const uncompressedBrewQuery = HomebrewModel.find({ 'text' : { '$exists': true } }).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)=>{ - junkBrewQuery.exec((err, objs)=>{ - if(err) return res.status(500).send(err); - return res.json({ count: objs.length }); - }); + HomebrewModel.aggregate(junkBrewPipeline).option({ maxTimeMS: 60000 }) + .then((objs)=>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)=>{ - junkBrewQuery.remove().exec((err, objs)=>{ - if(err) return res.status(500).send(err); - return res.json({ count: objs.length }); - }); + HomebrewModel.aggregate(junkBrewPipeline).option({ maxTimeMS: 60000 }) + .then((docs)=>{ + 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 */ - -router.get('/admin/lookup/:id', mw.adminOnly, async (req, res, next) => { - try { - const brew = await HomebrewModel.findOne({ - $or: [ - { editId: { $regex: req.params.id, $options: 'i' } }, - { shareId: { $regex: req.params.id, $options: 'i' } }, +router.get('/admin/lookup/:id', mw.adminOnly, async (req, res, next)=>{ + HomebrewModel.findOne({ + $or : [ + { editId: { $regex: req.params.id, $options: 'i' } }, + { shareId: { $regex: req.params.id, $options: 'i' } }, ] - }).exec(); - - if (!brew) { - // No document found - return res.status(404).json({ error: 'Document not found' }); - } - - return res.json(brew); - } catch (error) { - console.error(error); - return res.status(500).json({ error: 'Internal Server Error' }); - } + }).exec() + .then((brew)=>{ + if(!brew) // No document found + return res.status(404).json({ error: 'Document not found' }); + else + return res.json(brew); + }) + .catch((err)=>{ + console.error(err); + return res.status(500).json({ error: 'Internal Server Error' }); + }); }); /* Find 50 brews that aren't compressed yet */ router.get('/admin/finduncompressed', mw.adminOnly, (req, res)=>{ - uncompressedBrewQuery.exec((err, objs)=>{ - if(err) return res.status(500).send(err); - objs = objs.map((obj)=>{return obj._id;}); - return res.json({ count: objs.length, ids: objs }); - }); -}); + const query = uncompressedBrewQuery.clone(); -/* Compresses the "text" field of a brew to binary */ -router.put('/admin/compress/:id', (req, res)=>{ - HomebrewModel.get({ _id: req.params.id }) - .then((brew)=>{ - 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); - }); + query.exec() + .then((objs)=>{ + const ids = objs.map((obj)=>obj._id); + res.json({ count: ids.length, ids }); }) .catch((err)=>{ - console.log(err); - return res.status(500).send('Error while saving'); + console.error(err); + res.status(500).send(err.message || 'Internal Server Error'); }); }); -router.get('/admin/stats', mw.adminOnly, async (req, res) => { - try { - const totalBrewsCount = await HomebrewModel.countDocuments({}); - const publishedBrewsCount = await HomebrewModel.countDocuments({ published: true }); - - return res.json({ - totalBrews: totalBrewsCount, - totalPublishedBrews: publishedBrewsCount - }); - } catch (error) { - console.error(error); - return res.status(500).json({ error: 'Internal Server Error' }); - } + +/* Compresses the "text" field of a brew to binary */ +router.put('/admin/compress/:id', (req, res)=>{ + HomebrewModel.findOne({ _id: req.params.id }) + .then((brew)=>{ + if(!brew) + return res.status(404).send('Brew not found'); + + if(brew.text) { + brew.textBin = brew.textBin || zlib.deflateRawSync(brew.text); //Don't overwrite textBin if exists + brew.text = undefined; + } + + 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 { - const count = await HomebrewModel.countDocuments({}); - return res.json({ - totalBrews: count - }); + const totalBrewsCount = await HomebrewModel.countDocuments({}); + const publishedBrewsCount = await HomebrewModel.countDocuments({ published: true }); + + return res.json({ + totalBrews : totalBrewsCount, + totalPublishedBrews : publishedBrewsCount + }); } catch (error) { - console.error(error); - return res.status(500).json({ error: 'Internal Server Error' }); + console.error(error); + return res.status(500).json({ error: 'Internal Server Error' }); } }); -*/ router.get('/admin', mw.adminOnly, (req, res)=>{ templateFn('admin', { url : req.originalUrl }) - .then((page)=>res.send(page)) - .catch((err)=>res.sendStatus(500)); + .then((page)=>res.send(page)) + .catch((err)=>res.sendStatus(500)); }); module.exports = router; diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 5be80ac97..43729a539 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -28,6 +28,28 @@ renderer.paragraph = function(text){ return `

${text}

\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 = `${text}`; + return out; +}; + const mustacheSpans = { name : 'mustacheSpans', 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(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 = `${text}`; - return out; -}; - const nonWordAndColonTest = /[^\w:]/g; const cleanUrl = function (sanitize, base, href) { if(sanitize) {