Merge branch 'v12.x'

main
落雨楓 2 years ago
commit ba39ceabe0

@ -0,0 +1,3 @@
{
"extends": "nodebb/lib"
}

@ -1,7 +1,9 @@
Persona theme for NodeBB 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 ## Addons

@ -1,4 +1,10 @@
{ {
"mobile-menu-side": "Switch which side each mobile menu is on", "settings.title": "Theme settings",
"post-quick-reply": "Post quick reply" "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)"
} }

@ -32,7 +32,7 @@
margin-bottom: 1em; margin-bottom: 1em;
background-origin: content-box; background-origin: content-box;
width: 100%; width: 100%;
top: 50px; top: calc(var(--panel-offset) - 20px);
position: absolute; position: absolute;
left: auto; left: auto;
right: 0px; right: 0px;
@ -45,7 +45,7 @@
border: 4px solid white; border: 4px solid white;
border-radius: 50%; border-radius: 50%;
.fab.btn-morph { .persona-fab.btn-morph {
top: 93px; top: 93px;
right: 4px; right: 4px;
position: absolute; position: absolute;
@ -77,7 +77,7 @@
&:hover { &:hover {
.controls { .controls {
.opacity(0.8); opacity: 0.8;
} }
} }
@ -85,7 +85,7 @@
text-align: center; text-align: center;
height: 200px; height: 200px;
line-height: 200px; line-height: 200px;
.opacity(0); opacity: 0;
.transition(opacity .15s linear); .transition(opacity .15s linear);
cursor: pointer; cursor: pointer;
pointer-events: none; pointer-events: none;
@ -317,7 +317,7 @@
background-color: lighten(@brand-primary, 10%); background-color: lighten(@brand-primary, 10%);
} }
.fab { .persona-fab {
color: white; color: white;
font-size: 20px; font-size: 20px;
} }

