nodebb-theme-harmony 0.0.9 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,9 @@
1
1
  {
2
2
  "skins": "Skins",
3
3
  "collapse": "Collapse",
4
- "login-register-to-search": "Login or register to search."
4
+ "login-register-to-search": "Login or register to search.",
5
+ "settings.title": "Theme settings",
6
+ "settings.enableQuickReply": "Enable quick reply",
7
+ "settings.centerHeaderElements": "Center header elements",
8
+ "settings.stickyToolbar": "Sticky toolbar"
5
9
  }
@@ -2,6 +2,21 @@
2
2
 
3
3
  const Controllers = module.exports;
4
4
 
5
+ const accountHelpers = require.main.require('./src/controllers/accounts/helpers');
6
+ const helpers = require.main.require('./src/controllers/helpers');
7
+
5
8
  Controllers.renderAdminPage = (req, res) => {
6
9
  res.render('admin/plugins/harmony', {});
7
10
  };
11
+
12
+ Controllers.renderThemeSettings = async (req, res, next) => {
13
+ const userData = await accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, req.query);
14
+ if (!userData) {
15
+ return next();
16
+ }
17
+
18
+ userData.title = '[[harmony:settings.title]]';
19
+ userData.breadcrumbs = helpers.buildBreadcrumbs([{ text: userData.username, url: `/user/${userData.userslug}` }, { text: '[[harmony:settings.title]]' }]);
20
+
21
+ res.render('account/theme', userData);
22
+ };
package/library.js CHANGED
@@ -8,11 +8,24 @@ const controllers = require('./lib/controllers');
8
8
 
9
9
  const library = module.exports;
10
10
 
11
+ const defaults = {
12
+ enableQuickReply: 'on',
13
+ centerHeaderElements: 'off',
14
+ stickyToolbar: 'on',
15
+ };
16
+
11
17
  library.init = async function (params) {
12
- const { router } = params;
18
+ const { router, middleware } = params;
13
19
  const routeHelpers = require.main.require('./src/routes/helpers');
14
20
 
15
21
  routeHelpers.setupAdminPageRoute(router, '/admin/plugins/harmony', [], controllers.renderAdminPage);
22
+
23
+ routeHelpers.setupPageRoute(router, '/user/:userslug/theme', [
24
+ middleware.exposeUid,
25
+ middleware.ensureLoggedIn,
26
+ middleware.canViewUsers,
27
+ middleware.checkAccountPermissions,
28
+ ], controllers.renderThemeSettings);
16
29
  };
17
30
 
