Skip to content

feat: archive and restore wishes #183

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
433f3ef
watch for pug file changes and rebuild
cj13579 Jan 23, 2025
63c4307
support archive and restore in table mode
cj13579 Jan 23, 2025
e73e8c0
add card support and delete confirmation
cj13579 Jan 23, 2025
870ef01
add support for pledge auto archiving
cj13579 Jan 23, 2025
5795bb1
add docs for auto archiving
cj13579 Jan 23, 2025
07d7541
only list active items
cj13579 Jan 23, 2025
7a7bd1b
remove unnecessary comma
cj13579 Jan 23, 2025
732d996
fix order or archive table columns
cj13579 Jan 23, 2025
debbacc
Merge branch 'master' into archive-wishes
cj13579 Jan 24, 2025
f944297
pugs don't need rebuilds, rappers do
cj13579 Jan 24, 2025
eae7599
add parentheses for readability
cj13579 Jan 25, 2025
b088c7b
move wishlist generation to mixins
cj13579 Jan 25, 2025
e39125e
Merge branch 'master' into archive-wishes
cj13579 Jan 25, 2025
01a7d3d
Merge branch 'master' into archive-wishes
cj13579 Jan 25, 2025
43fbd20
expandable menu of options to declutter table
cj13579 Jan 25, 2025
85112b6
localized sensible heading
cj13579 Jan 25, 2025
b8660fd
Merge remote-tracking branch 'origin' into archive-wishes
Wingysam Jan 25, 2025
7734271
Merge remote-tracking branch 'origin' into archive-wishes
Wingysam Jan 25, 2025
a937789
Merge remote-tracking branch 'origin' into archive-wishes
Wingysam Jan 25, 2025
9405ff2
fix linting issues
cj13579 Jan 29, 2025
1b2f506
add linting convenience script
cj13579 Jan 29, 2025
afe5a84
discord like table options (wip)
cj13579 Jan 29, 2025
ad5b3d8
Possible solution for #154?
cj13579 Jan 29, 2025
0e11329
Resolve auto archiving issue
cj13579 Jan 29, 2025
798512e
Fix code style issues with Prettier
lint-action Mar 4, 2025
4f601cc
Merge branch 'master' into archive-wishes
cj13579 Mar 6, 2025
7a8b5a7
add "super admin" capability
cj13579 Mar 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ LISTS_PUBLIC=false
TABLE=true
# Allow Markdown in item notes. Does not work with TABLE=false. Defaults to false.
MARKDOWN=false
# Auto archive pledges
AUTO_ARCHIVE_PLEDGES=false

## Custom HTML Snippets
# These are inserted into specific locations in the relevant page
Expand Down Expand Up @@ -154,7 +156,6 @@ MARKDOWN=false
# OIDC_ISSUER=https://accounts.google.com
# OIDC_PROVIDER_NAME=Google


# Profile picture upload max size in MB
UPLOAD_PFP_MAX_SIZE=5
```
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 <[email protected]>",
"license": "AGPL-3.0",
Expand Down
2 changes: 2 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ 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 === 'true',
base: '', // automatically set below
superAdmins: process.env.SUPER_ADMINS === 'true',
}

if (config.guestPassword) config.wishlist.public = false
Expand Down
9 changes: 9 additions & 0 deletions src/languages/en-us.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,15 @@ export const strings = {
WISHLIST_URL_LABEL: `Item URL or Name (<a href="${_CC.config.base}supported-sites">Supported Sites</a>)`,
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',
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`,
Expand Down
45 changes: 30 additions & 15 deletions src/languages/fr-fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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",
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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.',
Expand Down Expand Up @@ -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',
Expand Down
59 changes: 56 additions & 3 deletions src/routes/wishlist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -31,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,
Expand Down Expand Up @@ -58,8 +63,15 @@ export default function (db) {
async (req, res) => {
try {
const wishlist = await wishlistManager.get(req.params.user)
var items = null
await wishlist.fetch()
const items = await wishlist.itemsVisibleToUser(req.user._id)
if (req.user.admin && _CC.config.superAdmins) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be updated to pass this logic into the itemsVisibleToUser function. I'll do that.

items = await wishlist.items
}
else {
items = await wishlist.itemsVisibleToUser(req.user._id)
}


const compiledNotes = {}
if (_CC.config.wishlist.note.markdown) {
Expand Down Expand Up @@ -106,7 +118,6 @@ export default function (db) {
} catch (error) {
req.flash('error', `${error}`)
}

res.redirect(`/wishlist/${req.params.user}`)
})

Expand Down Expand Up @@ -253,5 +264,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
}
52 changes: 52 additions & 0 deletions src/static/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,55 @@ img.logo-image {
.print-gift:last-child {
margin-bottom: 0;
}

summary {
font-weight: bold;
font-size: 18px;
}

summary:hover {
cursor: pointer;
}

.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;
}

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;
}
5 changes: 5 additions & 0 deletions src/static/js/wishlist.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,8 @@ document.addEventListener('DOMContentLoaded', () => {
updateArrow('price', sortBy)
})
})

function _toggleMenu(button) {
const menu = button.nextElementSibling
menu.classList.toggle('active')
}
16 changes: 16 additions & 0 deletions src/structures/Wishlist.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,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()
}

Expand Down Expand Up @@ -183,6 +187,18 @@ 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) {
Expand Down
Loading
Loading