This commit is contained in:
Barış Soner Uşaklı
2022-08-22 12:56:46 -04:00
parent 2a8f1e4cba
commit 196d22be16
14 changed files with 513 additions and 78 deletions

View File

@@ -37,7 +37,7 @@
"bcryptjs": "2.4.3",
"benchpressjs": "2.4.3",
"body-parser": "1.20.0",
"bootbox": "5.5.3",
"bootbox": "https://github.com/makeusabrew/bootbox.git#v6-wip",
"bootstrap": "5.2.0",
"chalk": "4.1.2",
"chart.js": "2.9.4",

View File

@@ -0,0 +1 @@
@import "bootstrap/scss/bootstrap";

45
public/scss/flags.scss Normal file
View File

@@ -0,0 +1,45 @@
/*
Flags page CSS
- Originally in ACP
- Now available in front-end for global mods as well
*/
.page-flags {
// hide the all categories li element
[component="flags/filters"] [component="category/dropdown"] [data-all="all"] {
display: none;
}
}
.page-manage-flags, .page-posts-flags {
.post-container > .row {
margin-bottom: 2rem;
}
.flag-reporters {
font-size: 1.2rem;
ul {
padding-left: 0;
li {
list-style-type: none;
img, .user-icon {
@include user-icon-style(18px, 1rem);
margin-right: 1rem;
}
}
}
}
.flag-post-body {
img, .user-icon {
@include user-icon-style(24px, 1.5rem);
}
}
[component="posts/flag/history"] .avatar {
margin-right: 1rem;
}
}

184
public/scss/generics.scss Normal file
View File

@@ -0,0 +1,184 @@
@mixin define-if-not-set(){
$gray-base: #000;
$gray-darker: lighten($gray-base, 13.5%); // #222
$gray-dark: lighten($gray-base, 20%); // #333
$gray: lighten($gray-base, 33.5%); // #555
$gray-light: lighten($gray-base, 46.7%); // #777
$gray-lighter: lighten($gray-base, 93.5%); // #eee
$brand-primary: darken(#428bca, 6.5%); // #337ab7
$brand-success: #5cb85c;
$brand-info: #5bc0de;
$brand-warning: #f0ad4e;
$brand-danger: #d9534f;
}
@include define-if-not-set();
#move_thread_modal .category-list {
height: 500px;
overflow-y: auto;
overflow-x: hidden;
}
.topic-watch-dropdown {
.help-text {
margin-left: 20px;
}
}
.category-list {
padding: 0;
li {
@include inline-block;
@include pointer;
padding: 0.5em;
margin: 0.25em;
@include border-radius(3px);
&.disabled {
background-color: #888!important;
opacity: 0.5;
}
}
}
.user-list {
padding-left: 2rem;
padding-top: 1rem;
li {
@include pointer;
display: inline-block;
list-style-type: none;
padding: 0.5rem 1rem;
&:hover {
background: #eee;
}
.avatar {
float: left;
margin-right: 1rem;
}
span {
vertical-align: middle;
display: inline-block;
}
}
}
@mixin user-icon() {
display: inline-block;
text-align: center;
color: $gray-300;
font-weight: normal;
vertical-align: middle;
overflow: hidden; /* stops alt text from overflowing past boundaries if image does not load */
white-space: nowrap;
&:before {
content: '';
display: inline-block;
height: 100%;
vertical-align: middle;
}
}
.avatar {
/* Contains the user icon class as a mixin, so there's no need to include that in the template */
@include user-icon;
&.avatar-xs {
width: 16px;
height: 16px;
@include user-icon-style(16px, 1rem);
}
&.avatar-sm {
width: 24px;
height: 24px;
@include user-icon-style(24px, 1.5rem);
}
&.avatar-sm2x {
width: 48px;
height: 48px;
@include user-icon-style(48px, 1.5rem);
}
&.avatar-md {
width: 32px;
height: 32px;
@include user-icon-style(32px, 1.5rem);
}
&.avatar-lg {
width: 64px;
height: 64px;
@include user-icon-style(64px, 4rem);
}
&.avatar-xl {
width: 128px;
height: 128px;
@include user-icon-style(128px, 7.5rem);
}
&.avatar-rounded {
border-radius: 50%;
}
}
.ban-modal {
.form-inline, .form-group {
width: 100%;
}
.units {
line-height: 5rem;
}
}
.admin .ban-modal .units {
line-height: 1.846;
}
#crop-picture-modal {
#cropped-image {
max-width: 100%;
}
.cropper-container.cropper-bg {
max-width: 100%;
}
}
.necro-post {
color: rgba(127,127,127,.5);
font-size: 1.5em;
margin-bottom: 20px;
text-align: center;
text-transform: uppercase;
}
.timeline-event {
display: flex;
align-items: center;
justify-content: center;
.timeline-badge {
padding: 1rem;
}
}
.imagedrop {
position: absolute;
text-align: center;
font-size: 24px;
color: $gray-300;
width: 100%;
display: none;
}

