From 196d22be160863001b520943ac004b5b58f3dfad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 22 Aug 2022 12:56:46 -0400 Subject: [PATCH] bs5 prep --- install/package.json | 2 +- public/scss/admin/admin.scss | 1 + public/scss/flags.scss | 45 +++++++++ public/scss/generics.scss | 184 +++++++++++++++++++++++++++++++++++ public/scss/global.scss | 8 ++ public/scss/install.scss | 101 +++++++++++++++++++ public/scss/jquery-ui.scss | 10 ++ public/scss/mixins.scss | 80 +++++++++++++++ public/scss/modals.scss | 19 ++++ public/src/overrides.js | 22 ----- src/meta/css.js | 82 +++++++++------- src/meta/minifier.js | 9 +- src/plugins/index.js | 8 +- src/plugins/load.js | 20 ++-- 14 files changed, 513 insertions(+), 78 deletions(-) create mode 100644 public/scss/admin/admin.scss create mode 100644 public/scss/flags.scss create mode 100644 public/scss/generics.scss create mode 100644 public/scss/global.scss create mode 100644 public/scss/install.scss create mode 100644 public/scss/jquery-ui.scss create mode 100644 public/scss/mixins.scss create mode 100644 public/scss/modals.scss diff --git a/install/package.json b/install/package.json index cac061f8c7..f27026b2e8 100644 --- a/install/package.json +++ b/install/package.json @@ -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", diff --git a/public/scss/admin/admin.scss b/public/scss/admin/admin.scss new file mode 100644 index 0000000000..d8dc8d22a9 --- /dev/null +++ b/public/scss/admin/admin.scss @@ -0,0 +1 @@ +@import "bootstrap/scss/bootstrap"; \ No newline at end of file diff --git a/public/scss/flags.scss b/public/scss/flags.scss new file mode 100644 index 0000000000..9b9312a885 --- /dev/null +++ b/public/scss/flags.scss @@ -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; + } +} \ No newline at end of file diff --git a/public/scss/generics.scss b/public/scss/generics.scss new file mode 100644 index 0000000000..f8bb049422 --- /dev/null +++ b/public/scss/generics.scss @@ -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; +} \ No newline at end of file diff --git a/public/scss/global.scss b/public/scss/global.scss new file mode 100644 index 0000000000..7fb8234708 --- /dev/null +++ b/public/scss/global.scss @@ -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. + + ========== +*/ + diff --git a/public/scss/install.scss b/public/scss/install.scss new file mode 100644 index 0000000000..7e67e0bb3e --- /dev/null +++ b/public/scss/install.scss @@ -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; + } + } +} \ No newline at end of file diff --git a/public/scss/jquery-ui.scss b/public/scss/jquery-ui.scss new file mode 100644 index 0000000000..ea14c5ca97 --- /dev/null +++ b/public/scss/jquery-ui.scss @@ -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'; diff --git a/public/scss/mixins.scss b/public/scss/mixins.scss new file mode 100644 index 0000000000..d22b04ceff --- /dev/null +++ b/public/scss/mixins.scss @@ -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; +} \ No newline at end of file diff --git a/public/scss/modals.scss b/public/scss/modals.scss new file mode 100644 index 0000000000..5130ee6b15 --- /dev/null +++ b/public/scss/modals.scss @@ -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; + } +} \ No newline at end of file diff --git a/public/src/overrides.js b/public/src/overrides.js index 4e02be3d9d..f933f5f7ec 100644 --- a/public/src/overrides.js +++ b/public/src/overrides.js @@ -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); diff --git a/src/meta/css.js b/src/meta/css.js index ea347d4af9..728ce3aa0d 100644 --- a/src/meta/css.js +++ b/src/meta/css.js @@ -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 }; diff --git a/src/meta/minifier.js b/src/meta/minifier.js index cfb740755f..c0dbf6ece4 100644 --- a/src/meta/minifier.js +++ b/src/meta/minifier.js @@ -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 }; diff --git a/src/plugins/index.js b/src/plugins/index.js index d1a3bd92bf..3587dc091c 100644 --- a/src/plugins/index.js +++ b/src/plugins/index.js @@ -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; diff --git a/src/plugins/load.js b/src/plugins/load.js index 99ee26df3d..d6c0375820 100644 --- a/src/plugins/load.js +++ b/src/plugins/load.js @@ -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':