18
31
  library.addAdminNavigation = async function (header) {
@@ -24,6 +37,24 @@ library.addAdminNavigation = async function (header) {
24
37
  return header;
25
38
  };
26
39
 
40
+ library.addProfileItem = async (data) => {
41
+ data.links.push({
42
+ id: 'theme',
43
+ route: 'theme',
44
+ icon: 'fa-paint-brush',
45
+ name: '[[harmony:settings.title]]',
46
+ visibility: {
47
+ self: true,
48
+ other: false,
49
+ moderator: false,
50
+ globalMod: false,
51
+ admin: false,
52
+ },
53
+ });
54
+
55
+ return data;
56
+ };
57
+
27
58
  library.defineWidgetAreas = async function (areas) {
28
59
  const locations = ['header', 'sidebar', 'footer'];
29
60
  const templates = [
@@ -55,18 +86,44 @@ library.defineWidgetAreas = async function (areas) {
55
86
  return areas;
56
87
  };
57
88
 
89
+ async function loadThemeConfig(uid) {
90
+ const [themeConfig, userConfig] = await Promise.all([
91
+ meta.settings.get('harmony'),
92
+ user.getSettings(uid),
93
+ ]);
94
+ return { ...defaults, ...themeConfig, ...userConfig };
95
+ }
96
+
58
97
  library.getThemeConfig = async function (config) {
59
- const { enableQuickReply, centerHeaderElements } = await meta.settings.get('harmony');
60
- config.enableQuickReply = enableQuickReply === 'on';
61
- config.centerHeaderElements = centerHeaderElements === 'on';
98
+ const themeConfig = await loadThemeConfig(config.uid);
99
+ config.enableQuickReply = themeConfig.enableQuickReply === 'on';
100
+ config.centerHeaderElements = themeConfig.centerHeaderElements === 'on';
101
+ config.stickyToolbar = themeConfig.stickyToolbar === 'on';
62
102
  return config;
63
103
  };
64
104
 
105
+ library.getAdminSettings = async function (hookData) {
106
+ if (hookData.plugin === 'harmony') {
107
+ hookData.values = {
108
+ ...defaults,
109
+ ...hookData.values,
110
+ };
111
+ }
112
+ return hookData;
113
+ };
114
+
115
+ library.saveUserSettings = async function (hookData) {
116
+ Object.keys(defaults).forEach((key) => {
117
+ hookData.settings[key] = hookData.data[key] || undefined;
118
+ });
119
+ return hookData;
120
+ };
121
+
65
122
  library.addUserToTopic = async function (hookData) {
66
- const { enableQuickReply } = await meta.settings.get('harmony');
123
+ const { enableQuickReply } = await loadThemeConfig(hookData.req.uid);
67
124
  if (enableQuickReply === 'on') {
68
125
  if (hookData.req.user) {
69
- const userData = await user.getUserData(hookData.req.user.uid);
126
+ const userData = await user.getUserData(hookData.req.uid);
70
127
  hookData.templateData.loggedInUser = userData;
71
128
  } else {
72
129
  hookData.templateData.loggedInUser = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-theme-harmony",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "nbbpm": {
5
5
  "compatibility": "^3.0.0"
6
6
  },
package/plugin.json CHANGED
@@ -5,6 +5,9 @@
5
5
  { "hook": "filter:admin.header.build", "method": "addAdminNavigation" },
6
6
  { "hook": "filter:widgets.getAreas", "method": "defineWidgetAreas" },
7
7
  { "hook": "filter:config.get", "method": "getThemeConfig" },
8
+ { "hook": "filter:settings.get", "method": "getAdminSettings"},
9
+ { "hook": "filter:user.saveSettings", "method": "saveUserSettings" },
10
+ { "hook": "filter:user.profileMenu", "method": "addProfileItem" },
8
11
  { "hook": "filter:topic.build", "method": "addUserToTopic" },
9
12
  { "hook": "filter:middleware.renderHeader", "method": "filterMiddlewareRenderHeader" },
10
13
  { "hook": "filter:middleware.render", "method": "removeFinalBreadcrumb" }
@@ -13,7 +16,8 @@
13
16
  "public/harmony.js"
14
17
  ],
15
18
  "modules": {
16
- "../admin/plugins/harmony.js": "public/admin.js"
19
+ "../admin/plugins/harmony.js": "public/admin.js",
20
+ "../client/account/theme.js": "public/settings.js"
17
21
  },
18
22
  "staticDirs": {
19
23
  "inter": "node_modules/@fontsource/inter/files",
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ define('forum/account/theme', ['forum/account/header', 'api', 'settings', 'alerts'], function (header, api, settings, alerts) {
4
+ const Theme = {};
5
+
6
+ Theme.init = () => {
7
+ header.init();
8
+ Theme.setupForm();
9
+ };
10
+
11
+ Theme.setupForm = () => {
12
+ const saveEl = document.getElementById('save');
13
+ if (saveEl) {
14
+ const formEl = document.getElementById('theme-settings');
15
+ saveEl.addEventListener('click', async () => {
16
+ const themeSettings = settings.helper.serializeForm($(formEl));
17
+ await api.put(`/users/${app.user.uid}/settings`, {
18
+ settings: {
19
+ ...themeSettings,
20
+ },
21
+ });
22
+ alerts.success('[[success:settings-saved]]');
23
+ });
24
+ }
25
+ };
26
+
27
+ return Theme;
28
+ });
@@ -1,137 +1,142 @@
1
- <!-- THIS FILE IS STILL PERSONA -->
2
-
3
1
  <div class="account">
4
2
  <!-- IMPORT partials/account/header.tpl -->
5
- <!-- IF sso.length --><div><!-- ENDIF sso.length -->
6
- <div class="row">
7
- <div class="col-md-3 col-sm-4">
8
- <div class="account-picture-block text-center">
9
- <div class="row mb-3">
10
- <div class="col-12 hidden-xs">
11
- <!-- IF picture -->
12
- <img id="user-current-picture" class="avatar avatar-rounded" style="--avatar-size: 128px;" src="{picture}" />
13
- <!-- ELSE -->
14
- <div class="avatar avatar-rounded" style="background-color: {icon:bgColor}; --avatar-size: 128px;">{icon:text}</div>
15
- <!-- ENDIF picture -->
16
- </div>
17
- </div>
18
- <ul class="list-group mb-3">
19
- <!-- IF allowProfilePicture -->
20
- <a id="changePictureBtn" href="#" class="list-group-item">[[user:change_picture]]</a>
21
- <!-- ENDIF allowProfilePicture -->
22
- <!-- IF !username:disableEdit -->
23
- <a href="{config.relative_path}/user/{userslug}/edit/username" class="list-group-item">[[user:change_username]]</a>
24
- <!-- ENDIF !username:disableEdit -->
25
- <!-- IF !email:disableEdit -->
26
- <a href="{config.relative_path}/user/{userslug}/edit/email" class="list-group-item">[[user:change_email]]</a>
27
- <!-- ENDIF !email:disableEdit -->
28
- <!-- IF canChangePassword -->
29
- <a href="{config.relative_path}/user/{userslug}/edit/password" class="list-group-item">[[user:change_password]]</a>
30
- <!-- ENDIF canChangePassword -->
31
- {{{each editButtons}}}
32
- <a href="{config.relative_path}{editButtons.link}" class="list-group-item">{editButtons.text}</a>
33
- {{{end}}}
34
- </ul>
35
-
36
- <!-- IF config.requireEmailConfirmation -->
37
- <!-- IF email -->
38
- <!-- IF isSelf -->
39
- <a id="confirm-email" href="#" class="btn btn-warning <!-- IF email:confirmed -->hide<!-- ENDIF email:confirmed -->">[[user:confirm_email]]</a><br/><br/>
40
- <!-- ENDIF isSelf -->
41
- <!-- ENDIF email -->
42
- <!-- ENDIF config.requireEmailConfirmation -->
43
-
44
- <!-- IF allowAccountDelete -->
45
- <!-- IF isSelf -->
46
- <a id="deleteAccountBtn" href="#" class="btn btn-danger">[[user:delete_account]]</a><br/><br/>
47
- <!-- ENDIF isSelf -->
48
- <!-- ENDIF allowAccountDelete -->
49
3
 
50
- </div>
4
+ <div class="row gx-5">
5
+ <!-- IMPORT partials/account/sidebar-left.tpl -->
6
+
7
+ <div class="col-12 col-md-9 col-lg-10 ps-md-5">
8
+ <div class="d-flex justify-content-end mb-3 sticky-lg-top" style="top: 1rem;">
9
+ <button id="submitBtn" class="btn btn-primary">[[global:save_changes]]</button>
51
10
  </div>
11
+ <div class="row">
12
+ <div class="col-xl-2 col-12">
13
+ <div class="account-picture-block text-center">
14
+ <div class="row mb-3">
15
+ <div class="col-12 hidden-xs">
16
+ <!-- IF picture -->
17
+ <img id="user-current-picture" class="avatar avatar-rounded" style="--avatar-size: 128px;" src="{picture}" />
18
+ <!-- ELSE -->
19
+ <div class="avatar avatar-rounded" style="background-color: {icon:bgColor}; --avatar-size: 128px;">{icon:text}</div>
20
+ <!-- ENDIF picture -->
21
+ </div>
22
+ </div>
23
+ <ul class="list-group mb-3 text-sm text-nowrap">
24
+ <!-- IF allowProfilePicture -->
25
+ <a id="changePictureBtn" href="#" class="list-group-item px-1 text-decoration-none">[[user:change_picture]]</a>
26
+ <!-- ENDIF allowProfilePicture -->
27
+ <!-- IF !username:disableEdit -->
28
+ <a href="{config.relative_path}/user/{userslug}/edit/username" class="list-group-item px-1 text-decoration-none">[[user:change_username]]</a>
29
+ <!-- ENDIF !username:disableEdit -->
30
+ <!-- IF !email:disableEdit -->
31
+ <a href="{config.relative_path}/user/{userslug}/edit/email" class="list-group-item px-1 text-decoration-none">[[user:change_email]]</a>
32
+ <!-- ENDIF !email:disableEdit -->
33
+ <!-- IF canChangePassword -->
34
+ <a href="{config.relative_path}/user/{userslug}/edit/password" class="list-group-item px-1 text-decoration-none">[[user:change_password]]</a>
35
+ <!-- ENDIF canChangePassword -->
36
+ {{{each editButtons}}}
37
+ <a href="{config.relative_path}{editButtons.link}" class="list-group-item px-1 text-decoration-none">{editButtons.text}</a>
38
+ {{{end}}}
39
+ </ul>
52
40
 
53
- <div class="<!-- IF !sso.length -->col-md-9 col-sm-8<!-- ELSE -->col-md-5 col-sm-4<!-- ENDIF !sso.length -->">
54
- <form role="form" component="profile/edit/form">
55
- <div class="mb-2">
56
- <label class="form-label fw-bold" for="fullname">[[user:fullname]]</label>
57
- <input class="form-control" type="text" id="fullname" name="fullname" placeholder="[[user:fullname]]" value="{fullname}">
58
- </div>
59
- <!-- IF allowWebsite -->
60
- <div class="mb-2">
61
- <label class="form-label fw-bold" for="website">[[user:website]]</label>
62
- <input class="form-control" type="text" id="website" name="website" placeholder="http://..." value="{website}">
63
- </div>
64
- <!-- ENDIF allowWebsite -->
41
+ <!-- IF config.requireEmailConfirmation -->
42
+ <!-- IF email -->
43
+ <!-- IF isSelf -->
44
+ <a id="confirm-email" href="#" class="btn btn-warning <!-- IF email:confirmed -->hide<!-- ENDIF email:confirmed -->">[[user:confirm_email]]</a><br/><br/>
45
+ <!-- ENDIF isSelf -->
46
+ <!-- ENDIF email -->
47
+ <!-- ENDIF config.requireEmailConfirmation -->
65
48
 
66
- <div class="mb-2">
67
- <label class="form-label fw-bold" for="location">[[user:location]]</label>
68
- <input class="form-control" type="text" id="location" name="location" placeholder="[[user:location]]" value="{location}">
69
- </div>
49
+ <!-- IF allowAccountDelete -->
50
+ <!-- IF isSelf -->
51
+ <a id="deleteAccountBtn" href="#" class="btn btn-danger">[[user:delete_account]]</a><br/><br/>
52
+ <!-- ENDIF isSelf -->
53
+ <!-- ENDIF allowAccountDelete -->
70
54
 
71
- <div class="mb-2">
72
- <label class="form-label fw-bold" for="birthday">[[user:birthday]]</label>
73
- <input class="form-control" type="date" id="birthday" name="birthday" value="{birthday}" placeholder="mm/dd/yyyy">
74
55
  </div>
56
+ </div>
75
57
 
76
- <div class="mb-2">
77
- <label class="form-label fw-bold" for="groupTitle">[[user:grouptitle]]</label>
58
+ <div class="col-xl-5 col-12">
59
+ <form role="form" component="profile/edit/form">
60
+ <div class="mb-2">
61
+ <label class="form-label fw-bold" for="fullname">[[user:fullname]]</label>
62
+ <input class="form-control" type="text" id="fullname" name="fullname" placeholder="[[user:fullname]]" value="{fullname}">
63
+ </div>
64
+ <!-- IF allowWebsite -->
65
+ <div class="mb-2">
66
+ <label class="form-label fw-bold" for="website">[[user:website]]</label>
67
+ <input class="form-control" type="text" id="website" name="website" placeholder="http://..." value="{website}">
68
+ </div>
69
+ <!-- ENDIF allowWebsite -->
78
70
 
79
- <select class="form-select" id="groupTitle" name="groupTitle" <!-- IF allowMultipleBadges --> size="{groupSelectSize}" multiple<!-- ENDIF allowMultipleBadges -->>
80
- <option value="">[[user:no-group-title]]</option>
81
- {{{each groups}}}
82
- <!-- IF groups.userTitleEnabled -->
83
- <option value="{groups.displayName}" <!-- IF groups.selected -->selected<!-- ENDIF groups.selected -->>{groups.userTitle}</option>
84
- <!-- ENDIF groups.userTitleEnabled -->
85
- {{{end}}}
86
- </select>
87
- <!-- IF allowMultipleBadges -->
88
- <span>[[user:group-order-help]]</span>
89
- <i role="button" component="group/order/up" class="fa fa-chevron-up"></i> <i role="button" component="group/order/down" class="fa fa-chevron-down"></i>
90
- <!-- ENDIF -->
91
- </div>
71
+ <div class="mb-2">
72
+ <label class="form-label fw-bold" for="location">[[user:location]]</label>
73
+ <input class="form-control" type="text" id="location" name="location" placeholder="[[user:location]]" value="{location}">
74
+ </div>
92
75
 
93
- <!-- IF allowAboutMe -->
94
- <div class="mb-2">
95
- <label class="form-label fw-bold" for="aboutme">[[user:aboutme]]</label> <small><label id="aboutMeCharCountLeft"></label></small>
96
- <textarea class="form-control" id="aboutme" name="aboutme" rows="5">{aboutme}</textarea>
97
- </div>
98
- <!-- ENDIF allowAboutMe -->
76
+ <div class="mb-2">
77
+ <label class="form-label fw-bold" for="birthday">[[user:birthday]]</label>
78
+ <input class="form-control" type="date" id="birthday" name="birthday" value="{birthday}" placeholder="mm/dd/yyyy">
79
+ </div>
99
80
 
100
- <!-- IF allowSignature -->
101
- <!-- IF !disableSignatures -->
102
- <div class="mb-2">
103
- <label class="form-label fw-bold" for="signature">[[user:signature]]</label> <small><label id="signatureCharCountLeft"></label></small>
104
- <textarea class="form-control" id="signature" name="signature" rows="5">{signature}</textarea>
105
- </div>
106
- <!-- ENDIF !disableSignatures -->
107
- <!-- ENDIF allowSignature -->
81
+ <div class="mb-2">
82
+ <label class="form-label fw-bold" for="groupTitle">[[user:grouptitle]]</label>
108
83
 
109
- <a id="submitBtn" href="#" class="btn btn-primary">[[global:save_changes]]</a>
110
- </form>
84
+ <select class="form-select" id="groupTitle" name="groupTitle" <!-- IF allowMultipleBadges --> size="{groupSelectSize}" multiple<!-- ENDIF allowMultipleBadges -->>
85
+ <option value="">[[user:no-group-title]]</option>
86
+ {{{each groups}}}
87
+ <!-- IF groups.userTitleEnabled -->
88
+ <option value="{groups.displayName}" <!-- IF groups.selected -->selected<!-- ENDIF groups.selected -->>{groups.userTitle}</option>
89
+ <!-- ENDIF groups.userTitleEnabled -->
90
+ {{{end}}}
91
+ </select>
92
+ <!-- IF allowMultipleBadges -->
93
+ <span>[[user:group-order-help]]</span>
94
+ <i role="button" component="group/order/up" class="fa fa-chevron-up"></i> <i role="button" component="group/order/down" class="fa fa-chevron-down"></i>
95
+ <!-- ENDIF -->
96
+ </div>
111
97
 
112
- <hr class="visible-xs visible-sm"/>
113
- </div>
98
+ <!-- IF allowAboutMe -->
99
+ <div class="mb-2">
100
+ <label class="form-label fw-bold" for="aboutme">[[user:aboutme]]</label> <small><label id="aboutMeCharCountLeft"></label></small>
101
+ <textarea class="form-control" id="aboutme" name="aboutme" rows="5">{aboutme}</textarea>
102
+ </div>
103
+ <!-- ENDIF allowAboutMe -->
114
104
 
115
- <!-- IF sso.length -->
116
- <div class="col-md-4 col-sm-4">
117
- <label>[[user:sso.title]]</label>
118
- <div class="list-group">
119
- {{{each sso}}}
120
- <div class="list-group-item">
121
- <!-- IF ../deauthUrl -->
122
- <a data-component="{../component}" class="btn btn-outline-secondary btn-sm float-end" href="{../deauthUrl}">[[user:sso.dissociate]]</a>
123
- <!-- END -->
124
- <a data-component="{../component}" href="{../url}" target="<!-- IF ../associated -->_blank<!-- ELSE -->_top<!-- ENDIF ../associated -->">
125
- <!-- IF ../icon --><i class="fa {../icon}"></i><!-- ENDIF ../icon -->
126
- <!-- IF ../associated -->[[user:sso.associated]]<!-- ELSE -->[[user:sso.not-associated]]<!-- ENDIF ../associated -->
127
- {../name}
128
- </a>
105
+ <!-- IF allowSignature -->
106
+ <!-- IF !disableSignatures -->
107
+ <div class="mb-2">
108
+ <label class="form-label fw-bold" for="signature">[[user:signature]]</label> <small><label id="signatureCharCountLeft"></label></small>
109
+ <textarea class="form-control" id="signature" name="signature" rows="5">{signature}</textarea>
110
+ </div>
111
+ <!-- ENDIF !disableSignatures -->
112
+ <!-- ENDIF allowSignature -->
113
+ </form>
114
+
115
+ <hr class="visible-xs visible-sm"/>
116
+ </div>
117
+
118
+ {{{ if sso.length }}}
119
+ <div class="col-xl-5 col-12">
120
+ <label class="form-label text-sm fw-semibold">[[user:sso.title]]</label>
121
+ <div class="list-group">
122
+ {{{each sso}}}
123
+ <div class="list-group-item d-flex justify-content-between">
124
+ <a class="text-sm text-reset text-decoration-none" data-component="{../component}" href="{../url}" target="<!-- IF ../associated -->_blank<!-- ELSE -->_top<!-- ENDIF ../associated -->">
125
+ <!-- IF ../icon --><i class="fa {../icon}"></i><!-- ENDIF ../icon -->
126
+ <!-- IF ../associated -->[[user:sso.associated]]<!-- ELSE -->[[user:sso.not-associated]]<!-- ENDIF ../associated -->
127
+ {../name}
128
+ </a>
129
+ <!-- IF ../deauthUrl -->
130
+ <a data-component="{../component}" class="btn btn-outline-secondary btn-sm" href="{../deauthUrl}">[[user:sso.dissociate]]</a>
131
+ <!-- END -->
132
+ </div>
133
+ {{{end}}}
129
134
  </div>
130
- {{{end}}}
131
135
  </div>
136
+ {{{ end }}}
132
137
  </div>
133
- <!-- ENDIF sso.length -->
134
138
  </div>
135
- <!-- IF sso.length --></div><!-- ENDIF sso.length -->
139
+ </div>
136
140
  </div>
137
141
 
142
+