8
public/scss/global.scss Normal file
View File

@@ -0,0 +1,8 @@
/*
This stylesheet is applied to all themes and all pages.
They can be overridden by themes, though their presence (or initial settings) may be depended upon by
client-side logic in core.
==========
*/

101
public/scss/install.scss Normal file
View File

@@ -0,0 +1,101 @@
@import "./admin/vars";
.working {
width: 24px;
height: 24px;
position: relative;
display: inline-block;
vertical-align: bottom;
&::before, &::after {
content: ' ';
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #fff;
opacity: 0.6;
position: absolute;
top: 0;
left: 0;
-webkit-animation: sk-bounce 2.0s infinite ease-in-out;
animation: sk-bounce 2.0s infinite ease-in-out;
}
&::after {
-webkit-animation-delay: -1.0s;
animation-delay: -1.0s;
}
}
@-webkit-keyframes sk-bounce {
0%, 100% { -webkit-transform: scale(0.0) }
50% { -webkit-transform: scale(1.0) }
}
@keyframes sk-bounce {
0%, 100% {
transform: scale(0.0);
-webkit-transform: scale(0.0);
} 50% {
transform: scale(1.0);
-webkit-transform: scale(1.0);
}
}
.btn, .form-control, .navbar {
border-radius: 0;
}
.container {
font-size: 18px;
margin-bottom: 100px;
}
body, small, p, div {
font-family: $font-family-sans-serif;
}
.input-row {
margin-bottom: 20px;
.form-control {
margin-bottom: 5px;
}
.help-text {
pointer-events: none;
line-height: 20px;
color: #888;
font-size: 85%;
display: none;
}
.input-field {
border-right: 5px solid #FFF;
}
&.active {
.input-field {
border-right-color: #38B44A;
padding-right: 20px;
}
.help-text {
display: block;
}
}
&.error {
.input-field {
border-right-color: #BF3E11;
padding-right: 20px;
}
.help-text {
display: block;
}
}
}

View File

@@ -0,0 +1,10 @@
@import 'jquery-ui/themes/base/core';
@import 'jquery-ui/themes/base/menu';
@import 'jquery-ui/themes/base/button';
@import 'jquery-ui/themes/base/datepicker';
@import 'jquery-ui/themes/base/autocomplete';
@import 'jquery-ui/themes/base/resizable';
@import 'jquery-ui/themes/base/selectable';
@import 'jquery-ui/themes/base/draggable';
@import 'jquery-ui/themes/base/sortable';
@import 'jquery-ui/themes/base/theme';

80
public/scss/mixins.scss Normal file
View File

