diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..cbdeb84 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "nodebb/lib" +} \ No newline at end of file diff --git a/README.md b/README.md index d28a64f..b706b93 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ Persona theme for NodeBB ==================== -Persona is the new default theme for NodeBB as of v0.7.1 +The Persona theme is the default theme for NodeBB for versions spanning v0.7.1 through to v2.x + +For the v3.x release line, Persona will be a supported theme bundled with NodeBB, but will not be active by default. ## Addons diff --git a/languages/en-GB/persona.json b/languages/en-GB/persona.json index 5ad6456..e7d1945 100644 --- a/languages/en-GB/persona.json +++ b/languages/en-GB/persona.json @@ -1,4 +1,10 @@ { - "mobile-menu-side": "Switch which side each mobile menu is on", - "post-quick-reply": "Post quick reply" + "settings.title": "Theme settings", + "settings.intro": "You can customise your theme settings here. Settings are stored on a per-device basis, so you are able to have different settings on different devices (phone, tablet, desktop, etc.)", + "settings.mobile-menu-side": "Switch which side each mobile menu is on", + "settings.autoHidingNavbar": "Automatically hide the navbar on scroll", + "settings.autoHidingNavbar-xs": "Very small screens (e.g. phones in portrait mode)", + "settings.autoHidingNavbar-sm": "Smaller screens (e.g. phones, some tablets)", + "settings.autoHidingNavbar-md": "Medium sized screens (e.g. tablets in landscape mode)", + "settings.autoHidingNavbar-lg": "Larger screens (e.g. desktop computers)" } \ No newline at end of file diff --git a/languages/hu/en-GB/persona.json b/languages/hu/persona.json similarity index 100% rename from languages/hu/en-GB/persona.json rename to languages/hu/persona.json diff --git a/less/account.less b/less/account.less index 23e27b4..eb9dfe6 100644 --- a/less/account.less +++ b/less/account.less @@ -32,7 +32,7 @@ margin-bottom: 1em; background-origin: content-box; width: 100%; - top: 50px; + top: calc(var(--panel-offset) - 20px); position: absolute; left: auto; right: 0px; @@ -45,7 +45,7 @@ border: 4px solid white; border-radius: 50%; - .fab.btn-morph { + .persona-fab.btn-morph { top: 93px; right: 4px; position: absolute; @@ -77,7 +77,7 @@ &:hover { .controls { - .opacity(0.8); + opacity: 0.8; } } @@ -85,7 +85,7 @@ text-align: center; height: 200px; line-height: 200px; - .opacity(0); + opacity: 0; .transition(opacity .15s linear); cursor: pointer; pointer-events: none; @@ -317,7 +317,7 @@ background-color: lighten(@brand-primary, 10%); } - .fab { + .persona-fab { color: white; font-size: 20px; } diff --git a/less/category.less b/less/category.less index 5e2403e..0e20db4 100644 --- a/less/category.less +++ b/less/category.less @@ -113,7 +113,7 @@ border: 1px solid @btn-success-border; color: @btn-success-color; } - } + } } .fa-stack { diff --git a/less/chats.less b/less/chats.less index 916beca..741483d 100644 --- a/less/chats.less +++ b/less/chats.less @@ -1,7 +1,5 @@ // Make chats page edge-to-edge .page-user-chats { - padding-top: 50px; - #content.container { width: auto; padding: 0; @@ -12,7 +10,6 @@ } #panel { - padding-top: 0px; padding-bottom: 0px; } @@ -25,31 +22,21 @@ .chats-full, .chat-modal { display: flex; flex-wrap: nowrap; + height: calc(100vh - var(--panel-offset)); [component="chat/nav-wrapper"] { flex: 1; flex-direction: column; - box-shadow: 0 3px 9px rgba(0,0,0,.5); - + padding: 0px 15px; .chats-list { flex: 1; overflow-y: auto; margin-bottom: 0; - height: ~"calc(100% - 3em)"; + height: ~"calc(100% - 5em)"; } .chat-search { - background-color: @panel-default-heading-bg; - border-bottom: 1px solid @gray-dark; - - input { - background-color: @gray-dark; - color: @gray-lighter; - border-radius: 0; - border: none; - height: ~"calc(3em - 2px)"; - } - + padding-bottom: 15px; ul { width: 100%; } @@ -59,7 +46,6 @@ padding: 0; overflow-x: hidden; overflow-y: auto; - border-top: 1px solid @gray-lighter; li { position: relative; @@ -71,8 +57,7 @@ border-left: 1px solid; border-right: 1px solid; border-bottom: 1px solid; - border-color: #eee; - background: #fff; + border-color: @gray-lighter; i { position: relative; @@ -93,7 +78,7 @@ [component="chat/main-wrapper"] { flex: 3; - + padding-bottom: 15px; .alert { margin: 1em; } @@ -111,9 +96,7 @@ [component="chat/header"] { padding: @panel-heading-padding; - background-color: @gray-dark; - border-bottom: none; - color: @gray-lighter; + border-bottom: 1px solid @modal-header-border-color; span { font-weight: 500; @@ -121,13 +104,11 @@ .close { margin-left: 0.5em; - color: @gray-lighter; } .members { a { font-weight: 600; - color: @gray-lighter; } } @@ -163,7 +144,7 @@ [component="chat/message/remaining"] { position: absolute; - right: 5.25em; + right: 10em; z-index: 2; bottom: 0; color: @gray-light; @@ -175,11 +156,6 @@ margin-top: 10px; } -.chat-list { - margin-top: -6px; - margin-left: -1px; -} - .chats-list { padding: 0; overflow-x: hidden; @@ -193,10 +169,10 @@ } > li { + display: flex; position: relative; clear: both; list-style-type: none; - padding: 0.5em; height: 80px; .pointer; @@ -214,36 +190,29 @@ &.unread { background: lighten(@brand-primary, 35%); border-bottom: 0; - } .teaser-content { - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; font-size: 13px; opacity: 0.8; } - .room-name { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - display: block; - [component="chat/title"] { + .notification-chat-content { + .room-name { + white-space: nowrap; overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - max-width: 350px; - display: inline-block; + text-overflow: ellipsis; + display: block; + [component="chat/title"] { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + max-width: 350px; + display: inline-block; + } } } - .teaser-content, .room-name { - padding-left: 80px; - } - &.bg-primary { background: @brand-primary; border-bottom: 0; @@ -263,7 +232,8 @@ .teaser-timestamp { font-size: 10px; - margin-top: 10px; + margin-top: .5rem; + margin-right: .5rem; } a { @@ -325,15 +295,12 @@ } .main-avatar { - position: absolute; - top: 0px; - left: 0px; - .avatar { height: 8rem; width: 8rem; font-size: 4rem; border-radius: 0; + background: @gray-lighter; } } @@ -613,6 +580,13 @@ [component="chat/nav-wrapper"][data-loaded="0"] + [component="chat/main-wrapper"] { display: none; } + + .chats-full, .chat-modal { + height: calc(100vh - var(--panel-offset)); + } + [component="chat/messages"] { + width: calc(100vw); + } } [data-action="pop-out"] { diff --git a/less/groups.less b/less/groups.less index e4e3e17..8e138f3 100644 --- a/less/groups.less +++ b/less/groups.less @@ -22,14 +22,14 @@ margin-bottom: 1em; background-origin: content-box; width: 100%; - top: 50px; + top: calc(var(--panel-offset) - 20px); position: absolute; left: auto; right: 0px; &:hover { .controls { - .opacity(0.8); + opacity: 0.8; } } @@ -37,7 +37,7 @@ text-align: center; min-height: 200px; line-height: 200px; - .opacity(0); + opacity: 0; .transition(opacity .15s linear); cursor: pointer; pointer-events: none; diff --git a/less/header.less b/less/header.less index 2a793fa..fe37399 100644 --- a/less/header.less +++ b/less/header.less @@ -1,36 +1,61 @@ .header, .slideout-menu { + .notifications.dropdown, .chats.dropdown { + .dropdown-menu { + padding: 0; + } + } + .notification-list { overflow-x: hidden; overflow-y: auto; max-height: 250px; padding: 0; - color: @gray-dark; - - li.no-notifs { - text-align: center; - } li { + display: flex; + gap: 1rem; width: 400px; text-align: left; list-style-type: none; - padding: 0.5em; - clear: both; + padding: .75em 0.5em; - &.loading-text { - text-align: center; + &:hover { + background-color: @dropdown-link-hover-bg; } - a { - white-space: normal; - margin: 0; - text-overflow: ellipsis; + &.loading-text, &.no-notifs { + justify-content: center; - .text { - margin-left: 40px; - margin-right: 60px; - display: block; - min-height: 32px; + a { + color: inherit; + } + } + + .notification-chat-content { + flex: 1; + + a { + white-space: normal; + margin: 0; + text-overflow: ellipsis; + + .text { + margin-left: 40px; + margin-right: 60px; + display: block; + min-height: 32px; + } + } + } + + .notification-chat-controls { + display: flex; + flex-direction: column; + align-items: center; + gap: .5rem; + + .relTime { + font-size: 10px; } } @@ -51,9 +76,13 @@ .pointer; } } - + &.unread { - .bg-variant(@state-warning-bg); + background-color: @state-warning-bg; + + &:hover { + background-color: darken(@state-warning-bg, 5%); + } .mark-read:after { font-weight: 900; @@ -68,22 +97,24 @@ > li { .pointer; width: 500px; - padding-bottom: 1rem; + padding: 0; margin: 0; + gap: 0; + overflow-y: hidden; + + .notification-chat-content { + padding: 0.5rem; + overflow: hidden; + text-overflow: ellipsis; + } .teaser-content { white-space: nowrap; max-height: 19px; - padding-left: 90px; - padding-right: 10px; - } - - &:hover { - background: @gray-lighter; } &:not(:last-child) { - border-bottom: 1px solid @gray-lighter; + border-bottom: 1px solid @dropdown-border; } &.no_active a { @@ -173,15 +204,20 @@ } .notif-dropdown-link { - // margin-top: 1em; border-top: 1px solid rgba(163, 163, 163, 0.5); - padding: 0 5px 0 5px; - + .btn-group-justified { + table-layout: auto; + } a { - display: block; text-align: center; - padding: 0.5em 0; + padding: 0.5em 0.5em; font-weight: 600; + color: @dropdown-link-color; + + &:hover { + color: @dropdown-link-hover-color; + background: @dropdown-link-hover-bg; + } } } @@ -407,24 +443,3 @@ html[data-dir="rtl"] { text-align: right; } } - -#mobile-menu:focus i.fa-bars { - -webkit-animation: fa-spin 2s infinite linear; - animation: fa-spin 2s infinite linear; - - &::before { - font-family: "FontAwesome"; - content: "\f110"; - } -} - -@keyframes fa-spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} \ No newline at end of file diff --git a/less/mixins.less b/less/mixins.less index 8baf69d..403e0aa 100644 --- a/less/mixins.less +++ b/less/mixins.less @@ -27,13 +27,6 @@ } } -.opacity(@opacity: 1) { - -moz-opacity: @opacity; - opacity: @opacity; - -ms-filter: ~`"progid:DXImageTransform.Microsoft.Alpha(opacity=(" + "@{opacity}" * 100 + "))"`; - filter: ~`"alpha(opacity = (" + "@{opacity}" * 100 + "))"`; -} - .border-radius (@radius: 5px) { -webkit-border-radius: @radius; -moz-border-radius: @radius; @@ -65,7 +58,7 @@ margin-left: -2.5rem; } - .avatar, .timeline-badge { + .icon .avatar, .timeline-badge { // Opaque ring position: relative; z-index: 1; @@ -107,6 +100,10 @@ &+.timeline-event:before { display: none; } + + .timeline-text.timeago { + display: none; + } } } @@ -152,6 +149,8 @@ display: flex; align-items: center; justify-content: center; + flex-shrink: 0; + width: 32px; height: 32px; padding: 0; diff --git a/less/mobile.less b/less/mobile.less index fb5bcf9..ef35ef7 100644 --- a/less/mobile.less +++ b/less/mobile.less @@ -34,15 +34,9 @@ } @media (max-width: @screen-md-max) { - body { - padding-top: 0; - padding-bottom: 0; - } - #panel { background-color: inherit; min-height: 100%; - padding-top: 80px; padding-bottom: 40px; } @@ -59,20 +53,59 @@ html[data-dir="rtl"] button& { margin-left: 0; } - + mobile-menu { + .unread-count::after { + left: 32px; + } + } .header & .notification-icon { left: auto; right: 7px; top: 10px; - + &[component="notifications/icon"] { + right: 41px; + } &.unread-count::after { position: static; } } } + .navbar-header .navbar-search { + input[name="term"] { + width: 150px; + } - #menu { - padding-top: 100px; + padding: 4px 0px 4px 0px; + } + #menu .menu-section { + padding-top: 20px; + } + #chats-menu { + .nav-pills { + [component="user/status"] { + position: absolute; + right: 24px; + } + + background-color: #101010; + height: 50px; + li { + margin: 0; + padding: 0; + width: 33%; + text-align: center; + height: 100%; + a { + height: 100%; + padding-top: 15px; + } + &.active { + a { + background-color: #1D1F20; + } + } + } + } } .slideout-menu { @@ -141,10 +174,18 @@ white-space: nowrap; text-overflow: ellipsis; } + .teaser-timestamp { + font-size: 10px; + margin-right: 0.5rem; + margin-top: 0.5rem; + } } } .menu-section { + .notification-list-mobile li .text { + display: block; + } .chat-list, .notification-list-mobile { .user-link { display: inline; @@ -152,6 +193,15 @@ .unread { background-color: inherit; } + + .notification-chat-content { + padding-top: 10px; + padding-right: 20px; + } + + .notification-chat-controls { + display: none; + } } .chat-list .unread .room-name::after, .notification-list-mobile .unread a::after { @@ -171,7 +221,6 @@ font-style: normal; &:after { - left: 5px; top: -1px; padding: 3px 7px; background: #333; @@ -190,10 +239,6 @@ margin: 0; } - .menu-section { - margin: 25px 0; - } - .menu-section-title { text-transform: uppercase; color: #85888d; @@ -206,7 +251,7 @@ .menu-section-list { padding: 0; - margin: 10px 0; + margin: 0; list-style: none; a, button { diff --git a/less/modules/fab.less b/less/modules/fab.less index 4afcdd1..59111d1 100644 --- a/less/modules/fab.less +++ b/less/modules/fab.less @@ -1,4 +1,4 @@ -.fab { +.persona-fab { box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.156863), 0px 2px 10px 0px rgba(0, 0, 0, 0.117647); background-color: @brand-primary; @@ -11,11 +11,11 @@ width: 55.5px; } -.btn-group.open .dropdown-toggle.fab { +.btn-group.open .dropdown-toggle.persona-fab { box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.156863), 0px 2px 10px 0px rgba(0, 0, 0, 0.117647); } -.fab.btn-morph { +.persona-fab.btn-morph { padding: 0; &.heart { diff --git a/less/modules/taskbar.less b/less/modules/taskbar.less index 6b20334..dcc83be 100644 --- a/less/modules/taskbar.less +++ b/less/modules/taskbar.less @@ -6,14 +6,8 @@ .taskbar { display: none; - z-index: @zindex-popover; - left: auto; - - // Bootswatch fix - &.navbar-fixed-bottom { - z-index: @zindex-popover; - left: auto; - } + left: 15px; + right: auto; margin-top: 0; .transition(.15s ease-in opacity); @@ -35,11 +29,14 @@ } .navbar-nav { + float: unset; + display: flex; + flex-direction: column; padding-right: 15px; padding-bottom: 15px; li { - float: left; + margin-top: 1rem; &.new a { -webkit-animation-name: bounceIn; @@ -162,7 +159,6 @@ &.taskbar-composer, &.taskbar-chat { a { text-align: center; - margin-left: 15px; i { font-size: 1.8rem; diff --git a/less/modules/usercard.less b/less/modules/usercard.less index da7b72b..f75a3be 100644 --- a/less/modules/usercard.less +++ b/less/modules/usercard.less @@ -57,7 +57,7 @@ } } - .fab.btn-morph { + .persona-fab.btn-morph { top: 75px; right: 15px; position: absolute; diff --git a/less/search.less b/less/search.less index 835c0b3..925af0d 100644 --- a/less/search.less +++ b/less/search.less @@ -64,7 +64,6 @@ font-weight: 400; line-height: 1.42857143; white-space: nowrap; - color: @gray-dark; } } .quick-search-title { @@ -74,10 +73,18 @@ .snippet { word-break: break-word; white-space: normal; + font-size: initial; } } } +@media (max-width: @screen-xs-max) { + .quick-search-container { + left: 0px; + right: 0px; + } +} + .quick-search-results, .search-results { .post-info { font-size: 12px; diff --git a/less/style.less b/less/style.less index 73930d1..65a84f5 100644 --- a/less/style.less +++ b/less/style.less @@ -4,20 +4,13 @@ html { } body { - @media (min-width: 979px) - { - padding-top: 70px; - } - - @media (max-width: 979px) - { - padding-top: 70px; - padding-bottom: 50px; - } - min-height: 100%; } +#panel { + padding-top: var(--panel-offset); +} + @media (max-width: @screen-xs-max) { .slideout-panel { min-height: 100vh; @@ -273,6 +266,10 @@ a.permalink { } } +.deco-none, .deco-none:link, .deco-none:hover { + color: inherit; + text-decoration: inherit; +} .disabled a { pointer-events: none; diff --git a/less/tags.less b/less/tags.less index 95a8622..164a71e 100644 --- a/less/tags.less +++ b/less/tags.less @@ -30,7 +30,8 @@ .tag { text-transform: uppercase; font-size: 10px; - background: lighten(@gray-lighter, 2.5%); + background: #e9ecef; + color: #7a8288; padding: 5px; white-space: nowrap; } diff --git a/less/topic.less b/less/topic.less index cbc2af0..873ee5e 100644 --- a/less/topic.less +++ b/less/topic.less @@ -41,7 +41,7 @@ } .topic-header { position: sticky; - top: @navbar-height; + top: calc(var(--panel-offset) - 20px); background-color: @body-bg; z-index: @zindex-navbar; margin-left: -15px; @@ -163,20 +163,18 @@ display: inline-block; padding: 1rem; - &:first-child { - padding-right: 0.5rem; - } - - &:last-child { - padding-left: 0.5rem; - } - &:focus { text-decoration: none; } } - [component="post/upvote"].upvoted, [component="post/downvote"].downvoted { + [component="post/upvote"].upvoted i::before { + content: @fa-var-chevron-circle-up; + color: @brand-primary; + } + + [component="post/downvote"].downvoted i::before { + content: @fa-var-chevron-circle-down; color: @brand-primary; } @@ -188,7 +186,6 @@ [component="post/parent"] { border: 0; font-size: 10px; - background-color: #f0f0f0; } .threaded-replies { @@ -374,6 +371,7 @@ } .quick-reply { + position: relative; .icon { position: relative; border-radius: 50%; @@ -421,7 +419,7 @@ .topic { &.deleted { - .opacity(0.3); + opacity: 0.3; .votes { display: none; @@ -446,7 +444,7 @@ &.deleted { > .content { - .opacity(0.3); + opacity: 0.3; } .votes { @@ -661,6 +659,16 @@ z-index: 1; } +.selection-tooltip-container { + position: absolute; + padding: 5px; + border: 1px solid @well-border; + background-color: @well-bg; + border-radius: 3px; + margin: 10px 0px 0px 0px; + z-index: 1; +} + @media screen and (min-width: @screen-sm-min) { .fork-thread-card { max-width: 33%; diff --git a/lib/controllers.js b/lib/controllers.js new file mode 100644 index 0000000..338bb7a --- /dev/null +++ b/lib/controllers.js @@ -0,0 +1,22 @@ +'use strict'; + +const accountHelpers = require.main.require('./src/controllers/accounts/helpers'); +const helpers = require.main.require('./src/controllers/helpers'); + +const Controllers = module.exports; + +Controllers.renderAdminPage = (req, res) => { + res.render('admin/plugins/persona', {}); +}; + +Controllers.renderThemeSettings = async (req, res, next) => { + const userData = await accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, req.query); + if (!userData) { + return next(); + } + + userData.title = '[[persona:settings.title]]'; + userData.breadcrumbs = helpers.buildBreadcrumbs([{ text: userData.username, url: `/user/${userData.userslug}` }, { text: '[[persona:settings.title]]' }]); + + res.render('account/theme', userData); +}; diff --git a/library.js b/library.js index c5e7473..6b51e72 100644 --- a/library.js +++ b/library.js @@ -1,43 +1,67 @@ 'use strict'; -var meta = require.main.require('./src/meta'); -var user = require.main.require('./src/user'); +const meta = require.main.require('./src/meta'); +const user = require.main.require('./src/user'); +const translator = require.main.require('./src/translator'); -var library = {}; +const controllers = require('./lib/controllers'); -library.init = function(params, callback) { - var app = params.router; - var middleware = params.middleware; +const library = module.exports; - app.get('/admin/plugins/persona', middleware.admin.buildHeader, renderAdmin); - app.get('/api/admin/plugins/persona', renderAdmin); +library.init = async function (params) { + const { router, middleware } = params; + const routeHelpers = require.main.require('./src/routes/helpers'); + routeHelpers.setupAdminPageRoute(router, '/admin/plugins/persona', [], controllers.renderAdminPage); - callback(); + routeHelpers.setupPageRoute(router, '/user/:userslug/theme', [ + middleware.exposeUid, + middleware.ensureLoggedIn, + middleware.canViewUsers, + middleware.checkAccountPermissions, + ], controllers.renderThemeSettings); }; -library.addAdminNavigation = function(header, callback) { +library.addAdminNavigation = async function (header) { header.plugins.push({ route: '/plugins/persona', icon: 'fa-paint-brush', - name: 'Persona Theme' + name: 'Persona Theme', }); + return header; +}; - callback(null, header); +library.addProfileItem = async (data) => { + data.links.push({ + id: 'theme', + route: 'theme', + icon: 'fa-paint-brush', + name: await translator.translate('[[persona:settings.title]]'), + visibility: { + self: true, + other: false, + moderator: false, + globalMod: false, + admin: false, + }, + }); + + return data; }; -library.defineWidgetAreas = function(areas, callback) { +library.defineWidgetAreas = async function (areas) { const locations = ['header', 'sidebar', 'footer']; const templates = [ 'categories.tpl', 'category.tpl', 'topic.tpl', 'users.tpl', - 'unread.tpl', 'recent.tpl', 'popular.tpl', 'top.tpl', 'tags.tpl', 'tag.tpl' + 'unread.tpl', 'recent.tpl', 'popular.tpl', 'top.tpl', 'tags.tpl', 'tag.tpl', + 'login.tpl', 'register.tpl', ]; function capitalizeFirst(str) { - return str.charAt(0).toUpperCase() + str.slice(1) + return str.charAt(0).toUpperCase() + str.slice(1); } - templates.forEach(template => { - locations.forEach(location => { + templates.forEach((template) => { + locations.forEach((location) => { areas.push({ - name: capitalizeFirst(template.split('.')[0]) + ' ' + capitalizeFirst(location), + name: `${capitalizeFirst(template.split('.')[0])} ${capitalizeFirst(location)}`, template: template, location: location, }); @@ -46,16 +70,25 @@ library.defineWidgetAreas = function(areas, callback) { areas = areas.concat([ { - name: "Account Header", - template: "account/profile.tpl", - location: "header" + name: 'Main post header', + template: 'topic.tpl', + location: 'mainpost-header', + }, + { + name: 'Main post footer', + template: 'topic.tpl', + location: 'mainpost-footer', + }, + { + name: 'Account Header', + template: 'account/profile.tpl', + location: 'header', }, ]); - - callback(null, areas); + return areas; }; -library.getThemeConfig = async function(config) { +library.getThemeConfig = async function (config) { const settings = await meta.settings.get('persona'); config.hideSubCategories = settings.hideSubCategories === 'on'; config.hideCategoryLastPost = settings.hideCategoryLastPost === 'on'; @@ -63,23 +96,23 @@ library.getThemeConfig = async function(config) { return config; }; -function renderAdmin(req, res, next) { - res.render('admin/plugins/persona', {}); -} - library.addUserToTopic = async function (hookData) { - if (hookData.req.user) { - const userData = await user.getUserData(hookData.req.user.uid); - hookData.templateData.loggedInUser = userData; - } else { - hookData.templateData.loggedInUser = { - uid: 0, - username: '[[global:guest]]', - picture: user.getDefaultAvatar(), - 'icon:text': '?', - 'icon:bgColor': '#aaa', - }; + const settings = await meta.settings.get('persona'); + if (settings.enableQuickReply === 'on') { + if (hookData.req.user) { + const userData = await user.getUserData(hookData.req.user.uid); + hookData.templateData.loggedInUser = userData; + } else { + hookData.templateData.loggedInUser = { + uid: 0, + username: '[[global:guest]]', + picture: user.getDefaultAvatar(), + 'icon:text': '?', + 'icon:bgColor': '#aaa', + }; + } } + return hookData; }; diff --git a/package.json b/package.json index a5ce820..33d158d 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,17 @@ { "name": "nodebb-theme-persona", - "version": "11.2.2", + "version": "12.1.15", "nbbpm": { - "compatibility": "^1.18.0" + "compatibility": "^2.0.0" }, "description": "Persona theme for NodeBB", "main": "library.js", "repository": { "type": "git", - "url": "https://github.com/psychobunny/nodebb-theme-persona" + "url": "https://github.com/NodeBB/nodebb-theme-persona" + }, + "scripts": { + "lint": "eslint ." }, "keywords": [ "nodebb", @@ -20,26 +23,31 @@ "contributors": [ { "name": "Andrew Rodrigues", - "email": "andrew@designcreateplay.com", + "email": "andrew@nodebb.org", "url": "https://github.com/psychobunny" }, { "name": "Julian Lam", - "email": "julian@designcreateplay.com", + "email": "julian@nodebb.org", "url": "https://github.com/julianlam" }, { "name": "Barış Soner Uşaklı", - "email": "baris@designcreateplay.com", + "email": "baris@nodebb.org", "url": "https://github.com/barisusakli" } ], "license": "BSD-2-Clause", "bugs": { - "url": "https://github.com/psychobunny/nodebb-theme-persona/issues" + "url": "https://github.com/NodeBB/nodebb-theme-persona/issues" }, "dependencies": { "pulling": "^2.0.0", "striptags": "^3.2.0" + }, + "devDependencies": { + "eslint": "^7.32.0", + "eslint-config-nodebb": "^0.0.2", + "eslint-plugin-import": "^2.24.2" } } diff --git a/plugin.json b/plugin.json index 9a1a01b..4716e49 100644 --- a/plugin.json +++ b/plugin.json @@ -5,18 +5,17 @@ { "hook": "filter:config.get", "method": "getThemeConfig" }, { "hook": "static:app.load", "method": "init" }, { "hook": "filter:admin.header.build", "method": "addAdminNavigation" }, + { "hook": "filter:user.profileMenu", "method": "addProfileItem" }, { "hook": "filter:topic.build", "method": "addUserToTopic" } ], "scripts": [ - "public/persona.js", "public/modules/autohidingnavbar.js", - "public/modules/quickreply.js" + "public/persona.js" ], "modules": { - "pulling.js": "node_modules/pulling/build/pulling-drawer.js" + "../admin/plugins/persona.js": "public/admin.js", + "persona/quickreply.js": "public/modules/quickreply.js", + "../client/account/theme.js": "public/settings.js" }, - "acpScripts": [ - "public/admin.js" - ], "languages": "languages" } \ No newline at end of file diff --git a/public/.eslintrc b/public/.eslintrc new file mode 100644 index 0000000..bf4af75 --- /dev/null +++ b/public/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "nodebb/public" +} \ No newline at end of file diff --git a/public/admin.js b/public/admin.js index 53fd658..0a9aa8a 100644 --- a/public/admin.js +++ b/public/admin.js @@ -1,24 +1,15 @@ 'use strict'; -/* globals $, app */ - -define('admin/plugins/persona', ['settings'], function(Settings) { +define('admin/plugins/persona', ['settings'], function (Settings) { var ACP = {}; - ACP.init = function() { + ACP.init = function () { Settings.load('persona', $('.persona-settings')); - $('#save').on('click', function() { - Settings.save('persona', $('.persona-settings'), function() { - app.alert({ - type: 'success', - alert_id: 'persona-saved', - title: 'Settings Saved', - message: 'Persona settings saved' - }); - }); + $('#save').on('click', function () { + Settings.save('persona', $('.persona-settings')); }); }; return ACP; -}); \ No newline at end of file +}); diff --git a/public/modules/quickreply.js b/public/modules/quickreply.js index 185c999..998433d 100644 --- a/public/modules/quickreply.js +++ b/public/modules/quickreply.js @@ -1,14 +1,15 @@ -"use strict"; - -/*globals $, app, ajaxify, socket*/ +'use strict'; define('persona/quickreply', [ - 'components', 'composer/autocomplete', 'api' -], function(components, autocomplete, api) { + 'components', 'composer', 'composer/autocomplete', 'api', + 'alerts', 'uploadHelpers', 'mousetrap', +], function ( + components, composer, autocomplete, api, + alerts, uploadHelpers, mousetrap +) { var QuickReply = {}; - QuickReply.init = function() { - + QuickReply.init = function () { var element = components.get('topic/quickreply/text'); var data = { element: element, @@ -16,40 +17,85 @@ define('persona/quickreply', [ options: { style: { 'z-index': 100, - } + }, // listPosition: function(position) { // this.$el.css(this._applyPlacement(position)); // this.$el.css('position', 'absolute'); // return this; // } - } + }, }; $(window).trigger('composer:autocomplete:init', data); - autocomplete._active['persona_qr'] = autocomplete.setup(data); + autocomplete._active.persona_qr = autocomplete.setup(data); // data.element.textcomplete(data.strategies, data.options); // $('.textcomplete-wrapper').css('height', '100%').find('textarea').css('height', '100%'); - components.get('topic/quickreply/button').on('click', function(e) { + mousetrap.bind('ctrl+return', (e) => { + if (e.target === element.get(0)) { + components.get('topic/quickreply/button').get(0).click(); + } + }); + + uploadHelpers.init({ + dragDropAreaEl: $('[component="topic/quickreply/container"] .quickreply-message'), + pasteEl: element, + uploadFormEl: $('[component="topic/quickreply/upload"]'), + inputEl: element, + route: '/api/post/upload', + callback: function (uploads) { + let text = element.val(); + uploads.forEach((upload) => { + text = text + (text ? '\n' : '') + (upload.isImage ? '!' : '') + `[${upload.filename}](${upload.url})`; + }); + element.val(text); + }, + }); + + var ready = true; + components.get('topic/quickreply/button').on('click', function (e) { e.preventDefault(); + if (!ready) { + return; + } + var replyMsg = components.get('topic/quickreply/text').val(); var replyData = { tid: ajaxify.data.tid, handle: undefined, - content: replyMsg + content: replyMsg, }; + + ready = false; api.post(`/topics/${ajaxify.data.tid}`, replyData, function (err, data) { + ready = true; if (err) { - return app.alertError(err.message); + return alerts.error(err); } if (data && data.queued) { - app.alertSuccess(data.message); + alerts.alert({ + type: 'success', + title: '[[global:alert.success]]', + message: data.message, + timeout: 10000, + clickfn: function () { + ajaxify.go(`/post-queue/${data.id}`); + }, + }); } components.get('topic/quickreply/text').val(''); - autocomplete._active['persona_qr'].hide(); + autocomplete._active.persona_qr.hide(); }); }); + + components.get('topic/quickreply/expand').on('click', (e) => { + e.preventDefault(); + + const textEl = components.get('topic/quickreply/text'); + composer.newReply(ajaxify.data.tid, undefined, ajaxify.data.title, utils.escapeHTML(textEl.val())); + textEl.val(''); + }); }; return QuickReply; diff --git a/public/persona.js b/public/persona.js index 0e24b3a..94ccf2d 100644 --- a/public/persona.js +++ b/public/persona.js @@ -1,35 +1,36 @@ -"use strict"; - -/*globals ajaxify, config, utils, app, socket, window, document, $*/ +'use strict'; $(document).ready(function () { setupNProgress(); + setupTaskbar(); setupEditedByIcon(); + setupMobileMenu(); setupQuickReply(); configureNavbarHiding(); - fixHeaderPadding(); + updatePanelOffset(); $(window).on('resize', utils.debounce(configureNavbarHiding, 200)); - $(window).on('resize', fixHeaderPadding); - - $(window).on('action:app.loggedIn', function () { - setupMobileMenu(); - }); - - $(window).on('action:app.load', function () { - setupTaskbar(); - setupMobileMenu(); - }); - - function fixHeaderPadding() { - var env = utils.findBootstrapEnvironment(); - if(!$('#header-menu').hasClass('hidden')){ - if (env === 'sm' || env === 'xs' || env === 'md') { - $('#panel').css('padding-top', $('#header-menu').outerHeight(true)); - } else { - $('#panel').css('padding-top', $('#header-menu').outerHeight(true) - 70); - } + $(window).on('resize', updatePanelOffset); + + function updatePanelOffset() { + const headerEl = document.getElementById('header-menu'); + + if (!headerEl) { + console.warn('[persona/updatePanelOffset] Could not find #header-menu, panel offset unchanged.'); + return; } + + const headerRect = headerEl.getBoundingClientRect(); + const headerStyle = window.getComputedStyle(headerEl); + + let offset = + headerRect.y + headerRect.height + + (parseInt(headerStyle.marginTop, 10) || 0) + + (parseInt(headerStyle.marginBottom, 10) || 0); + + offset = Math.max(0, offset); + document.documentElement.style.setProperty('--panel-offset', `${offset}px`); + localStorage.setItem('panelOffset', offset); } var lastBSEnv = ''; @@ -37,42 +38,65 @@ $(document).ready(function () { if (!$.fn.autoHidingNavbar) { return; } - var env = utils.findBootstrapEnvironment(); - // if env didn't change don't destroy and recreate - if (env === lastBSEnv) { - return; - } - lastBSEnv = env; - var navbarEl = $(".navbar-fixed-top"); - navbarEl.autoHidingNavbar('destroy').removeData('plugin_autoHidingNavbar'); - navbarEl.css('top', ''); - - if (env === 'xs' || env === 'sm') { - navbarEl.autoHidingNavbar({ - showOnBottom: false, - }); - } - function fixTopCss(topValue) { - if (ajaxify.data.template.topic) { - $('.topic .topic-header').css({top: topValue }); - } else { - var topicListHeader = $('.topic-list-header'); - if (topicListHeader.length) { - topicListHeader.css({ top: topValue }); - } + require(['hooks', 'storage'], (hooks, Storage) => { + let preference = ['xs', 'sm']; + + try { + preference = JSON.parse(Storage.getItem('persona:navbar:autohide')) || preference; + } catch (e) { + console.warn('[persona/settings] Unable to parse value for navbar autohiding'); } - } + var env = utils.findBootstrapEnvironment(); + // if env didn't change don't destroy and recreate + if (env === lastBSEnv) { + return; + } + lastBSEnv = env; + var navbarEl = $('.navbar-fixed-top'); + navbarEl.autoHidingNavbar('destroy').removeData('plugin_autoHidingNavbar'); + navbarEl.css('top', ''); + + hooks + .on('filter:navigator.scroll', (data) => { + navbarEl.autoHidingNavbar('setDisableAutohide', true); + return data; + }) + .on('action:navigator.scrolled', () => { + navbarEl.autoHidingNavbar('setDisableAutohide', false); + }); - navbarEl.off('show.autoHidingNavbar') - .on('show.autoHidingNavbar', function() { - fixTopCss(''); - }); + hooks.fire('filter:persona.configureNavbarHiding', { + resizeEnvs: preference, + }).then(({ resizeEnvs }) => { + if (resizeEnvs.includes(env)) { + navbarEl.autoHidingNavbar({ + showOnBottom: false, + }); + } + + function fixTopCss(topValue) { + if (ajaxify.data.template.topic) { + $('.topic .topic-header').css({ top: topValue }); + } else { + var topicListHeader = $('.topic-list-header'); + if (topicListHeader.length) { + topicListHeader.css({ top: topValue }); + } + } + } + + navbarEl.off('show.autoHidingNavbar') + .on('show.autoHidingNavbar', function () { + fixTopCss(''); + }); - navbarEl.off('hide.autoHidingNavbar') - .on('hide.autoHidingNavbar', function() { - fixTopCss('0px'); + navbarEl.off('hide.autoHidingNavbar') + .on('hide.autoHidingNavbar', function () { + fixTopCss('0px'); + }); }); + }); } function setupNProgress() { @@ -155,7 +179,8 @@ $(document).ready(function () { function setupEditedByIcon() { function activateEditedTooltips() { $('[data-pid] [component="post/editor"]').each(function () { - var el = $(this), icon; + var el = $(this); + var icon; if (!el.attr('data-editor')) { return; @@ -184,14 +209,14 @@ $(document).ready(function () { return; } - require(['pulling', 'storage'], function (Pulling, Storage) { + require(['pulling/build/pulling-drawer', 'storage', 'alerts', 'search'], function (Pulling, Storage, alerts, search) { if (!Pulling) { return; } // initialization - var chatMenuVisible = !config.disableChat && app.user && parseInt(app.user.uid, 10); + var chatMenuVisible = app.user && parseInt(app.user.uid, 10); var swapped = !!Storage.getItem('persona:menus:legacy-layout'); var margin = window.innerWidth; @@ -244,8 +269,9 @@ $(document).ready(function () { $(window).on('resize action:ajaxify.start', function () { navSlideout.close(); - if (chatsSlideout) { chatsSlideout.close(); } - $('.account .cover').css('top', $('[component="navbar"]').height()); + if (chatsSlideout) { + chatsSlideout.close(); + } }); navSlideout @@ -272,14 +298,6 @@ $(document).ready(function () { navSlideout.enable().toggle(); }); - function loadNotifications() { - require(['notifications'], function (notifications) { - notifications.loadNotifications($('#menu [data-section="notifications"] ul')); - }); - } - - navSlideout.on('opened', loadNotifications); - if (chatMenuVisible) { navSlideout.on('beforeopen', function () { chatsSlideout.close(); @@ -289,22 +307,29 @@ $(document).ready(function () { }); } - $('#menu [data-section="navigation"] ul').html($('#main-nav').html() + ($('#search-menu').html() || '') + ($('#logged-out-menu').html() || '')); + $('#menu [data-section="navigation"] ul').html( + $('#main-nav').html() + + ($('#logged-out-menu').html() || '') + ); - $('#user-control-list').children().clone(true, true).appendTo($('#menu [data-section="profile"] ul')); + $('#user-control-list').children().clone(true, true).appendTo($('#chats-menu [data-section="profile"] ul')); socket.on('event:user_status_change', function (data) { if (parseInt(data.uid, 10) === app.user.uid) { - app.updateUserStatus($('#menu [component="user/status"]'), data.status); + app.updateUserStatus($('#chats-menu [component="user/status"]'), data.status); navSlideout.close(); } }); - // right slideout chats menu + // right slideout notifications & chats menu - function loadChats() { - require(['chat'], function (chat) { - chat.loadChatsDropdown($('#chats-menu .chat-list')); + function loadNotificationsAndChats() { + require(['notifications', 'chat'], function (notifications, chat) { + const notifList = $('#chats-menu [data-section="notifications"] ul'); + notifications.loadNotifications(notifList, function () { + notifList.find('.deco-none').removeClass('deco-none'); + chat.loadChatsDropdown($('#chats-menu .chat-list')); + }); }); } @@ -318,7 +343,7 @@ $(document).ready(function () { }); chatsSlideout - .on('opened', loadChats) + .on('opened', loadNotificationsAndChats) .on('beforeopen', function () { navSlideout.close().disable(); }) @@ -327,31 +352,35 @@ $(document).ready(function () { }); } - // add a checkbox in the user settings page - // so users can swap the sides the menus appear on - - function setupSetting() { - if (ajaxify.data.template['account/settings'] && !document.getElementById('persona:menus:legacy-layout')) { - require(['translator'], function (translator) { - translator.translate('[[persona:mobile-menu-side]]', function (translated) { - $('
') - .appendTo('#content .account > .row > div:first-child') - .find('input') - .prop('checked', Storage.getItem('persona:menus:legacy-layout', 'true')) - .change(function (e) { - if (e.target.checked) { - Storage.setItem('persona:menus:legacy-layout', 'true'); - } else { - Storage.removeItem('persona:menus:legacy-layout'); - } - }); - }); - }) + const searchInputEl = $('.navbar-header .navbar-search input[name="term"]'); + const searchButton = $('.navbar-header .navbar-search button[type="button"]'); + searchButton.off('click').on('click', function () { + if (!config.loggedIn && !app.user.privileges['search:content']) { + alerts.alert({ + message: '[[error:search-requires-login]]', + timeout: 3000, + }); + ajaxify.go('login'); + return false; } - } - $(window).on('action:ajaxify.end', setupSetting); - setupSetting(); + searchButton.addClass('hidden'); + searchInputEl.removeClass('hidden').focus(); + return false; + }); + searchInputEl.on('blur', function () { + searchInputEl.addClass('hidden'); + searchButton.removeClass('hidden'); + }); + search.enableQuickSearch({ + searchElements: { + inputEl: searchInputEl, + resultEl: $('.navbar-header .navbar-search .quick-search-container'), + }, + searchOptions: { + in: config.searchDefaultInQuick, + }, + }); }); } @@ -436,13 +465,13 @@ $(document).ready(function () { } function setupFavouriteMorph(parent, uid, username) { - require(['api'], function (api) { + require(['api', 'alerts'], function (api, alerts) { parent.find('.btn-morph').click(function (ev) { var type = $(this).hasClass('plus') ? 'follow' : 'unfollow'; var method = $(this).hasClass('plus') ? 'put' : 'del'; api[method]('/users/' + uid + '/follow').then(() => { - app.alertSuccess('[[global:alert.' + type + ', ' + username + ']]'); + alerts.success('[[global:alert.' + type + ', ' + username + ']]'); }); $(this).toggleClass('plus').toggleClass('heart'); @@ -452,9 +481,9 @@ $(document).ready(function () { $(this).prepend(''); } - var drop = $(this).find('b.drop').removeClass('animate'), - x = ev.pageX - drop.width() / 2 - $(this).offset().left, - y = ev.pageY - drop.height() / 2 - $(this).offset().top; + var drop = $(this).find('b.drop').removeClass('animate'); + var x = ev.pageX - (drop.width() / 2) - $(this).offset().left; + var y = ev.pageY - (drop.height() / 2) - $(this).offset().top; drop.css({ top: y + 'px', left: x + 'px' }).addClass('animate'); }); diff --git a/public/settings.js b/public/settings.js new file mode 100644 index 0000000..5e2359a --- /dev/null +++ b/public/settings.js @@ -0,0 +1,53 @@ +'use strict'; + +define('forum/account/theme', ['forum/account/header', 'storage', 'settings', 'alerts'], function (header, Storage, settings, alerts) { + const Theme = {}; + + Theme.init = () => { + header.init(); + Theme.setupForm(); + }; + + Theme.setupForm = () => { + const saveEl = document.getElementById('save'); + const formEl = document.getElementById('theme-settings'); + const [sidebarSwapped, autohideNavbarEnvs] = [ + !!Storage.getItem('persona:menus:legacy-layout'), + Storage.getItem('persona:navbar:autohide'), + ]; + + document.getElementById('persona:menus:legacy-layout').checked = sidebarSwapped; + try { + const parsed = JSON.parse(autohideNavbarEnvs) || ['xs', 'sm']; + parsed.forEach((env) => { + const optionEl = document.getElementById('persona:navbar:autohide').querySelector(`option[value="${env}"]`); + optionEl.selected = true; + }); + } catch (e) { + console.warn(e); + } + + if (saveEl) { + saveEl.addEventListener('click', () => { + const themeSettings = settings.helper.serializeForm($(formEl)); + Object.keys(themeSettings).forEach((key) => { + if (key === 'persona:menus:legacy-layout') { + if (themeSettings[key] === 'on') { + Storage.setItem('persona:menus:legacy-layout', 'true'); + } else { + Storage.removeItem('persona:menus:legacy-layout'); + } + + return; + } + + Storage.setItem(key, themeSettings[key]); + }); + + alerts.success('[[success:settings-saved]]'); + }); + } + }; + + return Theme; +}); diff --git a/templates/account/controversial.tpl b/templates/account/controversial.tpl new file mode 100644 index 0000000..21b7d5b --- /dev/null +++ b/templates/account/controversial.tpl @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/account/edit/email.tpl b/templates/account/edit/email.tpl deleted file mode 100644 index f423637..0000000 --- a/templates/account/edit/email.tpl +++ /dev/null @@ -1,30 +0,0 @@ -
+ {buildAvatar(history.mutes.user, "sm", true)}
+
+ {history.mutes.user.username}
+
+ — {../timestampReadable}
+ {{{ if ../until }}}
+ [[user:info.muted-until, {../untilReadable}]]
+ {{{ end }}}
+
+ [[user:info.banned-reason-label]]: {../reason}
+