From 855f9a4827b57fa0a5ecb4077b777407591f0082 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 24 Sep 2024 01:56:38 +0000 Subject: [PATCH 01/42] docs(contributor): contrib-readme-action has updated readme --- README.md | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 4b645501..c6747413 100644 --- a/README.md +++ b/README.md @@ -156,10 +156,10 @@ Hi, I'm Wingy. I made this app. My website is [samwing.dev](https://samwing.dev)
- - Wingysam + + cj13579
- Wingysam + cj13579
@@ -191,13 +191,6 @@ Hi, I'm Wingy. I made this app. My website is [samwing.dev](https://samwing.dev) cj13579 - - - frostyfrog -
- frostyfrog -
- dbarenholz @@ -211,15 +204,15 @@ Hi, I'm Wingy. I made this app. My website is [samwing.dev](https://samwing.dev)
Laeborg
- - + LarsStegman
LarsStegman
- + + BootVirtual @@ -254,15 +247,15 @@ Hi, I'm Wingy. I made this app. My website is [samwing.dev](https://samwing.dev)
Na0mir
- - + ndlanier
ndlanier
- + + RestartFU @@ -297,15 +290,15 @@ Hi, I'm Wingy. I made this app. My website is [samwing.dev](https://samwing.dev)
StS82
- - + jskiddie
jskiddie
- + + toastie89 From 33a9a7adddec9533e048091a6ec784eb8c87ae0c Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Thu, 26 Sep 2024 21:49:38 -0300 Subject: [PATCH 02/42] layout users in table --- src/languages/en-us.ts | 4 ++++ src/views/adminSettings.pug | 25 ++++++++++++++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/languages/en-us.ts b/src/languages/en-us.ts index e0c0fd9c..9fecd3b4 100644 --- a/src/languages/en-us.ts +++ b/src/languages/en-us.ts @@ -31,6 +31,10 @@ export const strings = { ADMIN_SETTINGS_USERS_EDIT: 'Edit', ADMIN_SETTINGS_USERS_HEADER: 'Users', ADMIN_SETTINGS_VERSION_INFO: 'Version Info', + ADMIN_SETTINGS_TABLE_USERNAME: 'Username', + ADMIN_SETTINGS_TABLE_ADMIN_USER: 'Admin', + ADMIN_SETTINGS_TABLE_WISHLIST_COUNT: 'Wishlist items', + ADMIN_SETTINGS_TABLE_EDIT: 'Edit', ADMIN_USER_EDIT_ACCOUNT_UNCONFIRMED: "This account hasn't been confirmed.", ADMIN_USER_EDIT_ADMIN_ISADMIN: name => `${name} is an admin.`, ADMIN_USER_EDIT_ADMIN_NOTADMIN: name => `${name} is not an admin.`, diff --git a/src/views/adminSettings.pug b/src/views/adminSettings.pug index b91b0086..d834952a 100644 --- a/src/views/adminSettings.pug +++ b/src/views/adminSettings.pug @@ -2,13 +2,24 @@ extends layout.pug block content h2= lang('ADMIN_SETTINGS_USERS_HEADER') - each user in users - span.is-size-6.inline= user.id - a(href=`${_CC.config.base}admin-settings/edit/${user.id}`) - span.is-size-7.icon.has-text-info - i.fas.fa-edit - span.is-sr-only - = lang('ADMIN_SETTINGS_USERS_EDIT') + table.table + thead + th=lang('ADMIN_SETTINGS_TABLE_USERNAME') + th=lang('ADMIN_SETTINGS_TABLE_ADMIN_USER') + th=lang('ADMIN_SETTINGS_TABLE_WISHLIST_COUNT') + th=lang('ADMIN_SETTINGS_TABLE_EDIT') + tbody + each user in users + tr(id=user.id) + td=user.id + td=user.doc.admin + td=Object.keys(user.doc.wishlist).length + td + a(href=`${_CC.config.base}admin-settings/edit/${user.id}`) + span.is-size-7.icon.has-text-info + i.fas.fa-edit + span.is-sr-only + = lang('ADMIN_SETTINGS_USERS_EDIT') br h3= lang('ADMIN_SETTINGS_USERS_ADD_HEADER') form(action=`${_CC.config.base}admin-settings/add`, method='POST') From a17f974afa6034c77ae572fac77f4acb8cd13cdd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 00:29:34 +0000 Subject: [PATCH 03/42] docs(contributor): contrib-readme-action has updated readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d059073a..73600610 100644 --- a/README.md +++ b/README.md @@ -158,10 +158,10 @@ Hi, I'm Wingy. I made this app. My website is [samwing.dev](https://samwing.dev)
- - Wingysam + + cj13579
- Wingysam + cj13579
From 0111b1f548212c3fe1e529f6596456b992634c43 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Sat, 28 Sep 2024 17:49:58 -0300 Subject: [PATCH 04/42] Move an item to bottom of the list (#141) * docs(contributor): contrib-readme-action has updated readme * Move item to bottom of list --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/languages/en-us.ts | 2 ++ src/routes/wishlist/index.js | 4 +++- src/structures/Wishlist.js | 9 +++++++++ src/views/wishlist.pug | 17 +++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/languages/en-us.ts b/src/languages/en-us.ts index 9fecd3b4..6055cc63 100644 --- a/src/languages/en-us.ts +++ b/src/languages/en-us.ts @@ -164,9 +164,11 @@ export const strings = { WISHLIST_IMAGE: 'Image', WISHLIST_ITEM_MISSING: 'Failed to find item', WISHLIST_MOVE_DOWN: 'Move Down', + WISHLIST_MOVE_BOTTOM: 'Move Bottom', WISHLIST_MOVE_GUARD: 'Not correct user', WISHLIST_MOVE_INVALID: 'Invalid move', WISHLIST_MOVE_ITEM_DOWN: 'Move Item Down', + WISHLIST_MOVE_ITEM_BOTTOM: 'Move Item Bottom', WISHLIST_MOVE_ITEM_TOP: 'Move Item Top', WISHLIST_MOVE_ITEM_UP: 'Move Item Up', WISHLIST_MOVE_SUCCESS: 'Successfully moved item!', diff --git a/src/routes/wishlist/index.js b/src/routes/wishlist/index.js index 618eefea..316a2c7f 100644 --- a/src/routes/wishlist/index.js +++ b/src/routes/wishlist/index.js @@ -168,7 +168,9 @@ export default function (db) { if (req.params.direction === 'top') { await wishlist.moveTop(req.params.itemId) - } else if (req.params.direction === 'up') { + } else if (req.params.direction === 'bottom') { + await wishlist.moveBottom(req.params.itemId) + }else if (req.params.direction === 'up') { await wishlist.move(req.params.itemId, -1) } else if (req.params.direction === 'down') { await wishlist.move(req.params.itemId, 1) diff --git a/src/structures/Wishlist.js b/src/structures/Wishlist.js index 3308b5d8..75c68240 100644 --- a/src/structures/Wishlist.js +++ b/src/structures/Wishlist.js @@ -138,6 +138,15 @@ export class Wishlist { await this.save() } + async moveBottom (id) { + const index = this.items.findIndex(item => item.id === id) + if (index === -1) throw new Error(_CC.lang('WISHLIST_ITEM_MISSING')) + + const item = this.items.splice(index, 1)[0] + this.items.push(item) + await this.save() + } + async setItemData (id, data) { const item = await this.get(id) diff --git a/src/views/wishlist.pug b/src/views/wishlist.pug index e5b8bc9c..34daad04 100644 --- a/src/views/wishlist.pug +++ b/src/views/wishlist.pug @@ -45,6 +45,7 @@ block content th= lang('WISHLIST_MOVE_TOP') th= lang('WISHLIST_MOVE_UP') th= lang('WISHLIST_MOVE_DOWN') + th= lang('WISHLIST_MOVE_BOTTOM') else th= lang('WISHLIST_PLEDGE') th= lang('WISHLIST_DELETE') @@ -129,6 +130,17 @@ block content ) span.icon i.fas.fa-arrow-down + td(data-label=lang('WISHLIST_MOVE_ITEM_BOTTOM')) + form.bottomForm.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/bottom/${item.id}`) + .field.inline + .control.inline + button.button.is-text( + type='submit', + style='text-decoration: none;', + disabled=index === items.length - 1 + ) + span.icon + i.fas.fa-angle-double-down else td(data-label=lang('WISHLIST_PLEDGE')) if req.params.user !== req.user._id && !item.pledgedBy @@ -238,6 +250,11 @@ block content .field.inline .control.inline input.inline.button(type='submit' value=lang('WISHLIST_MOVE_ITEM_DOWN')) + if index !== items.length - 1 && req.user._id === req.params.user + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/bottom/${item.id}`) + .field.inline + .control.inline + input.inline.button(type='submit' value=lang('WISHLIST_MOVE_ITEM_BOTTOM')) form(method='POST') .field label.label!=lang('WISHLIST_URL_LABEL') From 2473973bfedf0ad66393531b752b66c509109afc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 28 Sep 2024 20:50:10 +0000 Subject: [PATCH 05/42] docs(contributor): contrib-readme-action has updated readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c6747413..21284a8c 100644 --- a/README.md +++ b/README.md @@ -156,10 +156,10 @@ Hi, I'm Wingy. I made this app. My website is [samwing.dev](https://samwing.dev)
- - cj13579 + + Wingysam
- cj13579 + Wingysam
From a32f9389b84b4ba942d4b88916335c3602876514 Mon Sep 17 00:00:00 2001 From: Ben Siddans <96931223+bsiddans@users.noreply.github.com> Date: Sun, 29 Sep 2024 07:36:53 +1000 Subject: [PATCH 06/42] Set location of PouchDB log file with environment variable. (#93) * Set location of PouchDB log file with environment variable. * Use config system * Use config system --------- Co-authored-by: Wingy --- Dockerfile | 8 +++++--- README.md | 2 ++ src/config/index.ts | 1 + src/index.ts | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1537061c..e0ade4dc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,14 +6,16 @@ ENV NODE_ENV production ENV PORT 80 ENV DB_EXPOSE_PORT 8080 ENV DB_PREFIX /data/dbs/ +ENV DB_LOG_FILE /dev/null + ENV SECRET_DIRNAME /data WORKDIR /usr/src/app COPY . . -RUN npm install --production --silent \ - && npm run build \ - && mkdir -p /data/dbs +RUN npm install --production --silent && + npm run build && + mkdir -p /data/dbs EXPOSE 80 diff --git a/README.md b/README.md index 21284a8c..d242819e 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,8 @@ Add environment variables with a .env. Example: ## Core Settings # Where to store databases, can be a CouchDB compatible server or directory. DB_PREFIX=dbs/ +# Location of DB log file (if needed for debugging). +DB_LOG_FILE=/dev/null # Where to send someone if they need to log in DEFAULT_FAILURE_REDIRECT=/login # Port to listen on diff --git a/src/config/index.ts b/src/config/index.ts index 543e782f..dd3ae864 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -3,6 +3,7 @@ const config = { defaultFailureRedirect: process.env.DEFAULT_FAILURE_REDIRECT || '/login', port: Number(process.env.PORT) || 3000, dbExposePort: Number(process.env.DB_EXPOSE_PORT) || null, + dbLogFile: process.env.DB_LOG_FILE || '/dev/null', proxyServer: process.env.PROXY_SERVER || undefined, secret: process.env.SECRET || (await import('./secret/index.js')).default, sessionMaxAge: Number(process.env.SESSION_MAX_AGE) || 1000 * 60 * 60 * 24 * 7, diff --git a/src/index.ts b/src/index.ts index 64f42f00..fb73e8f0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -118,6 +118,6 @@ app.listen(config.port, () => { logger.success('express', `Express server starte ;(() => { if (!config.dbExposePort) return const dbExposeApp = express() - dbExposeApp.use('/', ExpressPouchDB(PouchDB, { inMemoryConfig: true })) + dbExposeApp.use('/', ExpressPouchDB(PouchDB, { inMemoryConfig: true, logPath: config.dbLogFile })) dbExposeApp.listen(config.dbExposePort, () => { logger.success('db expose', `DB has been exposed on port ${config.dbExposePort}`) }) })() From 95472f008e443303c2269c2f0988dcce47fd5f52 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 28 Sep 2024 21:37:02 +0000 Subject: [PATCH 07/42] docs(contributor): contrib-readme-action has updated readme --- README.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d242819e..af4ff24d 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,13 @@ Hi, I'm Wingy. I made this app. My website is [samwing.dev](https://samwing.dev)
+ + + bsiddans +
+ bsiddans +
+ BootVirtual @@ -249,15 +256,15 @@ Hi, I'm Wingy. I made this app. My website is [samwing.dev](https://samwing.dev)
Na0mir
- + + ndlanier
ndlanier
- - + RestartFU @@ -292,15 +299,15 @@ Hi, I'm Wingy. I made this app. My website is [samwing.dev](https://samwing.dev)
StS82
- + + jskiddie
jskiddie
- - + toastie89 From f914b999055b28c7f7e4634c77ee8664ffbe501e Mon Sep 17 00:00:00 2001 From: jskiddie <153759737+jskiddie@users.noreply.github.com> Date: Tue, 1 Oct 2024 18:57:28 +0200 Subject: [PATCH 08/42] The Dockerfile didn't wok for me, here's a fix (#145) * Added automation to keep contributers section current * Updated README.md to use new automation to keep contributers current * changed branch name * fix: Added backslashes and typescript install to Dockerfile * fix: Dockerfile, allow changes to be applied --------- Co-authored-by: jskidie <153759737+jskidie@users.noreply.github.com> --- Dockerfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index e0ade4dc..180ffcad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,10 +13,12 @@ ENV SECRET_DIRNAME /data WORKDIR /usr/src/app COPY . . -RUN npm install --production --silent && - npm run build && - mkdir -p /data/dbs +RUN npm install --production --silent && \ + npm install -g typescript \ + npm run build && \ + mkdir -p /data/dbs EXPOSE 80 CMD ./Dockerstart.sh + From be76412603c0ff56132231db1551936c8e1551f5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 16:57:41 +0000 Subject: [PATCH 09/42] docs(contributor): contrib-readme-action has updated readme --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index af4ff24d..d059073a 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,13 @@ Hi, I'm Wingy. I made this app. My website is [samwing.dev](https://samwing.dev) + + + jskiddie +
+ jskiddie +
+ bsiddans @@ -249,15 +256,15 @@ Hi, I'm Wingy. I made this app. My website is [samwing.dev](https://samwing.dev)
mtrunt
- + + Na0mir
Na0mir
- - + ndlanier @@ -292,21 +299,14 @@ Hi, I'm Wingy. I made this app. My website is [samwing.dev](https://samwing.dev)
CantisW
- + + StS82
StS82
- - - - - jskiddie -
- jskiddie -
From c73b3e9afb694a5a179d8d8b885d59ee8bb3eec4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 00:29:34 +0000 Subject: [PATCH 10/42] docs(contributor): contrib-readme-action has updated readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d059073a..73600610 100644 --- a/README.md +++ b/README.md @@ -158,10 +158,10 @@ Hi, I'm Wingy. I made this app. My website is [samwing.dev](https://samwing.dev)
- - Wingysam + + cj13579
- Wingysam + cj13579
From fe03827d0c03ad467ca04d4d72976d2832892b16 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Wed, 9 Oct 2024 21:08:27 -0300 Subject: [PATCH 11/42] Ability to create pledge only users --- src/languages/en-us.ts | 4 ++++ src/routes/adminSettings/index.js | 20 ++++++++++++++++++++ src/routes/setup/index.js | 3 ++- src/views/admin-user-edit.pug | 9 +++++++++ src/views/adminSettings.pug | 8 +++++++- src/views/includes/navbar.pug | 3 ++- src/views/profile.pug | 31 ++++++++++++++++--------------- src/views/wishlists.pug | 4 ++-- 8 files changed, 62 insertions(+), 20 deletions(-) diff --git a/src/languages/en-us.ts b/src/languages/en-us.ts index 6055cc63..4bc33cb0 100644 --- a/src/languages/en-us.ts +++ b/src/languages/en-us.ts @@ -15,6 +15,7 @@ export const strings = { ADMIN_SETTINGS_USERS_ADD_HEADER: 'Add user', ADMIN_SETTINGS_USERS_ADD_PLACEHOLDER: 'john', ADMIN_SETTINGS_USERS_ADD_USERNAME: 'Username', + ADMIN_SETTINGS_USERS_ADD_PLEDGE_ONLY: 'Pledge only user?', ADMIN_SETTINGS_USERS_ADD_ERROR_USERNAME_EMPTY: 'Username cannot be empty.', ADMIN_SETTINGS_USERS_EDIT_DELETE_FAIL_ADMIN: 'Failed to remove: user is admin.', ADMIN_SETTINGS_USERS_EDIT_DELETE_SUCCESS: name => `Successfully removed user ${name}`, @@ -28,11 +29,14 @@ export const strings = { ADMIN_SETTINGS_USERS_EDIT_PROMOTE_SUCCESS: name => `${name} is now an admin.`, ADMIN_SETTINGS_USERS_EDIT_RENAMED_USER: 'Renamed user!', ADMIN_SETTINGS_USERS_EDIT_SAME_NAME: 'Username is same as new username.', + ADMIN_USER_EDIT_PLEDGE_ONLY: 'Pledge only', + ADMIN_USER_EDIT_PLEDGE_ONLY_BUTTON: 'Change pledge only', ADMIN_SETTINGS_USERS_EDIT: 'Edit', ADMIN_SETTINGS_USERS_HEADER: 'Users', ADMIN_SETTINGS_VERSION_INFO: 'Version Info', ADMIN_SETTINGS_TABLE_USERNAME: 'Username', ADMIN_SETTINGS_TABLE_ADMIN_USER: 'Admin', + ADMIN_SETTINGS_TABLE_PLEDGE_ONLY: 'Pledge only', ADMIN_SETTINGS_TABLE_WISHLIST_COUNT: 'Wishlist items', ADMIN_SETTINGS_TABLE_EDIT: 'Edit', ADMIN_USER_EDIT_ACCOUNT_UNCONFIRMED: "This account hasn't been confirmed.", diff --git a/src/routes/adminSettings/index.js b/src/routes/adminSettings/index.js index 87bbcac8..bd41adb8 100644 --- a/src/routes/adminSettings/index.js +++ b/src/routes/adminSettings/index.js @@ -44,10 +44,14 @@ export default function ({ db, ensurePfp }) { }) } + const pledgeOnlyValue = req.body.newPledgeOnlyUser + const pledgeOnly = pledgeOnlyValue === 'on' + await db.put({ _id: username, admin: false, wishlist: [], + pledgeOnly: pledgeOnly, signupToken: nanoid(SECRET_TOKEN_LENGTH), expiry: new Date().getTime() + SECRET_TOKEN_LIFETIME @@ -138,6 +142,22 @@ export default function ({ db, ensurePfp }) { } }) + router.post('/edit/pledgeOnly/:userToChange', verifyAuth(), async (req, res) => { + + const newPledgeValue = req.body.editPledgeOnlyUser + const newPledge = newPledgeValue === 'on' + + const userDoc = await db.get(req.params.userToChange) + userDoc.pledgeOnly = newPledge + try { + await db.put(userDoc) + await _CC.wishlistManager.clearCache() + } catch (error) { + req.flash('error', error.message) + return res.redirect(`/admin-settings/edit/${req.params.userToChange}`) + } + }) + router.post('/edit/impersonate/:userToEdit', verifyAuth(), async (req, res) => { if (!req.user.admin) return res.redirect('/') req.login({ _id: req.params.userToEdit }, err => { diff --git a/src/routes/setup/index.js b/src/routes/setup/index.js index 36cfa46d..848781f2 100644 --- a/src/routes/setup/index.js +++ b/src/routes/setup/index.js @@ -27,7 +27,8 @@ export default function ({ db, ensurePfp }) { _id: username, password: adminPasswordHash, admin: true, - wishlist: [] + wishlist: [], + pledgeOnly: false }) resolve() }) diff --git a/src/views/admin-user-edit.pug b/src/views/admin-user-edit.pug index c7869334..0976aa00 100644 --- a/src/views/admin-user-edit.pug +++ b/src/views/admin-user-edit.pug @@ -44,6 +44,15 @@ block content .field .control input.button.is-primary(type='submit' value=lang('ADMIN_USER_EDIT_CHANGE_USERNAME')) + .column.is-narrow + h2= lang('ADMIN_USER_EDIT_PLEDGE_ONLY') + form(action=`${_CC.config.base}admin-settings/edit/pledgeOnly/${user._id}`, method='POST') + .field + .control + input(type='checkbox', name='editPledgeOnlyUser', checked=user.pledgeOnly) + .field + .control + input.input.button(type='submit', value=lang('ADMIN_USER_EDIT_PLEDGE_ONLY_BUTTON'), style='margin-top: 1em;') .column.is-narrow h2= lang('ADMIN_USER_EDIT_ADMIN') //- Yes, ternary exists, but I think the code is cleaner with a more "naive" style :) diff --git a/src/views/adminSettings.pug b/src/views/adminSettings.pug index d834952a..a53d68e6 100644 --- a/src/views/adminSettings.pug +++ b/src/views/adminSettings.pug @@ -6,13 +6,15 @@ block content thead th=lang('ADMIN_SETTINGS_TABLE_USERNAME') th=lang('ADMIN_SETTINGS_TABLE_ADMIN_USER') + th=lang('ADMIN_SETTINGS_TABLE_PLEDGE_ONLY') th=lang('ADMIN_SETTINGS_TABLE_WISHLIST_COUNT') th=lang('ADMIN_SETTINGS_TABLE_EDIT') tbody each user in users tr(id=user.id) td=user.id - td=user.doc.admin + td=user.doc.admin + td=user.doc.pledgeOnly td=Object.keys(user.doc.wishlist).length td a(href=`${_CC.config.base}admin-settings/edit/${user.id}`) @@ -30,6 +32,10 @@ block content input.input(type='text', name='newUserUsername', placeholder=lang('ADMIN_SETTINGS_USERS_ADD_PLACEHOLDER')) span.icon.is-small.is-left i.fas.fa-user + .field + label.label= lang('ADMIN_SETTINGS_USERS_ADD_PLEDGE_ONLY') + .control.has-icons-left + input(type='checkbox', name='newPledgeOnlyUser') .field .control input.button.is-primary(type='submit' value=lang('ADMIN_SETTINGS_USERS_ADD_BUTTON')) diff --git a/src/views/includes/navbar.pug b/src/views/includes/navbar.pug index c110f53a..ce35d6be 100644 --- a/src/views/includes/navbar.pug +++ b/src/views/includes/navbar.pug @@ -28,7 +28,8 @@ nav.navbar.is-fixed-top(role='navigation', aria-label='main navigation',style='b .navbar-item.has-dropdown.is-hoverable a.navbar-link= req.user._id .navbar-dropdown - +navBarLink(`${_CC.config.base}wishlist/${req.user._id}`, lang('NAVBAR_WISHLIST')) + if req.user.pledgeOnly != true + +navBarLink(`${_CC.config.base}wishlist/${req.user._id}`, lang('NAVBAR_WISHLIST')) +navBarLink(`${_CC.config.base}profile`, lang('NAVBAR_PROFILE')) if req.user.admin +navBarLink(`${_CC.config.base}admin-settings`, lang('NAVBAR_ADMIN')) diff --git a/src/views/profile.pug b/src/views/profile.pug index 72390d12..c243bd23 100644 --- a/src/views/profile.pug +++ b/src/views/profile.pug @@ -36,21 +36,22 @@ block content button.button.is-primary(type='submit') span.icon i.fas.fa-save - h2= lang('PROFILE_SHARED_INFORMATION') - form(action=`${_CC.config.base}profile/info`, method='POST') - .columns.is-multiline.is-mobile - .column.is-narrow - +sharedInfoProp('shoeSize', 'PROFILE_SHOE_SIZE') - +sharedInfoProp('ringSize', 'PROFILE_RING_SIZE') - +sharedInfoProp('dressSize', 'PROFILE_DRESS_SIZE') - .column.is-narrow - +sharedInfoProp('sweaterSize', 'PROFILE_SWEATER_SIZE') - +sharedInfoProp('shirtSize', 'PROFILE_SHIRT_SIZE') - +sharedInfoProp('pantsSize', 'PROFILE_PANTS_SIZE') - .column.is-narrow - +sharedInfoProp('coatSize', 'PROFILE_COAT_SIZE') - +sharedInfoProp('hatSize', 'PROFILE_HAT_SIZE') - +sharedInfoProp('phoneModel', 'PROFILE_PHONE_MODEL') + if req.user.pledgeOnly != true + h2= lang('PROFILE_SHARED_INFORMATION') + form(action=`${_CC.config.base}profile/info`, method='POST') + .columns.is-multiline.is-mobile + .column.is-narrow + +sharedInfoProp('shoeSize', 'PROFILE_SHOE_SIZE') + +sharedInfoProp('ringSize', 'PROFILE_RING_SIZE') + +sharedInfoProp('dressSize', 'PROFILE_DRESS_SIZE') + .column.is-narrow + +sharedInfoProp('sweaterSize', 'PROFILE_SWEATER_SIZE') + +sharedInfoProp('shirtSize', 'PROFILE_SHIRT_SIZE') + +sharedInfoProp('pantsSize', 'PROFILE_PANTS_SIZE') + .column.is-narrow + +sharedInfoProp('coatSize', 'PROFILE_COAT_SIZE') + +sharedInfoProp('hatSize', 'PROFILE_HAT_SIZE') + +sharedInfoProp('phoneModel', 'PROFILE_PHONE_MODEL') h2= lang('PROFILE_SECURITY') a.button.is-primary(href=`${_CC.config.base}profile/password`) diff --git a/src/views/wishlists.pug b/src/views/wishlists.pug index 4243928a..7f141404 100644 --- a/src/views/wishlists.pug +++ b/src/views/wishlists.pug @@ -52,7 +52,7 @@ block content div!= _CC.config.customHtml.wishlists ul.noStyle.noLeftMargin - if req.user._id !== '_CCUNKNOWN' + if req.user._id !== '_CCUNKNOWN' && req.user.pledgeOnly != true li .box a(href=`${_CC.config.base}wishlist/${req.user._id}`, style='color: #4a4a4a;') @@ -67,7 +67,7 @@ block content progress.progress(value=1, max=1) +wishlistDetails(req.user.wishlist, req.user._id) each user in users - if req.user._id !== user.id + if req.user._id !== user.id && user.doc.pledgeOnly != true li .box a(href=`${_CC.config.base}wishlist/${user.id}`, style='color: #4a4a4a;') From cc185ca7618899779e794f5b2b80092727f338f1 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Fri, 11 Oct 2024 20:23:20 -0300 Subject: [PATCH 12/42] add localization --- src/languages/en-us.ts | 4 +++- src/views/adminSettings.pug | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/languages/en-us.ts b/src/languages/en-us.ts index 6055cc63..2ff72fd1 100644 --- a/src/languages/en-us.ts +++ b/src/languages/en-us.ts @@ -201,5 +201,7 @@ export const strings = { WISHLIST_URL_REQUIRED: 'Item URL or Name is required', WISHLISTS_COUNTS_SELF: name => `${name}: ???/???`, WISHLISTS_COUNTS: (name, pledged, total) => `${name}: ${pledged}/${total}`, - WISHLISTS_TITLE: `${_CC.config.siteTitle} - Wishlists` + WISHLISTS_TITLE: `${_CC.config.siteTitle} - Wishlists`, + YES: 'Yes', + NO: 'No' } as const diff --git a/src/views/adminSettings.pug b/src/views/adminSettings.pug index d834952a..e88428bc 100644 --- a/src/views/adminSettings.pug +++ b/src/views/adminSettings.pug @@ -12,7 +12,7 @@ block content each user in users tr(id=user.id) td=user.id - td=user.doc.admin + td=user.doc.admin ? lang('YES') : lang('NO') td=Object.keys(user.doc.wishlist).length td a(href=`${_CC.config.base}admin-settings/edit/${user.id}`) From 24929e8591c99d1f021c3a5a2fb6aebbff31897b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 31 Dec 2024 13:09:21 +0000 Subject: [PATCH 13/42] docs(contributor): contrib-readme-action has updated readme --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5cc0031e..0ab844de 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,13 @@ Hi, I'm Wingy. I made this app. My website is [samwing.dev](https://samwing.dev) PeteS4
+ + + jskiddie +
+ jskiddie +
+ dbarenholz @@ -208,21 +215,14 @@ Hi, I'm Wingy. I made this app. My website is [samwing.dev](https://samwing.dev)
Laeborg
- + + LarsStegman
LarsStegman
- - - - - jskiddie -
- jskiddie -
From 3f362e56d27673f226ff9d84dc25bfe32ee68c60 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Tue, 31 Dec 2024 10:36:29 -0400 Subject: [PATCH 14/42] Return when user object is updated --- src/routes/adminSettings/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/adminSettings/index.js b/src/routes/adminSettings/index.js index d81e41a9..bc70e717 100644 --- a/src/routes/adminSettings/index.js +++ b/src/routes/adminSettings/index.js @@ -153,6 +153,7 @@ export default function ({ db, ensurePfp }) { try { await db.put(userDoc) await _CC.wishlistManager.clearCache() + return res.redirect(`/admin-settings/edit/${req.params.userToChange}`) } catch (error) { req.flash('error', error.message) return res.redirect(`/admin-settings/edit/${req.params.userToChange}`) From 08a4a99dca2062bf82d9399e7de845cd9f113af4 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Tue, 31 Dec 2024 10:59:16 -0400 Subject: [PATCH 15/42] Option to disable guest suggestions --- README.md | 3 +++ src/config/index.ts | 3 ++- src/languages/en-us.ts | 1 + src/views/wishlist.pug | 50 +++++++++++++++++++++++------------------- 4 files changed, 33 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 0ab844de..d49ddccf 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,9 @@ MARKDOWN=false # Once you've created a client ID and secret in your Google project use the below environment variables to enable SSO # GOOGLE_CLIENT_ID=abc123 # GOOGLE_CLIENT_SECRET=123abc + +# Set to false to disable pledge only users from being able to suggest items to another user's list +GUEST_SUGGEST=true ``` ## Default Profile Pictures diff --git a/src/config/index.ts b/src/config/index.ts index 4688df1c..8214d91f 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -25,7 +25,8 @@ const config = { googleSSOClientSecret: process.env.GOOGLE_CLIENT_SECRET || null, googleSSOEnabled: false, rootUrl: appendSlash(process.env.ROOT_URL ?? process.env.ROOT_PATH ?? '/'), - base: '' // automatically set below + base: '', // automatically set below + guestSuggest: process.env.GUEST_SUGGEST === 'true' } if (config.guestPassword) config.wishlist.public = false diff --git a/src/languages/en-us.ts b/src/languages/en-us.ts index 45b57662..0527dc28 100644 --- a/src/languages/en-us.ts +++ b/src/languages/en-us.ts @@ -215,6 +215,7 @@ export const strings = { WISHLIST_URL_LABEL: `Item URL or Name (Supported Sites)`, WISHLIST_URL_PLACEHOLDER: 'https://www.amazon.com/dp/B00ZV9RDKK', WISHLIST_URL_REQUIRED: 'Item URL or Name is required', + WISHLIST_GUEST_SUGGEST_DISABLED: 'Item suggestions are disabled.', WISHLISTS_COUNTS_SELF: name => `${name}: ???/???`, WISHLISTS_COUNTS: (name, pledged, total) => `${name}: ${pledged}/${total}`, WISHLISTS_TITLE: `${_CC.config.siteTitle} - Wishlists`, diff --git a/src/views/wishlist.pug b/src/views/wishlist.pug index 34daad04..28114629 100644 --- a/src/views/wishlist.pug +++ b/src/views/wishlist.pug @@ -255,30 +255,34 @@ block content .field.inline .control.inline input.inline.button(type='submit' value=lang('WISHLIST_MOVE_ITEM_BOTTOM')) - form(method='POST') - .field - label.label!=lang('WISHLIST_URL_LABEL') - .control.has-icons-left - input.input( - type='text', - name='itemUrlOrName', - placeholder=lang('WISHLIST_URL_PLACEHOLDER') - ) - span.icon.is-small.is-left - i.fas.fa-gift - .field - label.label= lang('WISHLIST_NOTE') - .control - textarea.textarea( - name='note', - placeholder=lang('WISHLIST_OPTIONAL') - ) - .field.is-grouped - .control - input.button(type='submit' value=(req.user._id === req.params.user ? lang('WISHLIST_ADD') : lang('WISHLIST_PLEDGE_ITEM'))) - if req.user._id !== req.params.user + + if ! global._CC.config.guestSuggest && req.user.pledgeOnly + .notification.is-info= lang('WISHLIST_GUEST_SUGGEST_DISABLED') + else + form(method='POST') + .field + label.label!=lang('WISHLIST_URL_LABEL') + .control.has-icons-left + input.input( + type='text', + name='itemUrlOrName', + placeholder=lang('WISHLIST_URL_PLACEHOLDER') + ) + span.icon.is-small.is-left + i.fas.fa-gift + .field + label.label= lang('WISHLIST_NOTE') .control - input.inline.button(type='submit', value=lang('WISHLIST_SUGGEST'), name='suggest') + textarea.textarea( + name='note', + placeholder=lang('WISHLIST_OPTIONAL') + ) + .field.is-grouped + .control + input.button(type='submit' value=(req.user._id === req.params.user ? lang('WISHLIST_ADD') : lang('WISHLIST_PLEDGE_ITEM'))) + if req.user._id !== req.params.user + .control + input.inline.button(type='submit', value=lang('WISHLIST_SUGGEST'), name='suggest') script(src=`${_CC.config.base}js/wishlist.js`) block print From d99ad9bcb240ea7387e9d9c4719c8fe3babf22de Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Tue, 31 Dec 2024 11:25:45 -0400 Subject: [PATCH 16/42] Update if condition to enable self-adding --- src/views/wishlist.pug | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/wishlist.pug b/src/views/wishlist.pug index 28114629..3be114a8 100644 --- a/src/views/wishlist.pug +++ b/src/views/wishlist.pug @@ -256,8 +256,8 @@ block content .control.inline input.inline.button(type='submit' value=lang('WISHLIST_MOVE_ITEM_BOTTOM')) - if ! global._CC.config.guestSuggest && req.user.pledgeOnly - .notification.is-info= lang('WISHLIST_GUEST_SUGGEST_DISABLED') + if ((!global._CC.config.pledgeSuggest && req.user.pledgeOnly) || !global._CC.config.suggestionsEnabled || req.user._id !== req.params.user) + .notification.is-info= lang('WISHLIST_SUGGESTIONS_DISABLED') else form(method='POST') .field From f7e5b261f549494f380d76a840e58e733c256927 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Tue, 31 Dec 2024 11:26:22 -0400 Subject: [PATCH 17/42] Add global suggestions disabled support --- README.md | 5 ++++- src/config/index.ts | 3 ++- src/languages/en-us.ts | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d49ddccf..9f713486 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,10 @@ MARKDOWN=false # GOOGLE_CLIENT_SECRET=123abc # Set to false to disable pledge only users from being able to suggest items to another user's list -GUEST_SUGGEST=true +PLEDGE_SUGGEST=true + +# Set to false to disable all users from being able to suggest items to another users's list +SUGGESTIONS_ENABLED=true ``` ## Default Profile Pictures diff --git a/src/config/index.ts b/src/config/index.ts index 8214d91f..7f0f0feb 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -26,7 +26,8 @@ const config = { googleSSOEnabled: false, rootUrl: appendSlash(process.env.ROOT_URL ?? process.env.ROOT_PATH ?? '/'), base: '', // automatically set below - guestSuggest: process.env.GUEST_SUGGEST === 'true' + pledgeSuggest: process.env.PLEDGE_SUGGEST === 'true' ? true : process.env.PLEDGE_SUGGEST || true, + suggestionsEnabled: process.env.SUGGESTIONS_ENABLED === 'true' ? true : process.env.SUGGESTIONS_ENABLED || true } if (config.guestPassword) config.wishlist.public = false diff --git a/src/languages/en-us.ts b/src/languages/en-us.ts index 0527dc28..f72127d8 100644 --- a/src/languages/en-us.ts +++ b/src/languages/en-us.ts @@ -215,7 +215,7 @@ export const strings = { WISHLIST_URL_LABEL: `Item URL or Name (Supported Sites)`, WISHLIST_URL_PLACEHOLDER: 'https://www.amazon.com/dp/B00ZV9RDKK', WISHLIST_URL_REQUIRED: 'Item URL or Name is required', - WISHLIST_GUEST_SUGGEST_DISABLED: 'Item suggestions are disabled.', + WISHLIST_SUGGESTIONS_DISABLED: 'Item suggestions are disabled.', WISHLISTS_COUNTS_SELF: name => `${name}: ???/???`, WISHLISTS_COUNTS: (name, pledged, total) => `${name}: ${pledged}/${total}`, WISHLISTS_TITLE: `${_CC.config.siteTitle} - Wishlists`, From 916f053f448c47e0c970d86ab5cac8ca36e6c651 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Tue, 31 Dec 2024 11:26:29 -0400 Subject: [PATCH 18/42] Dev convenience --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ab26b32d..3f6daa96 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node built/manager.js", - "dev": "nodemon --watch src -e ts,js --exec 'npm run build && node built/index.js'", + "dev": "nodemon --watch src -e ts,js,pug --exec 'npm run build && node built/index.js'", "postinstall": "node postinstall.cjs", "build": "tsc", "release": "docker buildx build . --platform linux/amd64,linux/arm64 -t wingysam/christmas-community -t wingysam/christmas-community:$(./node_modules/node-jq/bin/jq -r .version < package.json) --push" From f0ed668b3d6fb02938604a8a02cec34d71c44cc1 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Tue, 31 Dec 2024 11:32:57 -0400 Subject: [PATCH 19/42] small refactor --- src/views/includes/navbar.pug | 2 +- src/views/profile.pug | 2 +- src/views/wishlists.pug | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/views/includes/navbar.pug b/src/views/includes/navbar.pug index 30a0fbaa..1c42ac01 100644 --- a/src/views/includes/navbar.pug +++ b/src/views/includes/navbar.pug @@ -30,7 +30,7 @@ nav.navbar.is-fixed-top(role='navigation', aria-label='main navigation',style='b .navbar-item.has-dropdown.is-hoverable a.navbar-link= req.user._id .navbar-dropdown - if req.user.pledgeOnly != true + if !req.user.pledgeOnly +navBarLink(`${_CC.config.base}wishlist/${req.user._id}`, lang('NAVBAR_WISHLIST')) +navBarLink(`${_CC.config.base}profile`, lang('NAVBAR_PROFILE')) if req.user.admin diff --git a/src/views/profile.pug b/src/views/profile.pug index 1cbcdfe4..8934be28 100644 --- a/src/views/profile.pug +++ b/src/views/profile.pug @@ -36,7 +36,7 @@ block content button.button.is-primary(type='submit') span.icon i.fas.fa-save - if req.user.pledgeOnly != true + if !req.user.pledgeOnly h2= lang('PROFILE_SHARED_INFORMATION') form(action=`${_CC.config.base}profile/info`, method='POST') .columns.is-multiline.is-mobile diff --git a/src/views/wishlists.pug b/src/views/wishlists.pug index 7f141404..66dae17c 100644 --- a/src/views/wishlists.pug +++ b/src/views/wishlists.pug @@ -52,7 +52,7 @@ block content div!= _CC.config.customHtml.wishlists ul.noStyle.noLeftMargin - if req.user._id !== '_CCUNKNOWN' && req.user.pledgeOnly != true + if req.user._id !== '_CCUNKNOWN' && !req.user.pledgeOnly li .box a(href=`${_CC.config.base}wishlist/${req.user._id}`, style='color: #4a4a4a;') @@ -67,7 +67,7 @@ block content progress.progress(value=1, max=1) +wishlistDetails(req.user.wishlist, req.user._id) each user in users - if req.user._id !== user.id && user.doc.pledgeOnly != true + if req.user._id !== user.id && !user.doc.pledgeOnly li .box a(href=`${_CC.config.base}wishlist/${user.id}`, style='color: #4a4a4a;') From 433f3ef7811c5eb51ba3c9dd4bf630b98e578b14 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Wed, 22 Jan 2025 20:57:08 -0400 Subject: [PATCH 20/42] watch for pug file changes and rebuild --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0f4aa703..ce00d521 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node built/manager.js", - "dev": "nodemon --watch src -e ts,js --exec 'npm run build && node built/index.js'", + "dev": "nodemon --watch src -e ts,js,pug --exec 'npm run build && node built/index.js'", "postinstall": "node postinstall.cjs", "build": "tsc", "release": "docker buildx build . --platform linux/amd64,linux/arm64 -t wingysam/christmas-community -t wingysam/christmas-community:$(./node_modules/node-jq/bin/jq -r .version < package.json) --push" From 63c4307f7ed763eb00947fff9f48afd89c2e0d8c Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Wed, 22 Jan 2025 20:57:29 -0400 Subject: [PATCH 21/42] support archive and restore in table mode --- src/languages/en-us.ts | 9 +++- src/routes/wishlist/index.js | 42 ++++++++++++++++ src/static/css/main.css | 9 ++++ src/structures/Wishlist.js | 13 +++++ src/views/wishlist.pug | 93 +++++++++++++++++++++++++++++++++++- 5 files changed, 163 insertions(+), 3 deletions(-) diff --git a/src/languages/en-us.ts b/src/languages/en-us.ts index 0a8b4bb0..85866c16 100644 --- a/src/languages/en-us.ts +++ b/src/languages/en-us.ts @@ -208,7 +208,14 @@ export const strings = { WISHLIST_URL_LABEL: `Item URL or Name (Supported Sites)`, WISHLIST_URL_PLACEHOLDER: 'https://www.amazon.com/dp/B00ZV9RDKK', WISHLIST_URL_REQUIRED: 'Item URL or Name is required', + WISHLIST_ARCHIVE: 'Archive', + WISHLIST_RESTORE: 'Restore', + WISHLIST_ARCHIVED_ITEMS: 'Archived Items', + WISHLIST_ARCHIVE_SUCCESS : 'Successfully archived item.', + WISHLIST_RESTORE_SUCCESS : 'Successfully restored item.', + WISHLIST_ARCHIVE_GUARD: 'You are not allowed to archive this item', + WISHLIST_RESTORE_GUARD: 'You are not allowed to restore this item', WISHLISTS_COUNTS_SELF: name => `${name}: ???/???`, WISHLISTS_COUNTS: (name, pledged, total) => `${name}: ${pledged}/${total}`, - WISHLISTS_TITLE: `${_CC.config.siteTitle} - Wishlists` + WISHLISTS_TITLE: `${_CC.config.siteTitle} - Wishlists`, } as const diff --git a/src/routes/wishlist/index.js b/src/routes/wishlist/index.js index 04253c74..2c30bd4e 100644 --- a/src/routes/wishlist/index.js +++ b/src/routes/wishlist/index.js @@ -239,5 +239,47 @@ export default function (db) { res.redirect(`/wishlist/${req.params.user}/note/${req.params.id}`) }) + router.post('/:user/archive/:itemId', verifyAuth(), async (req, res) => { + try { + const wishlist = await wishlistManager.get(req.params.user) + const item = await wishlist.get(req.params.itemId) + + const isOwnWishlist = req.user._id === wishlist.username + const addedByUser = item.addedBy === req.user._id + if (!isOwnWishlist && !addedByUser) { + throw new Error(_CC.lang('WISHLIST_ARCHIVE_GUARD')) + } + + await wishlist.archive(item.id) + + req.flash('success', _CC.lang('WISHLIST_ARCHIVE_SUCCESS')) + } catch (error) { + req.flash('error', `${error}`) + } + + res.redirect(`/wishlist/${req.params.user}`) + }) + + router.post('/:user/restore/:itemId', verifyAuth(), async (req, res) => { + try { + const wishlist = await wishlistManager.get(req.params.user) + const item = await wishlist.get(req.params.itemId) + + const isOwnWishlist = req.user._id === wishlist.username + const addedByUser = item.addedBy === req.user._id + if (!isOwnWishlist && !addedByUser) { + throw new Error(_CC.lang('WISHLIST_RESTORE_GUARD')) + } + + await wishlist.restore(item.id) + + req.flash('success', _CC.lang('WISHLIST_RESTORE_SUCCESS')) + } catch (error) { + req.flash('error', `${error}`) + } + + res.redirect(`/wishlist/${req.params.user}`) + }) + return router } diff --git a/src/static/css/main.css b/src/static/css/main.css index 0b4dfdd9..9ca5fa78 100644 --- a/src/static/css/main.css +++ b/src/static/css/main.css @@ -64,4 +64,13 @@ img.logo-image { .print-gift:last-child { margin-bottom: 0; +} + +summary{ + font-weight: bold; + font-size: 18px; + } + +summary:hover { + cursor: pointer; } \ No newline at end of file diff --git a/src/structures/Wishlist.js b/src/structures/Wishlist.js index 260d3079..b0918887 100644 --- a/src/structures/Wishlist.js +++ b/src/structures/Wishlist.js @@ -178,6 +178,19 @@ export class Wishlist { await this.save() } + + async archive (id) { + const item = await this.get(id) + item.archived = true + await this.save() + } + + async restore (id) { + const item = await this.get(id) + item.archived = false + await this.save() + } + } function parseURL (string) { diff --git a/src/views/wishlist.pug b/src/views/wishlist.pug index 212691fe..67c29aa3 100644 --- a/src/views/wishlist.pug +++ b/src/views/wishlist.pug @@ -56,8 +56,10 @@ block content else th= lang('WISHLIST_PLEDGE') th= lang('WISHLIST_DELETE') + if req.params.user === req.user._id + th= lang('WISHLIST_ARCHIVE') tbody - each item, index in items + each item, index in items.filter(item => !item.archived) tr(id=item.id) td.rank= index + 1 td @@ -179,7 +181,31 @@ block content ) span.icon i.fas.fa-trash - + if req.params.user === req.user._id + if item.archived + td(data-label='Unarchive Item') + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/unarchive/${item.id}`) + .field.inline + .control.inline + button.button.is-text( + type='submit', + style='text-decoration: none;', + disabled=item.addedBy !== req.user._id + ) + span.icon + i.fas.fa-undo + else + td(data-label='Archive Item') + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/archive/${item.id}`) + .field.inline + .control.inline + button.button.is-text( + type='submit', + style='text-decoration: none;', + disabled=item.addedBy !== req.user._id + ) + span.icon + i.fas.fa-archive else each item, index in items if req.user._id === item.addedBy || req.params.user !== req.user._id @@ -288,6 +314,69 @@ block content input.inline.button(type='submit', value=lang('WISHLIST_SUGGEST'), name='suggest') script(src=`${_CC.config.base}js/wishlist.js`) + br + details + summary= lang('WISHLIST_ARCHIVED_ITEMS') + if global._CC.config.wishlist.table + .box + table.table.has-mobile-cards + thead + tr + th # + th(style='width: 10%;')= lang('WISHLIST_IMAGE') + th(style='width: 25%;')= lang('WISHLIST_NAME') + th(style='width: 50%;')= lang('WISHLIST_NOTE') + th(style='width: 10%;')= lang('WISHLIST_PRICE') + th(style='width: 10%;')= lang('WISHLIST_RESTORE') + tbody + each item, index in items.filter(item => item.archived) + tr(id=item.id) + td.rank= index + 1 + td + figure(style='width: 100%; margin: 0;') + img(src=item.image, style='width: 100%; max-height: 20em; object-fit: contain;') + if item.url + td.ugc(data-label=lang('WISHLIST_NAME')) + if item.pledgedBy && item.addedBy != req.user._id + strike + a( + href=item.url, + rel='noopener noreferrer', + target='_blank' + )= (item.name ? item.name : item.url) + else + a( + href=item.url, + rel='noopener noreferrer', + target='_blank' + )= (item.name ? item.name : item.url) + else + td.ugc(data-label='Name') + if item.pledgedBy && item.addedBy != req.user._id + strike + span= item.name + else + span= item.name + td= item.price + if _CC.config.wishlist.note.markdown + td.ugc(data-label=lang('WISHLIST_NOTE')) + div!= compiledNotes[item.id] + else + td.ugc(data-label=lang('WISHLIST_NOTE'))= item.note + td + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/restore/${item.id}`) + .field.inline + .control.inline + button.button.is-text( + type='submit', + style='text-decoration: none;', + disabled=item.addedBy !== req.user._id + ) + span.icon + i.fas.fa-undo + + + block print h1 #{req.params.user}'s Wishlist .print-gifts From e73e8c0807513d9772c7d306241e8739b224a972 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Wed, 22 Jan 2025 21:26:06 -0400 Subject: [PATCH 22/42] add card support and delete confirmation --- src/languages/en-us.ts | 1 + src/views/wishlist.pug | 73 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/src/languages/en-us.ts b/src/languages/en-us.ts index 85866c16..b9f0b9ed 100644 --- a/src/languages/en-us.ts +++ b/src/languages/en-us.ts @@ -215,6 +215,7 @@ export const strings = { WISHLIST_RESTORE_SUCCESS : 'Successfully restored item.', WISHLIST_ARCHIVE_GUARD: 'You are not allowed to archive this item', WISHLIST_RESTORE_GUARD: 'You are not allowed to restore this item', + WISHLIST_DELETE_CONFIRM: 'Are you sure you want to delete this item?', WISHLISTS_COUNTS_SELF: name => `${name}: ???/???`, WISHLISTS_COUNTS: (name, pledged, total) => `${name}: ${pledged}/${total}`, WISHLISTS_TITLE: `${_CC.config.siteTitle} - Wishlists`, diff --git a/src/views/wishlist.pug b/src/views/wishlist.pug index 67c29aa3..94713edd 100644 --- a/src/views/wishlist.pug +++ b/src/views/wishlist.pug @@ -170,7 +170,8 @@ block content td(data-label='Delete Item') form.inline( method='POST', - action=`${_CC.config.base}wishlist/${req.params.user}/remove/${item.id}` + action=`${_CC.config.base}wishlist/${req.params.user}/remove/${item.id}`, + onsubmit=`return confirm("${lang('WISHLIST_DELETE_CONFIRM')}")` ) .field.inline .control.inline @@ -207,7 +208,7 @@ block content span.icon i.fas.fa-archive else - each item, index in items + each item, index in items.filter(item => !item.archived) if req.user._id === item.addedBy || req.params.user !== req.user._id .box if item.price @@ -264,10 +265,10 @@ block content .control.inline input.inline.button(type='submit' value=lang('WISHLIST_UNPLEDGE')) if req.user._id === req.params.user - form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/remove/${item.id}`) + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/remove/${item.id}`, onsubmit=`return confirm("${lang('WISHLIST_DELETE_CONFIRM')}")`) .field.inline .control.inline - input.inline.button.is-warning(type='submit' value=lang('WISHLIST_DELETE')) + input.inline.button.is-danger(type='submit' value=lang('WISHLIST_DELETE')) if req.user._id === req.params.user || req.user._id === item.addedBy form.inline(method='GET', action=`${_CC.config.base}wishlist/${req.params.user}/note/${item.id}`) .field.inline @@ -288,6 +289,11 @@ block content .field.inline .control.inline input.inline.button(type='submit' value=lang('WISHLIST_MOVE_ITEM_BOTTOM')) + if req.user._id === req.params.user + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/archive/${item.id}`) + .field.inline + .control.inline + input.inline.button.is-warning(type='submit' value=lang('WISHLIST_ARCHIVE')) form(method='POST')#addWishlistItem .field label.label!=lang('WISHLIST_URL_LABEL') @@ -374,8 +380,63 @@ block content ) span.icon i.fas.fa-undo - - + else + each item, index in items.filter(item => item.archived) + if req.user._id === item.addedBy || req.params.user !== req.user._id + .box + if item.price + .tag + i.fas.fa-tag(style='margin-right: .5em;') + span= item.price + span.overflowWrap + if item.price + span= ' ' + if item.url + if item.pledgedBy && item.addedBy != req.user._id + strike + a( + href=item.url, + rel='noopener noreferrer', + target='_blank' + )= (item.name ? item.name : item.url) + else + a( + href=item.url, + rel='noopener noreferrer', + target='_blank' + )= (item.name ? item.name : item.url) + else + if item.pledgedBy && item.addedBy != req.user._id + strike + span= item.name + else + span= item.name + hr + if item.note || item.image + .columns + if item.image + .column + figure(style='width: 100%; padding: 2em;') + img(src=item.image, style='width: 100%;') + .column.is-three-quarters + if item.note + textarea.textarea.has-fixed-size( + style='height: 100%;' + readonly + )= item.note + hr + div + if req.user._id === req.params.user + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/remove/${item.id}`, onsubmit=`return confirm("${lang('WISHLIST_DELETE_CONFIRM')}")`) + .field.inline + .control.inline + input.inline.button.is-danger(type='submit' value=lang('WISHLIST_DELETE')) + span= ' ' + if req.user._id === req.params.user + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/restore/${item.id}`) + .field.inline + .control.inline + input.inline.button.is-success(type='submit' value=lang('WISHLIST_RESTORE')) block print h1 #{req.params.user}'s Wishlist From 870ef01f034c522e5e89edaace02c63528127497 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Wed, 22 Jan 2025 21:56:00 -0400 Subject: [PATCH 23/42] add support for pledge auto archiving --- src/config/index.ts | 1 + src/routes/wishlist/index.js | 6 +++++- src/structures/Wishlist.js | 4 ++++ src/views/wishlists.pug | 4 ++-- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/config/index.ts b/src/config/index.ts index 6c11e4c3..80a6aabc 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -30,6 +30,7 @@ const config = { oidcProviderName: process.env.OIDC_PROVIDER_NAME || 'Google', oidcEnabled: false, rootUrl: appendSlash(process.env.ROOT_URL ?? process.env.ROOT_PATH ?? '/'), + autoArchivePledges: process.env.AUTO_ARCHIVE_PLEDGES || false, base: '' // automatically set below } diff --git a/src/routes/wishlist/index.js b/src/routes/wishlist/index.js index 2c30bd4e..f9f78fd0 100644 --- a/src/routes/wishlist/index.js +++ b/src/routes/wishlist/index.js @@ -12,11 +12,15 @@ const DOMPurify = createDOMPurify(window) const totals = wishlist => { let unpledged = 0 let pledged = 0 + let active = 0 + let archived = 0 wishlist.forEach(wishItem => { if (wishItem.pledgedBy) pledged += 1 else unpledged += 1 + if (wishItem.archived) archived += 1 + else active += 1 }) - return { unpledged, pledged } + return { unpledged, pledged, active, archived } } export default function (db) { diff --git a/src/structures/Wishlist.js b/src/structures/Wishlist.js index b0918887..9e4f5691 100644 --- a/src/structures/Wishlist.js +++ b/src/structures/Wishlist.js @@ -98,6 +98,10 @@ export class Wishlist { async pledge (id, user) { const item = await this.get(id) item.pledgedBy = user + + if (_CC.config.autoArchivePledges) { + item.archived = true + } await this.save() } diff --git a/src/views/wishlists.pug b/src/views/wishlists.pug index 4243928a..d179d8a5 100644 --- a/src/views/wishlists.pug +++ b/src/views/wishlists.pug @@ -77,8 +77,8 @@ block content figure.image.is-square.is-fullwidth.is-marginless(style='display: inline-block;') img.is-rounded.is-fullwidth(src=user.doc.pfp, style='object-fit: cover;') .column - span= lang('WISHLISTS_COUNTS', user.id, totals(user.doc.wishlist).pledged, user.doc.wishlist.length) - progress.progress.is-info(value=totals(user.doc.wishlist).pledged, max=user.doc.wishlist.length) + span= lang('WISHLISTS_COUNTS', user.id, totals(user.doc.wishlist).pledged, totals(user.doc.wishlist).active) + progress.progress.is-info(value=totals(user.doc.wishlist).pledged, max=totals(user.doc.wishlist).active) +wishlistDetails(user.doc.wishlist) script. document.querySelectorAll('.wishlist-dropdown-button') From 5795bb1e6b5e6f153cf13281c12526409c9eb3fb Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Wed, 22 Jan 2025 21:57:12 -0400 Subject: [PATCH 24/42] add docs for auto archiving --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 8a45f079..c257d3fa 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,9 @@ MARKDOWN=false # OIDC_TOKEN_URL=https://oauth2.googleapis.com/token # OIDC_ISSUER=https://accounts.google.com # OIDC_PROVIDER_NAME=Google + +## Auto archive pledges +AUTO_ARCHIVE_PLEDGES=false ``` ## Default Profile Pictures From 07d754102da553da22a44be00514d648b5e4b772 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Wed, 22 Jan 2025 22:04:40 -0400 Subject: [PATCH 25/42] only list active items --- src/views/wishlists.pug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/wishlists.pug b/src/views/wishlists.pug index d179d8a5..08adfe10 100644 --- a/src/views/wishlists.pug +++ b/src/views/wishlists.pug @@ -8,7 +8,7 @@ mixin wishlistDetails(wishlist, onlyShowAddedByUser) i.fas.fa-chevron-down ul.wishlist-dropdown for item in wishlist - if onlyShowAddedByUser === undefined || item.addedBy === onlyShowAddedByUser + if !item.archived && onlyShowAddedByUser === undefined || item.addedBy === onlyShowAddedByUser li if item.pledgedBy && !onlyShowAddedByUser strike= item.name From 7a7bd1b42158c5bf87533c1a4828ed7c735ebe9a Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Wed, 22 Jan 2025 22:09:40 -0400 Subject: [PATCH 26/42] remove unnecessary comma --- src/languages/en-us.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/en-us.ts b/src/languages/en-us.ts index b9f0b9ed..3a8e5a89 100644 --- a/src/languages/en-us.ts +++ b/src/languages/en-us.ts @@ -218,5 +218,5 @@ export const strings = { WISHLIST_DELETE_CONFIRM: 'Are you sure you want to delete this item?', WISHLISTS_COUNTS_SELF: name => `${name}: ???/???`, WISHLISTS_COUNTS: (name, pledged, total) => `${name}: ${pledged}/${total}`, - WISHLISTS_TITLE: `${_CC.config.siteTitle} - Wishlists`, + WISHLISTS_TITLE: `${_CC.config.siteTitle} - Wishlists` } as const From 732d996f336f79f8135288720f40c068ac5a5008 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Thu, 23 Jan 2025 17:42:33 -0400 Subject: [PATCH 27/42] fix order or archive table columns --- src/views/wishlist.pug | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/views/wishlist.pug b/src/views/wishlist.pug index 94713edd..0a73ced3 100644 --- a/src/views/wishlist.pug +++ b/src/views/wishlist.pug @@ -333,7 +333,8 @@ block content th(style='width: 25%;')= lang('WISHLIST_NAME') th(style='width: 50%;')= lang('WISHLIST_NOTE') th(style='width: 10%;')= lang('WISHLIST_PRICE') - th(style='width: 10%;')= lang('WISHLIST_RESTORE') + th(style='width: 5%;')= lang('WISHLIST_DELETE') + th(style='width: 5%;')= lang('WISHLIST_RESTORE') tbody each item, index in items.filter(item => item.archived) tr(id=item.id) @@ -363,12 +364,27 @@ block content span= item.name else span= item.name - td= item.price if _CC.config.wishlist.note.markdown td.ugc(data-label=lang('WISHLIST_NOTE')) div!= compiledNotes[item.id] else td.ugc(data-label=lang('WISHLIST_NOTE'))= item.note + td= item.price + td(data-label='Delete Item') + form.inline( + method='POST', + action=`${_CC.config.base}wishlist/${req.params.user}/remove/${item.id}`, + onsubmit=`return confirm("${lang('WISHLIST_DELETE_CONFIRM')}")` + ) + .field.inline + .control.inline + button.button.is-text( + type='submit', + style='text-decoration: none;', + disabled=item.addedBy !== req.user._id + ) + span.icon + i.fas.fa-trash td form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/restore/${item.id}`) .field.inline From f944297acc02290877e41bc64562be45ab64e998 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Fri, 24 Jan 2025 17:00:36 -0400 Subject: [PATCH 28/42] pugs don't need rebuilds, rappers do https://en.wikipedia.org/wiki/Guns_Don%27t_Kill_People,_Rappers_Do --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ee9e215c..1b16dd4e 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node built/manager.js", - "dev": "nodemon --watch src -e ts,js,pug --exec 'npm run build && node built/index.js'", + "dev": "nodemon --watch src -e ts,js --exec 'npm run build && node built/index.js'", "postinstall": "node postinstall.cjs", "build": "tsc", "release": "docker buildx build . --platform linux/amd64,linux/arm64 -t wingysam/christmas-community -t wingysam/christmas-community:$(./node_modules/node-jq/bin/jq -r .version < package.json) --push" From 477153ca183f11036ffd14f5c537ebdc7e1ba555 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Fri, 24 Jan 2025 17:42:21 -0400 Subject: [PATCH 29/42] tidy up item adding --- src/config/index.ts | 4 ++-- src/static/css/main.css | 5 +++++ src/views/wishlist.pug | 16 +++++----------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/config/index.ts b/src/config/index.ts index f9b4f6b5..74b57ce3 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -31,8 +31,8 @@ const config = { oidcEnabled: false, rootUrl: appendSlash(process.env.ROOT_URL ?? process.env.ROOT_PATH ?? '/'), base: '', // automatically set below - pledgeSuggest: process.env.PLEDGE_SUGGEST === 'true' ? true : process.env.PLEDGE_SUGGEST || true, - suggestionsEnabled: process.env.SUGGESTIONS_ENABLED === 'true' ? true : process.env.SUGGESTIONS_ENABLED || true + pledgeSuggest: (process.env.PLEDGE_SUGGEST === 'true'), + suggestionsEnabled: (process.env.SUGGESTIONS_ENABLED === 'true') } if (config.guestPassword) config.wishlist.public = false diff --git a/src/static/css/main.css b/src/static/css/main.css index 0b4dfdd9..a97e32f8 100644 --- a/src/static/css/main.css +++ b/src/static/css/main.css @@ -64,4 +64,9 @@ img.logo-image { .print-gift:last-child { margin-bottom: 0; +} + +a.disabled { + pointer-events: none; + cursor: default; } \ No newline at end of file diff --git a/src/views/wishlist.pug b/src/views/wishlist.pug index bb1d8770..d2440076 100644 --- a/src/views/wishlist.pug +++ b/src/views/wishlist.pug @@ -18,6 +18,9 @@ block title span= lang('WISHLIST_TITLE', name) .level-right .level-item + if ((!global._CC.config.pledgeSuggest && req.user.pledgeOnly) || !global._CC.config.suggestionsEnabled) + span= " " + else a(href="#addWishlistItem").button.button.is-primary.is-rounded span.icon i.fas.fa-plus @@ -263,10 +266,10 @@ block content .control.inline input.inline.button(type='submit' value=lang('WISHLIST_MOVE_ITEM_BOTTOM')) - if ((!global._CC.config.pledgeSuggest && req.user.pledgeOnly) || !global._CC.config.suggestionsEnabled || req.user._id !== req.params.user) + if ((!global._CC.config.pledgeSuggest && req.user.pledgeOnly) || !global._CC.config.suggestionsEnabled) .notification.is-info= lang('WISHLIST_SUGGESTIONS_DISABLED') else - form(method='POST') + form(method='POST')#addWishlistItem .field label.label!=lang('WISHLIST_URL_LABEL') .control.has-icons-left @@ -279,15 +282,6 @@ block content i.fas.fa-gift .field label.label= lang('WISHLIST_NOTE') - .control - textarea.textarea( - name='note', - placeholder=lang('WISHLIST_OPTIONAL') - ) - .field.is-grouped - .control - input.button(type='submit' value=(req.user._id === req.params.user ? lang('WISHLIST_ADD') : lang('WISHLIST_PLEDGE_ITEM'))) - if req.user._id !== req.params.user .control textarea.textarea( name='note', From eae7599d23c06e513dad0635347b54cc0329e898 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Fri, 24 Jan 2025 20:58:47 -0400 Subject: [PATCH 30/42] add parentheses for readability --- src/views/wishlists.pug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/wishlists.pug b/src/views/wishlists.pug index 08adfe10..ce3b528e 100644 --- a/src/views/wishlists.pug +++ b/src/views/wishlists.pug @@ -8,7 +8,7 @@ mixin wishlistDetails(wishlist, onlyShowAddedByUser) i.fas.fa-chevron-down ul.wishlist-dropdown for item in wishlist - if !item.archived && onlyShowAddedByUser === undefined || item.addedBy === onlyShowAddedByUser + if (!item.archived && onlyShowAddedByUser === undefined) || item.addedBy === onlyShowAddedByUser li if item.pledgedBy && !onlyShowAddedByUser strike= item.name From b088c7b77b180e580780e2bd054ff996e05964da Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Fri, 24 Jan 2025 21:43:26 -0400 Subject: [PATCH 31/42] move wishlist generation to mixins --- src/views/wishlist.pug | 584 +++++++++++++++++------------------------ 1 file changed, 240 insertions(+), 344 deletions(-) diff --git a/src/views/wishlist.pug b/src/views/wishlist.pug index 0a73ced3..b2743ce9 100644 --- a/src/views/wishlist.pug +++ b/src/views/wishlist.pug @@ -6,93 +6,68 @@ mixin sharedInfoProp(key, label) th= lang(label) td= sharedInfo[key] -block title - h1 - .level - .level-left - .level-item - button.button.is-rounded(onclick='this.firstChild.firstChild.classList.add("fa-pulse"); location.reload()') - span.icon - i.fas.fa-sync-alt - .level-item - span= lang('WISHLIST_TITLE', name) - .level-right - .level-item - a(href="#addWishlistItem").button.button.is-primary.is-rounded - span.icon - i.fas.fa-plus - span= lang('WISHLIST_ADD_NEW') - - -block content - script(type='data/user_id')= req.user._id - table.table(style='width: fit-content;') - tbody - +sharedInfoProp('shoeSize', 'PROFILE_SHOE_SIZE') - +sharedInfoProp('ringSize', 'PROFILE_RING_SIZE') - +sharedInfoProp('dressSize', 'PROFILE_DRESS_SIZE') - +sharedInfoProp('sweaterSize', 'PROFILE_SWEATER_SIZE') - +sharedInfoProp('shirtSize', 'PROFILE_SHIRT_SIZE') - +sharedInfoProp('pantsSize', 'PROFILE_PANTS_SIZE') - +sharedInfoProp('coatSize', 'PROFILE_COAT_SIZE') - +sharedInfoProp('hatSize', 'PROFILE_HAT_SIZE') - +sharedInfoProp('phoneModel', 'PROFILE_PHONE_MODEL') - if global._CC.config.wishlist.table - .box - table.table.has-mobile-cards - thead - th # - th(style='width: 15%;')= lang('WISHLIST_IMAGE') - th(style='width: 25%;')= lang('WISHLIST_NAME') - th(style='width: 50%;')= lang('WISHLIST_NOTE') - th(style='width: 10%;')= lang('WISHLIST_PRICE') +mixin wishlistTable(items, archived) + .box + table.table.has-mobile-cards + thead + th # + th(style='width: 15%;')= lang('WISHLIST_IMAGE') + th(style='width: 25%;')= lang('WISHLIST_NAME') + th(style='width: 50%;')= lang('WISHLIST_NOTE') + th(style='width: 10%;')= lang('WISHLIST_PRICE') + if req.params.user === req.user._id th= lang('WISHLIST_EDIT_ITEM') - th= lang('WISHLIST_ADDED_BY') - if req.params.user === req.user._id - th= lang('WISHLIST_MOVE_TOP') - th= lang('WISHLIST_MOVE_UP') - th= lang('WISHLIST_MOVE_DOWN') - th= lang('WISHLIST_MOVE_BOTTOM') - else + th= lang('WISHLIST_ADDED_BY') + if req.params.user === req.user._id && !archived + th= lang('WISHLIST_MOVE_TOP') + th= lang('WISHLIST_MOVE_UP') + th= lang('WISHLIST_MOVE_DOWN') + th= lang('WISHLIST_MOVE_BOTTOM') + else + if !archived th= lang('WISHLIST_PLEDGE') + if req.params.user === req.user._id th= lang('WISHLIST_DELETE') - if req.params.user === req.user._id - th= lang('WISHLIST_ARCHIVE') - tbody - each item, index in items.filter(item => !item.archived) - tr(id=item.id) - td.rank= index + 1 - td - figure(style='width: 100%; margin: 0;') - img(src=item.image, style='width: 100%; max-height: 20em; object-fit: contain;') - if item.url - td.ugc(data-label=lang('WISHLIST_NAME')) - if item.pledgedBy && item.addedBy != req.user._id - strike - a( - href=item.url, - rel='noopener noreferrer', - target='_blank' - )= (item.name ? item.name : item.url) - else - a( - href=item.url, - rel='noopener noreferrer', - target='_blank' - )= (item.name ? item.name : item.url) - else - td.ugc(data-label='Name') - if item.pledgedBy && item.addedBy != req.user._id - strike - span= item.name - else + if archived + th= lang('WISHLIST_RESTORE') + else + th= lang('WISHLIST_ARCHIVE') + tbody + each item, index in items + tr(id=item.id) + td.rank= index + 1 + td + figure(style='width: 100%; margin: 0;') + img(src=item.image, style='width: 100%; max-height: 20em; object-fit: contain;') + if item.url + td.ugc(data-label=lang('WISHLIST_NAME')) + if item.pledgedBy && item.addedBy != req.user._id + strike + a( + href=item.url, + rel='noopener noreferrer', + target='_blank' + )= (item.name ? item.name : item.url) + else + a( + href=item.url, + rel='noopener noreferrer', + target='_blank' + )= (item.name ? item.name : item.url) + else + td.ugc(data-label='Name') + if item.pledgedBy && item.addedBy != req.user._id + strike span= item.name - if _CC.config.wishlist.note.markdown - td.ugc(data-label=lang('WISHLIST_NOTE')) - div!= compiledNotes[item.id] - else - td.ugc(data-label=lang('WISHLIST_NOTE'))= item.note - td.ugc(data-label=lang('WISHLIST_PRICE'))= item.price + else + span= item.name + if _CC.config.wishlist.note.markdown + td.ugc(data-label=lang('WISHLIST_NOTE')) + div!= compiledNotes[item.id] + else + td.ugc(data-label=lang('WISHLIST_NOTE'))= item.note + td.ugc(data-label=lang('WISHLIST_PRICE'))= item.price + if req.params.user === req.user._id td(data-label=lang('WISHLIST_EDIT_ITEM')) form.inline(method='GET', action=`${_CC.config.base}wishlist/${req.params.user}/note/${item.id}`) .field.inline @@ -104,53 +79,54 @@ block content ) span.icon i.far.fa-edit - td.ugc(data-label=lang('WISHLIST_ADDED_BY'))= item.addedBy - if req.params.user === req.user._id - td(data-label=lang('WISHLIST_MOVE_ITEM_TOP')) - form.topForm.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/top/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=index === 0 - ) - span.icon - i.fas.fa-angle-double-up - td(data-label=lang('WISHLIST_MOVE_ITEM_UP')) - form.upForm.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/up/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=index === 0 - ) - span.icon - i.fas.fa-arrow-up - td(data-label=lang('WISHLIST_MOVE_ITEM_DOWN')) - form.downForm.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/down/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=index === items.length - 1 - ) - span.icon - i.fas.fa-arrow-down - td(data-label=lang('WISHLIST_MOVE_ITEM_BOTTOM')) - form.bottomForm.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/bottom/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=index === items.length - 1 - ) - span.icon - i.fas.fa-angle-double-down - else + td.ugc(data-label=lang('WISHLIST_ADDED_BY'))= item.addedBy + if req.params.user === req.user._id && !archived + td(data-label=lang('WISHLIST_MOVE_ITEM_TOP')) + form.topForm.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/top/${item.id}`) + .field.inline + .control.inline + button.button.is-text( + type='submit', + style='text-decoration: none;', + disabled=index === 0 + ) + span.icon + i.fas.fa-angle-double-up + td(data-label=lang('WISHLIST_MOVE_ITEM_UP')) + form.upForm.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/up/${item.id}`) + .field.inline + .control.inline + button.button.is-text( + type='submit', + style='text-decoration: none;', + disabled=index === 0 + ) + span.icon + i.fas.fa-arrow-up + td(data-label=lang('WISHLIST_MOVE_ITEM_DOWN')) + form.downForm.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/down/${item.id}`) + .field.inline + .control.inline + button.button.is-text( + type='submit', + style='text-decoration: none;', + disabled=index === items.length - 1 + ) + span.icon + i.fas.fa-arrow-down + td(data-label=lang('WISHLIST_MOVE_ITEM_BOTTOM')) + form.bottomForm.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/bottom/${item.id}`) + .field.inline + .control.inline + button.button.is-text( + type='submit', + style='text-decoration: none;', + disabled=index === items.length - 1 + ) + span.icon + i.fas.fa-angle-double-down + else + if !archived td(data-label=lang('WISHLIST_PLEDGE')) if req.params.user !== req.user._id && !item.pledgedBy form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/pledge/${item.id}`) @@ -167,6 +143,7 @@ block content span.ugc=lang('WISHLIST_PLEDGED_GUEST') else span.ugc=lang('WISHLIST_PLEDGED', item.pledgedBy) + if req.params.user === req.user._id td(data-label='Delete Item') form.inline( method='POST', @@ -182,98 +159,99 @@ block content ) span.icon i.fas.fa-trash - if req.params.user === req.user._id - if item.archived - td(data-label='Unarchive Item') - form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/unarchive/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=item.addedBy !== req.user._id - ) - span.icon - i.fas.fa-undo - else - td(data-label='Archive Item') - form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/archive/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=item.addedBy !== req.user._id - ) - span.icon - i.fas.fa-archive - else - each item, index in items.filter(item => !item.archived) - if req.user._id === item.addedBy || req.params.user !== req.user._id - .box - if item.price - .tag - i.fas.fa-tag(style='margin-right: .5em;') - span= item.price - span.overflowWrap - if item.price - span= ' ' - if item.url - if item.pledgedBy && item.addedBy != req.user._id - strike - a( - href=item.url, - rel='noopener noreferrer', - target='_blank' - )= (item.name ? item.name : item.url) + if archived + td(data-label='Unarchive Item') + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/unarchive/${item.id}`) + .field.inline + .control.inline + button.button.is-text( + type='submit', + style='text-decoration: none;', + disabled=item.addedBy !== req.user._id + ) + span.icon + i.fas.fa-undo else + td(data-label='Archive Item') + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/archive/${item.id}`) + .field.inline + .control.inline + button.button.is-text( + type='submit', + style='text-decoration: none;', + disabled=item.addedBy !== req.user._id + ) + span.icon + i.fas.fa-archive + +mixin wishlistBox(items, archived) + each item, index in items + if req.user._id === item.addedBy || req.params.user !== req.user._id + .box + if item.price + .tag + i.fas.fa-tag(style='margin-right: .5em;') + span= item.price + span.overflowWrap + if item.price + span= ' ' + if item.url + if item.pledgedBy && item.addedBy != req.user._id + strike a( href=item.url, rel='noopener noreferrer', target='_blank' )= (item.name ? item.name : item.url) else - if item.pledgedBy && item.addedBy != req.user._id - strike - span= item.name - else + a( + href=item.url, + rel='noopener noreferrer', + target='_blank' + )= (item.name ? item.name : item.url) + else + if item.pledgedBy && item.addedBy != req.user._id + strike span= item.name - hr - if item.note || item.image - .columns - if item.image - .column - figure(style='width: 100%; padding: 2em;') - img(src=item.image, style='width: 100%;') - .column.is-three-quarters - if item.note - textarea.textarea.has-fixed-size( - style='height: 100%;' - readonly - )= item.note - span.overflowWrap=lang('WISHLIST_ADDED_BY_USER', item.addedBy) - hr - div - if req.params.user !== req.user._id && !item.pledgedBy - form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/pledge/${item.id}`) - .field.inline - .control.inline - input.inline.button.is-primary(type='submit' value=lang('WISHLIST_PLEDGE')) - if item.pledgedBy === req.user._id - form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/unpledge/${item.id}`) - .field.inline - .control.inline - input.inline.button(type='submit' value=lang('WISHLIST_UNPLEDGE')) - if req.user._id === req.params.user - form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/remove/${item.id}`, onsubmit=`return confirm("${lang('WISHLIST_DELETE_CONFIRM')}")`) - .field.inline - .control.inline - input.inline.button.is-danger(type='submit' value=lang('WISHLIST_DELETE')) - if req.user._id === req.params.user || req.user._id === item.addedBy - form.inline(method='GET', action=`${_CC.config.base}wishlist/${req.params.user}/note/${item.id}`) - .field.inline - .control.inline - input.inline.button(type='submit', value=lang('WISHLIST_EDIT_ITEM')) + else + span= item.name + hr + if item.note || item.image + .columns + if item.image + .column + figure(style='width: 100%; padding: 2em;') + img(src=item.image, style='width: 100%;') + .column.is-three-quarters + if item.note + textarea.textarea.has-fixed-size( + style='height: 100%;' + readonly + )= item.note + span.overflowWrap=lang('WISHLIST_ADDED_BY_USER', item.addedBy) + hr + div + if req.params.user !== req.user._id && !item.pledgedBy + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/pledge/${item.id}`) + .field.inline + .control.inline + input.inline.button.is-primary(type='submit' value=lang('WISHLIST_PLEDGE')) + if item.pledgedBy === req.user._id + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/unpledge/${item.id}`) + .field.inline + .control.inline + input.inline.button(type='submit' value=lang('WISHLIST_UNPLEDGE')) + if req.user._id === req.params.user + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/remove/${item.id}`, onsubmit=`return confirm("${lang('WISHLIST_DELETE_CONFIRM')}")`) + .field.inline + .control.inline + input.inline.button.is-danger(type='submit' value=lang('WISHLIST_DELETE')) + if req.user._id === req.params.user || req.user._id === item.addedBy + form.inline(method='GET', action=`${_CC.config.base}wishlist/${req.params.user}/note/${item.id}`) + .field.inline + .control.inline + input.inline.button(type='submit', value=lang('WISHLIST_EDIT_ITEM')) + if !archived if index !== 0 && req.user._id === req.params.user form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/up/${item.id}`) .field.inline @@ -289,11 +267,56 @@ block content .field.inline .control.inline input.inline.button(type='submit' value=lang('WISHLIST_MOVE_ITEM_BOTTOM')) - if req.user._id === req.params.user + if req.user._id === req.params.user + if !archived form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/archive/${item.id}`) .field.inline .control.inline input.inline.button.is-warning(type='submit' value=lang('WISHLIST_ARCHIVE')) + else + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/restore/${item.id}`) + .field.inline + .control.inline + input.inline.button.is-success(type='submit' value=lang('WISHLIST_RESTORE')) + +block title + h1 + .level + .level-left + .level-item + button.button.is-rounded(onclick='this.firstChild.firstChild.classList.add("fa-pulse"); location.reload()') + span.icon + i.fas.fa-sync-alt + .level-item + span= lang('WISHLIST_TITLE', name) + .level-right + .level-item + a(href="#addWishlistItem").button.button.is-primary.is-rounded + span.icon + i.fas.fa-plus + span= lang('WISHLIST_ADD_NEW') + + +block content + script(type='data/user_id')= req.user._id + table.table(style='width: fit-content;') + tbody + +sharedInfoProp('shoeSize', 'PROFILE_SHOE_SIZE') + +sharedInfoProp('ringSize', 'PROFILE_RING_SIZE') + +sharedInfoProp('dressSize', 'PROFILE_DRESS_SIZE') + +sharedInfoProp('sweaterSize', 'PROFILE_SWEATER_SIZE') + +sharedInfoProp('shirtSize', 'PROFILE_SHIRT_SIZE') + +sharedInfoProp('pantsSize', 'PROFILE_PANTS_SIZE') + +sharedInfoProp('coatSize', 'PROFILE_COAT_SIZE') + +sharedInfoProp('hatSize', 'PROFILE_HAT_SIZE') + +sharedInfoProp('phoneModel', 'PROFILE_PHONE_MODEL') + + if global._CC.config.wishlist.table + +wishlistTable(items.filter(item => !item.archived), false) + else + +wishlistBox(items.filter(item => !item.archived), false) + + br form(method='POST')#addWishlistItem .field label.label!=lang('WISHLIST_URL_LABEL') @@ -319,140 +342,13 @@ block content .control input.inline.button(type='submit', value=lang('WISHLIST_SUGGEST'), name='suggest') script(src=`${_CC.config.base}js/wishlist.js`) - br details summary= lang('WISHLIST_ARCHIVED_ITEMS') if global._CC.config.wishlist.table - .box - table.table.has-mobile-cards - thead - tr - th # - th(style='width: 10%;')= lang('WISHLIST_IMAGE') - th(style='width: 25%;')= lang('WISHLIST_NAME') - th(style='width: 50%;')= lang('WISHLIST_NOTE') - th(style='width: 10%;')= lang('WISHLIST_PRICE') - th(style='width: 5%;')= lang('WISHLIST_DELETE') - th(style='width: 5%;')= lang('WISHLIST_RESTORE') - tbody - each item, index in items.filter(item => item.archived) - tr(id=item.id) - td.rank= index + 1 - td - figure(style='width: 100%; margin: 0;') - img(src=item.image, style='width: 100%; max-height: 20em; object-fit: contain;') - if item.url - td.ugc(data-label=lang('WISHLIST_NAME')) - if item.pledgedBy && item.addedBy != req.user._id - strike - a( - href=item.url, - rel='noopener noreferrer', - target='_blank' - )= (item.name ? item.name : item.url) - else - a( - href=item.url, - rel='noopener noreferrer', - target='_blank' - )= (item.name ? item.name : item.url) - else - td.ugc(data-label='Name') - if item.pledgedBy && item.addedBy != req.user._id - strike - span= item.name - else - span= item.name - if _CC.config.wishlist.note.markdown - td.ugc(data-label=lang('WISHLIST_NOTE')) - div!= compiledNotes[item.id] - else - td.ugc(data-label=lang('WISHLIST_NOTE'))= item.note - td= item.price - td(data-label='Delete Item') - form.inline( - method='POST', - action=`${_CC.config.base}wishlist/${req.params.user}/remove/${item.id}`, - onsubmit=`return confirm("${lang('WISHLIST_DELETE_CONFIRM')}")` - ) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=item.addedBy !== req.user._id - ) - span.icon - i.fas.fa-trash - td - form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/restore/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=item.addedBy !== req.user._id - ) - span.icon - i.fas.fa-undo + +wishlistTable(items.filter(item => !item.archived), true) else - each item, index in items.filter(item => item.archived) - if req.user._id === item.addedBy || req.params.user !== req.user._id - .box - if item.price - .tag - i.fas.fa-tag(style='margin-right: .5em;') - span= item.price - span.overflowWrap - if item.price - span= ' ' - if item.url - if item.pledgedBy && item.addedBy != req.user._id - strike - a( - href=item.url, - rel='noopener noreferrer', - target='_blank' - )= (item.name ? item.name : item.url) - else - a( - href=item.url, - rel='noopener noreferrer', - target='_blank' - )= (item.name ? item.name : item.url) - else - if item.pledgedBy && item.addedBy != req.user._id - strike - span= item.name - else - span= item.name - hr - if item.note || item.image - .columns - if item.image - .column - figure(style='width: 100%; padding: 2em;') - img(src=item.image, style='width: 100%;') - .column.is-three-quarters - if item.note - textarea.textarea.has-fixed-size( - style='height: 100%;' - readonly - )= item.note - hr - div - if req.user._id === req.params.user - form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/remove/${item.id}`, onsubmit=`return confirm("${lang('WISHLIST_DELETE_CONFIRM')}")`) - .field.inline - .control.inline - input.inline.button.is-danger(type='submit' value=lang('WISHLIST_DELETE')) - span= ' ' - if req.user._id === req.params.user - form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/restore/${item.id}`) - .field.inline - .control.inline - input.inline.button.is-success(type='submit' value=lang('WISHLIST_RESTORE')) + +wishlistBox(items.filter(item => item.archived), true) block print h1 #{req.params.user}'s Wishlist From 43fbd20563b13c216c3cbc11cd9f7d74636a3acf Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Fri, 24 Jan 2025 22:52:38 -0400 Subject: [PATCH 32/42] expandable menu of options to declutter table --- src/languages/en-us.ts | 1 + src/routes/wishlist/index.js | 2 +- src/static/css/main.css | 24 ++++- src/static/js/wishlist.js | 5 ++ src/views/wishlist.pug | 165 ++++++++++++++++++----------------- 5 files changed, 113 insertions(+), 84 deletions(-) diff --git a/src/languages/en-us.ts b/src/languages/en-us.ts index 8353094a..ef5e2592 100644 --- a/src/languages/en-us.ts +++ b/src/languages/en-us.ts @@ -221,6 +221,7 @@ export const strings = { WISHLIST_ARCHIVE_GUARD: 'You are not allowed to archive this item', WISHLIST_RESTORE_GUARD: 'You are not allowed to restore this item', WISHLIST_DELETE_CONFIRM: 'Are you sure you want to delete this item?', + WISHLIST_ITEM_OPTIONS: 'Options', WISHLISTS_COUNTS_SELF: name => `${name}: ???/???`, WISHLISTS_COUNTS: (name, pledged, total) => `${name}: ${pledged}/${total}`, WISHLISTS_TITLE: `${_CC.config.siteTitle} - Wishlists`, diff --git a/src/routes/wishlist/index.js b/src/routes/wishlist/index.js index f9f78fd0..6410a169 100644 --- a/src/routes/wishlist/index.js +++ b/src/routes/wishlist/index.js @@ -35,6 +35,7 @@ export default function (db) { if (row.doc.admin) return res.redirect(`/wishlist/${row.doc._id}`) } } + console.log(docs) res.render('wishlists', { title: _CC.lang('WISHLISTS_TITLE'), users: docs.rows, totals }) }) @@ -100,7 +101,6 @@ export default function (db) { } catch (error) { req.flash('error', `${error}`) } - res.redirect(`/wishlist/${req.params.user}`) }) diff --git a/src/static/css/main.css b/src/static/css/main.css index 9ca5fa78..203e9805 100644 --- a/src/static/css/main.css +++ b/src/static/css/main.css @@ -73,4 +73,26 @@ summary{ summary:hover { cursor: pointer; -} \ No newline at end of file +} + +.hamburger { + font-size: 24px; + background: none; + border: none; + cursor: pointer; +} + +.hidden-menu { + display: none; + margin-top: 10px; +} + +.hidden-menu button { + display: block; + margin: 5px 0; +} + +.hidden-menu.active { + display: block; +} + diff --git a/src/static/js/wishlist.js b/src/static/js/wishlist.js index 07ae70d0..deaaf423 100644 --- a/src/static/js/wishlist.js +++ b/src/static/js/wishlist.js @@ -149,3 +149,8 @@ document.addEventListener('DOMContentLoaded', () => { updateArrow('price', sortBy) }) }) + +function toggleMenu(button) { + const menu = button.nextElementSibling; + menu.classList.toggle('active'); +} diff --git a/src/views/wishlist.pug b/src/views/wishlist.pug index 13e561b7..42c01026 100644 --- a/src/views/wishlist.pug +++ b/src/views/wishlist.pug @@ -15,23 +15,17 @@ mixin wishlistTable(items, archived) th(style='width: 25%;')= lang('WISHLIST_NAME') th(style='width: 50%;')= lang('WISHLIST_NOTE') th(style='width: 10%;')= lang('WISHLIST_PRICE') - if req.params.user === req.user._id - th= lang('WISHLIST_EDIT_ITEM') th= lang('WISHLIST_ADDED_BY') if req.params.user === req.user._id && !archived - th= lang('WISHLIST_MOVE_TOP') th= lang('WISHLIST_MOVE_UP') th= lang('WISHLIST_MOVE_DOWN') - th= lang('WISHLIST_MOVE_BOTTOM') else if !archived th= lang('WISHLIST_PLEDGE') - if req.params.user === req.user._id - th= lang('WISHLIST_DELETE') if archived - th= lang('WISHLIST_RESTORE') + th= " " else - th= lang('WISHLIST_ARCHIVE') + th= " " tbody each item, index in items tr(id=item.id) @@ -67,34 +61,11 @@ mixin wishlistTable(items, archived) else td.ugc(data-label=lang('WISHLIST_NOTE'))= item.note td.ugc(data-label=lang('WISHLIST_PRICE'))= item.price - if req.params.user === req.user._id - td(data-label=lang('WISHLIST_EDIT_ITEM')) - form.inline(method='GET', action=`${_CC.config.base}wishlist/${req.params.user}/note/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;' - disabled=item.addedBy !== req.user._id - ) - span.icon - i.far.fa-edit if item.addedBy === '_CCUNKNOWN' td.ugc(data-label=lang('WISHLIST_ADDED_BY'))= lang('WISHLIST_ADDED_BY_GUEST') else td.ugc(data-label=lang('WISHLIST_ADDED_BY'))= item.addedBy if req.params.user === req.user._id && !archived - td(data-label=lang('WISHLIST_MOVE_ITEM_TOP')) - form.topForm.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/top/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=index === 0 - ) - span.icon - i.fas.fa-angle-double-up td(data-label=lang('WISHLIST_MOVE_ITEM_UP')) form.upForm.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/up/${item.id}`) .field.inline @@ -117,17 +88,6 @@ mixin wishlistTable(items, archived) ) span.icon i.fas.fa-arrow-down - td(data-label=lang('WISHLIST_MOVE_ITEM_BOTTOM')) - form.bottomForm.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/bottom/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=index === items.length - 1 - ) - span.icon - i.fas.fa-angle-double-down else if !archived td(data-label=lang('WISHLIST_PLEDGE')) @@ -147,45 +107,86 @@ mixin wishlistTable(items, archived) else span.ugc=lang('WISHLIST_PLEDGED', item.pledgedBy) if req.params.user === req.user._id - td(data-label='Delete Item') - form.inline( - method='POST', - action=`${_CC.config.base}wishlist/${req.params.user}/remove/${item.id}`, - onsubmit=`return confirm("${lang('WISHLIST_DELETE_CONFIRM')}")` - ) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=item.addedBy !== req.user._id - ) - span.icon - i.fas.fa-trash - if archived - td(data-label='Unarchive Item') - form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/unarchive/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=item.addedBy !== req.user._id - ) - span.icon - i.fas.fa-undo - else - td(data-label='Archive Item') - form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/archive/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=item.addedBy !== req.user._id - ) - span.icon - i.fas.fa-archive + td(data-label=lang('WISHLIST_ITEM_OPTIONS')) + button.hamburger(onclick="toggleMenu(this)") ☰ + //- Hidden menu of buttons + div.hidden-menu.hidden + if req.params.user === req.user._id + form.inline(method='GET', action=`${_CC.config.base}wishlist/${req.params.user}/note/${item.id}`) + .field.inline + .control.inline + button.button.is-text( + type='submit', + style='text-decoration: none;' + disabled=item.addedBy !== req.user._id + ) + span.icon(title=lang('WISHLIST_EDIT_ITEM')) + i.far.fa-edit + if !archived + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/up/${item.id}`) + .field.inline + .control.inline + button.button.is-text( + type='submit', + style='text-decoration: none;', + disabled=index === 0 + ) + span.icon(title=lang('WISHLIST_MOVE_ITEM_UP')) + i.fas.fa-arrow-up + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/down/${item.id}`) + .field.inline + .control.inline + button.button.is-text( + type='submit', + style='text-decoration: none;', + disabled=index === items.length - 1 + ) + span.icon(title=lang('WISHLIST_MOVE_ITEM_DOWN')) + i.fas.fa-arrow-down + form.topForm.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/top/${item.id}`) + .field.inline + .control.inline + button.button.is-text( + type='submit', + style='text-decoration: none;', + disabled=index === 0 + ) + span.icon(title=lang('WISHLIST_MOVE_ITEM_TOP')) + i.fas.fa-angle-double-up + form.bottomForm.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/bottom/${item.id}`) + .field.inline + .control.inline + button.button.is-text( + type='submit', + style='text-decoration: none;', + disabled=index === items.length - 1 + ) + span.icon(title=lang('WISHLIST_MOVE_ITEM_BOTTOM')) + i.fas.fa-angle-double-down + + if !archived + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/archive/${item.id}`) + .field.inline + .control.inline + button.button.is-text( + type='submit', + style='text-decoration: none;', + disabled=item.addedBy !== req.user._id + ) + span.icon(title=lang('WISHLIST_ARCHIVE')) + i.fas.fa-archive + else + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/restore/${item.id}`) + .field.inline + .control.inline + button.button.is-text( + type='submit', + style='text-decoration: none;', + disabled=item.addedBy !== req.user._id + ) + span.icon(title=lang('WISHLIST_RESTORE')) + i.fas.fa-undo + mixin wishlistBox(items, archived) each item, index in items @@ -352,7 +353,7 @@ block content details summary= lang('WISHLIST_ARCHIVED_ITEMS') if global._CC.config.wishlist.table - +wishlistTable(items.filter(item => !item.archived), true) + +wishlistTable(items.filter(item => item.archived), true) else +wishlistBox(items.filter(item => item.archived), true) From 85112b6e51c262cc248849b130839219968dd18c Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Fri, 24 Jan 2025 23:01:08 -0400 Subject: [PATCH 33/42] localized sensible heading --- src/views/wishlist.pug | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/views/wishlist.pug b/src/views/wishlist.pug index 42c01026..8497b9bc 100644 --- a/src/views/wishlist.pug +++ b/src/views/wishlist.pug @@ -22,10 +22,7 @@ mixin wishlistTable(items, archived) else if !archived th= lang('WISHLIST_PLEDGE') - if archived - th= " " - else - th= " " + th= lang('WISHLIST_ITEM_OPTIONS') tbody each item, index in items tr(id=item.id) From 9405ff2f15adc26b1355f6d83ef9ec4ee32a67f9 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Tue, 28 Jan 2025 21:45:36 -0400 Subject: [PATCH 34/42] fix linting issues --- src/static/css/main.css | 29 ++++++++++++++--------------- src/static/js/wishlist.js | 6 +++--- src/structures/Wishlist.js | 5 ++--- src/views/wishlist.pug | 2 +- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/static/css/main.css b/src/static/css/main.css index 5446d480..becce2f6 100644 --- a/src/static/css/main.css +++ b/src/static/css/main.css @@ -110,36 +110,35 @@ img.logo-image { } .print-gift:last-child { - margin-bottom: 0; + margin-bottom: 0; } -summary{ - font-weight: bold; - font-size: 18px; - } +summary { + font-weight: bold; + font-size: 18px; +} summary:hover { cursor: pointer; } .hamburger { - font-size: 24px; - background: none; - border: none; - cursor: pointer; + font-size: 24px; + background: none; + border: none; + cursor: pointer; } .hidden-menu { - display: none; - margin-top: 10px; + display: none; + margin-top: 10px; } .hidden-menu button { - display: block; - margin: 5px 0; + display: block; + margin: 5px 0; } .hidden-menu.active { - display: block; + display: block; } - diff --git a/src/static/js/wishlist.js b/src/static/js/wishlist.js index aa4e3a0f..79806e3f 100644 --- a/src/static/js/wishlist.js +++ b/src/static/js/wishlist.js @@ -164,7 +164,7 @@ document.addEventListener('DOMContentLoaded', () => { }) }) -function toggleMenu(button) { - const menu = button.nextElementSibling; - menu.classList.toggle('active'); +function _toggleMenu(button) { + const menu = button.nextElementSibling + menu.classList.toggle('active') } diff --git a/src/structures/Wishlist.js b/src/structures/Wishlist.js index baf6f892..886dd115 100644 --- a/src/structures/Wishlist.js +++ b/src/structures/Wishlist.js @@ -188,18 +188,17 @@ export class Wishlist { await this.save() } - async archive (id) { + async archive(id) { const item = await this.get(id) item.archived = true await this.save() } - async restore (id) { + async restore(id) { const item = await this.get(id) item.archived = false await this.save() } - } function parseURL(string) { diff --git a/src/views/wishlist.pug b/src/views/wishlist.pug index 8497b9bc..59ee45a0 100644 --- a/src/views/wishlist.pug +++ b/src/views/wishlist.pug @@ -105,7 +105,7 @@ mixin wishlistTable(items, archived) span.ugc=lang('WISHLIST_PLEDGED', item.pledgedBy) if req.params.user === req.user._id td(data-label=lang('WISHLIST_ITEM_OPTIONS')) - button.hamburger(onclick="toggleMenu(this)") ☰ + button.hamburger(onclick="_toggleMenu(this)") ☰ //- Hidden menu of buttons div.hidden-menu.hidden if req.params.user === req.user._id From 1b2f5067efb38382985fdfcd480f4674b8114d90 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Tue, 28 Jan 2025 21:45:47 -0400 Subject: [PATCH 35/42] add linting convenience script --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index afab656c..c5f61c4b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "dev": "nodemon --watch src -e ts,js --exec 'npm run build && node built/index.js'", "postinstall": "node postinstall.cjs", "build": "tsc", - "release": "docker buildx build . --platform linux/amd64,linux/arm64 -t wingysam/christmas-community -t wingysam/christmas-community:$(./node_modules/node-jq/bin/jq -r .version < package.json) --push" + "release": "docker buildx build . --platform linux/amd64,linux/arm64 -t wingysam/christmas-community -t wingysam/christmas-community:$(./node_modules/node-jq/bin/jq -r .version < package.json) --push", + "lint": "eslint src && prettier . --write" }, "author": "Wingysam ", "license": "AGPL-3.0", From afe5a84c693ec75317503d448751643e6e1c88c1 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Tue, 28 Jan 2025 23:20:20 -0400 Subject: [PATCH 36/42] discord like table options (wip) --- src/static/css/main.css | 22 +++++ src/views/wishlist.pug | 193 ++++++++++++++++------------------------ 2 files changed, 98 insertions(+), 117 deletions(-) diff --git a/src/static/css/main.css b/src/static/css/main.css index becce2f6..2ac4559f 100644 --- a/src/static/css/main.css +++ b/src/static/css/main.css @@ -142,3 +142,25 @@ summary:hover { .hidden-menu.active { display: block; } + +button.button.dropdown-item.is-text { + display: grid; + grid-template-columns: auto 1fr auto; + align-items: center; + width: 100%; + text-align: left; + padding-right: 10px; /* extra Spacing to push icons to the right a bunch */ + text-decoration: none; +} + +/* Ensure the text stays left-aligned */ +button.button.dropdown-item.is-text span { + grid-column: 1; + text-align: left; +} + +/* Ensure the icon is pulled to the right */ +button.button.dropdown-item.is-text i { + grid-column: 3; + margin-left: auto; +} diff --git a/src/views/wishlist.pug b/src/views/wishlist.pug index 59ee45a0..0873ea3d 100644 --- a/src/views/wishlist.pug +++ b/src/views/wishlist.pug @@ -16,12 +16,8 @@ mixin wishlistTable(items, archived) th(style='width: 50%;')= lang('WISHLIST_NOTE') th(style='width: 10%;')= lang('WISHLIST_PRICE') th= lang('WISHLIST_ADDED_BY') - if req.params.user === req.user._id && !archived - th= lang('WISHLIST_MOVE_UP') - th= lang('WISHLIST_MOVE_DOWN') - else - if !archived - th= lang('WISHLIST_PLEDGE') + if !req.params.user === req.user._id && !archived + th= lang('WISHLIST_PLEDGE') th= lang('WISHLIST_ITEM_OPTIONS') tbody each item, index in items @@ -62,127 +58,90 @@ mixin wishlistTable(items, archived) td.ugc(data-label=lang('WISHLIST_ADDED_BY'))= lang('WISHLIST_ADDED_BY_GUEST') else td.ugc(data-label=lang('WISHLIST_ADDED_BY'))= item.addedBy - if req.params.user === req.user._id && !archived - td(data-label=lang('WISHLIST_MOVE_ITEM_UP')) - form.upForm.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/up/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=index === 0 - ) - span.icon - i.fas.fa-arrow-up - td(data-label=lang('WISHLIST_MOVE_ITEM_DOWN')) - form.downForm.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/down/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=index === items.length - 1 - ) - span.icon - i.fas.fa-arrow-down - else - if !archived - td(data-label=lang('WISHLIST_PLEDGE')) - if req.params.user !== req.user._id && !item.pledgedBy - form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/pledge/${item.id}`) - .field.inline - .control.inline - input.inline.button.is-primary(type='submit' value=lang('WISHLIST_PLEDGE_ITEM')) - if item.pledgedBy === req.user._id - form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/unpledge/${item.id}`) - .field.inline - .control.inline - input.inline.button(type='submit' value=lang('WISHLIST_UNPLEDGE')) - if item.pledgedBy && item.pledgedBy !== req.user._id - if item.pledgedBy === '_CCUNKNOWN' - span.ugc=lang('WISHLIST_PLEDGED_GUEST') - else - span.ugc=lang('WISHLIST_PLEDGED', item.pledgedBy) + if !req.params.user === req.user._id && !archived + td(data-label=lang('WISHLIST_PLEDGE')) + if req.params.user !== req.user._id && !item.pledgedBy + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/pledge/${item.id}`) + .field.inline + .control.inline + input.inline.button.is-primary(type='submit' value=lang('WISHLIST_PLEDGE_ITEM')) + if item.pledgedBy === req.user._id + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/unpledge/${item.id}`) + .field.inline + .control.inline + input.inline.button(type='submit' value=lang('WISHLIST_UNPLEDGE')) + if item.pledgedBy && item.pledgedBy !== req.user._id + if item.pledgedBy === '_CCUNKNOWN' + span.ugc=lang('WISHLIST_PLEDGED_GUEST') + else + span.ugc=lang('WISHLIST_PLEDGED', item.pledgedBy) if req.params.user === req.user._id td(data-label=lang('WISHLIST_ITEM_OPTIONS')) - button.hamburger(onclick="_toggleMenu(this)") ☰ - //- Hidden menu of buttons - div.hidden-menu.hidden - if req.params.user === req.user._id - form.inline(method='GET', action=`${_CC.config.base}wishlist/${req.params.user}/note/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;' - disabled=item.addedBy !== req.user._id - ) - span.icon(title=lang('WISHLIST_EDIT_ITEM')) - i.far.fa-edit + div.dropdown.is-right.is-hoverable + div.dropdown-trigger + button.button.is-primary( + aria-haspopup='true', + aria-controls=`dropdown-menu${index}` + ) + span.icon + i.fas.fa-cog + div.dropdown-menu(id=`dropdown-menu${index}`, role='menu') + div.dropdown-content + form.inline(method='GET', action=`${_CC.config.base}wishlist/${req.params.user}/note/${item.id}`) + button.button.dropdown-item.is-text( + type='submit' + ) + span= lang('WISHLIST_EDIT_ITEM') + i.far.fa-edit + if !archived form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/up/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=index === 0 - ) - span.icon(title=lang('WISHLIST_MOVE_ITEM_UP')) - i.fas.fa-arrow-up + button.button.dropdown-item.is-text( + type='submit', + disabled=index === 0 + ) + span= lang('WISHLIST_MOVE_ITEM_UP') + i.fas.fa-arrow-up + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/down/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=index === items.length - 1 - ) - span.icon(title=lang('WISHLIST_MOVE_ITEM_DOWN')) - i.fas.fa-arrow-down - form.topForm.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/top/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=index === 0 - ) - span.icon(title=lang('WISHLIST_MOVE_ITEM_TOP')) - i.fas.fa-angle-double-up - form.bottomForm.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/bottom/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=index === items.length - 1 - ) - span.icon(title=lang('WISHLIST_MOVE_ITEM_BOTTOM')) - i.fas.fa-angle-double-down + button.button.dropdown-item.is-text( + type='submit', + disabled=index === items.length - 1 + ) + span= lang('WISHLIST_MOVE_ITEM_DOWN') + i.fas.fa-arrow-down + + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/top/${item.id}`) + button.button.dropdown-item.is-text( + type='submit', + disabled=index === 0 + ) + span= lang('WISHLIST_MOVE_ITEM_TOP') + i.fas.fa-angle-double-up + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/bottom/${item.id}`) + button.button.dropdown-item.is-text( + type='submit', + disabled=index === items.length - 1 + ) + span= lang('WISHLIST_MOVE_ITEM_BOTTOM') + i.fas.fa-angle-double-down + + hr.dropdown-divider if !archived form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/archive/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=item.addedBy !== req.user._id - ) - span.icon(title=lang('WISHLIST_ARCHIVE')) - i.fas.fa-archive + button.button.dropdown-item.is-text.has-text-danger( + type='submit' + ) + span= lang('WISHLIST_ARCHIVE') + i.fas.fa-archive else form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/restore/${item.id}`) - .field.inline - .control.inline - button.button.is-text( - type='submit', - style='text-decoration: none;', - disabled=item.addedBy !== req.user._id - ) - span.icon(title=lang('WISHLIST_RESTORE')) - i.fas.fa-undo + button.button.dropdown-item.is-text.has-text-danger( + type='submit' + ) + span= lang('WISHLIST_RESTORE') + i.fas.fa-restore mixin wishlistBox(items, archived) From ad5b3d849f426392b5cebb7d88c91e33cda44147 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Tue, 28 Jan 2025 23:39:42 -0400 Subject: [PATCH 37/42] Possible solution for #154? --- src/views/wishlist.pug | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/views/wishlist.pug b/src/views/wishlist.pug index 0873ea3d..7c8c2591 100644 --- a/src/views/wishlist.pug +++ b/src/views/wishlist.pug @@ -11,14 +11,15 @@ mixin wishlistTable(items, archived) table.table.has-mobile-cards thead th # - th(style='width: 15%;')= lang('WISHLIST_IMAGE') - th(style='width: 25%;')= lang('WISHLIST_NAME') - th(style='width: 50%;')= lang('WISHLIST_NOTE') + th(style='width: 10%;')= lang('WISHLIST_IMAGE') + th(style='width: 20%;')= lang('WISHLIST_NAME') + th(style='width: 30%;')= lang('WISHLIST_NOTE') th(style='width: 10%;')= lang('WISHLIST_PRICE') th= lang('WISHLIST_ADDED_BY') - if !req.params.user === req.user._id && !archived - th= lang('WISHLIST_PLEDGE') - th= lang('WISHLIST_ITEM_OPTIONS') + if !(req.params.user === req.user._id) + th(style='width: 10%;')= lang('WISHLIST_PLEDGE') + else + th(style='width: 10%;')= lang('WISHLIST_ITEM_OPTIONS') tbody each item, index in items tr(id=item.id) @@ -58,9 +59,9 @@ mixin wishlistTable(items, archived) td.ugc(data-label=lang('WISHLIST_ADDED_BY'))= lang('WISHLIST_ADDED_BY_GUEST') else td.ugc(data-label=lang('WISHLIST_ADDED_BY'))= item.addedBy - if !req.params.user === req.user._id && !archived + if !(req.params.user === req.user._id) td(data-label=lang('WISHLIST_PLEDGE')) - if req.params.user !== req.user._id && !item.pledgedBy + if req.params.user !== req.user._id && !item.pledgedBy && !item.archived form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/pledge/${item.id}`) .field.inline .control.inline From 0e1132946a648fdef79a610f59aed7474f1bae42 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Tue, 28 Jan 2025 23:39:54 -0400 Subject: [PATCH 38/42] Resolve auto archiving issue --- src/config/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/index.ts b/src/config/index.ts index 7df2f040..6bd092e4 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -39,7 +39,7 @@ const config = { oidcEnabled: false, pfpUploadMaxSize: process.env.UPLOAD_PFP_MAX_SIZE || 5, rootUrl: appendSlash(process.env.ROOT_URL ?? process.env.ROOT_PATH ?? '/'), - autoArchivePledges: process.env.AUTO_ARCHIVE_PLEDGES || false, + autoArchivePledges: process.env.AUTO_ARCHIVE_PLEDGES === 'true', base: '', // automatically set below } From 798512e85bfb4037234249bd42eb7dacd3371480 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Tue, 4 Mar 2025 01:07:37 +0000 Subject: [PATCH 39/42] Fix code style issues with Prettier --- src/languages/fr-fr.ts | 45 ++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/languages/fr-fr.ts b/src/languages/fr-fr.ts index 30bb1049..829d28a0 100644 --- a/src/languages/fr-fr.ts +++ b/src/languages/fr-fr.ts @@ -18,24 +18,30 @@ export const strings = { ADMIN_SETTINGS_USERS_ADD_HEADER: 'Ajouter un utilisateur', ADMIN_SETTINGS_USERS_ADD_PLACEHOLDER: 'jean', ADMIN_SETTINGS_USERS_ADD_USERNAME: "Nom d'utilisateur", - ADMIN_SETTINGS_USERS_ADD_ERROR_USERNAME_EMPTY: "Le nom d'utilisateur ne peut pas être vide.", + ADMIN_SETTINGS_USERS_ADD_ERROR_USERNAME_EMPTY: + "Le nom d'utilisateur ne peut pas être vide.", ADMIN_SETTINGS_USERS_EDIT_DELETE_FAIL_ADMIN: "Échec de la suppression : l'utilisateur est un administrateur.", ADMIN_SETTINGS_USERS_EDIT_DELETE_SUCCESS: (name) => `Utilisateur supprimé avec succès ${name}`, - ADMIN_SETTINGS_USERS_EDIT_DEMOTE_NOT_ADMIN: "l'utilisateur n'est pas un administrateur", - ADMIN_SETTINGS_USERS_EDIT_DEMOTE_SELF: 'Vous ne pouvez pas vous supprimer vous même.', + ADMIN_SETTINGS_USERS_EDIT_DEMOTE_NOT_ADMIN: + "l'utilisateur n'est pas un administrateur", + ADMIN_SETTINGS_USERS_EDIT_DEMOTE_SELF: + 'Vous ne pouvez pas vous supprimer vous même.', ADMIN_SETTINGS_USERS_EDIT_DEMOTE_SUCCESS: (name) => `${name} n'est plus un administrateur.`, ADMIN_SETTINGS_USERS_EDIT_IMPERSONATE_SUCCESS: (name) => `Vous êtes maintenant ${name}.`, - ADMIN_SETTINGS_USERS_EDIT_NO_USERNAME_PROVIDED: "Aucun nom d'utilisateur fourni", - ADMIN_SETTINGS_USERS_EDIT_PROMOTE_ALREADY_ADMIN: 'cet utilisateur est déjà administrateur', + ADMIN_SETTINGS_USERS_EDIT_NO_USERNAME_PROVIDED: + "Aucun nom d'utilisateur fourni", + ADMIN_SETTINGS_USERS_EDIT_PROMOTE_ALREADY_ADMIN: + 'cet utilisateur est déjà administrateur', ADMIN_SETTINGS_USERS_EDIT_PROMOTE_DEMOTE_NOT_FOUND: 'Utilisateur non trouvé.', ADMIN_SETTINGS_USERS_EDIT_PROMOTE_SUCCESS: (name) => `${name} est maintenant un administrateur.`, ADMIN_SETTINGS_USERS_EDIT_RENAMED_USER: 'Utilisateur renommé!', - ADMIN_SETTINGS_USERS_EDIT_SAME_NAME: "L'ancien nom d'utilisateur est le même que le nouveau nom d'utilisateur.", + ADMIN_SETTINGS_USERS_EDIT_SAME_NAME: + "L'ancien nom d'utilisateur est le même que le nouveau nom d'utilisateur.", ADMIN_SETTINGS_USERS_EDIT: 'Modifier', ADMIN_SETTINGS_USERS_HEADER: 'Utilisateurs', ADMIN_SETTINGS_VERSION_INFO: 'Informations sur la version', @@ -45,7 +51,8 @@ export const strings = { ADMIN_SETTINGS_TABLE_EDIT: 'Modifier', ADMIN_USER_EDIT_ACCOUNT_UNCONFIRMED: "Ce compte n'a pas été confirmé.", ADMIN_USER_EDIT_ADMIN_ISADMIN: (name) => `${name} est un administrateur.`, - ADMIN_USER_EDIT_ADMIN_NOTADMIN: (name) => `${name} n'est pas un administrateur.`, + ADMIN_USER_EDIT_ADMIN_NOTADMIN: (name) => + `${name} n'est pas un administrateur.`, ADMIN_USER_EDIT_ADMIN: 'Admin', ADMIN_USER_EDIT_CHANGE_NAME: 'Changer de nom', ADMIN_USER_EDIT_CHANGE_USERNAME: "Changer de nom d'utilisateur", @@ -55,9 +62,11 @@ export const strings = { ADMIN_USER_EDIT_DELETE_USER: (name) => `Supprimer l'utilisateur ${name}`, ADMIN_USER_EDIT_DEMOTE_SELF: 'Vous ne pouvez pas vous rétrograder', ADMIN_USER_EDIT_DEMOTE: (name) => `Rétrograder ${name}`, - ADMIN_USER_EDIT_EDITING_USER: (name) => `Modification de l'utilisateur "${name}"`, + ADMIN_USER_EDIT_EDITING_USER: (name) => + `Modification de l'utilisateur "${name}"`, ADMIN_USER_EDIT_GENERATE_NEW_LINK: 'Générer un nouveau lien', - ADMIN_USER_EDIT_IMPERSONATE_BUTTON: (name) => `Se connecter en tant que ${name}`, + ADMIN_USER_EDIT_IMPERSONATE_BUTTON: (name) => + `Se connecter en tant que ${name}`, ADMIN_USER_EDIT_IMPERSONATE_HEADER: 'se faire passer pour', ADMIN_USER_EDIT_LINK_EXPIRY_FUTURE: (fromNow) => `Le lien suivant expire le ${fromNow}`, // fromNow is localized by moment @@ -71,9 +80,12 @@ export const strings = { ADMIN_USER_EDIT_RESET_PASSWORD_HASLINK: 'Il y a un lien de réinitialisation de mot de passe pour cet utilisateur.', ADMIN_USER_EDIT_RESET_PASSWORD_HEADER: 'Réinitialiser le mot de passe', - ADMIN_USER_EDIT_RESET_PASSWORD_LINK_CANCEL: 'Supprimer le lien de réinitialisation du mot de passe', - ADMIN_USER_EDIT_RESET_PASSWORD_LINK_CREATE: 'Créer un lien de réinitialisation de mot de passe', - ADMIN_USER_EDIT_RESET_PASSWORD_LINK_REFRESH: 'Actualiser le lien de réinitialisation du mot de passe', + ADMIN_USER_EDIT_RESET_PASSWORD_LINK_CANCEL: + 'Supprimer le lien de réinitialisation du mot de passe', + ADMIN_USER_EDIT_RESET_PASSWORD_LINK_CREATE: + 'Créer un lien de réinitialisation de mot de passe', + ADMIN_USER_EDIT_RESET_PASSWORD_LINK_REFRESH: + 'Actualiser le lien de réinitialisation du mot de passe', ADMIN_USER_EDIT_USERNAME: "Nom d'utilisateur", BACK_BUTTON: 'Retour', CONFIRM_ACCOUNT_EXPIRED: @@ -136,7 +148,8 @@ export const strings = { PROFILE_PASSWORD_REQUIRED_NEW: 'Un nouveau mot de passe est requis', PROFILE_PASSWORD_REQUIRED_OLD: "L'ancien mot de passe est requis", PROFILE_PASSWORD_SUCCESS: 'Les modifications on été enregistré avec succès!', - PROFILE_PASSWORD_TITLE: (name) => `Paramètres de profil - Mot de passe - ${name}`, + PROFILE_PASSWORD_TITLE: (name) => + `Paramètres de profil - Mot de passe - ${name}`, PROFILE_PHONE_MODEL: 'Modèle de téléphone', PROFILE_RING_SIZE: 'Taille de bague', PROFILE_SAVE_PFP_DISABLED: 'Les photos de profil sont désactivées.', @@ -197,10 +210,12 @@ export const strings = { WISHLIST_ADDED_BY_GUEST: 'Utilisateur Invité', WISHLIST_ADDED_BY: 'Ajouté par', WISHLIST_ADDED_ITEM_TO_OWN_WISHLIST: "Article ajouté à la listes d'envies.", - WISHLIST_CONFLICT: 'Les éléments ont été ajoutés trop rapidement. Veuillez réessayer.', + WISHLIST_CONFLICT: + 'Les éléments ont été ajoutés trop rapidement. Veuillez réessayer.', WISHLIST_DELETE: 'Supprimer', WISHLIST_EDIT_ITEM: "Modifier l'article", - WISHLIST_FETCH_FAIL: "Échec de la récupération de la liste de souhaits : l'utilisateur existe-t-il ?", + WISHLIST_FETCH_FAIL: + "Échec de la récupération de la liste de souhaits : l'utilisateur existe-t-il ?", WISHLIST_IMAGE: 'Image', WISHLIST_ITEM_MISSING: "Impossible de trouver l'article", WISHLIST_MOVE_DOWN: 'Descendre', From b1026593f40e000d75e239c7f8043121ef01be4d Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Mon, 3 Mar 2025 22:06:32 -0400 Subject: [PATCH 40/42] chore(release): v1.1.0 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ce8b36f..4cb936a8 100644 --- a/README.md +++ b/README.md @@ -413,4 +413,4 @@ Hi, I'm Wingy. I made this app. My website is [samwing.dev](https://samwing.dev) - + From 6c47e66898bc95705b0f3b19bd019d8ae6e1a7e1 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Fri, 4 Jul 2025 11:17:23 -0300 Subject: [PATCH 41/42] add option to auto add sso users if they dont exist --- README.md | 2 ++ src/config/index.ts | 1 + src/index.ts | 26 ++++++++++++++++++++++++++ src/languages/en-us.ts | 2 ++ src/routes/index.js | 26 +++++--------------------- src/utils/ensurePfp.js | 24 ++++++++++++++++++++++++ 6 files changed, 60 insertions(+), 21 deletions(-) create mode 100644 src/utils/ensurePfp.js diff --git a/README.md b/README.md index 50f0e001..6427b3bd 100644 --- a/README.md +++ b/README.md @@ -147,12 +147,14 @@ MARKDOWN=false ## OIDC Provider Details # You can configure single sign-on to your Christmas Community instance using any OIDC provider. # Once you've created a client ID and secret in your authentication provider use the below environment variables to enable single sign on. +# The values specified below are the defaults # OIDC_CLIENT_ID= # OIDC_CLIENT_SECRET= # OIDC_AUTHORIZATION_URL=https://accounts.google.com/o/oauth2/auth # OIDC_TOKEN_URL=https://oauth2.googleapis.com/token # OIDC_ISSUER=https://accounts.google.com # OIDC_PROVIDER_NAME=Google +# OIDC_AUTO_CREATE_USER=false # Profile picture upload max size in MB diff --git a/src/config/index.ts b/src/config/index.ts index 5d487591..89f7f5a6 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -40,6 +40,7 @@ const config = { pfpUploadMaxSize: process.env.UPLOAD_PFP_MAX_SIZE || 5, rootUrl: appendSlash(process.env.ROOT_URL ?? process.env.ROOT_PATH ?? '/'), base: '', // automatically set below + oidcAutoCreateUser: process.env.OIDC_AUTO_CREATE_USER || false, } if (config.guestPassword) config.wishlist.public = false diff --git a/src/index.ts b/src/index.ts index 90749a38..6551450f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,7 @@ import config from './config/index.js' import PouchDB from './PouchDB.js' import logger from './logger.js' import { doDbMigrations } from './dbMigration.js' +import ensurePfp from './utils/ensurePfp.js' // from https://github.com/ai/nanoid/blob/main/url-alphabet/index.js const nanoidWithoutUnderscores = customAlphabet( @@ -98,6 +99,31 @@ if (config.oidcEnabled) { }) if (docs.docs.length === 1) { return done(null, docs.docs[0]) + } else if (config.oidcAutoCreateUser) { + // Auto-create user if enabled + const username = + profile.username || + profile.displayName || + profile.emails?.[0]?.value || + oidcId + const newUser = { + _id: username, + username, + displayName: profile.displayName || username, + emails: profile.emails || [], + oauthConnections: { oidc: oidcId }, + createdAt: new Date().toISOString(), + wishlist: [], + // Add any other required fields here + } + try { + await db.put(newUser) + await ensurePfp(db, config, username) + const createdUser = await db.get(username) + return done(null, createdUser) + } catch (err) { + return done(err) + } } else { // Handle other errors, including missing user return done(null, false, { diff --git a/src/languages/en-us.ts b/src/languages/en-us.ts index 30aed8e6..6a164a47 100644 --- a/src/languages/en-us.ts +++ b/src/languages/en-us.ts @@ -102,6 +102,8 @@ export const strings = { LOGIN_SSO_UNLINK_FAILURE: 'Failed to unlink account', LOGIN_SSO_LINK_FAILURE_ACCOUNT_EXISTS: 'The external account is already linked to another account on this site!', + LOGIN_SSO_CREATED_USER: (username) => + `Congratulations, ${username}! You've been added and automatically logged in!`, LOGOUT_BUTTON: 'Log Out', NAVBAR_ADMIN: 'Admin Settings', NAVBAR_LOGIN: 'Log In', diff --git a/src/routes/index.js b/src/routes/index.js index efe7e9d8..f4608429 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -2,6 +2,7 @@ import publicRoute from '../middlewares/publicRoute.js' import express from 'express' import path from 'path' import fs from 'fs/promises' +import ensurePfp from '../utils/ensurePfp.js' import Api from './api/index.js' import Setup from './setup/index.js' @@ -17,27 +18,10 @@ import ManifestJson from './manifest.json/index.js' import OIDC from './oidc/index.js' export default ({ db, config }) => { - async function ensurePfp(username) { - if (!config.pfp) return - const user = await db.get(username) - if (user.pfp) return - - const { rows } = await db.allDocs({ include_docs: true }) - - const unfilteredPool = await fs.readdir('src/static/img/default-pfps') - const filteredPool = unfilteredPool.filter( - (file) => !rows.find((row) => row.doc.pfp?.default === file), - ) - const pool = filteredPool.length ? filteredPool : unfilteredPool - - user.pfp = { default: _CC._.sample(pool) } - await db.put(user) - } - ;(async () => { const { rows } = await db.allDocs({ include_docs: true }) for (const row of rows) { - await ensurePfp(row.id) + await ensurePfp(db, config, row.id) } })() @@ -63,7 +47,7 @@ export default ({ db, config }) => { router.use('/api', Api()) - router.use('/setup', Setup({ db, ensurePfp })) + router.use('/setup', Setup({ db, ensurePfp: (username) => ensurePfp(db, config, username) })) router.use('/login', Login({ config })) router.use('/logout', Logout()) @@ -73,9 +57,9 @@ export default ({ db, config }) => { router.use('/wishlist', Wishlist(db)) router.use('/supported-sites', SupportedSites()) - router.use('/profile', Profile({ db, config, ensurePfp })) + router.use('/profile', Profile({ db, config, ensurePfp: (username) => ensurePfp(db, config, username) })) - router.use('/admin-settings', AdminSettings({ db, ensurePfp })) + router.use('/admin-settings', AdminSettings({ db, ensurePfp: (username) => ensurePfp(db, config, username) })) router.use('/manifest.json', ManifestJson({ config })) diff --git a/src/utils/ensurePfp.js b/src/utils/ensurePfp.js new file mode 100644 index 00000000..fa3bc92c --- /dev/null +++ b/src/utils/ensurePfp.js @@ -0,0 +1,24 @@ +import fs from 'fs/promises' + +/** + * Ensures a user has a profile picture. If not, assigns a random default. + * @param {object} db - The user database (PouchDB instance) + * @param {object} config - The app config + * @param {string} username - The username to ensure pfp for + * @returns {Promise} + */ +export default async function ensurePfp(db, config, username) { + if (!config.pfp) return + const user = await db.get(username) + if (user.pfp) return + + const { rows } = await db.allDocs({ include_docs: true }) + const unfilteredPool = await fs.readdir('src/static/img/default-pfps') + const filteredPool = unfilteredPool.filter( + (file) => !rows.find((row) => row.doc.pfp?.default === file), + ) + const pool = filteredPool.length ? filteredPool : unfilteredPool + + user.pfp = { default: _CC._.sample(pool) } + await db.put(user) +} From 15e9f2b4ccee6a446c731b2c64c2605a54f453b7 Mon Sep 17 00:00:00 2001 From: Christopher Blake Date: Fri, 4 Jul 2025 11:54:15 -0300 Subject: [PATCH 42/42] admins can edit, archive and reorder any list --- README.md | 3 ++ src/config/index.ts | 1 + src/routes/wishlist/index.js | 15 ++++++---- src/views/wishlist.pug | 56 ++++++++++++++++++++---------------- 4 files changed, 45 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 0d807e71..4ec95efc 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,9 @@ PLEDGE_SUGGEST=true # Set to false to disable all users from being able to suggest items to another users's list SUGGESTIONS_ENABLED=true + +## Admin users can optionally edit, archive, remove, and reorder any item on any wishlist when set to true (default: false). +# ADMIN_CAN_EDIT_ALL_WISHLISTS=false ``` ## Default Profile Pictures diff --git a/src/config/index.ts b/src/config/index.ts index 90ceec4e..a35d5b19 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -44,6 +44,7 @@ const config = { pledgeSuggest: (process.env.PLEDGE_SUGGEST === 'true'), suggestionsEnabled: (process.env.SUGGESTIONS_ENABLED === 'true'), oidcAutoCreateUser: process.env.OIDC_AUTO_CREATE_USER || false, + adminCanEditAllWishlists: process.env.ADMIN_CAN_EDIT_ALL_WISHLISTS|| false, } if (config.guestPassword) config.wishlist.public = false diff --git a/src/routes/wishlist/index.js b/src/routes/wishlist/index.js index 325e3ea5..9fb36c83 100644 --- a/src/routes/wishlist/index.js +++ b/src/routes/wishlist/index.js @@ -158,7 +158,8 @@ export default function (db) { const isOwnWishlist = req.user._id === wishlist.username const addedByUser = item.addedBy === req.user._id - if (!isOwnWishlist && !addedByUser) { + const isAdmin = req.user.admin && _CC.config.adminCanEditAllWishlists; + if (!isOwnWishlist && !addedByUser && !isAdmin) { throw new Error(_CC.lang('WISHLIST_REMOVE_GUARD')) } @@ -177,7 +178,8 @@ export default function (db) { verifyAuth(), async (req, res) => { try { - if (req.user._id !== req.params.user) { + const isAdmin = req.user.admin && _CC.config.adminCanEditAllWishlists; + if (req.user._id !== req.params.user && !isAdmin) { throw new Error(_CC.lang('WISHLIST_MOVE_GUARD')) } @@ -222,7 +224,8 @@ export default function (db) { const isOwnWishlist = req.user._id === req.params.user const addedByUser = req.user._id === item.addedBy - if (!isOwnWishlist && !addedByUser) { + const isAdmin = req.user.admin && _CC.config.adminCanEditAllWishlists; + if (!isOwnWishlist && !addedByUser && !isAdmin) { throw new Error(_CC.lang('NOTE_GUARD')) } @@ -264,7 +267,8 @@ export default function (db) { const isOwnWishlist = req.user._id === wishlist.username const addedByUser = item.addedBy === req.user._id - if (!isOwnWishlist && !addedByUser) { + const isAdmin = req.user.admin && _CC.config.adminCanEditAllWishlists; + if (!isOwnWishlist && !addedByUser && !isAdmin) { throw new Error(_CC.lang('WISHLIST_ARCHIVE_GUARD')) } @@ -285,7 +289,8 @@ export default function (db) { const isOwnWishlist = req.user._id === wishlist.username const addedByUser = item.addedBy === req.user._id - if (!isOwnWishlist && !addedByUser) { + const isAdmin = req.user.admin && _CC.config.adminCanEditAllWishlists; + if (!isOwnWishlist && !addedByUser && !isAdmin) { throw new Error(_CC.lang('WISHLIST_RESTORE_GUARD')) } diff --git a/src/views/wishlist.pug b/src/views/wishlist.pug index 6dd1c555..cf917cf8 100644 --- a/src/views/wishlist.pug +++ b/src/views/wishlist.pug @@ -18,7 +18,7 @@ mixin wishlistTable(items, archived) th= lang('WISHLIST_ADDED_BY') if !(req.params.user === req.user._id) th(style='width: 10%;')= lang('WISHLIST_PLEDGE') - else + if req.params.user === req.user._id || (req.user.admin && _CC.config.adminCanEditAllWishlists) th(style='width: 10%;')= lang('WISHLIST_ITEM_OPTIONS') tbody each item, index in items @@ -76,7 +76,7 @@ mixin wishlistTable(items, archived) span.ugc=lang('WISHLIST_PLEDGED_GUEST') else span.ugc=lang('WISHLIST_PLEDGED', item.pledgedBy) - if req.params.user === req.user._id + if req.params.user === req.user._id || (req.user.admin && _CC.config.adminCanEditAllWishlists) td(data-label=lang('WISHLIST_ITEM_OPTIONS')) div.dropdown.is-right.is-hoverable div.dropdown-trigger @@ -147,7 +147,7 @@ mixin wishlistTable(items, archived) mixin wishlistBox(items, archived) each item, index in items - if req.user._id === item.addedBy || req.params.user !== req.user._id + if req.user._id === item.addedBy || req.params.user !== req.user._id || (req.user.admin && _CC.config.adminCanEditAllWishlists) .box if item.price .tag @@ -205,39 +205,36 @@ mixin wishlistBox(items, archived) .field.inline .control.inline input.inline.button(type='submit' value=lang('WISHLIST_UNPLEDGE')) - if req.user._id === req.params.user + if req.user._id === req.params.user || (req.user.admin && _CC.config.adminCanEditAllWishlists) form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/remove/${item.id}`, onsubmit=`return confirm("${lang('WISHLIST_DELETE_CONFIRM')}")`) .field.inline .control.inline input.inline.button.is-danger(type='submit' value=lang('WISHLIST_DELETE')) - if req.user._id === req.params.user || req.user._id === item.addedBy form.inline(method='GET', action=`${_CC.config.base}wishlist/${req.params.user}/note/${item.id}`) .field.inline .control.inline input.inline.button(type='submit', value=lang('WISHLIST_EDIT_ITEM')) if !archived - if index !== 0 && req.user._id === req.params.user + if index !== 0 form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/up/${item.id}`) .field.inline .control.inline input.inline.button(type='submit' value=lang('WISHLIST_MOVE_ITEM_UP')) - if index !== items.length - 1 && req.user._id === req.params.user + if index !== items.length - 1 form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/down/${item.id}`) .field.inline .control.inline input.inline.button(type='submit' value=lang('WISHLIST_MOVE_ITEM_DOWN')) - if index !== items.length - 1 && req.user._id === req.params.user form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/bottom/${item.id}`) .field.inline .control.inline input.inline.button(type='submit' value=lang('WISHLIST_MOVE_ITEM_BOTTOM')) - if req.user._id === req.params.user if !archived form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/archive/${item.id}`) .field.inline .control.inline input.inline.button.is-warning(type='submit' value=lang('WISHLIST_ARCHIVE')) - else + else form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/restore/${item.id}`) .field.inline .control.inline @@ -337,31 +334,40 @@ block content .field.inline .control.inline input.inline.button(type='submit' value=lang('WISHLIST_UNPLEDGE')) - if req.user._id === req.params.user - form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/remove/${item.id}`) + if req.user._id === req.params.user || (req.user.admin && _CC.config.adminCanEditAllWishlists) + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/remove/${item.id}`, onsubmit=`return confirm("${lang('WISHLIST_DELETE_CONFIRM')}")`) .field.inline .control.inline - input.inline.button.is-warning(type='submit' value=lang('WISHLIST_DELETE')) - if req.user._id === req.params.user || req.user._id === item.addedBy + input.inline.button.is-danger(type='submit' value=lang('WISHLIST_DELETE')) form.inline(method='GET', action=`${_CC.config.base}wishlist/${req.params.user}/note/${item.id}`) .field.inline .control.inline input.inline.button(type='submit', value=lang('WISHLIST_EDIT_ITEM')) - if index !== 0 && req.user._id === req.params.user - form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/up/${item.id}`) - .field.inline - .control.inline - input.inline.button(type='submit' value=lang('WISHLIST_MOVE_ITEM_UP')) - if index !== items.length - 1 && req.user._id === req.params.user - form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/down/${item.id}`) + if !archived + if index !== 0 + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/up/${item.id}`) + .field.inline + .control.inline + input.inline.button(type='submit' value=lang('WISHLIST_MOVE_ITEM_UP')) + if index !== items.length - 1 + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/down/${item.id}`) + .field.inline + .control.inline + input.inline.button(type='submit' value=lang('WISHLIST_MOVE_ITEM_DOWN')) + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/bottom/${item.id}`) + .field.inline + .control.inline + input.inline.button(type='submit' value=lang('WISHLIST_MOVE_ITEM_BOTTOM')) + if !archived + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/archive/${item.id}`) .field.inline .control.inline - input.inline.button(type='submit' value=lang('WISHLIST_MOVE_ITEM_DOWN')) - if index !== items.length - 1 && req.user._id === req.params.user - form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/move/bottom/${item.id}`) + input.inline.button.is-warning(type='submit' value=lang('WISHLIST_ARCHIVE')) + else + form.inline(method='POST', action=`${_CC.config.base}wishlist/${req.params.user}/restore/${item.id}`) .field.inline .control.inline - input.inline.button(type='submit' value=lang('WISHLIST_MOVE_ITEM_BOTTOM')) + input.inline.button.is-success(type='submit' value=lang('WISHLIST_RESTORE')) if ((!global._CC.config.pledgeSuggest && req.user.pledgeOnly) || !global._CC.config.suggestionsEnabled) .notification.is-info= lang('WISHLIST_SUGGESTIONS_DISABLED')