@ -1,7 +1,5 @@
// Make chats page edge-to-edge // Make chats page edge-to-edge
.page-user-chats { .page-user-chats {
padding-top: 50px;
#content.container { #content.container {
width: auto; width: auto;
padding: 0; padding: 0;
@ -12,7 +10,6 @@
} }
#panel { #panel {
padding-top: 0px;
padding-bottom: 0px; padding-bottom: 0px;
} }
@ -25,31 +22,21 @@
.chats-full, .chat-modal { .chats-full, .chat-modal {
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
height: calc(100vh - var(--panel-offset));
[component="chat/nav-wrapper"] { [component="chat/nav-wrapper"] {
flex: 1; flex: 1;
flex-direction: column; flex-direction: column;
box-shadow: 0 3px 9px rgba(0,0,0,.5); padding: 0px 15px;
.chats-list { .chats-list {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
margin-bottom: 0; margin-bottom: 0;
height: ~"calc(100% - 3em)"; height: ~"calc(100% - 5em)";
} }
.chat-search { .chat-search {
background-color: @panel-default-heading-bg; padding-bottom: 15px;
border-bottom: 1px solid @gray-dark;
input {
background-color: @gray-dark;
color: @gray-lighter;
border-radius: 0;
border: none;
height: ~"calc(3em - 2px)";
}
ul { ul {
width: 100%; width: 100%;
} }
@ -59,7 +46,6 @@
padding: 0; padding: 0;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
border-top: 1px solid @gray-lighter;
li { li {
position: relative; position: relative;
@ -71,8 +57,7 @@
border-left: 1px solid; border-left: 1px solid;
border-right: 1px solid; border-right: 1px solid;
border-bottom: 1px solid; border-bottom: 1px solid;
border-color: #eee; border-color: @gray-lighter;
background: #fff;
i { i {
position: relative; position: relative;
@ -93,7 +78,7 @@
[component="chat/main-wrapper"] { [component="chat/main-wrapper"] {
flex: 3; flex: 3;
padding-bottom: 15px;
.alert { .alert {
margin: 1em; margin: 1em;
} }
@ -111,9 +96,7 @@
[component="chat/header"] { [component="chat/header"] {
padding: @panel-heading-padding; padding: @panel-heading-padding;
background-color: @gray-dark; border-bottom: 1px solid @modal-header-border-color;
border-bottom: none;
color: @gray-lighter;
span { span {
font-weight: 500; font-weight: 500;
@ -121,13 +104,11 @@
.close { .close {
margin-left: 0.5em; margin-left: 0.5em;
color: @gray-lighter;
} }
.members { .members {
a { a {
font-weight: 600; font-weight: 600;
color: @gray-lighter;
} }
} }
@ -163,7 +144,7 @@
[component="chat/message/remaining"] { [component="chat/message/remaining"] {
position: absolute; position: absolute;
right: 5.25em; right: 10em;
z-index: 2; z-index: 2;
bottom: 0; bottom: 0;
color: @gray-light; color: @gray-light;
@ -175,11 +156,6 @@
margin-top: 10px; margin-top: 10px;
} }
.chat-list {
margin-top: -6px;
margin-left: -1px;
}
.chats-list { .chats-list {
padding: 0; padding: 0;
overflow-x: hidden; overflow-x: hidden;
@ -193,10 +169,10 @@
} }
> li { > li {
display: flex;
position: relative; position: relative;
clear: both; clear: both;
list-style-type: none; list-style-type: none;
padding: 0.5em;
height: 80px; height: 80px;
.pointer; .pointer;
@ -214,18 +190,14 @@
&.unread { &.unread {
background: lighten(@brand-primary, 35%); background: lighten(@brand-primary, 35%);
border-bottom: 0; border-bottom: 0;
} }
.teaser-content { .teaser-content {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 13px; font-size: 13px;
opacity: 0.8; opacity: 0.8;
} }
.notification-chat-content {
.room-name { .room-name {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@ -239,9 +211,6 @@
display: inline-block; display: inline-block;
} }
} }
.teaser-content, .room-name {
padding-left: 80px;
} }
&.bg-primary { &.bg-primary {
@ -263,7 +232,8 @@
.teaser-timestamp { .teaser-timestamp {
font-size: 10px; font-size: 10px;
margin-top: 10px; margin-top: .5rem;
margin-right: .5rem;
} }
a { a {
@ -325,15 +295,12 @@
} }
.main-avatar { .main-avatar {
position: absolute;
top: 0px;
left: 0px;
.avatar { .avatar {
height: 8rem; height: 8rem;
width: 8rem; width: 8rem;
font-size: 4rem; font-size: 4rem;
border-radius: 0; border-radius: 0;
background: @gray-lighter;
} }
} }
@ -613,6 +580,13 @@
[component="chat/nav-wrapper"][data-loaded="0"] + [component="chat/main-wrapper"] { [component="chat/nav-wrapper"][data-loaded="0"] + [component="chat/main-wrapper"] {
display: none; display: none;
} }
.chats-full, .chat-modal {
height: calc(100vh - var(--panel-offset));
}
[component="chat/messages"] {
width: calc(100vw);
}
} }
[data-action="pop-out"] { [data-action="pop-out"] {

@ -22,14 +22,14 @@
margin-bottom: 1em; margin-bottom: 1em;
background-origin: content-box; background-origin: content-box;
width: 100%; width: 100%;
top: 50px; top: calc(var(--panel-offset) - 20px);
position: absolute; position: absolute;
left: auto; left: auto;
right: 0px; right: 0px;
&:hover { &:hover {
.controls { .controls {
.opacity(0.8); opacity: 0.8;
} }
} }
@ -37,7 +37,7 @@
text-align: center; text-align: center;
min-height: 200px; min-height: 200px;
line-height: 200px; line-height: 200px;
.opacity(0); opacity: 0;
.transition(opacity .15s linear); .transition(opacity .15s linear);
cursor: pointer; cursor: pointer;
pointer-events: none; pointer-events: none;

@ -1,26 +1,39 @@
.header, .slideout-menu { .header, .slideout-menu {
.notifications.dropdown, .chats.dropdown {
.dropdown-menu {
padding: 0;
}
}
.notification-list { .notification-list {
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
max-height: 250px; max-height: 250px;
padding: 0; padding: 0;
color: @gray-dark;
li.no-notifs {
text-align: center;
}
li { li {
display: flex;
gap: 1rem;
width: 400px; width: 400px;
text-align: left; text-align: left;
list-style-type: none; list-style-type: none;
padding: 0.5em; padding: .75em 0.5em;
clear: both;
&.loading-text { &:hover {
text-align: center; background-color: @dropdown-link-hover-bg;
}
&.loading-text, &.no-notifs {
justify-content: center;
a {
color: inherit;
}
} }
.notification-chat-content {
flex: 1;
a { a {
white-space: normal; white-space: normal;
margin: 0; margin: 0;
@ -33,6 +46,18 @@
min-height: 32px; min-height: 32px;
} }
} }
}
.notification-chat-controls {
display: flex;
flex-direction: column;
align-items: center;
gap: .5rem;
.relTime {
font-size: 10px;
}
}
.mark-read { .mark-read {
color: @text-muted; color: @text-muted;
@ -53,7 +78,11 @@
} }
&.unread { &.unread {
.bg-variant(@state-warning-bg); background-color: @state-warning-bg;
&:hover {
background-color: darken(@state-warning-bg, 5%);
}
.mark-read:after { .mark-read:after {
font-weight: 900; font-weight: 900;
@ -68,22 +97,24 @@
> li { > li {
.pointer; .pointer;
width: 500px; width: 500px;
padding-bottom: 1rem; padding: 0;
margin: 0; margin: 0;
gap: 0;
overflow-y: hidden;
.notification-chat-content {
padding: 0.5rem;
overflow: hidden;
text-overflow: ellipsis;
}
.teaser-content { .teaser-content {
white-space: nowrap; white-space: nowrap;
max-height: 19px; max-height: 19px;
padding-left: 90px;
padding-right: 10px;
}
&:hover {
background: @gray-lighter;
} }
&:not(:last-child) { &:not(:last-child) {
border-bottom: 1px solid @gray-lighter; border-bottom: 1px solid @dropdown-border;
} }
&.no_active a { &.no_active a {
@ -173,15 +204,20 @@
} }
.notif-dropdown-link { .notif-dropdown-link {
// margin-top: 1em;
border-top: 1px solid rgba(163, 163, 163, 0.5); border-top: 1px solid rgba(163, 163, 163, 0.5);
padding: 0 5px 0 5px; .btn-group-justified {
table-layout: auto;
}
a { a {
display: block;
text-align: center; text-align: center;
padding: 0.5em 0; padding: 0.5em 0.5em;
font-weight: 600; 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; 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);
}
}

@ -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) { .border-radius (@radius: 5px) {
-webkit-border-radius: @radius; -webkit-border-radius: @radius;
-moz-border-radius: @radius; -moz-border-radius: @radius;
@ -65,7 +58,7 @@
margin-left: -2.5rem; margin-left: -2.5rem;
} }
.avatar, .timeline-badge { .icon .avatar, .timeline-badge {
// Opaque ring // Opaque ring
position: relative; position: relative;
z-index: 1; z-index: 1;
@ -107,6 +100,10 @@
&+.timeline-event:before { &+.timeline-event:before {
display: none; display: none;
} }
.timeline-text.timeago {
display: none;
}
} }
} }
@ -152,6 +149,8 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex-shrink: 0;
width: 32px; width: 32px;
height: 32px; height: 32px;
padding: 0; padding: 0;

@ -34,15 +34,9 @@
} }
@media (max-width: @screen-md-max) { @media (max-width: @screen-md-max) {
body {
padding-top: 0;
padding-bottom: 0;
}
#panel { #panel {
background-color: inherit; background-color: inherit;
min-height: 100%; min-height: 100%;
padding-top: 80px;
padding-bottom: 40px; padding-bottom: 40px;
} }
@ -59,20 +53,59 @@
html[data-dir="rtl"] button& { html[data-dir="rtl"] button& {
margin-left: 0; margin-left: 0;
} }
&#mobile-menu {
.unread-count::after {
left: 32px;
}
}
.header & .notification-icon { .header & .notification-icon {
left: auto; left: auto;
right: 7px; right: 7px;
top: 10px; top: 10px;
&[component="notifications/icon"] {
right: 41px;
}
&.unread-count::after { &.unread-count::after {
position: static; position: static;
} }
} }
} }
.navbar-header .navbar-search {
input[name="term"] {
width: 150px;
}
#menu { padding: 4px 0px 4px 0px;
padding-top: 100px; }
#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 { .slideout-menu {
@ -141,10 +174,18 @@
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.teaser-timestamp {
font-size: 10px;
margin-right: 0.5rem;
margin-top: 0.5rem;
}
} }
} }
.menu-section { .menu-section {
.notification-list-mobile li .text {
display: block;
}
.chat-list, .notification-list-mobile { .chat-list, .notification-list-mobile {
.user-link { .user-link {
display: inline; display: inline;
@ -152,6 +193,15 @@
.unread { .unread {
background-color: inherit; background-color: inherit;
} }
.notification-chat-content {
padding-top: 10px;
padding-right: 20px;
}
.notification-chat-controls {
display: none;
}
} }
.chat-list .unread .room-name::after, .chat-list .unread .room-name::after,
.notification-list-mobile .unread a::after { .notification-list-mobile .unread a::after {
@ -171,7 +221,6 @@
font-style: normal; font-style: normal;
&:after { &:after {
left: 5px;
top: -1px; top: -1px;
padding: 3px 7px; padding: 3px 7px;
background: #333; background: #333;
@ -190,10 +239,6 @@
margin: 0; margin: 0;
} }
.menu-section {
margin: 25px 0;
}
.menu-section-title { .menu-section-title {
text-transform: uppercase; text-transform: uppercase;
color: #85888d; color: #85888d;
@ -206,7 +251,7 @@
.menu-section-list { .menu-section-list {
padding: 0; padding: 0;
margin: 10px 0; margin: 0;
list-style: none; list-style: none;
a, button { a, button {

@ -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); 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; background-color: @brand-primary;
@ -11,11 +11,11 @@
width: 55.5px; 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); 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; padding: 0;
&.heart { &.heart {

@ -6,14 +6,8 @@
.taskbar { .taskbar {
display: none; display: none;
z-index: @zindex-popover; left: 15px;
left: auto; right: auto;
// Bootswatch fix
&.navbar-fixed-bottom {
z-index: @zindex-popover;
left: auto;
}
margin-top: 0; margin-top: 0;
.transition(.15s ease-in opacity); .transition(.15s ease-in opacity);
@ -35,11 +29,14 @@
} }
.navbar-nav { .navbar-nav {
float: unset;
display: flex;
flex-direction: column;
padding-right: 15px; padding-right: 15px;
padding-bottom: 15px; padding-bottom: 15px;
li { li {
float: left; margin-top: 1rem;
&.new a { &.new a {
-webkit-animation-name: bounceIn; -webkit-animation-name: bounceIn;
@ -162,7 +159,6 @@
&.taskbar-composer, &.taskbar-chat { &.taskbar-composer, &.taskbar-chat {
a { a {
text-align: center; text-align: center;
margin-left: 15px;
i { i {
font-size: 1.8rem; font-size: 1.8rem;

@ -57,7 +57,7 @@
} }
} }
.fab.btn-morph { .persona-fab.btn-morph {
top: 75px; top: 75px;
right: 15px; right: 15px;
position: absolute; position: absolute;

@ -64,7 +64,6 @@
font-weight: 400; font-weight: 400;
line-height: 1.42857143; line-height: 1.42857143;
white-space: nowrap; white-space: nowrap;
color: @gray-dark;
} }
} }
.quick-search-title { .quick-search-title {
@ -74,10 +73,18 @@
.snippet { .snippet {
word-break: break-word; word-break: break-word;
white-space: normal; white-space: normal;
font-size: initial;
} }
} }
} }
@media (max-width: @screen-xs-max) {
.quick-search-container {
left: 0px;
right: 0px;
}
}
.quick-search-results, .search-results { .quick-search-results, .search-results {
.post-info { .post-info {
font-size: 12px; font-size: 12px;

@ -4,18 +4,11 @@ html {
} }
body { body {
@media (min-width: 979px) min-height: 100%;
{
padding-top: 70px;
} }
@media (max-width: 979px) #panel {
{ padding-top: var(--panel-offset);
padding-top: 70px;
padding-bottom: 50px;
}
min-height: 100%;
} }
@media (max-width: @screen-xs-max) { @media (max-width: @screen-xs-max) {
@ -273,6 +266,10 @@ a.permalink {
} }
} }
.deco-none, .deco-none:link, .deco-none:hover {
color: inherit;
text-decoration: inherit;
}
.disabled a { .disabled a {
pointer-events: none; pointer-events: none;

@ -30,7 +30,8 @@
.tag { .tag {
text-transform: uppercase; text-transform: uppercase;
font-size: 10px; font-size: 10px;
background: lighten(@gray-lighter, 2.5%); background: #e9ecef;
color: #7a8288;
padding: 5px; padding: 5px;
white-space: nowrap; white-space: nowrap;
} }

@ -41,7 +41,7 @@
} }
.topic-header { .topic-header {
position: sticky; position: sticky;
top: @navbar-height; top: calc(var(--panel-offset) - 20px);
background-color: @body-bg; background-color: @body-bg;
z-index: @zindex-navbar; z-index: @zindex-navbar;
margin-left: -15px; margin-left: -15px;
@ -163,20 +163,18 @@
display: inline-block; display: inline-block;
padding: 1rem; padding: 1rem;
&:first-child {
padding-right: 0.5rem;
}
&:last-child {
padding-left: 0.5rem;
}
&:focus { &:focus {
text-decoration: none; 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; color: @brand-primary;
} }
@ -188,7 +186,6 @@
[component="post/parent"] { [component="post/parent"] {
border: 0; border: 0;
font-size: 10px; font-size: 10px;
background-color: #f0f0f0;
} }
.threaded-replies { .threaded-replies {
@ -374,6 +371,7 @@
} }
.quick-reply { .quick-reply {
position: relative;
.icon { .icon {
position: relative; position: relative;
border-radius: 50%; border-radius: 50%;
@ -421,7 +419,7 @@
.topic { .topic {
&.deleted { &.deleted {
.opacity(0.3); opacity: 0.3;
.votes { .votes {
display: none; display: none;
@ -446,7 +444,7 @@
&.deleted { &.deleted {
> .content { > .content {
.opacity(0.3); opacity: 0.3;
} }
.votes { .votes {
@ -661,6 +659,16 @@
z-index: 1; 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) { @media screen and (min-width: @screen-sm-min) {
.fork-thread-card { .fork-thread-card {
max-width: 33%; max-width: 33%;

@ -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);
};

@ -1,43 +1,67 @@
'use strict'; 'use strict';
var meta = require.main.require('./src/meta'); const meta = require.main.require('./src/meta');
var user = require.main.require('./src/user'); 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) { const library = module.exports;
var app = params.router;
var middleware = params.middleware;
app.get('/admin/plugins/persona', middleware.admin.buildHeader, renderAdmin); library.init = async function (params) {
app.get('/api/admin/plugins/persona', renderAdmin); 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({ header.plugins.push({
route: '/plugins/persona', route: '/plugins/persona',
icon: 'fa-paint-brush', icon: 'fa-paint-brush',
name: 'Persona Theme' name: 'Persona Theme',
});
return 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,
},
}); });
callback(null, header); return data;
}; };
library.defineWidgetAreas = function(areas, callback) { library.defineWidgetAreas = async function (areas) {
const locations = ['header', 'sidebar', 'footer']; const locations = ['header', 'sidebar', 'footer'];
const templates = [ const templates = [
'categories.tpl', 'category.tpl', 'topic.tpl', 'users.tpl', '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) { function capitalizeFirst(str) {
return str.charAt(0).toUpperCase() + str.slice(1) return str.charAt(0).toUpperCase() + str.slice(1);
} }
templates.forEach(template => { templates.forEach((template) => {
locations.forEach(location => { locations.forEach((location) => {
areas.push({ areas.push({
name: capitalizeFirst(template.split('.')[0]) + ' ' + capitalizeFirst(location), name: `${capitalizeFirst(template.split('.')[0])} ${capitalizeFirst(location)}`,
template: template, template: template,
location: location, location: location,
}); });
@ -46,13 +70,22 @@ library.defineWidgetAreas = function(areas, callback) {
areas = areas.concat([ areas = areas.concat([
{ {
name: "Account Header", name: 'Main post header',
template: "account/profile.tpl", template: 'topic.tpl',
location: "header" location: 'mainpost-header',
},
{
name: 'Main post footer',
template: 'topic.tpl',
location: 'mainpost-footer',
},
{
name: 'Account Header',
template: 'account/profile.tpl',
location: 'header',
}, },
]); ]);
return areas;
callback(null, areas);
}; };
library.getThemeConfig = async function (config) { library.getThemeConfig = async function (config) {
@ -63,11 +96,9 @@ library.getThemeConfig = async function(config) {
return config; return config;
}; };
function renderAdmin(req, res, next) {
res.render('admin/plugins/persona', {});
}
library.addUserToTopic = async function (hookData) { library.addUserToTopic = async function (hookData) {
const settings = await meta.settings.get('persona');
if (settings.enableQuickReply === 'on') {
if (hookData.req.user) { if (hookData.req.user) {
const userData = await user.getUserData(hookData.req.user.uid); const userData = await user.getUserData(hookData.req.user.uid);
hookData.templateData.loggedInUser = userData; hookData.templateData.loggedInUser = userData;
@ -80,6 +111,8 @@ library.addUserToTopic = async function (hookData) {
'icon:bgColor': '#aaa', 'icon:bgColor': '#aaa',
}; };
} }
}
return hookData; return hookData;
}; };

@ -1,14 +1,17 @@
{ {
"name": "nodebb-theme-persona", "name": "nodebb-theme-persona",
"version": "11.2.2", "version": "12.1.15",
"nbbpm": { "nbbpm": {
"compatibility": "^1.18.0" "compatibility": "^2.0.0"
}, },
"description": "Persona theme for NodeBB", "description": "Persona theme for NodeBB",
"main": "library.js", "main": "library.js",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/psychobunny/nodebb-theme-persona" "url": "https://github.com/NodeBB/nodebb-theme-persona"
},
"scripts": {
"lint": "eslint ."
}, },
"keywords": [ "keywords": [
"nodebb", "nodebb",
@ -20,26 +23,31 @@
"contributors": [ "contributors": [
{ {
"name": "Andrew Rodrigues", "name": "Andrew Rodrigues",
"email": "andrew@designcreateplay.com", "email": "andrew@nodebb.org",
"url": "https://github.com/psychobunny" "url": "https://github.com/psychobunny"
}, },
{ {
"name": "Julian Lam", "name": "Julian Lam",
"email": "julian@designcreateplay.com", "email": "julian@nodebb.org",
"url": "https://github.com/julianlam" "url": "https://github.com/julianlam"
}, },
{ {
"name": "Barış Soner Uşaklı", "name": "Barış Soner Uşaklı",
"email": "baris@designcreateplay.com", "email": "baris@nodebb.org",
"url": "https://github.com/barisusakli" "url": "https://github.com/barisusakli"
} }
], ],
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"bugs": { "bugs": {
"url": "https://github.com/psychobunny/nodebb-theme-persona/issues" "url": "https://github.com/NodeBB/nodebb-theme-persona/issues"
}, },
"dependencies": { "dependencies": {
"pulling": "^2.0.0", "pulling": "^2.0.0",
"striptags": "^3.2.0" "striptags": "^3.2.0"
},
"devDependencies": {
"eslint": "^7.32.0",
"eslint-config-nodebb": "^0.0.2",
"eslint-plugin-import": "^2.24.2"
} }
} }

@ -5,18 +5,17 @@
{ "hook": "filter:config.get", "method": "getThemeConfig" }, { "hook": "filter:config.get", "method": "getThemeConfig" },
{ "hook": "static:app.load", "method": "init" }, { "hook": "static:app.load", "method": "init" },
{ "hook": "filter:admin.header.build", "method": "addAdminNavigation" }, { "hook": "filter:admin.header.build", "method": "addAdminNavigation" },
{ "hook": "filter:user.profileMenu", "method": "addProfileItem" },
{ "hook": "filter:topic.build", "method": "addUserToTopic" } { "hook": "filter:topic.build", "method": "addUserToTopic" }
], ],
"scripts": [ "scripts": [
"public/persona.js",
"public/modules/autohidingnavbar.js", "public/modules/autohidingnavbar.js",
"public/modules/quickreply.js" "public/persona.js"
], ],
"modules": { "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" "languages": "languages"
} }

@ -0,0 +1,3 @@
{
"extends": "nodebb/public"
}

@ -1,22 +1,13 @@
'use strict'; 'use strict';
/* globals $, app */
define('admin/plugins/persona', ['settings'], function (Settings) { define('admin/plugins/persona', ['settings'], function (Settings) {
var ACP = {}; var ACP = {};
ACP.init = function () { ACP.init = function () {
Settings.load('persona', $('.persona-settings')); Settings.load('persona', $('.persona-settings'));
$('#save').on('click', function () { $('#save').on('click', function () {
Settings.save('persona', $('.persona-settings'), function() { Settings.save('persona', $('.persona-settings'));
app.alert({
type: 'success',
alert_id: 'persona-saved',
title: 'Settings Saved',
message: 'Persona settings saved'
});
});
}); });
}; };

@ -1,14 +1,15 @@
"use strict"; 'use strict';
/*globals $, app, ajaxify, socket*/
define('persona/quickreply', [ define('persona/quickreply', [
'components', 'composer/autocomplete', 'api' 'components', 'composer', 'composer/autocomplete', 'api',
], function(components, autocomplete, api) { 'alerts', 'uploadHelpers', 'mousetrap',
], function (
components, composer, autocomplete, api,
alerts, uploadHelpers, mousetrap
) {
var QuickReply = {}; var QuickReply = {};
QuickReply.init = function () { QuickReply.init = function () {
var element = components.get('topic/quickreply/text'); var element = components.get('topic/quickreply/text');
var data = { var data = {
element: element, element: element,
@ -16,40 +17,85 @@ define('persona/quickreply', [
options: { options: {
style: { style: {
'z-index': 100, 'z-index': 100,
} },
// listPosition: function(position) { // listPosition: function(position) {
// this.$el.css(this._applyPlacement(position)); // this.$el.css(this._applyPlacement(position));
// this.$el.css('position', 'absolute'); // this.$el.css('position', 'absolute');
// return this; // return this;
// } // }
} },
}; };
$(window).trigger('composer:autocomplete:init', data); $(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); // data.element.textcomplete(data.strategies, data.options);
// $('.textcomplete-wrapper').css('height', '100%').find('textarea').css('height', '100%'); // $('.textcomplete-wrapper').css('height', '100%').find('textarea').css('height', '100%');
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) { components.get('topic/quickreply/button').on('click', function (e) {
e.preventDefault(); e.preventDefault();
if (!ready) {
return;
}
var replyMsg = components.get('topic/quickreply/text').val(); var replyMsg = components.get('topic/quickreply/text').val();
var replyData = { var replyData = {
tid: ajaxify.data.tid, tid: ajaxify.data.tid,
handle: undefined, handle: undefined,
content: replyMsg content: replyMsg,
}; };
ready = false;
api.post(`/topics/${ajaxify.data.tid}`, replyData, function (err, data) { api.post(`/topics/${ajaxify.data.tid}`, replyData, function (err, data) {
ready = true;
if (err) { if (err) {
return app.alertError(err.message); return alerts.error(err);
} }
if (data && data.queued) { 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(''); 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; return QuickReply;

@ -1,35 +1,36 @@
"use strict"; 'use strict';
/*globals ajaxify, config, utils, app, socket, window, document, $*/
$(document).ready(function () { $(document).ready(function () {
setupNProgress(); setupNProgress();
setupTaskbar();
setupEditedByIcon(); setupEditedByIcon();
setupMobileMenu();
setupQuickReply(); setupQuickReply();
configureNavbarHiding(); configureNavbarHiding();
fixHeaderPadding(); updatePanelOffset();
$(window).on('resize', utils.debounce(configureNavbarHiding, 200)); $(window).on('resize', utils.debounce(configureNavbarHiding, 200));
$(window).on('resize', fixHeaderPadding); $(window).on('resize', updatePanelOffset);
$(window).on('action:app.loggedIn', function () { function updatePanelOffset() {
setupMobileMenu(); const headerEl = document.getElementById('header-menu');
});
$(window).on('action:app.load', function () { if (!headerEl) {
setupTaskbar(); console.warn('[persona/updatePanelOffset] Could not find #header-menu, panel offset unchanged.');
setupMobileMenu(); return;
});
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);
}
} }
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 = ''; var lastBSEnv = '';
@ -37,17 +38,38 @@ $(document).ready(function () {
if (!$.fn.autoHidingNavbar) { if (!$.fn.autoHidingNavbar) {
return; return;
} }
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(); var env = utils.findBootstrapEnvironment();
// if env didn't change don't destroy and recreate // if env didn't change don't destroy and recreate
if (env === lastBSEnv) { if (env === lastBSEnv) {
return; return;
} }
lastBSEnv = env; lastBSEnv = env;
var navbarEl = $(".navbar-fixed-top"); var navbarEl = $('.navbar-fixed-top');
navbarEl.autoHidingNavbar('destroy').removeData('plugin_autoHidingNavbar'); navbarEl.autoHidingNavbar('destroy').removeData('plugin_autoHidingNavbar');
navbarEl.css('top', ''); navbarEl.css('top', '');
if (env === 'xs' || env === 'sm') { hooks
.on('filter:navigator.scroll', (data) => {
navbarEl.autoHidingNavbar('setDisableAutohide', true);
return data;
})
.on('action:navigator.scrolled', () => {
navbarEl.autoHidingNavbar('setDisableAutohide', false);
});
hooks.fire('filter:persona.configureNavbarHiding', {
resizeEnvs: preference,
}).then(({ resizeEnvs }) => {
if (resizeEnvs.includes(env)) {
navbarEl.autoHidingNavbar({ navbarEl.autoHidingNavbar({
showOnBottom: false, showOnBottom: false,
}); });
@ -73,6 +95,8 @@ $(document).ready(function () {
.on('hide.autoHidingNavbar', function () { .on('hide.autoHidingNavbar', function () {
fixTopCss('0px'); fixTopCss('0px');
}); });
});
});
} }
function setupNProgress() { function setupNProgress() {
@ -155,7 +179,8 @@ $(document).ready(function () {
function setupEditedByIcon() { function setupEditedByIcon() {
function activateEditedTooltips() { function activateEditedTooltips() {
$('[data-pid] [component="post/editor"]').each(function () { $('[data-pid] [component="post/editor"]').each(function () {
var el = $(this), icon; var el = $(this);
var icon;
if (!el.attr('data-editor')) { if (!el.attr('data-editor')) {
return; return;
@ -184,14 +209,14 @@ $(document).ready(function () {
return; return;
} }
require(['pulling', 'storage'], function (Pulling, Storage) { require(['pulling/build/pulling-drawer', 'storage', 'alerts', 'search'], function (Pulling, Storage, alerts, search) {
if (!Pulling) { if (!Pulling) {
return; return;
} }
// initialization // 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 swapped = !!Storage.getItem('persona:menus:legacy-layout');
var margin = window.innerWidth; var margin = window.innerWidth;
@ -244,8 +269,9 @@ $(document).ready(function () {
$(window).on('resize action:ajaxify.start', function () { $(window).on('resize action:ajaxify.start', function () {
navSlideout.close(); navSlideout.close();
if (chatsSlideout) { chatsSlideout.close(); } if (chatsSlideout) {
$('.account .cover').css('top', $('[component="navbar"]').height()); chatsSlideout.close();
}
}); });
navSlideout navSlideout
@ -272,14 +298,6 @@ $(document).ready(function () {
navSlideout.enable().toggle(); navSlideout.enable().toggle();
}); });
function loadNotifications() {
require(['notifications'], function (notifications) {
notifications.loadNotifications($('#menu [data-section="notifications"] ul'));
});
}
navSlideout.on('opened', loadNotifications);
if (chatMenuVisible) { if (chatMenuVisible) {
navSlideout.on('beforeopen', function () { navSlideout.on('beforeopen', function () {
chatsSlideout.close(); chatsSlideout.close();
@ -289,23 +307,30 @@ $(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) { socket.on('event:user_status_change', function (data) {
if (parseInt(data.uid, 10) === app.user.uid) { 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(); navSlideout.close();
} }
}); });
// right slideout chats menu // right slideout notifications & chats menu
function loadChats() { function loadNotificationsAndChats() {
require(['chat'], function (chat) { 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')); chat.loadChatsDropdown($('#chats-menu .chat-list'));
}); });
});
} }
if (chatMenuVisible) { if (chatMenuVisible) {
@ -318,7 +343,7 @@ $(document).ready(function () {
}); });
chatsSlideout chatsSlideout
.on('opened', loadChats) .on('opened', loadNotificationsAndChats)
.on('beforeopen', function () { .on('beforeopen', function () {
navSlideout.close().disable(); navSlideout.close().disable();
}) })
@ -327,31 +352,35 @@ $(document).ready(function () {
}); });
} }
// add a checkbox in the user settings page const searchInputEl = $('.navbar-header .navbar-search input[name="term"]');
// so users can swap the sides the menus appear on const searchButton = $('.navbar-header .navbar-search button[type="button"]');
searchButton.off('click').on('click', function () {
function setupSetting() { if (!config.loggedIn && !app.user.privileges['search:content']) {
if (ajaxify.data.template['account/settings'] && !document.getElementById('persona:menus:legacy-layout')) { alerts.alert({
require(['translator'], function (translator) { message: '[[error:search-requires-login]]',
translator.translate('[[persona:mobile-menu-side]]', function (translated) { timeout: 3000,
$('<div class="well checkbox"><label><input type="checkbox" id="persona:menus:legacy-layout"/><strong>' + translated + '</strong></label></div>')
.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');
}
});
}); });
}) ajaxify.go('login');
} return false;
} }
$(window).on('action:ajaxify.end', setupSetting); searchButton.addClass('hidden');
setupSetting(); 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) { function setupFavouriteMorph(parent, uid, username) {
require(['api'], function (api) { require(['api', 'alerts'], function (api, alerts) {
parent.find('.btn-morph').click(function (ev) { parent.find('.btn-morph').click(function (ev) {
var type = $(this).hasClass('plus') ? 'follow' : 'unfollow'; var type = $(this).hasClass('plus') ? 'follow' : 'unfollow';
var method = $(this).hasClass('plus') ? 'put' : 'del'; var method = $(this).hasClass('plus') ? 'put' : 'del';
api[method]('/users/' + uid + '/follow').then(() => { api[method]('/users/' + uid + '/follow').then(() => {
app.alertSuccess('[[global:alert.' + type + ', ' + username + ']]'); alerts.success('[[global:alert.' + type + ', ' + username + ']]');
}); });
$(this).toggleClass('plus').toggleClass('heart'); $(this).toggleClass('plus').toggleClass('heart');
@ -452,9 +481,9 @@ $(document).ready(function () {
$(this).prepend('<b class="drop"></b>'); $(this).prepend('<b class="drop"></b>');
} }
var drop = $(this).find('b.drop').removeClass('animate'), var drop = $(this).find('b.drop').removeClass('animate');
x = ev.pageX - drop.width() / 2 - $(this).offset().left, var x = ev.pageX - (drop.width() / 2) - $(this).offset().left;
y = ev.pageY - drop.height() / 2 - $(this).offset().top; var y = ev.pageY - (drop.height() / 2) - $(this).offset().top;
drop.css({ top: y + 'px', left: x + 'px' }).addClass('animate'); drop.css({ top: y + 'px', left: x + 'px' }).addClass('animate');
}); });

@ -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;
});

@ -0,0 +1 @@
<!-- IMPORT account/posts.tpl -->

@ -1,30 +0,0 @@
<div class="account">
<!-- IMPORT partials/account/header.tpl -->
<form class="form-horizontal edit-form">
<div class="control-group">
<label class="control-label" for="inputNewEmail">[[user:email]]</label>
<div class="controls">
<input class="form-control" type="text" id="inputNewEmail" placeholder="[[user:email]]" value="{email}">
</div>
</div>
<!-- disables autocomplete on FF --><input type="password" style="display:none">
<!-- IF isSelf -->
<div class="control-group">
<label class="control-label" for="inputCurrentPassword">[[user:current_password]]</label>
<div class="controls">
<input autocomplete="off" class="form-control" type="password" id="inputCurrentPassword" placeholder="[[user:current_password]]" value=""<!-- IF !hasPassword --> disabled<!-- ENDIF !hasPassword -->>
</div>
</div>
<!-- ENDIF isSelf -->
<input type="hidden" name="uid" id="inputUID" value="{uid}" />
<br/>
<div class="form-actions">
<button id="submitBtn" class="btn btn-primary btn-block"><i class="hide fa fa-spinner fa-spin"></i> [[user:change_email]]</button>
</div>
</form>
</div>

@ -146,6 +146,49 @@
<!-- ENDIF history.bans.length --> <!-- ENDIF history.bans.length -->
</div> </div>
</div> </div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
[[user:info.mute-history]]
{{{ if !muted }}}
{{{ if !isSelf }}}
<button class="btn btn-xs pull-right btn-danger" component="account/mute">[[user:mute_account]]</button>
{{{ end }}}
{{{ else }}}
{{{ if !isSelf }}}
<button class="btn btn-xs pull-right btn-success" component="account/unmute">[[user:unmute_account]]</button>
{{{ end }}}
{{{ end }}}
</h3>
</div>
<div class="panel-body">
{{{ if history.mutes.length }}}
<ul class="ban-history">
{{{ each history.mutes }}}
<li>
<p>
<a href="{config.relative_path}/user/{history.mutes.user.userslug}">{buildAvatar(history.mutes.user, "sm", true)}</a>
<strong>
<a href="<!-- IF history.mutes.user.userslug -->{config.relative_path}/user/{history.mutes.user.userslug}<!-- ELSE -->#<!-- ENDIF history.mutes.user.userslug -->" itemprop="author" data-username="{history.mutes.user.username}" data-uid="{history.mutes.user.uid}">{history.mutes.user.username}</a>
</strong>
<span class="timestamp timeago" title="{../timestampISO}"></span> &mdash; {../timestampReadable}<br />
{{{ if ../until }}}
<span class="expiry">[[user:info.muted-until, {../untilReadable}]]</span><br />
{{{ end }}}
<span class="reason"><strong>[[user:info.banned-reason-label]]</strong>: {../reason}</span>
</p>
</li>
{{{end}}}
</ul>
{{{ else }}}
<div class="alert alert-success">[[user:info.no-mute-history]]</div>
{{{ end }}}
</div>
</div>
<!-- IF isAdminOrGlobalModerator --> <!-- IF isAdminOrGlobalModerator -->
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">

@ -18,8 +18,8 @@
<h4>[[user:select-homepage]]</h4> <h4>[[user:select-homepage]]</h4>
<div class="well"> <div class="well">
<div class="form-group"> <div class="form-group">
<label for="dailyDigestFreq">[[user:homepage]]</label> <label for="homePageRoute">[[user:homepage]]</label>
<select class="form-control" data-property="homePageRoute"> <select class="form-control" id="homePageRoute" data-property="homePageRoute">
<option value="none">None</option> <option value="none">None</option>
{{{each homePageRoutes}}} {{{each homePageRoutes}}}
<option value="{homePageRoutes.route}" <!-- IF homePageRoutes.selected -->selected="1"<!-- ENDIF homePageRoutes.selected -->>{homePageRoutes.name}</option> <option value="{homePageRoutes.route}" <!-- IF homePageRoutes.selected -->selected="1"<!-- ENDIF homePageRoutes.selected -->>{homePageRoutes.name}</option>

@ -0,0 +1,27 @@
<div class="account">
<!-- IMPORT partials/account/header.tpl -->
<p>[[persona:settings.intro]]</p>
<hr />
<form id="theme-settings" role="form">
<div class="checkbox">
<label>
<input type="checkbox" id="persona:menus:legacy-layout" name="persona:menus:legacy-layout"> <strong>[[persona:settings.mobile-menu-side]]</strong>
</label>
</div><br />
<div class="form-group">
<label for="persona:navbar:autohide">[[persona:settings.autoHidingNavbar]]</label>
<select multiple class="form-control" name="persona:navbar:autohide" id="persona:navbar:autohide">
<option value="xs">[[persona:settings.autoHidingNavbar-xs]]</option>
<option value="sm">[[persona:settings.autoHidingNavbar-sm]]</option>
<option value="md">[[persona:settings.autoHidingNavbar-md]]</option>
<option value="lg">[[persona:settings.autoHidingNavbar-lg]]</option>
</select>
</div>
<button id="save" type="button" class="btn btn-primary">[[global:save_changes]]</button>
</form>
</div>

@ -6,30 +6,7 @@
<button id="chat-close-btn" type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button> <button id="chat-close-btn" type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<button type="button" class="close hidden-xs hidden-sm" data-action="maximize"><span aria-hidden="true"><i class="fa fa-expand"></i></span><span class="sr-only">[[modules:chat.maximize]]</span></button> <button type="button" class="close hidden-xs hidden-sm" data-action="maximize"><span aria-hidden="true"><i class="fa fa-expand"></i></span><span class="sr-only">[[modules:chat.maximize]]</span></button>
<button type="button" class="close hidden-xs hidden-sm" data-action="minimize"><span aria-hidden="true"><i class="fa fa-minus"></i></span><span class="sr-only">[[modules:chat.minimize]]</span></button> <button type="button" class="close hidden-xs hidden-sm" data-action="minimize"><span aria-hidden="true"><i class="fa fa-minus"></i></span><span class="sr-only">[[modules:chat.minimize]]</span></button>
<div class="dropdown pull-right"> <!-- IMPORT partials/chats/options.tpl -->
<button class="close" data-toggle="dropdown" component="chat/controlsToggle"><i class="fa fa-gear"></i></button>
<ul class="dropdown-menu dropdown-menu-right pull-right" component="chat/controls">
<li class="dropdown-header">[[modules:chat.options]]</li>
<li>
<a href="#" data-action="members"><i class="fa fa-fw fa-cog"></i> [[modules:chat.manage-room]]</a>
</li>
<li>
<a href="#" data-action="rename"><i class="fa fa-fw fa-edit"></i> [[modules:chat.rename-room]]</a>
</li>
<li>
<a href="#" data-action="leave"><i class="fa fa-fw fa-sign-out"></i> [[modules:chat.leave]]</a>
</li>
<!-- IF users.length -->
<li role="separator" class="divider"></li>
<li class="dropdown-header">[[modules:chat.in-room]]</li>
{{{each users}}}
<li>
<a href="{config.relative_path}/uid/{../uid}">{buildAvatar(users, "sm", true)} {../username}</a>
</li>
{{{end}}}
<!-- END -->
</ul>
</div>
<h4 component="chat/room/name"><!-- IF roomName -->{roomName}<!-- ELSE -->{usernames}<!-- ENDIF roomName --></h4> <h4 component="chat/room/name"><!-- IF roomName -->{roomName}<!-- ELSE -->{usernames}<!-- ENDIF roomName --></h4>
</div> </div>
@ -42,10 +19,15 @@
<div component="chat/composer"> <div component="chat/composer">
<textarea component="chat/input" placeholder="[[modules:chat.placeholder]]" class="form-control chat-input mousetrap" rows="1"></textarea> <textarea component="chat/input" placeholder="[[modules:chat.placeholder]]" class="form-control chat-input mousetrap" rows="1"></textarea>
<button component="chat/upload/button" class="btn btn-light" type="button"><i class="fa fa-fw fa-2x fa-upload"></i></button>
<button class="btn btn-primary" type="button" data-action="send"><i class="fa fa-fw fa-2x fa-paper-plane"></i></button> <button class="btn btn-primary" type="button" data-action="send"><i class="fa fa-fw fa-2x fa-paper-plane"></i></button>
<span component="chat/message/remaining">{maximumChatMessageLength}</span> <span component="chat/message/remaining">{maximumChatMessageLength}</span>
<form component="chat/upload" method="post" enctype="multipart/form-data">
<input type="file" name="files[]" multiple class="hidden"/>
</form>
</div> </div>
</div> </div>
<div class="imagedrop"><div>[[topic:composer.drag_and_drop_images]]</div></div>
</div> </div>
</div> </div>
</div> </div>

@ -1,7 +1,7 @@
<div class="chats-full"> <div class="chats-full">
<div component="chat/nav-wrapper" data-loaded="<!-- IF roomId -->1<!-- ELSE -->0<!-- END -->"> <div component="chat/nav-wrapper" data-loaded="<!-- IF roomId -->1<!-- ELSE -->0<!-- END -->">
<div class="chat-search dropdown"> <div class="chat-search dropdown">
<input class="form-control" type="text" component="chat/search" placeholder="[[users:enter_username]]" data-toggle="dropdown" /> <input class="form-control" type="text" component="chat/search" placeholder="[[users:search-user-for-chat]]" data-toggle="dropdown" />
<ul component="chat/search/list" class="dropdown-menu"></ul> <ul component="chat/search/list" class="dropdown-menu"></ul>
</div> </div>
<ul component="chat/recent" class="chats-list" data-nextstart="{nextStart}"> <ul component="chat/recent" class="chats-list" data-nextstart="{nextStart}">
@ -13,4 +13,5 @@
<div component="chat/main-wrapper"> <div component="chat/main-wrapper">
<!-- IMPORT partials/chats/message-window.tpl --> <!-- IMPORT partials/chats/message-window.tpl -->
</div> </div>
<div class="imagedrop"><div>[[topic:composer.drag_and_drop_images]]</div></div>
</div> </div>

@ -60,10 +60,9 @@
<div class="form-group"> <div class="form-group">
<h2 class="h4" for="state">[[flags:state]]</h2> <h2 class="h4" for="state">[[flags:state]]</h2>
<select class="form-control" id="state" name="state" disabled> <select class="form-control" id="state" name="state" disabled>
<option value="open">[[flags:state-open]]</option> {{{ each states }}}
<option value="wip">[[flags:state-wip]]</option> <option value="{@key}">{./label}</option>
<option value="resolved">[[flags:state-resolved]]</option> {{{ end }}}
<option value="rejected">[[flags:state-rejected]]</option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -147,7 +146,14 @@
<li><a href="#" data-action="chat">[[flags:start-new-chat]]</a></li> <li><a href="#" data-action="chat">[[flags:start-new-chat]]</a></li>
{{{ end }}} {{{ end }}}
<li role="separator" class="divider"></li> <li role="separator" class="divider"></li>
{{{ if privileges.ban }}}<li><a href="#" data-action="ban">[[user:ban_account]]</a></li>{{{ end }}} {{{ if privileges.ban }}}
<li class="{{{ if target.user.banned }}}hidden{{{ end }}}"><a href="#" data-action="ban">[[user:ban_account]]</a></li>
<li class="{{{ if !target.user.banned }}}hidden{{{ end }}}"><a href="#" data-action="unban">[[user:unban_account]]</a></li>
{{{ end }}}
{{{ if privileges.mute}}}
<li class="{{{ if target.user.muted }}}hidden{{{ end }}}"><a href="#" data-action="mute">[[user:mute_account]]</a></li>
<li class="{{{ if !target.user.muted }}}hidden{{{ end }}}"><a href="#" data-action="unmute">[[user:unmute_account]]</a></li>
{{{ end }}}
{{{ if privileges.admin:users }}} {{{ if privileges.admin:users }}}
<li><a href="#" data-action="delete-account">[[user:delete_account_as_admin]]</a></li> <li><a href="#" data-action="delete-account">[[user:delete_account_as_admin]]</a></li>
<li><a href="#" data-action="delete-content">[[user:delete_content]]</a></li> <li><a href="#" data-action="delete-content">[[user:delete_content]]</a></li>

@ -1,14 +1,6 @@
</div><!-- /.container#content --> </div><!-- /.container#content -->
</main> </main>
<!-- IF !isSpider --> <!-- IF !isSpider -->
<div class="topic-search hidden">
<div class="btn-group">
<button type="button" class="btn btn-default count"></button>
<button type="button" class="btn btn-default prev"><i class="fa fa-fw fa-angle-up"></i></button>
<button type="button" class="btn btn-default next"><i class="fa fa-fw fa-angle-down"></i></button>
</div>
</div>
<div component="toaster/tray" class="alert-window"> <div component="toaster/tray" class="alert-window">
<div id="reconnect-alert" class="alert alert-dismissable alert-warning clearfix hide" component="toaster/toast"> <div id="reconnect-alert" class="alert alert-dismissable alert-warning clearfix hide" component="toaster/toast">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button> <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
@ -17,10 +9,6 @@
</div> </div>
<!-- ENDIF !isSpider --> <!-- ENDIF !isSpider -->
<div class="hide">
<!-- IMPORT 500-embed.tpl -->
</div>
<!-- IMPORT partials/footer/js.tpl --> <!-- IMPORT partials/footer/js.tpl -->
</body> </body>
</html> </html>

@ -11,6 +11,7 @@
var app = { var app = {
user: JSON.parse('{{userJSON}}') user: JSON.parse('{{userJSON}}')
}; };
document.documentElement.style.setProperty('--panel-offset', `${localStorage.getItem('panelOffset') || 0}px`);
</script> </script>
{{{if useCustomHTML}}} {{{if useCustomHTML}}}

@ -1,8 +1,14 @@
<!-- IMPORT partials/breadcrumbs.tpl --> <!-- IMPORT partials/breadcrumbs.tpl -->
<div data-widget-area="header">
{{{each widgets.header}}}
{{widgets.header.html}}
{{{end}}}
</div>
<div class="row login">
<div class="{{{ if widgets.sidebar.length }}}col-lg-9 col-sm-12{{{ else }}}col-lg-12{{{ end }}}">
<div class="row"> <div class="row">
<!-- IF allowLocalLogin --> {{{ if allowLocalLogin }}}
<div class="<!-- IF alternate_logins -->col-md-6<!-- ELSE -->col-md-12<!-- ENDIF alternate_logins -->"> <div class="{{{ if alternate_logins }}}col-md-6{{{ else }}}col-md-12{{{ end }}}">
<div class="login-block"> <div class="login-block">
<div class="alert alert-danger" id="login-error-notify" <!-- IF error -->style="display:block"<!-- ELSE -->style="display: none;"<!-- ENDIF error -->> <div class="alert alert-danger" id="login-error-notify" <!-- IF error -->style="display:block"<!-- ELSE -->style="display: none;"<!-- ENDIF error -->>
<button type="button" class="close" data-dismiss="alert">&times;</button> <button type="button" class="close" data-dismiss="alert">&times;</button>
@ -58,10 +64,10 @@
</div> </div>
</div> </div>
<!-- ENDIF allowLocalLogin --> {{{ end }}}
<!-- IF alternate_logins --> {{{ if alternate_logins }}}
<div class="<!-- IF allowLocalLogin -->col-md-6<!-- ELSE -->col-md-12<!-- ENDIF allowLocalLogin -->"> <div class="{{{ if allowLocalLogin }}}col-md-6{{{ else }}}col-md-12{{{ end }}}">
<div class="alt-login-block"> <div class="alt-login-block">
<h4>[[login:alternative_logins]]</h4> <h4>[[login:alternative_logins]]</h4>
<ul class="alt-logins"> <ul class="alt-logins">
@ -71,5 +77,17 @@
</ul> </ul>
</div> </div>
</div> </div>
<!-- ENDIF alternate_logins --> {{{ end }}}
</div>
</div>
<div data-widget-area="sidebar" class="col-lg-3 col-sm-12 {{{ if !widgets.sidebar.length }}}hidden{{{ end }}}">
{{{each widgets.sidebar}}}
{{widgets.sidebar.html}}
{{{end}}}
</div>
</div>
<div data-widget-area="footer">
{{{each widgets.footer}}}
{{widgets.footer.html}}
{{{end}}}
</div> </div>

@ -1,3 +1,3 @@
<div id="taskbar" class="taskbar navbar-fixed-bottom"> <div id="taskbar" class="taskbar navbar-fixed-bottom">
<div class="navbar-inner"><ul class="nav navbar-nav pull-right"></ul></div> <div class="navbar-inner"><ul class="nav navbar-nav"></ul></div>
</div> </div>

@ -25,7 +25,7 @@
<span class="human-readable-number">{reputation}</span> <span class="human-readable-number">{reputation}</span>
</div> </div>
<button class="btn-morph fab <!-- IF banned --> hide<!-- ENDIF banned -->"> <button class="btn-morph persona-fab <!-- IF banned --> hide<!-- ENDIF banned -->">
<span> <span>
<span class="s1"></span> <span class="s1"></span>
<span class="s2"></span> <span class="s2"></span>

@ -17,7 +17,7 @@
<!-- IF loggedIn --> <!-- IF loggedIn -->
<!-- IF !isSelf --> <!-- IF !isSelf -->
<button class="btn-morph fab <!-- IF isFollowing -->heart<!-- ELSE -->plus<!-- ENDIF isFollowing -->" title="<!-- IF isFollowing -->[[global:unfollow]]<!-- ELSE -->[[global:follow]]<!-- ENDIF isFollowing -->"> <button class="btn-morph persona-fab <!-- IF isFollowing -->heart<!-- ELSE -->plus<!-- ENDIF isFollowing -->" title="<!-- IF isFollowing -->[[global:unfollow]]<!-- ELSE -->[[global:follow]]<!-- ENDIF isFollowing -->">
<span> <span>
<span class="s1"></span> <span class="s1"></span>
<span class="s2"></span> <span class="s2"></span>

@ -1,5 +1,5 @@
<div class="btn-group account-fab bottom-sheet"> <div class="btn-group account-fab bottom-sheet">
<button type="button" class="fab dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <button type="button" class="persona-fab dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-ellipsis-v"></i> <i class="fa fa-ellipsis-v"></i>
</button> </button>
<ul class="dropdown-menu dropdown-menu-right"> <ul class="dropdown-menu dropdown-menu-right">
@ -27,22 +27,32 @@
<li> <li>
<a href="{config.relative_path}/user/{userslug}" class="inline-block" id="profile">[[user:profile]]</a> <a href="{config.relative_path}/user/{userslug}" class="inline-block" id="profile">[[user:profile]]</a>
</li> </li>
<!-- IF showHidden --> <!-- IF canEdit -->
<li><a href="{config.relative_path}/user/{userslug}/edit">[[user:edit]]</a></li> <li><a href="{config.relative_path}/user/{userslug}/edit">[[user:edit]]</a></li>
<li><a href="{config.relative_path}/user/{userslug}/settings">[[user:settings]]</a></li> <li><a href="{config.relative_path}/user/{userslug}/settings">[[user:settings]]</a></li>
<!-- ENDIF showHidden --> <!-- ENDIF canEdit -->
<!-- IF !isSelf --> <!-- IF !isSelf -->
<!-- IF canBan --> {{{ if (canBan || canMute) }}}
<li role="separator" class="divider"></li> <li role="separator" class="divider"></li>
<li class="dropdown-header">[[user:admin_actions_label]]</li> <li class="dropdown-header">[[user:admin_actions_label]]</li>
{{{ end }}}
{{{ if canBan }}}
<li class="<!-- IF banned -->hide<!-- ENDIF banned -->"> <li class="<!-- IF banned -->hide<!-- ENDIF banned -->">
<a component="account/ban" href="#">[[user:ban_account]]</a> <a component="account/ban" href="#">[[user:ban_account]]</a>
</li> </li>
<li class="<!-- IF !banned -->hide<!-- ENDIF !banned -->"> <li class="<!-- IF !banned -->hide<!-- ENDIF !banned -->">
<a component="account/unban" href="#">[[user:unban_account]]</a> <a component="account/unban" href="#">[[user:unban_account]]</a>
</li> </li>
<!-- ENDIF canBan --> {{{ end }}}
{{{ if canMute }}}
<li class="<!-- IF muted -->hide<!-- ENDIF muted -->">
<a component="account/mute" href="#">[[user:mute_account]]</a>
</li>
<li class="<!-- IF !muted -->hide<!-- ENDIF !muted -->">
<a component="account/unmute" href="#">[[user:unmute_account]]</a>
</li>
{{{ end }}}
<!-- IF isAdmin --> <!-- IF isAdmin -->
<li> <li>
<a component="account/delete-account" href="#" class="">[[user:delete_account_as_admin]]</a> <a component="account/delete-account" href="#" class="">[[user:delete_account_as_admin]]</a>
@ -55,18 +65,19 @@
<li role="separator" class="divider"></li> <li role="separator" class="divider"></li>
<li><a href="{config.relative_path}/user/{userslug}/following">[[user:following]] <span class="badge badge-default pull-right formatted-number" title="{counts.following}">{counts.following}</span></a></li> <li><a href="{config.relative_path}/user/{userslug}/following">[[user:following]] <span class="badge badge-default pull-right formatted-number" title="{counts.following}">{counts.following}</span></a></li>
<li><a href="{config.relative_path}/user/{userslug}/followers">[[user:followers]] <span class="badge badge-default pull-right formatted-number" title="{counts.followers}">{counts.followers}</span></a></li> <li><a href="{config.relative_path}/user/{userslug}/followers">[[user:followers]] <span class="badge badge-default pull-right formatted-number" title="{counts.followers}">{counts.followers}</span></a></li>
<!-- IF showHidden --> <!-- IF canEdit -->
<li><a href="{config.relative_path}/user/{userslug}/blocks">[[user:blocks]] <span class="badge badge-default pull-right formatted-number" title="{counts.blocks}">{counts.blocks}</span></a></li> <li><a href="{config.relative_path}/user/{userslug}/blocks">[[user:blocks]] <span class="badge badge-default pull-right formatted-number" title="{counts.blocks}">{counts.blocks}</span></a></li>
<!-- ENDIF showHidden --> <!-- ENDIF canEdit -->
<li role="separator" class="divider"></li> <li role="separator" class="divider"></li>
<li><a href="{config.relative_path}/user/{userslug}/topics">[[global:topics]] <span class="badge badge-default pull-right formatted-number" title="{counts.topics}">{counts.topics}</span></a></li> <li><a href="{config.relative_path}/user/{userslug}/topics">[[global:topics]] <span class="badge badge-default pull-right formatted-number" title="{counts.topics}">{counts.topics}</span></a></li>
<li><a href="{config.relative_path}/user/{userslug}/posts">[[global:posts]] <span class="badge badge-default pull-right formatted-number" title="{counts.posts}">{counts.posts}</span></a></li> <li><a href="{config.relative_path}/user/{userslug}/posts">[[global:posts]] <span class="badge badge-default pull-right formatted-number" title="{counts.posts}">{counts.posts}</span></a></li>
<!-- IF !reputation:disabled --> <!-- IF !reputation:disabled -->
<li><a href="{config.relative_path}/user/{userslug}/best">[[global:best]] <span class="badge badge-default pull-right formatted-number" title="{counts.best}">{counts.best}</span></a></li> <li><a href="{config.relative_path}/user/{userslug}/best">[[global:best]] <span class="badge badge-default pull-right formatted-number" title="{counts.best}">{counts.best}</span></a></li>
<li><a href="{config.relative_path}/user/{userslug}/controversial">[[global:controversial]] <span class="badge badge-default pull-right formatted-number" title="{counts.controversial}">{counts.controversial}</span></a></li>
<!-- ENDIF !reputation:disabled --> <!-- ENDIF !reputation:disabled -->
<li><a href="{config.relative_path}/user/{userslug}/groups">[[global:header.groups]] <span class="badge badge-default pull-right formatted-number" title="{counts.groups}">{counts.groups}</span></a></li> <li><a href="{config.relative_path}/user/{userslug}/groups">[[global:header.groups]] <span class="badge badge-default pull-right formatted-number" title="{counts.groups}">{counts.groups}</span></a></li>
<!-- IF showHidden --> <!-- IF canEdit -->
<li><a href="{config.relative_path}/user/{userslug}/categories">[[user:watched_categories]] <span class="badge badge-default pull-right formatted-number" title="{counts.categoriesWatched}">{counts.categoriesWatched}</span></a></li> <li><a href="{config.relative_path}/user/{userslug}/categories">[[user:watched_categories]] <span class="badge badge-default pull-right formatted-number" title="{counts.categoriesWatched}">{counts.categoriesWatched}</span></a></li>
<li><a href="{config.relative_path}/user/{userslug}/bookmarks">[[user:bookmarks]] <span class="badge badge-default pull-right formatted-number" title="{counts.bookmarks}">{counts.bookmarks}</span></a></li> <li><a href="{config.relative_path}/user/{userslug}/bookmarks">[[user:bookmarks]] <span class="badge badge-default pull-right formatted-number" title="{counts.bookmarks}">{counts.bookmarks}</span></a></li>
<li><a href="{config.relative_path}/user/{userslug}/watched">[[user:watched]] <span class="badge badge-default pull-right formatted-number" title="{counts.watched}">{counts.watched}</span></a></li> <li><a href="{config.relative_path}/user/{userslug}/watched">[[user:watched]] <span class="badge badge-default pull-right formatted-number" title="{counts.watched}">{counts.watched}</span></a></li>
@ -78,7 +89,7 @@
<!-- ENDIF !downvote:disabled --> <!-- ENDIF !downvote:disabled -->
<!-- ENDIF !reputation:disabled --> <!-- ENDIF !reputation:disabled -->
<li><a href="{config.relative_path}/user/{userslug}/uploads">[[global:uploads]] <span class="badge badge-default pull-right formatted-number" title="{counts.uploaded}">{counts.uploaded}</span></a></li> <li><a href="{config.relative_path}/user/{userslug}/uploads">[[global:uploads]] <span class="badge badge-default pull-right formatted-number" title="{counts.uploaded}">{counts.uploaded}</span></a></li>
<!-- ENDIF showHidden --> <!-- ENDIF canEdit -->
{{{each profile_links}}} {{{each profile_links}}}
<!-- IF @first --> <!-- IF @first -->

@ -3,7 +3,7 @@
{{{each breadcrumbs}}} {{{each breadcrumbs}}}
<li<!-- IF @last --> component="breadcrumb/current"<!-- ENDIF @last --> itemscope="itemscope" itemprop="itemListElement" itemtype="http://schema.org/ListItem" <!-- IF @last -->class="active"<!-- ENDIF @last -->> <li<!-- IF @last --> component="breadcrumb/current"<!-- ENDIF @last --> itemscope="itemscope" itemprop="itemListElement" itemtype="http://schema.org/ListItem" <!-- IF @last -->class="active"<!-- ENDIF @last -->>
<meta itemprop="position" content="{@index}" /> <meta itemprop="position" content="{@index}" />
<!-- IF !@last --><a href="{breadcrumbs.url}" itemprop="item"><!-- ENDIF !@last --> {{{ if ./url }}}<a href="{breadcrumbs.url}" itemprop="item">{{{ end }}}
<span itemprop="name"> <span itemprop="name">
{breadcrumbs.text} {breadcrumbs.text}
<!-- IF @last --> <!-- IF @last -->
@ -11,7 +11,7 @@
<!-- IF rssFeedUrl --><a target="_blank" href="{rssFeedUrl}" itemprop="item"><i class="fa fa-rss-square"></i></a><!-- ENDIF rssFeedUrl --><!-- ENDIF !feeds:disableRSS --> <!-- IF rssFeedUrl --><a target="_blank" href="{rssFeedUrl}" itemprop="item"><i class="fa fa-rss-square"></i></a><!-- ENDIF rssFeedUrl --><!-- ENDIF !feeds:disableRSS -->
<!-- ENDIF @last --> <!-- ENDIF @last -->
</span> </span>
<!-- IF !@last --></a><!-- ENDIF !@last --> {{{ if ./url }}}</a>{{{ end }}}
</li> </li>
{{{end}}} {{{end}}}
</ol> </ol>

@ -6,11 +6,9 @@
<input type="text" class="form-control" autocomplete="off"> <input type="text" class="form-control" autocomplete="off">
</div> </div>
<ul component="category/list" class="dropdown-menu category-dropdown-menu" role="menu"> <ul component="category/list" class="dropdown-menu category-dropdown-menu" role="menu">
{{{ if allCategoriesUrl }}}
<li role="presentation" class="category" data-all="all"> <li role="presentation" class="category" data-all="all">
<a role="menu-item" href="{config.relative_path}/{allCategoriesUrl}"><i component="category/select/icon" class="fa fa-fw fa-check {{{if selectedCategory}}}invisible{{{end}}}"></i> [[unread:all_categories]]</a> <a role="menu-item" href="{config.relative_path}/{allCategoriesUrl}"><i component="category/select/icon" class="fa fa-fw fa-check {{{if selectedCategory}}}invisible{{{end}}}"></i> [[unread:all_categories]]</a>
</li> </li>
{{{ end }}}
{{{each categoryItems}}} {{{each categoryItems}}}
<li role="presentation" class="category {{{ if ../disabledClass }}}disabled{{{ end }}}" data-cid="{../cid}" data-parent-cid="{../parentCid}" data-name="{../name}"> <li role="presentation" class="category {{{ if ../disabledClass }}}disabled{{{ end }}}" data-cid="{../cid}" data-parent-cid="{../parentCid}" data-name="{../name}">
<a role="menu-item" href="#">{../level}<i component="category/select/icon" class="fa fa-fw fa-check {{{ if !../selected }}}invisible{{{ end }}}"></i><span component="category-markup" style="{{{ if ../match }}}font-weight: bold;{{{end}}}">{{{ if ../icon }}}<span class="fa-stack" style="{function.generateCategoryBackground}"><i class="fa fa-fw fa-stack-1x {../icon}" style="color: {../color};"></i></span>{{{ end }}} {../name}</span></a> <a role="menu-item" href="#">{../level}<i component="category/select/icon" class="fa fa-fw fa-check {{{ if !../selected }}}invisible{{{ end }}}"></i><span component="category-markup" style="{{{ if ../match }}}font-weight: bold;{{{end}}}">{{{ if ../icon }}}<span class="fa-stack" style="{function.generateCategoryBackground}"><i class="fa fa-fw fa-stack-1x {../icon}" style="color: {../color};"></i></span>{{{ end }}} {../name}</span></a>

@ -1,6 +1,7 @@
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"> <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<span component="category-selector-selected">{{{ if selectedCategory }}}<span class="fa-stack" style="{function.generateCategoryBackground, selectedCategory}"><i class="fa fa-fw fa-stack-1x {selectedCategory.icon}" style="color: {selectedCategory.color};"></i></span> {selectedCategory.name}{{{ else }}} <span component="category-selector-selected">{{{ if (selectedCategory && !showCategorySelectLabel) }}}<span class="fa-stack" style="{function.generateCategoryBackground, selectedCategory}"><i class="fa fa-fw fa-stack-1x {selectedCategory.icon}" style="color: {selectedCategory.color};"></i></span> {selectedCategory.name}{{{ else }}}
{{{ if selectCategoryLabel }}}{selectCategoryLabel}{{{ else }}}[[topic:thread_tools.select_category]]{{{ end }}}{{{ end }}}</span> <span class="caret"></span> <span class="visible-sm-inline visible-md-inline visible-lg-inline">{{{ if selectCategoryLabel }}}{selectCategoryLabel}{{{ else }}}[[topic:thread_tools.select_category]]{{{ end }}}</span><span class="visible-xs-inline"><i class="fa fa-fw {{{ if selectCategoryIcon }}}{selectCategoryIcon}{{{ else }}}fa-list{{{ end }}}"></i></span>
{{{ end }}}</span> <span class="caret"></span>
</button> </button>
<div component="category-selector-search" class="hidden"> <div component="category-selector-search" class="hidden">
<input type="text" class="form-control" autocomplete="off"> <input type="text" class="form-control" autocomplete="off">

@ -10,5 +10,6 @@
<li><a href="#" class="oldest_to_newest" data-sort="oldest_to_newest"><i class="fa fa-fw"></i> [[topic:oldest_to_newest]]</a></li> <li><a href="#" class="oldest_to_newest" data-sort="oldest_to_newest"><i class="fa fa-fw"></i> [[topic:oldest_to_newest]]</a></li>
<li><a href="#" class="most_posts" data-sort="most_posts"><i class="fa fa-fw"></i> [[topic:most_posts]]</a></li> <li><a href="#" class="most_posts" data-sort="most_posts"><i class="fa fa-fw"></i> [[topic:most_posts]]</a></li>
<li><a href="#" class="most_votes" data-sort="most_votes"><i class="fa fa-fw"></i> [[topic:most_votes]]</a></li> <li><a href="#" class="most_votes" data-sort="most_votes"><i class="fa fa-fw"></i> [[topic:most_votes]]</a></li>
<li><a href="#" class="most_views" data-sort="most_views"><i class="fa fa-fw"></i> [[topic:most_views]]</a></li>
</ul> </ul>
</div> </div>

@ -1,11 +1,41 @@
<!-- IF config.loggedIn --> {{{ if config.loggedIn }}}
<ul class="nav nav-pills">
<li>
<a href="#notifications" data-toggle="tab"><span class="counter unread-count" component="notifications/icon" data-content="{unreadCount.notification}"></span> <i class="fa fa-fw fa-bell"></i></a>
</li>
{{{ if !config.disableChat }}}
<li>
<a href="#chats" data-toggle="tab"><i class="counter unread-count" component="chat/icon" data-content="{unreadCount.chat}"></i> <i class="fa fa-fw fa-comment"></i></a>
</li>
{{{ end }}}
<li class="active">
<a href="#profile" data-toggle="tab">
{buildAvatar(user, "sm", true, "user-icon")}
<i component="user/status" class="fa fa-fw fa-circle status {user.status}"></i>
</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade active in" id="profile">
<section class="menu-section" data-section="profile">
<ul class="menu-section-list" component="header/usercontrol"></ul>
</section>
</div>
<div class="tab-pane fade" id="notifications">
<section class="menu-section" data-section="notifications">
<ul class="menu-section-list notification-list-mobile" component="notifications/list"></ul>
<p class="menu-section-list"><a href="{relative_path}/notifications">[[notifications:see_all]]</a></p>
</section>
</div>
{{{ if !config.disableChat }}}
<div class="tab-pane fade" id="chats">
<section class="menu-section" data-section="chats"> <section class="menu-section" data-section="chats">
<h3 class="menu-section-title">
[[global:header.chats]]
<i class="counter unread-count" component="chat/icon" data-content="{unreadCount.chat}"></i>
</h3>
<ul class="menu-section-list chat-list" component="chat/list"> <ul class="menu-section-list chat-list" component="chat/list">
<a class="navigation-link" href="{relative_path}/user/{user.userslug}/chats">[[modules:chat.see_all]]</a> <a class="navigation-link" href="{relative_path}/user/{user.userslug}/chats">[[modules:chat.see_all]]</a>
</ul> </ul>
</section> </section>
<!-- ENDIF config.loggedIn --> </div>
{{{ end }}}
</div>
{{{ end }}}

@ -1,14 +1,6 @@
<!-- IF rooms.length --> <!-- IF rooms.length -->
{{{each rooms}}} {{{each rooms}}}
<li class="<!-- IF ../unread -->unread<!-- ENDIF ../unread -->" data-roomid="{rooms.roomId}"> <li class="<!-- IF ../unread -->unread<!-- ENDIF ../unread -->" data-roomid="{rooms.roomId}">
<strong class="room-name">
<!-- IF !rooms.lastUser.uid -->
<span>[[modules:chat.no-users-in-room]]</span>
<!-- ELSE -->
<!-- IF rooms.roomName -->{rooms.roomName}<!-- ELSE -->{rooms.usernames}<!-- ENDIF rooms.roomName -->
<!-- ENDIF !rooms.lastUser.uid -->
</strong>
<div class="avatar-placeholder"></div>
{{{each rooms.users}}} {{{each rooms.users}}}
<!-- IF @first --> <!-- IF @first -->
<div class="main-avatar"> <div class="main-avatar">
@ -25,8 +17,20 @@
{{{end}}} {{{end}}}
</ul> </ul>
<span class="teaser-content">{rooms.teaser.content}</span> <div class="notification-chat-content">
<span class="teaser-timestamp pull-right">{rooms.teaser.timeago}</span> <strong class="room-name">
<!-- IF !rooms.lastUser.uid -->
<span>[[modules:chat.no-users-in-room]]</span>
<!-- ELSE -->
<!-- IF rooms.roomName -->{rooms.roomName}<!-- ELSE -->{rooms.usernames}<!-- ENDIF rooms.roomName -->
<!-- ENDIF !rooms.lastUser.uid -->
</strong>
<span class="teaser-content">
<strong class="teaser-username">{rooms.teaser.user.username}:</strong>
{rooms.teaser.content}
</span>
</div>
<div class="teaser-timestamp notification-chat-controls">{rooms.teaser.timeago}</div>
</li> </li>
{{{end}}} {{{end}}}
<!-- ELSE --> <!-- ELSE -->

@ -1,33 +1,10 @@
<!-- IF roomId --> <!-- IF roomId -->
<div component="chat/messages" class="expanded-chat" data-roomid="{roomId}"> <div component="chat/messages" class="expanded-chat" data-roomid="{roomId}">
<div component="chat/header"> <div component="chat/header">
<button type="button" class="close" data-action="pop-out"><span aria-hidden="true"><i class="fa fa-compress"></i></span><span class="sr-only">[[modules:chat.pop-out]]</span></button>
<button type="button" class="close" aria-label="Close" data-action="close"><span aria-hidden="true">&times;</span></button> <button type="button" class="close" aria-label="Close" data-action="close"><span aria-hidden="true">&times;</span></button>
<button type="button" class="close" data-action="pop-out"><span aria-hidden="true"><i class="fa fa-compress"></i></span><span class="sr-only">[[modules:chat.pop-out]]</span></button>
<div class="dropdown pull-right"> <!-- IMPORT partials/chats/options.tpl -->
<button class="close" data-toggle="dropdown" component="chat/controlsToggle"><i class="fa fa-gear"></i></button>
<ul class="dropdown-menu dropdown-menu-right pull-right" component="chat/controls">
<li class="dropdown-header">[[modules:chat.options]]</li>
<li>
<a href="#" data-action="members"><i class="fa fa-fw fa-cog"></i> [[modules:chat.manage-room]]</a>
</li>
<li>
<a href="#" data-action="rename"><i class="fa fa-fw fa-edit"></i> [[modules:chat.rename-room]]</a>
</li>
<li>
<a href="#" data-action="leave"><i class="fa fa-fw fa-sign-out"></i> [[modules:chat.leave]]</a>
</li>
<!-- IF users.length -->
<li role="separator" class="divider"></li>
<li class="dropdown-header">[[modules:chat.in-room]]</li>
{{{each users}}}
<li>
<a href="{config.relative_path}/uid/{../uid}">{buildAvatar(users, "sm", true)} {../username}</a>
</li>
{{{end}}}
<!-- END -->
</ul>
</div>
<span class="members"> <span class="members">
[[modules:chat.chatting_with]]: [[modules:chat.chatting_with]]:
{{{each users}}} {{{each users}}}
@ -41,8 +18,12 @@
</ul> </ul>
<div component="chat/composer"> <div component="chat/composer">
<textarea component="chat/input" placeholder="[[modules:chat.placeholder]]" class="form-control chat-input mousetrap" rows="2"></textarea> <textarea component="chat/input" placeholder="[[modules:chat.placeholder]]" class="form-control chat-input mousetrap" rows="2"></textarea>
<button component="chat/upload/button" class="btn btn-light" type="button"><i class="fa fa-fw fa-2x fa-upload"></i></button>
<button class="btn btn-primary" type="button" data-action="send"><i class="fa fa-fw fa-2x fa-paper-plane"></i></button> <button class="btn btn-primary" type="button" data-action="send"><i class="fa fa-fw fa-2x fa-paper-plane"></i></button>
<span component="chat/message/remaining">{maximumChatMessageLength}</span> <span component="chat/message/remaining">{maximumChatMessageLength}</span>
<form component="chat/upload" method="post" enctype="multipart/form-data">
<input type="file" name="files[]" multiple class="hidden"/>
</form>
</div> </div>
</div> </div>
<!-- ELSE --> <!-- ELSE -->

@ -0,0 +1,24 @@
<div class="dropdown pull-right">
<button class="close" data-toggle="dropdown" component="chat/controlsToggle"><i class="fa fa-gear"></i></button>
<ul class="dropdown-menu dropdown-menu-right pull-right" component="chat/controls">
<li class="dropdown-header">[[modules:chat.options]]</li>
<li>
<a href="#" data-action="members"><i class="fa fa-fw fa-cog"></i> [[modules:chat.manage-room]]</a>
</li>
<li>
<a href="#" data-action="rename"><i class="fa fa-fw fa-edit"></i> [[modules:chat.rename-room]]</a>
</li>
<li>
<a href="#" data-action="leave"><i class="fa fa-fw fa-sign-out"></i> [[modules:chat.leave]]</a>
</li>
<!-- IF users.length -->
<li role="separator" class="divider"></li>
<li class="dropdown-header">[[modules:chat.in-room]]</li>
{{{each users}}}
<li>
<a href="{config.relative_path}/uid/{../uid}">{buildAvatar(users, "sm", true)} {../username}</a>
</li>
{{{end}}}
<!-- END -->
</ul>
</div>

@ -1,12 +1,4 @@
<li component="chat/recent/room" data-roomid="{rooms.roomId}" class="<!-- IF rooms.unread -->unread<!-- ENDIF rooms.unread -->"> <li component="chat/recent/room" data-roomid="{rooms.roomId}" class="<!-- IF rooms.unread -->unread<!-- ENDIF rooms.unread -->">
<strong class="room-name">
<!-- IF !rooms.lastUser.uid -->
<span>[[modules:chat.no-users-in-room]]</span>
<!-- ELSE -->
<span component="chat/title"><!-- IF rooms.roomName -->{rooms.roomName}<!-- ELSE -->{rooms.usernames}<!-- ENDIF rooms.roomName --></span>
<!-- ENDIF !rooms.lastUser.uid -->
</strong>
<div class="avatar-placeholder"></div>
{{{each rooms.users}}} {{{each rooms.users}}}
<!-- IF @first --> <!-- IF @first -->
<div class="main-avatar"> <div class="main-avatar">
@ -22,4 +14,14 @@
</li> </li>
{{{end}}} {{{end}}}
</ul> </ul>
<div class="notification-chat-content">
<strong class="room-name">
<!-- IF !rooms.lastUser.uid -->
<span>[[modules:chat.no-users-in-room]]</span>
<!-- ELSE -->
<span component="chat/title"><!-- IF rooms.roomName -->{rooms.roomName}<!-- ELSE -->{rooms.usernames}<!-- ENDIF rooms.roomName --></span>
<!-- ENDIF !rooms.lastUser.uid -->
</strong>
</div>
</li> </li>

@ -1,12 +1,28 @@
<div class="navbar-header"> <div class="navbar-header">
<button type="button" class="navbar-toggle pull-left" id="mobile-menu"> <button type="button" class="navbar-toggle pull-left" id="mobile-menu">
<span component="notifications/icon" class="notification-icon fa fa-fw fa-bell-o unread-count" data-content="{unreadCount.notification}"></span> <i class="fa fa-lg fa-fw fa-bars unread-count" data-content="{unreadCount.mobileUnread}" data-unread-url="{unreadCount.unreadUrl}"></i>
<i class="fa fa-lg fa-fw fa-bars"></i>
</button> </button>
<button type="button" class="navbar-toggle hidden" id="mobile-chats"> {{{ if config.loggedIn }}}
<button type="button" class="navbar-toggle" id="mobile-chats">
<span component="notifications/icon" class="notification-icon fa fa-fw fa-bell-o unread-count" data-content="{unreadCount.notification}"></span>
<span component="chat/icon" class="notification-icon fa fa-fw fa-comments unread-count" data-content="{unreadCount.chat}"></span> <span component="chat/icon" class="notification-icon fa fa-fw fa-comments unread-count" data-content="{unreadCount.chat}"></span>
<i class="fa fa-lg fa-comment-o"></i> {buildAvatar(user, "md", true)}
</button> </button>
{{{ end }}}
{{{ if config.searchEnabled }}}
<div class="navbar-search visible-xs pull-right">
<form action="{config.relative_path}/search" method="GET">
<button type="button" class="btn btn-link"><i class="fa fa-lg fa-fw fa-search" title="[[global:header.search]]"></i></button>
<input autocomplete="off" type="text" class="form-control hidden" name="term" placeholder="[[global:search]]"/>
<button class="btn btn-primary hidden" type="submit"></button>
<input type="text" class="hidden" name="in" value="{config.searchDefaultInQuick}" />
</form>
<div class="quick-search-container hidden">
<div class="quick-search-results-container"></div>
</div>
</div>
{{{ end }}}
<!-- IF brand:logo --> <!-- IF brand:logo -->
<a href="<!-- IF brand:logo:url -->{brand:logo:url}<!-- ELSE -->{relative_path}/<!-- ENDIF brand:logo:url -->"> <a href="<!-- IF brand:logo:url -->{brand:logo:url}<!-- ELSE -->{relative_path}/<!-- ENDIF brand:logo:url -->">
@ -41,8 +57,12 @@
</li> </li>
</ul> </ul>
</li> </li>
<li class="notif-dropdown-link"><a href="#" class="mark-all-read">[[notifications:mark_all_read]]</a></li> <li class="notif-dropdown-link">
<li class="notif-dropdown-link"><a href="{relative_path}/notifications">[[notifications:see_all]]</a></li> <div class="btn-group btn-group-justified">
<a role="button" href="#" class="btn btn-secondary mark-all-read"><i class="fa fa-check-double"></i> [[notifications:mark_all_read]]</a>
<a class="btn btn-secondary" href="{relative_path}/notifications"><i class="fa fa-list"></i> [[notifications:see_all]]</a>
</div>
</li>
</ul> </ul>
</li> </li>
@ -59,8 +79,12 @@
</li> </li>
</ul> </ul>
</li> </li>
<li class="notif-dropdown-link"><a href="#" class="mark-all-read" component="chats/mark-all-read">[[modules:chat.mark_all_read]]</a></li> <li class="notif-dropdown-link">
<li class="notif-dropdown-link"><a href="{relative_path}/user/{user.userslug}/chats">[[modules:chat.see_all]]</a></li> <div class="btn-group btn-group-justified">
<a class="btn btn-secondary mark-all-read" href="#" component="chats/mark-all-read"><i class="fa fa-check-double"></i> [[modules:chat.mark_all_read]]</a>
<a class="btn btn-secondary" href="{relative_path}/user/{user.userslug}/chats"><i class="fa fa-comments"></i> [[modules:chat.see_all]]</a>
</div>
</li>
</ul> </ul>
</li> </li>
<!-- ENDIF canChat --> <!-- ENDIF canChat -->
@ -109,7 +133,7 @@
<i class="fa fa-fw fa-gear"></i> <span>[[user:settings]]</span> <i class="fa fa-fw fa-gear"></i> <span>[[user:settings]]</span>
</a> </a>
</li> </li>
<!-- IF showModMenu --> {{{ if showModMenu }}}
<li role="presentation" class="divider"></li> <li role="presentation" class="divider"></li>
<li class="dropdown-header">[[pages:moderator-tools]]</li> <li class="dropdown-header">[[pages:moderator-tools]]</li>
<li> <li>
@ -127,7 +151,16 @@
<i class="fa fa-fw fa-ban"></i> <span>[[pages:ip-blacklist]]</span> <i class="fa fa-fw fa-ban"></i> <span>[[pages:ip-blacklist]]</span>
</a> </a>
</li> </li>
<!-- ENDIF showModMenu --> {{{ else }}}
{{{ if postQueueEnabled }}}
<li>
<a href="{relative_path}/post-queue">
<i class="fa fa-fw fa-list-alt"></i> <span>[[pages:post-queue]]</span>
</a>
</li>
{{{ end }}}
{{{ end }}}
<li role="presentation" class="divider"></li> <li role="presentation" class="divider"></li>
<li component="user/logout"> <li component="user/logout">
<form method="post" action="{relative_path}/logout"> <form method="post" action="{relative_path}/logout">
@ -202,16 +235,24 @@
<ul id="main-nav" class="nav navbar-nav"> <ul id="main-nav" class="nav navbar-nav">
{{{each navigation}}} {{{each navigation}}}
<!-- IF function.displayMenuItem, @index --> <!-- IF function.displayMenuItem, @index -->
<li class="{navigation.class}"> <li class="{navigation.class}{{{ if navigation.dropdown }}} dropdown{{{ end }}}">
<a class="navigation-link" href="{navigation.route}" title="{navigation.title}" <!-- IF navigation.id -->id="{navigation.id}"<!-- ENDIF navigation.id --><!-- IF navigation.properties.targetBlank --> target="_blank"<!-- ENDIF navigation.properties.targetBlank -->> <a title="{navigation.title}" class="navigation-link {{{ if navigation.dropdown }}}dropdown-toggle{{{ end }}}"
<!-- IF navigation.iconClass --> {{{ if navigation.dropdown }}} href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" {{{ else }}} href="{navigation.route}"{{{ end }}} {{{ if navigation.id }}}id="{navigation.id}"{{{ end }}}{{{ if navigation.targetBlank }}} target="_blank"{{{ end }}}>
{{{ if navigation.iconClass }}}
<i class="fa fa-fw {navigation.iconClass}" data-content="{navigation.content}"></i> <i class="fa fa-fw {navigation.iconClass}" data-content="{navigation.content}"></i>
<!-- ENDIF navigation.iconClass --> {{{ end }}}
{{{ if navigation.text }}}
<!-- IF navigation.text -->
<span class="{navigation.textClass}">{navigation.text}</span> <span class="{navigation.textClass}">{navigation.text}</span>
<!-- ENDIF navigation.text --> {{{ end }}}
{{{ if navigation.dropdown}}}
<i class="fa fa-caret-down"></i>
{{{ end }}}
</a> </a>
{{{ if navigation.dropdown }}}
<ul class="dropdown-menu">
{navigation.dropdownContent}
</ul>
{{{ end }}}
</li> </li>
<!-- ENDIF function.displayMenuItem --> <!-- ENDIF function.displayMenuItem -->
{{{end}}} {{{end}}}

@ -13,11 +13,13 @@
<a href="{config.relative_path}/user/{notifications.user.userslug}"><div class="pull-left avatar avatar-md avatar-rounded" style="background-color: {notifications.user.icon:bgColor};">{notifications.user.icon:text}</div></a> <a href="{config.relative_path}/user/{notifications.user.userslug}"><div class="pull-left avatar avatar-md avatar-rounded" style="background-color: {notifications.user.icon:bgColor};">{notifications.user.icon:text}</div></a>
<!-- ENDIF notifications.image --> <!-- ENDIF notifications.image -->
{{{ if ./nid }}}<div class="pull-right mark-read" aria-label="Mark Read"></div>{{{ end }}} <a href="{notifications.path}" class="notification-chat-content deco-none">
<a href="{notifications.path}">
<span class="pull-right relTime">{notifications.timeago}</span>
<span class="text">{notifications.bodyShort}</span> <span class="text">{notifications.bodyShort}</span>
</a> </a>
<div class="clear"></div>
<div class="notification-chat-controls">
{{{ if ./nid }}}<div class="mark-read" aria-label="Mark Read"></div>{{{ end }}}
<span class="relTime">{notifications.timeago}</span>
</div>
</li> </li>
{{{end}}} {{{end}}}

@ -10,8 +10,18 @@
<small class="topic-category"><a href="{config.relative_path}/category/{../category.slug}">[[global:posted_in, {../category.name}]]</a></small> <small class="topic-category"><a href="{config.relative_path}/category/{../category.slug}">[[global:posted_in, {../category.name}]]</a></small>
{{{ if ../isMainPost }}}
{{{ if ../topic.tags.length }}}
<span class="tag-list">
{{{ each ../topic.tags }}}
<a href="{config.relative_path}/tags/{topic.tags.valueEncoded}"><span class="tag tag-item tag-class-{topic.tags.class}">{topic.tags.valueEscaped}</span></a>
{{{ end }}}
</span>
{{{ end }}}
{{{ end }}}
<div class="post-info"> <div class="post-info">
<a href="{config.relative_path}/user/{../user.userslug}">{buildAvatar(../user, "md", true, "user-img")}</a> <a href="{config.relative_path}/user/{../user.userslug}">{buildAvatar(../user, "md", true, "user-img not-responsive")}</a>
<div class="post-author"> <div class="post-author">
<a href="{config.relative_path}/user/{../user.userslug}">{../user.displayname}</a><br /> <a href="{config.relative_path}/user/{../user.userslug}">{../user.displayname}</a><br />

@ -1,7 +1,7 @@
<ul id="quick-search-results" class="quick-search-results"> <ul id="quick-search-results" class="quick-search-results">
{{{each posts}}} {{{each posts}}}
<li data-tid="{posts.topic.tid}" data-pid="{posts.pid}"> <li data-tid="{posts.topic.tid}" data-pid="{posts.pid}">
<a href="{config.relative_path}/post/{posts.pid}"> <a href="{config.relative_path}/post/{posts.pid}" class="deco-none">
{buildAvatar(posts.user, "sm", true)} {buildAvatar(posts.user, "sm", true)}
<span class="quick-search-title">{posts.topic.title}</span> <span class="quick-search-title">{posts.topic.title}</span>
<br/> <br/>

@ -0,0 +1,50 @@
<div id="results" class="search-results col-md-12" data-search-query="{search_query}">
<!-- IF matchCount -->
<div class="alert alert-info">[[search:results_matching, {matchCount}, {search_query}, {time}]] </div>
<!-- ELSE -->
<!-- IF search_query -->
<div class="alert alert-warning">[[search:no-matches]]</div>
<!-- ENDIF search_query -->
<!-- ENDIF matchCount -->
{{{each posts}}}
<div class="topic-row panel panel-default clearfix">
<div class="panel-body">
<a href="{config.relative_path}/user/{posts.user.userslug}">{buildAvatar(posts.user, "sm", true)}</a>
<span class="search-result-text search-result-title"><a href="{config.relative_path}/post/{posts.pid}">{posts.topic.title}</a></span>
<br/>
<!-- IF showAsPosts -->
<div class="search-result-text">
{posts.content}
<p class="fade-out"></p>
</div>
<!-- ENDIF showAsPosts -->
<small class="post-info pull-right">
<a href="{config.relative_path}/category/{posts.category.slug}"><span class="fa-stack" style="{function.generateCategoryBackground, posts.category}"><i style="color:{posts.category.color};" class="fa {posts.category.icon} fa-stack-1x"></i></span> {posts.category.name}</a> &bull;
<span class="timeago" title="{posts.timestampISO}"></span>
</small>
</div>
</div>
{{{end}}}
<!-- IF users.length -->
<ul id="users-container" class="users-container">
<!-- IMPORT partials/users_list.tpl -->
</ul>
<!-- ENDIF users.length -->
<!-- IF tags.length -->
<!-- IMPORT partials/tags_list.tpl -->
<!-- ENDIF tags.length -->
{{{ if categories.length }}}
<ul class="categories">
{{{each categories}}}
<!-- IMPORT partials/categories/item.tpl -->
{{{end}}}
</ul>
{{{ end }}}
<!-- IMPORT partials/paginator.tpl -->
</div>

@ -1,27 +1,4 @@
<div class="menu-profile">
<!-- IF user.uid -->
{buildAvatar(user, "lg", true, "user-icon")}
<i component="user/status" class="fa fa-fw fa-circle status {user.status}"></i>
<!-- ENDIF user.uid -->
</div>
<section class="menu-section" data-section="navigation"> <section class="menu-section" data-section="navigation">
<h3 class="menu-section-title">[[global:header.navigation]]</h3>
<ul class="menu-section-list"></ul> <ul class="menu-section-list"></ul>
</section> </section>
<!-- IF config.loggedIn -->
<section class="menu-section" data-section="profile">
<h3 class="menu-section-title">[[global:header.profile]]</h3>
<ul class="menu-section-list" component="header/usercontrol"></ul>
</section>
<section class="menu-section" data-section="notifications">
<h3 class="menu-section-title">
[[global:header.notifications]]
<span class="counter unread-count" component="notifications/icon" data-content="{unreadCount.notification}"></span>
</h3>
<ul class="menu-section-list notification-list-mobile" component="notifications/list"></ul>
<p class="menu-section-list"><a href="{relative_path}/notifications">[[notifications:see_all]]</a></p>
</section>
<!-- ENDIF config.loggedIn -->

@ -1,5 +1,5 @@
{{{each tags}}} {{{each tags}}}
<h3 class="pull-left tag-container"> <h3 class="pull-left tag-container">
<a href="{config.relative_path}/tags/{tags.valueEscaped}" data-value="{tags.valueEscaped}"><span class="tag-item" data-tag="{tags.valueEscaped}" style="<!-- IF tags.color -->color: {tags.color};<!-- ENDIF tags.color --><!-- IF tags.bgColor -->background-color: {tags.bgColor};<!-- ENDIF tags.bgColor -->">{tags.valueEscaped}</span><span class="tag-topic-count human-readable-number" title="{tags.score}">{tags.score}</span></a> <a href="{config.relative_path}/tags/{tags.valueEncoded}" data-value="{tags.valueEscaped}"><span class="tag-item tag-class-{tags.class}" data-tag="{tags.valueEscaped}">{tags.valueEscaped}</span><span class="tag-topic-count human-readable-number" title="{tags.score}">{tags.score}</span></a>
</h3> </h3>
{{{end}}} {{{end}}}

@ -1,13 +1 @@
<div component="topic/browsing-users" class="inline-block hidden-xs"> <!-- This partial intentionally left blank; overwritten by nodebb-plugin-browsing-users -->
{{{each browsingUsers}}}
<div class="pull-left" data-uid="{browsingUsers.uid}">
<a href="<!-- IF browsingUsers.userslug -->{config.relative_path}/user/{browsingUsers.userslug}<!-- ELSE -->#<!-- ENDIF browsingUsers.userslug -->">
<!-- IF browsingUsers.picture -->
<img class="avatar avatar-sm avatar-rounded" component="user/picture" src="{browsingUsers.picture}" align="left" itemprop="image" title="{browsingUsers.username}"/>
<!-- ELSE -->
<div class="avatar avatar-sm avatar-rounded" component="user/picture" title="{browsingUsers.username}" style="background-color: {browsingUsers.icon:bgColor};">{browsingUsers.icon:text}</div>
<!-- ENDIF browsingUsers.picture -->
</a>
</div>
{{{end}}}
</div>

@ -1,25 +0,0 @@
<li component="topic/event" class="timeline-event" data-topic-event-id="{id}">
<div class="timeline-badge">
<i class="fa {{{ if icon }}}{icon}{{{ else }}}fa-circle{{{ end }}}"></i>
</div>
<span class="timeline-text">
{{{ if ../href }}}
<a href="{config.relative_path}{../href}">{../text}</a>&nbsp;
{{{ else }}}
{text}&nbsp;
{{{ end }}}
</span>
{{{ if user }}}
{{{ if !./user.system }}}<span><a href="{config.relative_path}/user/{./user.userslug}">{buildAvatar(user, "xs", true)}&nbsp;{./user.username}</a></span>&nbsp;{{{ end }}}
{{{ if ./user.system }}}<span class="timeline-text">[[global:system-user]]</span>&nbsp;{{{ end }}}
{{{ else }}}
<span class="timeline-text">[[global:unknown-user]]</span>&nbsp;
{{{ end }}}
<span class="timeago timeline-text" title="{timestampISO}"></span>
{{{ if isAdminOrMod}}}
&nbsp;<span component="topic/event/delete" data-topic-event-id="{id}" class="timeline-text pointer" title="[[topic:delete-event]]"><i class="fa fa-trash"></i></span>
{{{ end }}}
</li>

@ -25,7 +25,14 @@
</div> </div>
</div> </div>
</div> </div>
<input type="text" class="form-control" id="indexInput" placeholder="[[global:pagination.enter_index]]"> <div class="row">
<div class="col-xs-6">
<button id="myNextPostBtn" class="btn btn-default form-control" disabled>[[topic:go-to-my-next-post]]</button>
</div>
<div class="col-xs-6">
<input type="number" class="form-control" id="indexInput" placeholder="[[global:pagination.enter_index]]">
</div>
</div>
</li> </li>
</ul> </ul>
</div> </div>

@ -72,7 +72,7 @@
</li> </li>
<!-- END --> <!-- END -->
<!-- IF config.loggedIn --> {{{ if config.loggedIn }}}
<li> <li>
<a component="post/bookmark" role="menuitem" tabindex="-1" href="#" data-bookmarked="{posts.bookmarked}"> <a component="post/bookmark" role="menuitem" tabindex="-1" href="#" data-bookmarked="{posts.bookmarked}">
<span class="menu-icon"> <span class="menu-icon">
@ -83,7 +83,13 @@
<span component="post/bookmark-count" class="bookmarkCount badge" data-bookmarks="{posts.bookmarks}">{posts.bookmarks}</span>&nbsp; <span component="post/bookmark-count" class="bookmarkCount badge" data-bookmarks="{posts.bookmarks}">{posts.bookmarks}</span>&nbsp;
</a> </a>
</li> </li>
<!-- ENDIF config.loggedIn --> {{{ end }}}
<li>
<a role="menuitem" tabindex="-1" href="#" data-clipboard-text="{posts.absolute_url}">
<i class="fa fa-fw fa-link"></i> [[topic:copy-permalink]]
</a>
</li>
<!-- IF postSharing.length --> <!-- IF postSharing.length -->
<!-- IF config.loggedIn --><li class="divider"></li><!-- ENDIF config.loggedIn --> <!-- IF config.loggedIn --><li class="divider"></li><!-- ENDIF config.loggedIn -->

@ -1,4 +1,4 @@
<span component="post/tools" class="dropdown moderator-tools bottom-sheet <!-- IF !posts.display_post_menu -->hidden<!-- ENDIF !posts.display_post_menu -->"> <span component="post/tools" class="dropdown moderator-tools bottom-sheet <!-- IF !posts.display_post_menu -->hidden<!-- ENDIF !posts.display_post_menu -->">
<a href="#" data-toggle="dropdown" data-ajaxify="false"><i class="fa fa-fw fa-ellipsis-v"></i></a> <a href="#" data-toggle="dropdown" data-ajaxify="false"><i class="fa fa-fw fa-ellipsis-v"></i></a>
<ul class="dropdown-menu dropdown-menu-right" role="menu"></ul> <ul class="dropdown-menu dropdown-menu-right hidden" role="menu"></ul>
</span> </span>

@ -1,3 +1,11 @@
{{{ if (!./index && widgets.mainpost-header.length) }}}
<div data-widget-area="mainpost-header">
{{{ each widgets.mainpost-header }}}
{widgets.mainpost-header.html}
{{{ end }}}
</div>
{{{ end }}}
<div class="clearfix post-header"> <div class="clearfix post-header">
<div class="icon pull-left"> <div class="icon pull-left">
<a href="<!-- IF posts.user.userslug -->{config.relative_path}/user/{posts.user.userslug}<!-- ELSE -->#<!-- ENDIF posts.user.userslug -->"> <a href="<!-- IF posts.user.userslug -->{config.relative_path}/user/{posts.user.userslug}<!-- ELSE -->#<!-- ENDIF posts.user.userslug -->">
@ -76,7 +84,8 @@
</a> </a>
{{{ end }}} {{{ end }}}
<small class="pull-right"> <small class="pull-right" component="post/actions">
<!-- IMPORT partials/topic/reactions.tpl -->
<span class="post-tools"> <span class="post-tools">
<a component="post/reply" href="#" class="no-select <!-- IF !privileges.topics:reply -->hidden<!-- ENDIF !privileges.topics:reply -->">[[topic:reply]]</a> <a component="post/reply" href="#" class="no-select <!-- IF !privileges.topics:reply -->hidden<!-- ENDIF !privileges.topics:reply -->">[[topic:reply]]</a>
<a component="post/quote" href="#" class="no-select <!-- IF !privileges.topics:reply -->hidden<!-- ENDIF !privileges.topics:reply -->">[[topic:quote]]</a> <a component="post/quote" href="#" class="no-select <!-- IF !privileges.topics:reply -->hidden<!-- ENDIF !privileges.topics:reply -->">[[topic:quote]]</a>
@ -103,3 +112,10 @@
</div> </div>
<div component="post/replies/container"></div> <div component="post/replies/container"></div>
</div> </div>
{{{ if (!./index && widgets.mainpost-footer.length) }}}
<div data-widget-area="mainpost-footer">
{{{ each widgets.mainpost-footer }}}
{widgets.mainpost-footer.html}
{{{ end }}}
</div>
{{{ end }}}

@ -1,5 +1,5 @@
<!-- IF privileges.topics:reply --> <!-- IF privileges.topics:reply -->
<div class="clearfix quick-reply"> <div component="topic/quickreply/container" class="clearfix quick-reply">
<div class="icon pull-left hidden-xs"> <div class="icon pull-left hidden-xs">
<a href="<!-- IF posts.user.userslug -->{config.relative_path}/user/{posts.user.userslug}<!-- ELSE -->#<!-- ENDIF posts.user.userslug -->"> <a href="<!-- IF posts.user.userslug -->{config.relative_path}/user/{posts.user.userslug}<!-- ELSE -->#<!-- ENDIF posts.user.userslug -->">
{buildAvatar(loggedInUser, "46", true, "", "user/picture")} {buildAvatar(loggedInUser, "46", true, "", "user/picture")}
@ -12,9 +12,17 @@
<input type="hidden" name="tid" value="{tid}" /> <input type="hidden" name="tid" value="{tid}" />
<input type="hidden" name="_csrf" value="{config.csrf_token}" /> <input type="hidden" name="_csrf" value="{config.csrf_token}" />
<div class="quickreply-message"> <div class="quickreply-message">
<textarea name="content" component="topic/quickreply/text" class="form-control" rows="5"></textarea> <textarea name="content" component="topic/quickreply/text" class="form-control mousetrap" rows="5" placeholder="[[modules:composer.textarea.placeholder]]"></textarea>
<div class="imagedrop"><div>[[topic:composer.drag_and_drop_images]]</div></div>
</div> </div>
<button type="submit" component="topic/quickreply/button" class="btn btn-primary pull-right">[[persona:post-quick-reply]]</button> <div class="btn-group pull-right">
<button type="submit" component="topic/quickreply/button" class="btn btn-primary">[[topic:post-quick-reply]]</button>
<button type="submit" component="topic/quickreply/expand" class="btn btn-default" formmethod="get"><i class="fa fa-expand"></i></button>
</div>
</form>
<form component="topic/quickreply/upload" method="post" enctype="multipart/form-data">
<input type="file" name="files[]" multiple class="hidden"/>
</form> </form>
</div> </div>
<!-- ENDIF privileges.topics:reply --> <!-- ENDIF privileges.topics:reply -->

@ -0,0 +1 @@
<!-- This partial intentionally left blank; overwritten by nodebb-plugin-reactions -->

@ -0,0 +1,3 @@
<div component="selection/tooltip" class="selection-tooltip-container">
<button component="selection/tooltip/quote" class="btn btn-sm btn-primary quote-tooltip-btn">[[topic:quote]]</button>
</div>

@ -1,5 +1,5 @@
{{{each tags}}} {{{each tags}}}
<a href="{config.relative_path}/tags/{tags.value}"> <a href="{config.relative_path}/tags/{tags.valueEncoded}">
<span class="tag tag-item tag-{tags.valueEscaped}" data-tag="{tags.value}" style="<!-- IF tags.color -->color: {tags.color};<!-- ENDIF tags.color --><!-- IF tags.bgColor -->background-color: {tags.bgColor};<!-- ENDIF tags.bgColor -->">{tags.valueEscaped}</span> <span class="tag tag-item tag-class-{tags.class}" data-tag="{tags.value}">{tags.valueEscaped}</span>
</a> </a>
{{{end}}} {{{end}}}

@ -1,9 +1,11 @@
<ul component="category" class="topic-list" itemscope itemtype="http://www.schema.org/ItemList" data-nextstart="{nextStart}" data-set="{set}"> <ul component="category" class="topic-list" itemscope itemtype="http://www.schema.org/ItemList" data-nextstart="{nextStart}" data-set="{set}">
<meta itemprop="itemListOrder" content="descending">
{{{each topics}}} {{{each topics}}}
<li component="category/topic" class="row clearfix category-item {function.generateTopicClass}" <!-- IMPORT partials/data/category.tpl -->> <li component="category/topic" class="row clearfix category-item {function.generateTopicClass}" <!-- IMPORT partials/data/category.tpl -->>
<link itemprop="url" content="{config.relative_path}/topic/{../slug}" />
<meta itemprop="name" content="{function.stripTags, ../title}" />
<meta itemprop="itemListOrder" content="descending" />
<meta itemprop="position" content="{../index}" />
<a id="{../index}" data-index="{../index}" component="topic/anchor"></a> <a id="{../index}" data-index="{../index}" component="topic/anchor"></a>
<meta itemprop="name" content="{function.stripTags, title}">
<div class="col-md-6 col-sm-9 col-xs-10 content"> <div class="col-md-6 col-sm-9 col-xs-10 content">
<div class="avatar pull-left"> <div class="avatar pull-left">
@ -38,7 +40,7 @@
<!-- IF !topics.noAnchor --> <!-- IF !topics.noAnchor -->
<a href="{config.relative_path}/topic/{topics.slug}<!-- IF topics.bookmark -->/{topics.bookmark}<!-- ENDIF topics.bookmark -->" itemprop="url">{topics.title}</a><br /> <a href="{config.relative_path}/topic/{topics.slug}<!-- IF topics.bookmark -->/{topics.bookmark}<!-- ENDIF topics.bookmark -->">{topics.title}</a><br />
<!-- ELSE --> <!-- ELSE -->
<span>{topics.title}</span><br /> <span>{topics.title}</span><br />
<!-- ENDIF !topics.noAnchor --> <!-- ENDIF !topics.noAnchor -->
@ -49,14 +51,14 @@
</small> </small>
<!-- ENDIF !template.category --> <!-- ENDIF !template.category -->
<!-- IF topics.tags.length --> {{{ if topics.tags.length }}}
<span class="tag-list hidden-xs"> <span class="tag-list hidden-xs">
{{{each topics.tags}}} {{{each topics.tags}}}
<a href="{config.relative_path}/tags/{topics.tags.valueEscaped}"><span class="tag tag-{topics.tags.valueEscaped}" style="<!-- IF topics.tags.color -->color: {topics.tags.color};<!-- ENDIF topics.tags.color --><!-- IF topics.tags.bgColor -->background-color: {topics.tags.bgColor};<!-- ENDIF topics.tags.bgColor -->">{topics.tags.valueEscaped}</span></a> <a href="{config.relative_path}/tags/{topics.tags.valueEncoded}"><span class="tag tag-item tag-class-{topics.tags.class}">{topics.tags.valueEscaped}</span></a>
{{{end}}} {{{end}}}
<small>&bull;</small> <small>&bull;</small>
</span> </span>
<!-- ENDIF topics.tags.length --> {{{ end }}}
<small class="hidden-xs"><span class="timeago" title="{topics.timestampISO}"></span> &bull; <a href="<!-- IF topics.user.userslug -->{config.relative_path}/user/{topics.user.userslug}<!-- ELSE -->#<!-- ENDIF topics.user.userslug -->">{topics.user.displayname}</a></small> <small class="hidden-xs"><span class="timeago" title="{topics.timestampISO}"></span> &bull; <a href="<!-- IF topics.user.userslug -->{config.relative_path}/user/{topics.user.userslug}<!-- ELSE -->#<!-- ENDIF topics.user.userslug -->">{topics.user.displayname}</a></small>
<small class="visible-xs-inline"> <small class="visible-xs-inline">

@ -9,28 +9,34 @@
</span> </span>
<br/> <br/>
<!-- IF section_online -->
<div class="lastonline">
<span class="timeago" title="{users.lastonlineISO}"></span>
</div>
<!-- ENDIF section_online -->
<!-- IF section_joindate --> <!-- IF section_joindate -->
<div title="joindate" class="joindate"> <div class="joindate">
<span class="timeago" title="{users.joindateISO}"></span> <span class="timeago" title="{users.joindateISO}"></span>
</div> </div>
<!-- ENDIF section_joindate --> <!-- ENDIF section_joindate -->
<!-- IF section_sort-reputation --> <!-- IF section_sort-reputation -->
<div title="reputation" class="reputation"> <div class="reputation">
<i class="fa fa-star"></i> <i class="fa fa-star"></i>
<span class="formatted-number">{users.reputation}</span> <span class="formatted-number">{users.reputation}</span>
</div> </div>
<!-- ENDIF section_sort-reputation --> <!-- ENDIF section_sort-reputation -->
<!-- IF section_sort-posts --> <!-- IF section_sort-posts -->
<div title="post count" class="post-count"> <div class="post-count">
<i class="fa fa-pencil"></i> <i class="fa fa-pencil"></i>
<span class="formatted-number">{users.postcount}</span> <span class="formatted-number">{users.postcount}</span>
</div> </div>
<!-- ENDIF section_sort-posts --> <!-- ENDIF section_sort-posts -->
<!-- IF section_flagged --> <!-- IF section_flagged -->
<div title="flag count" class="flag-count"> <div class="flag-count">
<i class="fa fa-flag"></i> <i class="fa fa-flag"></i>
<span><a class="formatted-number" href="{config.relative_path}/flags?targetUid={users.uid}">{users.flags}</a></span> <span><a class="formatted-number" href="{config.relative_path}/flags?targetUid={users.uid}">{users.flags}</a></span>
</div> </div>

@ -1,20 +1,49 @@
<!-- IMPORT partials/breadcrumbs.tpl --> <!-- IMPORT partials/breadcrumbs.tpl -->
{{{ if !singlePost }}}
<div class="btn-toolbar"> <div class="btn-toolbar">
<!-- IMPORT partials/category-filter-right.tpl --> <!-- IMPORT partials/category-filter-right.tpl -->
<div class="btn-group pull-right bottom-sheet" component="post-queue/bulk-actions">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" autocomplete="off" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-clone"></i> [[post-queue:bulk-actions]] <span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li><a href="#" data-action="accept-all">[[post-queue:accept-all]]</a></li>
<li><a href="#" data-action="accept-selected">[[post-queue:accept-selected]]</a></li>
<li class="divider"></li>
<li><a href="#" data-action="reject-all">[[post-queue:reject-all]]</a></li>
<li><a href="#" data-action="reject-selected">[[post-queue:reject-selected]]</a></li>
</ul>
</div> </div>
</div>
<hr/> <hr/>
{{{ end }}}
<div class="row"> <div class="row">
<div class="col-xs-12"> <div class="col-xs-12">
<div class="post-queue preventSlideout posts-list"> <div class="post-queue preventSlideout posts-list">
{{{ if !posts.length }}} {{{ if (!posts.length && isAdmin) }}}
<p class="panel-body"> {{{ if !singlePost }}}
[[post-queue:description, {config.relative_path}/admin/settings/post#post-queue]] <div class="alert alert-info">
</p> <p>[[post-queue:no-queued-posts]]</p>
{{{ if !enabled }}}<p>[[post-queue:enabling-help, {config.relative_path}/admin/settings/post#post-queue]]</p>{{{ end }}}
</div>
{{{ else }}}
<div class="alert alert-info">
<p>[[post-queue:no-single-post]]</p>
<p><a href=".">[[post-queue:back-to-list]]</a></p>
</div>
{{{ end }}}
{{{ end }}} {{{ end }}}
{{{ each posts }}} {{{ each posts }}}
<div class="panel panel-default" data-id="{posts.id}"> <div class="panel panel-default" data-id="{posts.id}">
<div class="panel-heading"> <div class="panel-heading">
{{{ if !singlePost }}}
<input type="checkbox" autocomplete="off" />
{{{ end }}}
<strong>{{{ if posts.data.tid }}}[[post-queue:reply]]{{{ else }}}[[post-queue:topic]]{{{ end }}}</strong> <strong>{{{ if posts.data.tid }}}[[post-queue:reply]]{{{ else }}}[[post-queue:topic]]{{{ end }}}</strong>
<span class="timeago pull-right" title={posts.data.timestampISO}></span> <span class="timeago pull-right" title={posts.data.timestampISO}></span>
</div> </div>
@ -62,9 +91,14 @@
</div> </div>
</div> </div>
<div class="panel-footer text-right"> <div class="panel-footer text-right">
<div class="btn-group"> <div>
<button class="btn btn-success btn-xs" data-action="accept"><i class="fa fa-check"></i> [[post-queue:accept]] </button> {{{ if canAccept }}}
<button class="btn btn-danger btn-xs" data-action="reject"><i class="fa fa-times"></i> [[post-queue:reject]]</button> <button class="btn btn-danger btn-xs" data-action="reject"><i class="fa fa-fw fa-times"></i> [[post-queue:reject]]</button>
<button class="btn btn-info btn-xs" data-action="notify"><i class="fa fa-fw fa-bell-o"></i> [[post-queue:notify]]</button>
<button class="btn btn-success btn-xs" data-action="accept"><i class="fa fa-fw fa-check"></i> [[post-queue:accept]] </button>
{{{ else }}}
<button class="btn btn-danger btn-xs" data-action="reject"><i class="fa fa-fw fa-times"></i> [[post-queue:remove]]</button>
{{{ end }}}
</div> </div>
</div> </div>
</div> </div>

@ -1,6 +1,11 @@
<!-- IMPORT partials/breadcrumbs.tpl --> <!-- IMPORT partials/breadcrumbs.tpl -->
<div data-widget-area="header">
{{{each widgets.header}}}
{{widgets.header.html}}
{{{end}}}
</div>
<div class="row register"> <div class="row register">
<div class="row {{{ if widgets.sidebar.length }}}col-lg-9 col-sm-12{{{ else }}}col-lg-12{{{ end }}}">
<div class="{register_window:spansize}"> <div class="{register_window:spansize}">
<div class="register-block"> <div class="register-block">
<div class="alert alert-danger<!-- IF !error --> hidden<!-- ENDIF !error -->" id="register-error-notify" > <div class="alert alert-danger<!-- IF !error --> hidden<!-- ENDIF !error -->" id="register-error-notify" >
@ -56,7 +61,7 @@
</div> </div>
</div> </div>
<!-- IF alternate_logins --> {{{ if alternate_logins }}}
<div class="col-md-6"> <div class="col-md-6">
<div class="alt-register-block"> <div class="alt-register-block">
<h4>[[register:alternative_registration]]</h4> <h4>[[register:alternative_registration]]</h4>
@ -67,5 +72,16 @@
</ul> </ul>
</div> </div>
</div> </div>
<!-- ENDIF alternate_logins --> {{{ end }}}
</div>
<div data-widget-area="sidebar" class="col-lg-3 col-sm-12 {{{ if !widgets.sidebar.length }}}hidden{{{ end }}}">
{{{each widgets.sidebar}}}
{{widgets.sidebar.html}}
{{{end}}}
</div>
</div>
<div data-widget-area="footer">
{{{each widgets.footer}}}
{{widgets.footer.html}}
{{{end}}}
</div> </div>

@ -36,12 +36,10 @@
<div class="row"> <div class="row">
<div class="col-xs-12 col-sm-8 col-sm-offset-2"> <div class="col-xs-12 col-sm-8 col-sm-offset-2">
<button class="btn btn-primary btn-block">[[topic:composer.submit]]</button> <div class="btn-group btn-block">
<button class="btn btn-block btn-primary">[[topic:composer.submit]]</button>
<button class="btn btn-block btn-link" formaction="{config.relative_path}/register/abort?_csrf={config.csrf_token}">{{{ if register }}}[[register:cancel_registration]]{{{ else }}}[[modules:bootbox.cancel]]{{{ end }}}</button>
</div>
</div> </div>
</div> </div>
</form>
<form role="form" method="post" action="{config.relative_path}/register/abort">
<p class="text-center">
<button class="btn btn-link">{{{ if register }}}[[register:cancel_registration]]{{{ else }}}[[modules:bootbox.cancel]]{{{ end }}}</button>
</p>
</form> </form>

@ -168,55 +168,6 @@
</div> </div>
<div class="row"> <div class="row">
<div id="results" class="search-results col-md-12" data-search-query="{search_query}"> <!-- IMPORT partials/search-results.tpl -->
<!-- IF matchCount -->
<div class="alert alert-info">[[search:results_matching, {matchCount}, {search_query}, {time}]] </div>
<!-- ELSE -->
<!-- IF search_query -->
<div class="alert alert-warning">[[search:no-matches]]</div>
<!-- ENDIF search_query -->
<!-- ENDIF matchCount -->
{{{each posts}}}
<div class="topic-row panel panel-default clearfix">
<div class="panel-body">
<a href="{config.relative_path}/user/{posts.user.userslug}">{buildAvatar(posts.user, "sm", true)}</a>
<span class="search-result-text search-result-title"><a href="{config.relative_path}/post/{posts.pid}">{posts.topic.title}</a></span>
<br/>
<!-- IF showAsPosts -->
<div class="search-result-text">
{posts.content}
<p class="fade-out"></p>
</div>
<!-- ENDIF showAsPosts -->
<small class="post-info pull-right">
<a href="{config.relative_path}/category/{posts.category.slug}"><span class="fa-stack" style="{function.generateCategoryBackground, posts.category}"><i style="color:{posts.category.color};" class="fa {posts.category.icon} fa-stack-1x"></i></span> {posts.category.name}</a> &bull;
<span class="timeago" title="{posts.timestampISO}"></span>
</small>
</div>
</div>
{{{end}}}
<!-- IF users.length -->
<ul id="users-container" class="users-container">
<!-- IMPORT partials/users_list.tpl -->
</ul>
<!-- ENDIF users.length -->
<!-- IF tags.length -->
<!-- IMPORT partials/tags_list.tpl -->
<!-- ENDIF tags.length -->
{{{ if categories.length }}}
<ul class="categories">
{{{each categories}}}
<!-- IMPORT partials/categories/item.tpl -->
{{{end}}}
</ul>
{{{ end }}}
<!-- IMPORT partials/paginator.tpl -->
</div>
</div> </div>
</div> </div>

@ -7,7 +7,7 @@
<div class="topic <!-- IF widgets.sidebar.length -->col-lg-9 col-sm-12<!-- ELSE -->col-lg-12<!-- ENDIF widgets.sidebar.length -->"> <div class="topic <!-- IF widgets.sidebar.length -->col-lg-9 col-sm-12<!-- ELSE -->col-lg-12<!-- ENDIF widgets.sidebar.length -->">
<div class="topic-header"> <div class="topic-header">
<h1 component="post/header" class="" itemprop="name"> <h1 component="post/header" class="" itemprop="name">
<span class="topic-title" component="topic/title"> <span class="topic-title">
<span component="topic/labels"> <span component="topic/labels">
<i component="topic/scheduled" class="fa fa-clock-o <!-- IF !scheduled -->hidden<!-- ENDIF !scheduled -->" title="[[topic:scheduled]]"></i> <i component="topic/scheduled" class="fa fa-clock-o <!-- IF !scheduled -->hidden<!-- ENDIF !scheduled -->" title="[[topic:scheduled]]"></i>
<i component="topic/pinned" class="fa fa-thumb-tack <!-- IF (scheduled || !pinned) -->hidden<!-- ENDIF (scheduled || !pinned) -->" title="{{{ if !pinExpiry }}}[[topic:pinned]]{{{ else }}}[[topic:pinned-with-expiry, {pinExpiryISO}]]{{{ end }}}"></i> <i component="topic/pinned" class="fa fa-thumb-tack <!-- IF (scheduled || !pinned) -->hidden<!-- ENDIF (scheduled || !pinned) -->" title="{{{ if !pinExpiry }}}[[topic:pinned]]{{{ else }}}[[topic:pinned-with-expiry, {pinExpiryISO}]]{{{ end }}}"></i>
@ -15,7 +15,7 @@
<i class="fa fa-arrow-circle-right <!-- IF !oldCid -->hidden<!-- ENDIF !oldCid -->" title="{{{ if privileges.isAdminOrMod }}}[[topic:moved-from, {oldCategory.name}]]{{{ else }}}[[topic:moved]]{{{ end }}}"></i> <i class="fa fa-arrow-circle-right <!-- IF !oldCid -->hidden<!-- ENDIF !oldCid -->" title="{{{ if privileges.isAdminOrMod }}}[[topic:moved-from, {oldCategory.name}]]{{{ else }}}[[topic:moved]]{{{ end }}}"></i>
{{{each icons}}}{@value}{{{end}}} {{{each icons}}}{@value}{{{end}}}
</span> </span>
{title} <span component="topic/title">{title}</span>
</span> </span>
</h1> </h1>
@ -36,7 +36,11 @@
{{{ if !feeds:disableRSS }}} {{{ if !feeds:disableRSS }}}
{{{ if rssFeedUrl }}}<a class="hidden-xs" target="_blank" href="{rssFeedUrl}"><i class="fa fa-rss-square"></i></a>{{{ end }}} {{{ if rssFeedUrl }}}<a class="hidden-xs" target="_blank" href="{rssFeedUrl}"><i class="fa fa-rss-square"></i></a>{{{ end }}}
{{{ end }}} {{{ end }}}
{{{ if browsingUsers }}}
<div class="inline-block hidden-xs">
<!-- IMPORT partials/topic/browsing-users.tpl --> <!-- IMPORT partials/topic/browsing-users.tpl -->
</div>
{{{ end }}}
<!-- IMPORT partials/post_bar.tpl --> <!-- IMPORT partials/post_bar.tpl -->
</div> </div>
@ -67,10 +71,17 @@
<!-- IMPORT partials/topic/post.tpl --> <!-- IMPORT partials/topic/post.tpl -->
</li> </li>
{renderTopicEvents(@index)} {renderTopicEvents(@index, config.topicPostSort)}
{{{end}}} {{{end}}}
</ul> </ul>
{{{ if browsingUsers }}}
<div class="visible-xs">
<!-- IMPORT partials/topic/browsing-users.tpl -->
<hr/>
</div>
{{{ end }}}
<!-- IF config.enableQuickReply --> <!-- IF config.enableQuickReply -->
<!-- IMPORT partials/topic/quickreply.tpl --> <!-- IMPORT partials/topic/quickreply.tpl -->
<!-- ENDIF config.enableQuickReply --> <!-- ENDIF config.enableQuickReply -->

@ -1,6 +1,14 @@
{{{ if !error }}}
<div class="alert alert-success"> <div class="alert alert-success">
<strong>[[global:alert.success]]</strong> <strong>[[global:alert.success]]</strong>
<p>[[email:unsub.success, {payload.template}]]</p> <p>[[email:unsub.success, {payload.template}]]</p>
{{{ else }}}
<div class="alert alert-warning">
<strong>[[email:unsub.failure.title]]</strong>
<p>[[email:unsub.failure.message, {error}, {config.relative_path}/me/settings]]</p>
{{{ end }}}
<hr />
<p> <p>
<a href="{config.relative_path}/">[[notifications:back_to_home, {config.siteTitle}]]</a> <a href="{config.relative_path}/">[[notifications:back_to_home, {config.siteTitle}]]</a>
</p> </p>

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save