@@ -0,0 +1,80 @@
@mixin no-select() {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@mixin pointer() {
cursor: pointer;
*cursor: hand;
}
@mixin inline-block() {
display: inline-block;
*display: inline;
zoom: 1;
}
@mixin clear() {
clear: both;
}
@mixin zebra() {
&:nth-child(even) {
background: rgba(191,191,191,0.2);
}
&:nth-child(odd) {
background: rgba(223,223,223,0.2);
}
}
@mixin border-radius($radius: 5px){
-webkit-border-radius: $radius;
-moz-border-radius: $radius;
-ms-border-radius: $radius;
-o-border-radius: $radius;
border-radius: $radius;
}
@mixin text-ellipsis() {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@mixin fix-lists() {
ul {
> li {
list-style-type: disc;
ul > li {
list-style-type: circle;
ul > li {
list-style-type: square;
}
}
}
}
> ul, > ol {
margin-bottom: 10px;
}
}
@mixin user-icon-style($size: 32px, $font-size: 1.5rem, $border-radius: inherit){
border-radius: $border-radius;
width: $size;
height: $size;
line-height: $size;
font-size: $font-size;
}
@mixin box-shadow($shadow){
-webkit-box-shadow: $shadow;
box-shadow: $shadow;
}

19
public/scss/modals.scss Normal file
View File

@@ -0,0 +1,19 @@
.tool-modal {
position: fixed;
bottom: 10%;
right: 2rem;
z-index: 1;
}
// TODO: port to bootstrap5 scss
// @media screen and (min-width: $screen-sm-min) {
// .tool-modal {
// max-width: 33%;
// }
// }
.topic-thumbs-modal {
img.media-object {
max-width: 20rem;
}
}

View File

@@ -85,28 +85,6 @@ if (typeof window !== 'undefined') {
};
}(jQuery || { fn: {} }));
(function () {
// FIX FOR #1245 - https://github.com/NodeBB/NodeBB/issues/1245
// from http://stackoverflow.com/questions/15931962/bootstrap-dropdown-disappear-with-right-click-on-firefox
// obtain a reference to the original handler
let _clearMenus = $._data(document, 'events').click.filter(function (el) {
return el.namespace === 'bs.data-api.dropdown' && el.selector === undefined;
});
if (_clearMenus.length) {
_clearMenus = _clearMenus[0].handler;
}
// disable the old listener
$(document)
.off('click.data-api.dropdown', _clearMenus)
.on('click.data-api.dropdown', function (e) {
// call the handler only when not right-click
if (e.button !== 2) {
_clearMenus();
}
});
}());
let timeagoFn;
overrides.overrideTimeagoCutoff = function () {
const cutoff = parseInt(ajaxify.data.timeagoCutoff || config.timeagoCutoff, 10);

View File

@@ -25,36 +25,37 @@ CSS.supportedSkins = [
const buildImports = {
client: function (source) {
return `@import "./theme";\n${source}\n${[
'@import "../public/vendor/fontawesome/less/regular.less";',
'@import "../public/vendor/fontawesome/less/solid.less";',
'@import "../public/vendor/fontawesome/less/brands.less";',
'@import "../public/vendor/fontawesome/less/fontawesome.less";',
'@import "../public/vendor/fontawesome/less/v4-shims.less";',
'@import "../public/vendor/fontawesome/less/nodebb-shims.less";',
'@import "../../public/less/jquery-ui.less";',
'@import (inline) "../node_modules/@adactive/bootstrap-tagsinput/src/bootstrap-tagsinput.css";',
'@import (inline) "../node_modules/cropperjs/dist/cropper.css";',
'@import "../../public/less/flags.less";',
'@import "../../public/less/generics.less";',
'@import "../../public/less/mixins.less";',
'@import "../../public/less/global.less";',
'@import "../../public/less/modals.less";',
].map(str => str.replace(/\//g, path.sep)).join('\n')}`;
'@import "../public/vendor/fontawesome/scss/regular.scss";',
'@import "../public/vendor/fontawesome/scss/solid.scss";',
'@import "../public/vendor/fontawesome/scss/brands.scss";',
'@import "../public/vendor/fontawesome/scss/fontawesome.scss";',
'@import "../public/vendor/fontawesome/scss/v4-shims.scss";',
'@import "../public/vendor/fontawesome/scss/nodebb-shims.scss";',
'@import "../../public/scss/jquery-ui.scss";',
'@import "../node_modules/@adactive/bootstrap-tagsinput/src/bootstrap-tagsinput";',
'@import "../node_modules/cropperjs/dist/cropper";',
'@import "../../public/scss/mixins.scss";',
'@import "../../public/scss/flags.scss";',
'@import "../../public/scss/generics.scss";',
'@import "../../public/scss/global.scss";',
'@import "../../public/scss/modals.scss";',
].join('\n')}`;
},
admin: function (source) {
return `${source}\n${[
'@import "../public/vendor/fontawesome/less/regular.less";',
'@import "../public/vendor/fontawesome/less/solid.less";',
'@import "../public/vendor/fontawesome/less/brands.less";',
'@import "../public/vendor/fontawesome/less/fontawesome.less";',
'@import "../public/vendor/fontawesome/less/v4-shims.less";',
'@import "../public/vendor/fontawesome/less/nodebb-shims.less";',
'@import "../public/less/admin/admin";',
'@import "../public/less/generics.less";',
'@import "../../public/less/jquery-ui.less";',
'@import (inline) "../node_modules/@adactive/bootstrap-tagsinput/src/bootstrap-tagsinput.css";',
'@import (inline) "../public/vendor/mdl/material.css";',
].map(str => str.replace(/\//g, path.sep)).join('\n')}`;
'@import "../public/vendor/fontawesome/scss/regular.scss";',
'@import "../public/vendor/fontawesome/scss/solid.scss";',
'@import "../public/vendor/fontawesome/scss/brands.scss";',
'@import "../public/vendor/fontawesome/scss/fontawesome.scss";',
'@import "../public/vendor/fontawesome/scss/v4-shims.scss";',
'@import "../public/vendor/fontawesome/scss/nodebb-shims.scss";',
'@import "../public/scss/admin/admin.scss";',
'@import "../../public/scss/mixins.scss";',
'@import "../public/scss/generics.scss";',
'@import "../../public/scss/jquery-ui.scss";',
'@import "../node_modules/@adactive/bootstrap-tagsinput/src/bootstrap-tagsinput";',
'@import "../public/vendor/mdl/material";',
].join('\n')}`;
},
};
@@ -75,9 +76,18 @@ async function getImports(files, prefix, extension) {
const pluginDirectories = [];
let source = '';
function fixPath(file) {
if (!file) {
return;
}
const parsed = path.parse(file);
const newFile = path.join(parsed.dir, parsed.name);
return newFile.replace(/\\/g, '/');
}
files.forEach((styleFile) => {
if (styleFile.endsWith(extension)) {
source += `${prefix + path.sep + styleFile}";`;
source += `${prefix + fixPath(styleFile)}";`;
} else {
pluginDirectories.push(styleFile);
}
@@ -85,7 +95,7 @@ async function getImports(files, prefix, extension) {
await Promise.all(pluginDirectories.map(async (directory) => {
const styleFiles = await file.walk(directory);
styleFiles.forEach((styleFile) => {
source += `${prefix + path.sep + styleFile}";`;
source += `${prefix + fixPath(styleFile)}";`;
});
}));
return source;
@@ -94,8 +104,8 @@ async function getImports(files, prefix, extension) {
async function getBundleMetadata(target) {
const paths = [
path.join(__dirname, '../../node_modules'),
path.join(__dirname, '../../public/less'),
path.join(__dirname, '../../public/vendor/fontawesome/less'),
path.join(__dirname, '../../public/scss'),
path.join(__dirname, '../../public/vendor/fontawesome/scss'),
];
// Skin support
@@ -122,10 +132,10 @@ async function getBundleMetadata(target) {
skinImport = skinImport.join('');
}
const [lessImports, cssImports, acpLessImports] = await Promise.all([
filterGetImports(plugins.lessFiles, '\n@import ".', '.less'),
filterGetImports(plugins.cssFiles, '\n@import (inline) ".', '.css'),
target === 'client' ? '' : filterGetImports(plugins.acpLessFiles, '\n@import ".', '.less'),
const [scssImports, cssImports, acpScssImports] = await Promise.all([
filterGetImports(plugins.scssFiles, '\n@import "', '.scss'),
filterGetImports(plugins.cssFiles, '\n@import "', ''),
target === 'client' ? '' : filterGetImports(plugins.acpScssFiles, '\n@import "', '.scss'),
]);
async function filterGetImports(files, prefix, extension) {
@@ -133,7 +143,7 @@ async function getBundleMetadata(target) {
return await getImports(filteredFiles, prefix, extension);
}
let imports = `${skinImport}\n${cssImports}\n${lessImports}\n${acpLessImports}`;
let imports = `${skinImport}\n${cssImports}\n${scssImports}\n${acpScssImports}`;
imports = buildImports[target](imports);
return { paths: paths, imports: imports };

View File

@@ -5,7 +5,7 @@ const os = require('os');
const uglify = require('uglify-es');
const async = require('async');
const winston = require('winston');
const less = require('less');
const sass = require('sass');
const postcss = require('postcss');
const autoprefixer = require('autoprefixer');
const clean = require('postcss-clean');
@@ -226,9 +226,8 @@ Minifier.js.minifyBatch = async function (scripts, fork) {
};
actions.buildCSS = async function buildCSS(data) {
const lessOutput = await less.render(data.source, {
paths: data.paths,
javascriptEnabled: false,
const scssOutput = sass.compileString(data.source, {
loadPaths: data.paths,
});
const postcssArgs = [autoprefixer];
@@ -237,7 +236,7 @@ actions.buildCSS = async function buildCSS(data) {
processImportFrom: ['local'],
}));
}
const result = await postcss(postcssArgs).process(lessOutput.css, {
const result = await postcss(postcssArgs).process(scssOutput.css.toString(), {
from: undefined,
});
return { code: result.css };

View File

@@ -33,8 +33,8 @@ Plugins.libraries = {};
Plugins.loadedHooks = {};
Plugins.staticDirs = {};
Plugins.cssFiles = [];
Plugins.lessFiles = [];
Plugins.acpLessFiles = [];
Plugins.scssFiles = [];
Plugins.acpScssFiles = [];
Plugins.clientScripts = [];
Plugins.acpScripts = [];
Plugins.libraryPaths = [];
@@ -98,8 +98,8 @@ Plugins.reload = async function () {
Plugins.staticDirs = {};
Plugins.versionWarning = [];
Plugins.cssFiles.length = 0;
Plugins.lessFiles.length = 0;
Plugins.acpLessFiles.length = 0;
Plugins.scssFiles.length = 0;
Plugins.acpScssFiles.length = 0;
Plugins.clientScripts.length = 0;
Plugins.acpScripts.length = 0;
Plugins.libraryPaths.length = 0;

View File

@@ -22,11 +22,11 @@ module.exports = function (Plugins) {
cssFiles: function (next) {
Plugins.data.getFiles(pluginData, 'css', next);
},
lessFiles: function (next) {
Plugins.data.getFiles(pluginData, 'less', next);
scssFiles: function (next) {
Plugins.data.getFiles(pluginData, 'scss', next);
},
acpLessFiles: function (next) {
Plugins.data.getFiles(pluginData, 'acpLess', next);
acpScssFiles: function (next) {
Plugins.data.getFiles(pluginData, 'acpScss', next);
},
clientScripts: function (next) {
Plugins.data.getScripts(pluginData, 'client', next);
@@ -55,8 +55,8 @@ module.exports = function (Plugins) {
Object.assign(Plugins.staticDirs, results.staticDirs || {});
add(Plugins.cssFiles, results.cssFiles);
add(Plugins.lessFiles, results.lessFiles);
add(Plugins.acpLessFiles, results.acpLessFiles);
add(Plugins.scssFiles, results.scssFiles);
add(Plugins.acpScssFiles, results.acpScssFiles);
add(Plugins.clientScripts, results.clientScripts);
add(Plugins.acpScripts, results.acpScripts);
Object.assign(meta.js.scripts.modules, results.modules || {});
@@ -74,8 +74,8 @@ module.exports = function (Plugins) {
'requirejs modules': ['modules'],
'client js bundle': ['clientScripts'],
'admin js bundle': ['acpScripts'],
'client side styles': ['cssFiles', 'lessFiles'],
'admin control panel styles': ['cssFiles', 'lessFiles', 'acpLessFiles'],
'client side styles': ['cssFiles', 'scssFiles'],
'admin control panel styles': ['cssFiles', 'scssFiles', 'acpScssFiles'],
languages: ['languageData'],
};
@@ -87,8 +87,8 @@ module.exports = function (Plugins) {
case 'clientScripts':
case 'acpScripts':
case 'cssFiles':
case 'lessFiles':
case 'acpLessFiles':
case 'scssFiles':
case 'acpScssFiles':
Plugins[field].length = 0;
break;
case 'languageData':