├── .editorconfig ├── .gitignore ├── README.MD ├── app.js ├── common.js ├── libs ├── fileupload.js ├── log.js ├── minifier.js ├── mongoose.js ├── myconfig.js └── sessionStore.js ├── models ├── admins.js ├── articles.js ├── categories.js └── tags.js ├── package.json ├── public ├── images │ ├── backgrounds │ │ └── logo.png │ ├── general │ │ └── ajax-loader.gif │ ├── icons │ │ ├── adminfavicon.png │ │ ├── favicon-32x32.png │ │ └── favicon.ico │ ├── mediadescription │ │ ├── i-bold.png │ │ ├── i-center.png │ │ ├── i-italic.png │ │ ├── i-link.png │ │ ├── i-reset.png │ │ └── i-strong.png │ ├── noimage-min.jpg │ ├── noimage-slide.jpg │ └── socialimage.png ├── scripts │ ├── admin │ │ ├── aliasmaker.js │ │ ├── articles.js │ │ ├── categories.js │ │ ├── click-actions.js │ │ ├── error.js │ │ ├── login.js │ │ ├── main.min.js │ │ ├── media-actions.js │ │ ├── mediadescription.js │ │ ├── tags.js │ │ └── texthighlighter │ │ │ ├── jquery.texthighlighter.js │ │ │ └── rangy-core.js │ ├── client │ │ ├── main.js │ │ └── main.min.js │ ├── jquery-1.10.2-min.js │ └── jquery-ui-min.js └── styles │ ├── admin.css │ ├── admin.min.css │ ├── main.css │ └── main.min.css ├── routes ├── admin │ ├── admin.js │ ├── articles.js │ ├── categories.js │ ├── editarticle.js │ ├── editcategory.js │ ├── login.js │ └── logout.js ├── client │ ├── article.js │ ├── category.js │ └── tag.js └── index.js └── templates ├── admin ├── articles │ ├── articles.ejs │ ├── body.ejs │ └── item.ejs ├── categories │ ├── body.ejs │ ├── categories.ejs │ └── item.ejs ├── common │ ├── error.ejs │ ├── footer.ejs │ ├── head.ejs │ ├── header.ejs │ ├── minscriptpack.ejs │ └── scriptpack.ejs ├── editarticle │ ├── body.ejs │ └── editarticle.ejs ├── editcategory │ ├── body.ejs │ └── editcategory.ejs ├── login │ ├── body.ejs │ └── login.ejs └── modules │ └── dateParser.ejs └── client ├── article ├── article.ejs └── body.ejs ├── category ├── body.ejs └── category.ejs ├── common ├── footer.ejs ├── head.ejs ├── header.ejs ├── scriptpack.ejs └── scrolltop.ejs ├── error ├── body.ejs └── error.ejs ├── modules ├── articleItem.ejs ├── articleItemArray.ejs ├── dateParser.ejs ├── moreButton.ejs ├── sideArticleItem.ejs └── tags.ejs └── tag ├── body.ejs └── tag.ejs /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .idea/ 3 | node_modules/ -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | ## Запускаем проект 2 | 3 | Стартуем сервер по адресу `localhost:3002`: 4 | 5 | ``` 6 | $ npm install 7 | $ npm run dev 8 | ``` 9 | Ссылка на курс «JavaScript Back-end. Практическое занятие»: 10 | https://www.youtube.com/watch?v=ZTxAFIH5uIg&list=PLOzk9ZaOL1u0BlibThBjfVAIWRyXAwttk 11 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 2 | var express = require('express'); 3 | var http = require('http'); 4 | var path = require('path'); 5 | var log = require('./libs/log')(module); 6 | var bodyParser = require('body-parser'); 7 | var myconfig = require('./libs/myconfig'); 8 | var session = require('express-session'); 9 | var async = require('async'); 10 | var favicon = require('express-favicon'); 11 | var multer = require('multer'); 12 | var compression = require('compression'); 13 | var common = require('./common'); 14 | var cluster = require('cluster'); 15 | 16 | var app = express(); 17 | 18 | app.use(favicon(__dirname + '/public/images/icons/favicon.ico')); 19 | 20 | // process.env.NODE_ENV = 'production'; 21 | 22 | // Для работы с garbage collector запустите проект с параметрами: 23 | // node --nouse-idle-notification --expose-gc app.js 24 | 25 | if (cluster.isMaster) { 26 | 27 | var cpuCount = require('os').cpus().length; 28 | 29 | for (var i = 0; i < cpuCount; i += 1) { 30 | cluster.schedulingPolicy = cluster.SCHED_NONE; 31 | cluster.fork(); 32 | } 33 | 34 | cluster.on('exit', function (worker) { 35 | console.log('Worker ' + worker.id + ' died :('); 36 | cluster.fork(); 37 | }); 38 | 39 | } else { 40 | 41 | app.use(compression()); 42 | 43 | app.set('views', __dirname + '/templates'); 44 | app.set('view engine', 'ejs'); 45 | 46 | app.use(bodyParser.urlencoded({ 47 | extended: true, 48 | limit: '50mb' 49 | })); 50 | 51 | app.use(multer( 52 | { 53 | dest: path.join(__dirname, 'public/uploads'), 54 | limits: { 55 | fieldNameSize: 999999999, 56 | fieldSize: 999999999 57 | } 58 | } 59 | ).any()); 60 | 61 | app.use(express.static(path.join(__dirname, 'public'))); 62 | app.use('/files', express.static('../files')); 63 | 64 | var sessionStore = require('./libs/sessionStore'); 65 | app.use(session({ 66 | secret: myconfig.session.secret, 67 | key: myconfig.session.key, 68 | cookie: myconfig.session.cookie, 69 | store: sessionStore 70 | })); 71 | 72 | 73 | //************************* Middleware *********************************** 74 | app.use(common.commonMiddleware); 75 | 76 | //************************* Routes *********************************** 77 | require('./routes')(app); 78 | 79 | //************************* GARBAGE магия *********************************** 80 | var gcInterval; 81 | function init() 82 | { 83 | gcInterval = setInterval(function() { gcDo(); }, 60000); 84 | } 85 | function gcDo() 86 | { 87 | global.gc(); 88 | clearInterval(gcInterval); 89 | init(); 90 | } 91 | init(); 92 | //************************************************************ 93 | 94 | //************************* 404 *********************************** 95 | app.use(function(req, res){ 96 | 97 | res.locals.metatitle = '404 Ничего не найдено'; 98 | res.locals.pagenoindex = 'yes'; 99 | res.status(404).render('./client/error/error'); 100 | }); 101 | 102 | //************************* Запуск сервера *********************************** 103 | var httpServer = http.createServer(app); 104 | 105 | function onListening(){ 106 | log.info('Listening on port %d', myconfig.port); 107 | } 108 | 109 | httpServer.on('listening', onListening); 110 | httpServer.listen(myconfig.port, '127.0.0.1'); 111 | 112 | 113 | } 114 | 115 | process.on('uncaughtException', function (err) { 116 | log.error((new Date).toUTCString() + ' uncaughtException:', err.message); 117 | log.error(err.stack); 118 | process.exit(1); 119 | }); 120 | 121 | -------------------------------------------------------------------------------- /common.js: -------------------------------------------------------------------------------- 1 | 2 | var log = require('./libs/log')(module); 3 | var Admins = require('./models/admins').Admin; 4 | var Categories = require('./models/categories').Category; 5 | var Myconfig = require('./libs/myconfig'); 6 | var async = require('async'); 7 | 8 | exports.commonMiddleware = function (req, res, next){ 9 | 10 | //====================================================================================// 11 | //******************* Языковые переменные ********************// 12 | //====================================================================================// 13 | 14 | var language = "default"; 15 | var langArray = []; 16 | 17 | for (var l = 0; l < Myconfig.languages.length; l++){ 18 | if (!Myconfig.languages[l].default){ 19 | langArray.push(Myconfig.languages[l].name) 20 | } 21 | } 22 | 23 | if (req.query && (req.query.language) && (langArray.indexOf(req.query.language) != -1)){ 24 | language = req.query.language; 25 | } 26 | 27 | //====================================================================================// 28 | //******************* Параллельный запуск функций ********************// 29 | //====================================================================================// 30 | 31 | async.parallel([ 32 | getEnvironment, 33 | getUserRights, 34 | getCurrentDate, 35 | getCategories 36 | ], function(err){ 37 | if (err){ 38 | log.error('------ Error ------ ' + err); 39 | next(); 40 | } else { 41 | next(); 42 | } 43 | }); 44 | 45 | //====================================================================================// 46 | //******************* Переменные res.locals ********************// 47 | //====================================================================================// 48 | function getEnvironment(callback){ 49 | 50 | var originalPageUrl = req.originalUrl; 51 | 52 | if (originalPageUrl.indexOf('?') != -1){ 53 | originalPageUrl = originalPageUrl.substr(0, originalPageUrl.indexOf('?')); 54 | } 55 | res.locals.pageurl = req.protocol + '://' + req.get('host') + originalPageUrl; 56 | res.locals.currenthost = req.headers.host; 57 | res.locals.companyname = Myconfig.companyname; 58 | res.locals.pagenoindex = 'no'; 59 | 60 | res.locals.language = language; 61 | res.locals.urltail = ''; 62 | if (language != 'default'){ 63 | res.locals.urltail = '?language=' + language; 64 | } 65 | res.locals.languages = Myconfig.languages; 66 | res.locals.locals = Myconfig.locals; 67 | 68 | res.locals.metatitle = ''; 69 | res.locals.metadescription = ''; 70 | res.locals.metakeywords = ''; 71 | res.locals.socialimage = Myconfig.images.socialimage; 72 | res.locals.noimage = Myconfig.images.noimage; 73 | 74 | res.locals.page = ''; 75 | res.locals.tags = []; 76 | res.locals.pagination = null; 77 | 78 | res.locals.env = process.env.NODE_ENV; 79 | 80 | callback(); 81 | } 82 | 83 | //====================================================================================// 84 | //******************* Проверка прав администратора ********************// 85 | //====================================================================================// 86 | function getUserRights(callback){ 87 | 88 | if (req.session.user){ 89 | 90 | Admins.findOne({_id: req.session.user}, function(err, user){ 91 | 92 | if (user){ 93 | 94 | res.locals.adminrights = user.rights; 95 | res.locals.adminname = user.username; 96 | 97 | } else { 98 | 99 | res.locals.adminrights = ''; 100 | res.locals.adminname = ''; 101 | } 102 | 103 | callback(); 104 | }) 105 | 106 | } else { 107 | 108 | res.locals.adminrights = ''; 109 | res.locals.adminname = ''; 110 | callback(); 111 | } 112 | } 113 | 114 | 115 | //====================================================================================// 116 | //******************* Переменные для текущего месяца и года ********************// 117 | //====================================================================================// 118 | function getCurrentDate(callback){ 119 | 120 | var date = new Date(); 121 | var month = date.getMonth(); 122 | month = Myconfig.locals.month[language][month]; 123 | 124 | res.locals.currentdate = month; 125 | res.locals.fullyear = date.getFullYear(); 126 | callback(); 127 | } 128 | 129 | //====================================================================================// 130 | //******************* Сбор категорий для основного меню блога ********************// 131 | //====================================================================================// 132 | function getCategories(callback) { 133 | 134 | Categories.find({ismain: false, lang: language}).sort({position: 1}).exec(function(err, categories){ 135 | res.locals.categories = categories; 136 | res.locals.category = null; 137 | callback(); 138 | }) 139 | } 140 | 141 | }; 142 | 143 | -------------------------------------------------------------------------------- /libs/log.js: -------------------------------------------------------------------------------- 1 | var winston = require('winston'); 2 | 3 | function getLogger(module) { 4 | 5 | var path = module.filename.split('/').slice(-2).join('/'); 6 | 7 | return new winston.Logger({ 8 | transports:[ 9 | new winston.transports.Console({ 10 | colorize: true, 11 | level: 'debug', 12 | label: path 13 | }), 14 | new (winston.transports.File)({ 15 | filename: 'node.log', 16 | label: path 17 | }) 18 | ] 19 | }); 20 | } 21 | 22 | module.exports = getLogger; -------------------------------------------------------------------------------- /libs/minifier.js: -------------------------------------------------------------------------------- 1 | 2 | var log = require('./log')(module); 3 | var compressor = require('node-minify'); 4 | var config = require('../libs/myconfig'); 5 | var async = require('async'); 6 | 7 | function makeProduction(callback) { 8 | 9 | async.parallel([ 10 | minifyAdminJs, 11 | minifyClientJs, 12 | minifyCss 13 | ], function(err){ 14 | if (err){ 15 | callback(err); 16 | } else { 17 | callback(null); 18 | } 19 | }); 20 | } 21 | 22 | var scriptPath = config.homepath + '/public/scripts'; 23 | 24 | function minifyAdminJs(callback){ 25 | 26 | var dir = scriptPath + '/admin/'; 27 | var scripts = [ 28 | dir + 'error.js', 29 | dir + 'mediadescription.js', 30 | dir + 'aliasmaker.js', 31 | dir + 'click-actions.js', 32 | dir + 'tags.js', 33 | dir + 'media-actions.js', 34 | dir + 'articles.js', 35 | dir + 'categories.js', 36 | dir + 'login.js']; 37 | 38 | compressor.minify({ 39 | compressor: 'yui-js', 40 | input: scripts, 41 | output: dir + 'main.min.js', 42 | options: { 43 | 'line-break': 80, 44 | charset: 'utf8' 45 | }, 46 | callback: function (err) { 47 | if (err){ 48 | callback(err); 49 | } else { 50 | callback(null); 51 | } 52 | } 53 | }); 54 | 55 | } 56 | 57 | function minifyClientJs(callback){ 58 | 59 | var dir = scriptPath + '/client/'; 60 | var scripts = [ 61 | dir + 'main.js']; 62 | 63 | compressor.minify({ 64 | compressor: 'yui-js', 65 | input: scripts, 66 | output: dir + 'main.min.js', 67 | options: { 68 | 'line-break': 80, 69 | charset: 'utf8' 70 | }, 71 | callback: function (err) { 72 | if (err){ 73 | callback(err); 74 | } else { 75 | callback(null); 76 | } 77 | } 78 | }); 79 | } 80 | 81 | var cssPath = config.homepath + '/public/styles/'; 82 | 83 | function minifyCss(callback){ 84 | 85 | var styles = [ 86 | 'admin.css', 87 | 'main.css']; 88 | 89 | async.each(styles, function (style, callback) { 90 | 91 | compressor.minify({ 92 | compressor: 'clean-css', 93 | input: cssPath + style, 94 | output: cssPath + style.substr(0, style.lastIndexOf('.')) + '.min.css', 95 | options: { 96 | advanced: false, 97 | aggressiveMerging: false 98 | }, 99 | callback: function (err) { 100 | callback(err); 101 | } 102 | }); 103 | }, function(err) { 104 | callback(err); 105 | }); 106 | } 107 | 108 | process.stdout.write('makeProduction'); 109 | makeProduction(function(err){ 110 | if (err) { 111 | log.error('============= makeProduction error ================ ' + err); 112 | } else { 113 | log.info('============== makeProduction success ==============='); 114 | } 115 | process.exit(); 116 | }); 117 | -------------------------------------------------------------------------------- /libs/mongoose.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var myconfig = require('./myconfig'); 3 | 4 | mongoose.set('debug', true); 5 | 6 | mongoose.connect(myconfig.mongoose.uri), myconfig.mongoose.options; 7 | 8 | module.exports = mongoose; -------------------------------------------------------------------------------- /libs/myconfig.js: -------------------------------------------------------------------------------- 1 | 2 | var log = require('./log')(module); 3 | var myconfig = {}; 4 | 5 | //============================================================// 6 | //***************** Common *******************// 7 | //============================================================// 8 | myconfig.port = 3002; 9 | myconfig.companyname = "#BlondieBlog"; 10 | myconfig.homepath = "/home/aida/Work/node/javascript-backend"; 11 | 12 | //============================================================// 13 | //***************** Limits *******************// 14 | //============================================================// 15 | myconfig.limits = { 16 | pageArticles: 9, 17 | relArticles: 4 18 | }; 19 | 20 | //============================================================// 21 | //***************** Graphics *******************// 22 | //============================================================// 23 | myconfig.images = { 24 | socialimage: "/images/socialimage.png", 25 | noimage: "/images/noimage.jpg" 26 | }; 27 | 28 | //============================================================// 29 | //***************** Mongoose *******************// 30 | //============================================================// 31 | myconfig.mongoose = { 32 | uri: "mongodb://127.0.0.1/superblog", 33 | options: { 34 | server: { 35 | socketOptions: { 36 | keepAlive: 1 37 | } 38 | } 39 | } 40 | }; 41 | 42 | //============================================================// 43 | //***************** Session *******************// 44 | //============================================================// 45 | myconfig.session = { 46 | "secret": "nodeJSForever", 47 | "key": "sid", 48 | "cookie": { 49 | "httpOnly": true, 50 | "maxAge": null 51 | } 52 | }; 53 | 54 | //============================================================// 55 | //***************** Messages *******************// 56 | //============================================================// 57 | myconfig.messages = { 58 | error: { 59 | auth: "Неверное имя пользователя или пароль", 60 | db: "Ошибка базы данных" 61 | } 62 | }; 63 | 64 | //============================================================// 65 | //***************** Localisation *******************// 66 | //============================================================// 67 | 68 | myconfig.languages = [ 69 | { 70 | name: 'de', 71 | alias: 'Deutsch', 72 | default: false 73 | }, 74 | { 75 | name: 'en', 76 | alias: 'English', 77 | default: false 78 | }, 79 | { 80 | name: 'ru', 81 | alias: 'Русский', 82 | default: true 83 | } 84 | ]; 85 | 86 | myconfig.locals = { 87 | month: { 88 | default: ['Янв', 'Фев', 'Март', 'Апр', 'Май', 'Июнь', 'Июль', 'Авг', 'Сен', 'Окт', 'Нояб', 'Дек'], 89 | de: ['Jan', 'Feb', 'März', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], 90 | en: ['Jan', 'Feb', 'March', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 91 | }, 92 | days: { 93 | default: ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'], 94 | de: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'], 95 | en: ['Sun', 'Mo', 'Tue', 'Wed', 'Th', 'Fr', 'Sa'] 96 | }, 97 | navigations: { 98 | more: { 99 | default: 'Больше', 100 | de: 'Mehr', 101 | en: 'More' 102 | }, 103 | views: { 104 | default: 'Просмотров: ', 105 | de: 'Gesehen: ', 106 | en: 'Viewed: ' 107 | }, 108 | published: { 109 | default: 'Добавлена: ', 110 | de: 'Veröffentlicht: ', 111 | en: 'Published: ' 112 | } 113 | }, 114 | messages: { 115 | found: { 116 | default: 'Статьи по запросу ', 117 | de: 'Suchergebnisse für ', 118 | en: 'Articles about ' 119 | }, 120 | notfound: { 121 | title: { 122 | default: 'Ничего не найдено по запросу ', 123 | de: 'Keine passenden Suchergebnisse gefunden für ', 124 | en: 'No posts matched your criteria: ' 125 | }, 126 | text: { 127 | default: 'Попробуйте сформулировать иначе', 128 | de: 'Nutzen Sie bitte anderen keyword', 129 | en: 'Please try another keyword' 130 | } 131 | }, 132 | page404: { 133 | text: { 134 | default: 'Ничего не найдено', 135 | de: 'Seite Nicht Gefunden', 136 | en: 'Nothing found' 137 | } 138 | } 139 | } 140 | }; 141 | 142 | module.exports = myconfig; -------------------------------------------------------------------------------- /libs/sessionStore.js: -------------------------------------------------------------------------------- 1 | 2 | var session = require('express-session'); 3 | var mongoose = require('../libs/mongoose'); 4 | var mongoStore = require('connect-mongo')(session); 5 | 6 | var sessionStore = new mongoStore({mongooseConnection: mongoose.connection}); 7 | 8 | module.exports = sessionStore; -------------------------------------------------------------------------------- /models/admins.js: -------------------------------------------------------------------------------- 1 | 2 | var crypto = require('crypto'); 3 | var log = require('../libs/log')(module); 4 | var async = require('async'); 5 | 6 | var mongoose = require('../libs/mongoose'), 7 | Schema = mongoose.Schema; 8 | 9 | var schema = new Schema({ 10 | 11 | username: { 12 | type: String, 13 | unique: true, 14 | required: true 15 | }, 16 | hashedPassword: { 17 | type: String, 18 | required: true 19 | }, 20 | salt: { 21 | type: String, 22 | required: true 23 | }, 24 | rights: { 25 | type: String, 26 | default: 'moderator' 27 | } 28 | }); 29 | 30 | schema.virtual('password') 31 | 32 | .set(function(password){ 33 | 34 | this.salt = Math.random() + 'salt'; 35 | this.hashedPassword = this.encryptPassword(password); 36 | }) 37 | 38 | .get(function(){ 39 | 40 | return 'Top secret!' 41 | }); 42 | 43 | 44 | schema.methods = { 45 | 46 | encryptPassword: function (password) { 47 | 48 | return crypto.createHmac('sha256', this.salt).update(password).digest('hex'); 49 | }, 50 | checkPassword: function (password) { 51 | 52 | return this.encryptPassword(password) === this.hashedPassword; 53 | } 54 | }; 55 | 56 | schema.statics = { 57 | 58 | authorize: function(username, password, callback){ 59 | 60 | var Admin = this; 61 | 62 | async.waterfall([ 63 | function(callback){ 64 | if (username){ 65 | Admin.findOne({username: username}, callback); 66 | } 67 | }, 68 | function(admin, callback){ 69 | if (admin){ 70 | if (admin.checkPassword(password)){ 71 | callback(null, admin); 72 | } else { 73 | callback(403); 74 | } 75 | } else { 76 | callback(403); 77 | } 78 | } 79 | ], callback); 80 | }, 81 | createAdmin: function(username, password, rights, callback){ 82 | 83 | var Admin = this; 84 | 85 | var userFilter = /^([a-zA-Z0-9_\-])+$/; 86 | var passFilter = /^[a-zA-Z0-9,!,%,&,@,#,$,\^,*,?,_,~,+]*$/; 87 | 88 | async.waterfall([ 89 | function(callback){ 90 | if (!userFilter.test(username)) { 91 | callback('userError'); 92 | } else { 93 | callback(null); 94 | } 95 | }, 96 | function(callback){ 97 | if ((!passFilter.test(password)) || (password.length < 4)) { 98 | callback('passwordError'); 99 | } else { 100 | callback(null); 101 | } 102 | }, 103 | function(callback){ 104 | Admin.findOne({username:username}, function(err, user){ 105 | if (user) { 106 | callback('doubleUser'); 107 | } else { 108 | callback(null); 109 | } 110 | }); 111 | } 112 | ], 113 | function(err){ 114 | 115 | if (err){ 116 | log.error("------ DB ERROR ----- " + err); 117 | callback(err); 118 | } else { 119 | 120 | var admin = new Admin({ 121 | username: username, 122 | password: password, 123 | rights:rights 124 | }); 125 | 126 | admin.save(function(err){ 127 | if (err) return callback(err); 128 | callback (null, admin); 129 | }); 130 | } 131 | }); 132 | } 133 | }; 134 | 135 | exports.Admin = mongoose.model('Admin', schema); 136 | -------------------------------------------------------------------------------- /models/articles.js: -------------------------------------------------------------------------------- 1 | var log = require('../libs/log')(module); 2 | 3 | var mongoose = require('../libs/mongoose'), 4 | 5 | Schema = mongoose.Schema; 6 | 7 | var schema = new Schema({ 8 | 9 | title: { 10 | type: String 11 | }, 12 | image: { 13 | type: String 14 | }, 15 | description: { 16 | type: String 17 | }, 18 | shortdescription: { 19 | type: String 20 | }, 21 | htmltitle: { 22 | type: String 23 | }, 24 | htmldescription: { 25 | type: String 26 | }, 27 | htmlkeywords: { 28 | type: String 29 | }, 30 | alias: { 31 | type: String 32 | }, 33 | categories: { 34 | type: Array 35 | }, 36 | tags: { 37 | type: Array 38 | }, 39 | created: { 40 | type: Date, 41 | default: Date.now() 42 | }, 43 | creator: { 44 | type: String 45 | }, 46 | moderated: { 47 | type: Date 48 | }, 49 | moderatedhistory: { 50 | type: Array 51 | }, 52 | published: { 53 | type: Boolean, 54 | default: true 55 | }, 56 | lang: { 57 | type: String, 58 | default: 'default' 59 | }, 60 | //------------ id основной статьи для локализации -------// 61 | parent: { 62 | type: String 63 | }, 64 | //------------ массив языков, на которые переведена статья ------------// 65 | locales: { 66 | type: Array 67 | }, 68 | views: { 69 | type: Number, 70 | default: 0 71 | } 72 | }); 73 | 74 | schema.index( 75 | {moderated: -1} 76 | ); 77 | 78 | schema.index( 79 | {title: 'text', description: 'text', shortdescription: 'text'}, 80 | {weights: {title:10, description:5, shortdescription: 5}}, 81 | {language_override: "ru"} 82 | ); 83 | 84 | schema.statics = { 85 | 86 | createArticle: function(title, shortdescription, htmltitle, htmldescription, description, htmlkeywords, alias, 87 | categories, tags, creator, callback){ 88 | 89 | var Articles = this; 90 | 91 | var date = new Date(); 92 | var article = new Articles({ 93 | title: title, 94 | shortdescription: shortdescription, 95 | description: description, 96 | htmltitle: htmltitle, 97 | htmldescription: htmldescription, 98 | htmlkeywords: htmlkeywords, 99 | alias: alias, 100 | categories: categories, 101 | tags:tags, 102 | creator: creator, 103 | moderated: date 104 | }); 105 | article.save(function(err){ 106 | if (err) { 107 | log.error("------ DB ERROR ----- " + err); 108 | callback('Невозможно добавить статью'); 109 | } else { 110 | callback (null, article); 111 | } 112 | }); 113 | }, 114 | createLocal: function(parent, language, title, shortdescription, description, htmltitle, htmldescription, htmlkeywords, 115 | tags, creator, callback){ 116 | 117 | var Articles = this; 118 | 119 | var date = new Date(); 120 | var article = new Articles({ 121 | parent: parent, 122 | lang: language, 123 | title: title, 124 | shortdescription: shortdescription, 125 | description: description, 126 | htmltitle: htmltitle, 127 | htmldescription: htmldescription, 128 | htmlkeywords: htmlkeywords, 129 | tags:tags, 130 | creator: creator, 131 | moderated: date 132 | }); 133 | article.save(function(err){ 134 | if (err){ 135 | log.error("------ DB ERROR ----- " + err); 136 | callback('Невозможно добавить статью'); 137 | } else { 138 | Articles.update( 139 | {_id: parent}, 140 | { 141 | $push: {locales: language} 142 | }, 143 | function(){ 144 | callback (null, article); 145 | } 146 | ); 147 | } 148 | }); 149 | }, 150 | editArticle: function(language, id, title, shortdescription, description, htmltitle, htmldescription, htmlkeywords, alias, categories, tags, moderator, callback){ 151 | 152 | var Articles = this; 153 | var date = new Date(); 154 | 155 | var setParams = {}; 156 | setParams.title = title; 157 | setParams.shortdescription = shortdescription; 158 | setParams.description = description; 159 | setParams.htmltitle = htmltitle; 160 | setParams.htmldescription = htmldescription; 161 | setParams.htmlkeywords = htmlkeywords; 162 | 163 | if (language == 'default'){ 164 | setParams.alias = alias; 165 | setParams.categories = categories; 166 | } 167 | 168 | setParams.tags = tags; 169 | setParams.moderated = date; 170 | 171 | Articles.update( 172 | {_id: id}, 173 | { 174 | $set:setParams 175 | }, function(err, opt){ 176 | if (opt){ 177 | var moderatedhistory = {}; 178 | moderatedhistory.moderator = moderator; 179 | moderatedhistory.date = date; 180 | Articles.update( 181 | {_id: id}, 182 | { 183 | $push: {moderatedhistory: moderatedhistory} 184 | }, 185 | function(){ 186 | callback(null, opt); 187 | } 188 | ) 189 | } else { 190 | log.error("------ DB ERROR ----- " + err); 191 | callback("Ошибка базы данных") 192 | } 193 | } 194 | ) 195 | }, 196 | saveMainImage: function(id, image, callback){ 197 | 198 | var Articles = this; 199 | Articles.update( 200 | {_id: id}, 201 | { 202 | $set: {image: image} 203 | }, function(err, opt){ 204 | if (opt){ 205 | callback(null, opt); 206 | } else { 207 | log.error("------ DB ERROR ----- " + err); 208 | callback("Ошибка базы данных") 209 | } 210 | } 211 | ) 212 | }, 213 | setPublished: function(id, flag, callback){ 214 | 215 | var Articles = this; 216 | var setParams = {}; 217 | if (flag){ 218 | setParams.published = true; 219 | } else { 220 | setParams.published = false; 221 | } 222 | 223 | Articles.update( 224 | {_id: id}, 225 | { 226 | $set:setParams 227 | }, function(err, opt){ 228 | if (opt){ 229 | callback(null, opt); 230 | } else { 231 | log.error("------ DB ERROR ----- " + err); 232 | callback("Ошибка базы данных") 233 | } 234 | } 235 | ) 236 | }, 237 | addView: function(id, callback){ 238 | 239 | var Articles = this; 240 | 241 | Articles.update( 242 | {_id: id}, 243 | { 244 | $inc: {views: 1} 245 | }, function(err, opt){ 246 | if (opt){ 247 | callback(null, opt); 248 | } else { 249 | log.error("------ DB ERROR ----- " + err); 250 | callback("Ошибка базы данных") 251 | } 252 | } 253 | ) 254 | 255 | }, 256 | deleteArticle: function(id, callback){ 257 | var Articles = this; 258 | 259 | Articles.findOne({_id: id}, function(err, article){ 260 | 261 | if (article){ 262 | 263 | if (article.lang == 'default'){ 264 | 265 | Articles.remove({parent: article._id}, function(){ 266 | 267 | Articles.remove({_id: article._id}, function(err){ 268 | if (err){ 269 | callback ("Невозможно удалить статью"); 270 | } else { 271 | callback (null, id); 272 | } 273 | }); 274 | }); 275 | } else { 276 | 277 | Articles.update( 278 | {_id: article.parent}, 279 | { 280 | $pull: {locales: article.lang} 281 | }, 282 | function(){ 283 | 284 | Articles.remove({_id: article._id}, function(err){ 285 | if (err){ 286 | callback ("Невозможно удалить статью"); 287 | } else { 288 | callback (null, id); 289 | } 290 | }); 291 | } 292 | ) 293 | } 294 | } else { 295 | callback ("Невозможно удалить статью"); 296 | } 297 | }); 298 | 299 | } 300 | 301 | }; 302 | 303 | exports.Article = mongoose.model('Article', schema); -------------------------------------------------------------------------------- /models/categories.js: -------------------------------------------------------------------------------- 1 | var log = require('../libs/log')(module); 2 | 3 | var mongoose = require('../libs/mongoose'), 4 | 5 | Schema = mongoose.Schema; 6 | 7 | var schema = new Schema({ 8 | 9 | title: { 10 | type: String 11 | }, 12 | description: { 13 | type: String 14 | }, 15 | shortdescription: { 16 | type: String 17 | }, 18 | htmltitle: { 19 | type: String 20 | }, 21 | htmldescription: { 22 | type: String 23 | }, 24 | menutitle: { 25 | type: String 26 | }, 27 | htmlkeywords: { 28 | type: String 29 | }, 30 | alias: { 31 | type: String 32 | }, 33 | position: { 34 | type: Number, 35 | default: 0 36 | }, 37 | lang: { 38 | type: String, 39 | default: 'default' 40 | }, 41 | //------------ parent category for localization -------// 42 | parent: { 43 | type: String 44 | }, 45 | ismain: { 46 | type: Boolean, 47 | default: false 48 | }, 49 | creator: { 50 | type: String 51 | }, 52 | created: { 53 | type: Date, 54 | default: Date.now() 55 | }, 56 | moderatedhistory: { 57 | type: Array 58 | } 59 | }); 60 | 61 | schema.index( 62 | {position: 1} 63 | ); 64 | 65 | schema.statics = { 66 | 67 | createCategory: function(title, alias, position, creator, callback){ 68 | 69 | var Categories = this; 70 | 71 | Categories 72 | .findOne({title:{$regex : new RegExp(title, "i")}}) 73 | .then(function (category) { 74 | if (category) { 75 | throw 'Категория с таким ЗАГОЛОВКОМ уже существует!'; 76 | } else { 77 | return Categories.findOne({alias: alias}) 78 | } 79 | }) 80 | .then(function (category) { 81 | if (category) { 82 | throw 'Категория с таким URL уже существует!'; 83 | } else { 84 | var categoryDoc = new Categories({title: title, alias: alias, position: position, creator: creator}); 85 | return categoryDoc.save(); 86 | } 87 | }) 88 | .then( function (category) { 89 | callback (null, category); 90 | }) 91 | .catch(function (err) { 92 | callback(err); 93 | }); 94 | 95 | }, 96 | createLocal: function(parent, language, title, shortdescription, description, htmltitle, htmldescription, htmlkeywords, menutitle, creator, callback){ 97 | 98 | var Categories = this; 99 | 100 | Categories.findOne({_id: parent}, function (err, parentCategory) { 101 | 102 | if (parentCategory){ 103 | 104 | var category = new Categories({ 105 | 106 | parent: parent, 107 | lang: language, 108 | title: title, 109 | shortdescription: shortdescription, 110 | description: description, 111 | htmltitle: htmltitle, 112 | htmldescription: htmldescription, 113 | htmlkeywords: htmlkeywords, 114 | menutitle: menutitle, 115 | creator: creator, 116 | ismain: parentCategory.ismain 117 | }); 118 | 119 | category.save(function(err){ 120 | 121 | if (err) { 122 | callback('Невозможно добавить категорию'); 123 | } else { 124 | callback (null, category); 125 | } 126 | }) 127 | 128 | } else { 129 | callback('Невозможно добавить категорию'); 130 | } 131 | }); 132 | 133 | }, 134 | setMain: function (parentId, isMain, callback) { 135 | 136 | var params = {}; 137 | if (isMain){ 138 | params = {parent: parentId} 139 | } 140 | 141 | var Categories = this; 142 | 143 | Categories.update( 144 | params, 145 | {$set: { 146 | ismain: isMain 147 | }}, 148 | {multi: true}, 149 | function(err){ 150 | if (err){ 151 | callback(err); 152 | } else { 153 | callback(null); 154 | } 155 | }); 156 | }, 157 | editCategory: function(language, categoryid, title, shortdescription, description, htmltitle, htmldescription, htmlkeywords, alias, menutitle, ismain, moderator, callback){ 158 | 159 | var Categories = this; 160 | 161 | Categories.findOne({alias: alias, lang: 'default'}, function(err, cat){ 162 | 163 | if (cat && (cat._id != categoryid)){ 164 | callback('Категория с таким URL уже существует!'); 165 | } else { 166 | var date = new Date(); 167 | var setParams = {}; 168 | setParams.title = title; 169 | setParams.shortdescription = shortdescription; 170 | setParams.description = description; 171 | setParams.htmltitle = htmltitle; 172 | setParams.htmldescription = htmldescription; 173 | setParams.htmlkeywords = htmlkeywords; 174 | setParams.menutitle = menutitle; 175 | 176 | if (language == 'default'){ 177 | setParams.alias = alias; 178 | setParams.ismain = ismain; 179 | } 180 | 181 | Categories.update( 182 | {_id:categoryid}, 183 | { 184 | $set:setParams 185 | 186 | }, function(err, opt){ 187 | if (opt){ 188 | var moderatedhistory = {}; 189 | moderatedhistory.moderator = moderator; 190 | moderatedhistory.date = date; 191 | Categories.update( 192 | {_id: categoryid}, 193 | { 194 | $push: {moderatedhistory: moderatedhistory} 195 | }, 196 | function(){ 197 | callback(null, opt); 198 | } 199 | ) 200 | } else { 201 | callback("Ошибка базы данных") 202 | } 203 | } 204 | ) 205 | } 206 | }); 207 | }, 208 | posUpdate: function(categoryid, pos, callback) { 209 | 210 | var Categories = this; 211 | 212 | Categories.update( 213 | {_id:categoryid}, 214 | { 215 | $set:{position: pos} 216 | 217 | }, function(err, opt){ 218 | if (opt){ 219 | callback(null, opt); 220 | } else { 221 | callback("Ошибка базы данных") 222 | } 223 | } 224 | ) 225 | }, 226 | deleteCategory: function(categoryid, callback){ 227 | 228 | var Categories = this; 229 | 230 | Categories.remove({_id: categoryid}, function(err){ 231 | if (err){ 232 | callback ("Невозможно удалить категорию"); 233 | } else { 234 | callback (null, categoryid); 235 | } 236 | }); 237 | } 238 | }; 239 | 240 | exports.Category = mongoose.model('Category', schema); 241 | -------------------------------------------------------------------------------- /models/tags.js: -------------------------------------------------------------------------------- 1 | 2 | var log = require('../libs/log')(module); 3 | var async = require('async'); 4 | 5 | var mongoose = require('../libs/mongoose'), 6 | Schema = mongoose.Schema; 7 | 8 | var schema = new Schema({ 9 | name: { 10 | type: String, 11 | unique: true, 12 | required: true 13 | }, 14 | created: { 15 | type: Date, 16 | default: Date.now 17 | } 18 | }); 19 | 20 | schema.index( 21 | {name: 1}, 22 | {unique: true, dropDups: true} 23 | ); 24 | 25 | schema.statics = { 26 | 27 | createTags: function (name, callback) { 28 | 29 | var Tags = this; 30 | 31 | name = name.toLowerCase(); 32 | 33 | Tags.findOne({name: name}, function(err, tagresult){ 34 | 35 | if (tagresult){ 36 | 37 | callback (null, 1); 38 | 39 | } else { 40 | 41 | var tag = new Tags({name: name}); 42 | 43 | tag.save(function(err){ 44 | 45 | if (err) { 46 | callback(err); 47 | } else { 48 | callback (null, tag); 49 | } 50 | }); 51 | } 52 | }) 53 | } 54 | }; 55 | 56 | exports.Tags = mongoose.model('Tags', schema); 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "javscript_backend", 3 | "version": "1.0.0", 4 | "description": "#BlondieCode. JavaScript Практическое занятие. Back-end", 5 | "main": "app.js", 6 | "author": { 7 | "name": "Aida Drogan", 8 | "email": "aida.drogan.box@gmail.com", 9 | "url": "https://www.youtube.com/channel/UCSsg9NLCse2XQCIRgfmJu3Q" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/droganaida/javascript-backend.git" 14 | }, 15 | "scripts": { 16 | "dev": "cross-env NODE_ENV=development node --nouse-idle-notification --expose-gc app.js", 17 | "pro": "cross-env NODE_ENV=production node --nouse-idle-notification --expose-gc app.js" 18 | }, 19 | "license": "ISC", 20 | "dependencies": { 21 | "async": "2.1.5", 22 | "body-parser": "1.17.1", 23 | "compression": "1.6.2", 24 | "connect-mongo": "1.3.2", 25 | "cross-env": "5.0.0", 26 | "ejs": "2.5.6", 27 | "express": "4.15.2", 28 | "express-favicon": "2.0.0", 29 | "express-session": "1.15.1", 30 | "http": "0.0.0", 31 | "imagemagick": "0.1.3", 32 | "mongoose": "4.9.0", 33 | "multer": "1.3.0", 34 | "node-minify": "2.0.3", 35 | "winston": "2.3.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /public/images/backgrounds/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/droganaida/javascript-backend/3d8b4315c137232ecf4820bbe8a2fe6cd7f71732/public/images/backgrounds/logo.png -------------------------------------------------------------------------------- /public/images/general/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/droganaida/javascript-backend/3d8b4315c137232ecf4820bbe8a2fe6cd7f71732/public/images/general/ajax-loader.gif -------------------------------------------------------------------------------- /public/images/icons/adminfavicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/droganaida/javascript-backend/3d8b4315c137232ecf4820bbe8a2fe6cd7f71732/public/images/icons/adminfavicon.png -------------------------------------------------------------------------------- /public/images/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/droganaida/javascript-backend/3d8b4315c137232ecf4820bbe8a2fe6cd7f71732/public/images/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/images/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/droganaida/javascript-backend/3d8b4315c137232ecf4820bbe8a2fe6cd7f71732/public/images/icons/favicon.ico -------------------------------------------------------------------------------- /public/images/mediadescription/i-bold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/droganaida/javascript-backend/3d8b4315c137232ecf4820bbe8a2fe6cd7f71732/public/images/mediadescription/i-bold.png -------------------------------------------------------------------------------- /public/images/mediadescription/i-center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/droganaida/javascript-backend/3d8b4315c137232ecf4820bbe8a2fe6cd7f71732/public/images/mediadescription/i-center.png -------------------------------------------------------------------------------- /public/images/mediadescription/i-italic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/droganaida/javascript-backend/3d8b4315c137232ecf4820bbe8a2fe6cd7f71732/public/images/mediadescription/i-italic.png -------------------------------------------------------------------------------- /public/images/mediadescription/i-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/droganaida/javascript-backend/3d8b4315c137232ecf4820bbe8a2fe6cd7f71732/public/images/mediadescription/i-link.png -------------------------------------------------------------------------------- /public/images/mediadescription/i-reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/droganaida/javascript-backend/3d8b4315c137232ecf4820bbe8a2fe6cd7f71732/public/images/mediadescription/i-reset.png -------------------------------------------------------------------------------- /public/images/mediadescription/i-strong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/droganaida/javascript-backend/3d8b4315c137232ecf4820bbe8a2fe6cd7f71732/public/images/mediadescription/i-strong.png -------------------------------------------------------------------------------- /public/images/noimage-min.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/droganaida/javascript-backend/3d8b4315c137232ecf4820bbe8a2fe6cd7f71732/public/images/noimage-min.jpg -------------------------------------------------------------------------------- /public/images/noimage-slide.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/droganaida/javascript-backend/3d8b4315c137232ecf4820bbe8a2fe6cd7f71732/public/images/noimage-slide.jpg -------------------------------------------------------------------------------- /public/images/socialimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/droganaida/javascript-backend/3d8b4315c137232ecf4820bbe8a2fe6cd7f71732/public/images/socialimage.png -------------------------------------------------------------------------------- /public/scripts/admin/aliasmaker.js: -------------------------------------------------------------------------------- 1 | 2 | //================================================================// 3 | //*********** aliasmaker.js ***********// 4 | //*********** © Aida Drogan - #BlondieCode 5 | //*********** Функция для транслитерации русских заголовков в ссылки 6 | //=========== ВЫЗОВ 7 | //*********** Наличие у элемента-инпута id="alias" 8 | //================================================================// 9 | 10 | $(document).ready(function(){ 11 | 12 | //================================================================// 13 | //*********** Input Focus ***********// 14 | //================================================================// 15 | $('body').on('focus', '#alias', function(){ 16 | 17 | if ($(this).val() == ""){ 18 | 19 | var text = $('#title').val(); 20 | 21 | text = text.replace(/ /g, "-").toLowerCase(); 22 | text = text.replace(/\./g, "-"); 23 | text = text.replace(/:/g, ""); 24 | text = text.replace(/,/g, ""); 25 | text = text.replace(/;/g, ""); 26 | 27 | text = text.replace(/а/g, "a"); 28 | text = text.replace(/б/g, "b"); 29 | text = text.replace(/в/g, "v"); 30 | text = text.replace(/г/g, "g"); 31 | text = text.replace(/д/g, "d"); 32 | text = text.replace(/е/g, "e"); 33 | text = text.replace(/ё/g, "yo"); 34 | text = text.replace(/ж/g, "j"); 35 | text = text.replace(/з/g, "z"); 36 | text = text.replace(/и/g, "i"); 37 | text = text.replace(/й/g, "i"); 38 | text = text.replace(/к/g, "k"); 39 | text = text.replace(/л/g, "l"); 40 | text = text.replace(/м/g, "m"); 41 | text = text.replace(/н/g, "n"); 42 | text = text.replace(/о/g, "o"); 43 | text = text.replace(/п/g, "p"); 44 | text = text.replace(/р/g, "r"); 45 | text = text.replace(/с/g, "s"); 46 | text = text.replace(/т/g, "t"); 47 | text = text.replace(/у/g, "u"); 48 | text = text.replace(/ф/g, "f"); 49 | text = text.replace(/х/g, "h"); 50 | text = text.replace(/ц/g, "c"); 51 | text = text.replace(/ч/g, "ch"); 52 | text = text.replace(/ш/g, "sh"); 53 | text = text.replace(/щ/g, "sch"); 54 | text = text.replace(/ъ/g, ""); 55 | text = text.replace(/ы/g, "y"); 56 | text = text.replace(/ь/g, ""); 57 | text = text.replace(/э/g, "e"); 58 | text = text.replace(/ю/g, "yu"); 59 | text = text.replace(/я/g, "ya"); 60 | 61 | $(this).val(text); 62 | } 63 | }); 64 | }); -------------------------------------------------------------------------------- /public/scripts/admin/articles.js: -------------------------------------------------------------------------------- 1 | 2 | //================================================================// 3 | //*********** articles.js ***********// 4 | //*********** © Aida Drogan - #BlondieCode 5 | //*********** Логика работы со статьями 6 | //*********** Поиск по статьям 7 | //*********** Создание новой стаьи 8 | //*********** Удаление статьи 9 | //*********** Редактирование статьи 10 | //*********** Публикация статьи и снятие с публикации 11 | //================================================================// 12 | 13 | $(document).ready(function(){ 14 | 15 | var errorTopMargin = 50; 16 | 17 | //================================================================// 18 | //*********** Создание статьи по запросу ***********// 19 | //================================================================// 20 | 21 | $('#search-article').on('keyup', function(e){ 22 | 23 | if ($(this).val().length > 1){ 24 | 25 | //------------ данные для ajax-запроса 26 | var data = {}; 27 | data.key = $(this).val(); 28 | 29 | //------------ { ДОПИСАНО БЭКЭНД-ДЕВЕЛОПЕРОМ 30 | data.action = 'searcharticle'; 31 | data.catid = window.location.href.substr(window.location.href.lastIndexOf('/') + 1); 32 | 33 | //------------ ajax-запрос на поиск статьи 34 | 35 | $.ajax({ 36 | url: "/admin/articles", 37 | type: 'POST', 38 | dataType: 'json', 39 | data: data, 40 | error: function(){ 41 | showError('Ошибка базы данных!', errorTopMargin); 42 | } 43 | }).done(function(data){ 44 | 45 | //------------ data.result - ответ от сервера (найденная статья) 46 | 47 | $('.articles').children().remove(); 48 | $('.articles').append(data.result); 49 | 50 | //------------ если ничего не найдено по запросу 51 | if (data.result.length == 0){ 52 | $('.articles').append('
Ой... Похоже ничего не найдено!
') 53 | } 54 | 55 | }); 56 | //------------ } 57 | } 58 | }); 59 | 60 | //------------------ функция для подстановки тегов tagMeBabe 61 | 62 | if ($('#tags').length){ 63 | 64 | var tagInput = $('#tags'); // элемент для ввода тега 65 | var tagAutoFillBar = $('.autofill-bar'); // панель для вывода результатов подстановки 66 | var tagAutoFillClass = 'autofill-bar'; // класс панели для вывода результатов подстановки (String) 67 | var tagContainer = $('#article-tags'); // контейнер для выгрузки выбранных тегов 68 | var tagItemClass = 't-item'; // класс элемента с тегом в контейнере 69 | var routeToTagSearch = '/admin/article'; // роут для ajax-запроса к серверу (совпадение тегов для постановки) 70 | 71 | tagMeBabe(tagInput, tagAutoFillBar, tagAutoFillClass, tagContainer, tagItemClass, routeToTagSearch); 72 | } 73 | 74 | 75 | //================================================================// 76 | //*********** Сохранение статьи ***********// 77 | //================================================================// 78 | 79 | function saveArticle(type, button){ 80 | 81 | //--------------- button - кнопка, с которой был переход 82 | //--------------- type - новая статья или редактирование существующей 83 | 84 | if (!button.hasClass('working')){ 85 | 86 | //------------ { ДОПИСАНО БЭКЭНД-ДЕВЕЛОПЕРОМ 87 | //------------ на каком языке статья 88 | var lang = $('.id-info').attr('data-lang'); 89 | //------------ } 90 | 91 | //--------------- проверка выбрана ли хоть одна категория 92 | //------------ ДОПИСАНО БЭКЭНД-ДЕВЕЛОПЕРОМ (lang != 'default') 93 | if (($('#article-categories .active').length > 0) || (lang != 'default')){ 94 | 95 | //--------------- проверка на обязательные поля 96 | if ($('#title').val().length == 0){ 97 | showError('Введите название статьи!', errorTopMargin); 98 | } else if ((lang == 'default') && ($('#alias').val().length == 0)) { 99 | showError('Введите URL статьи!', errorTopMargin); 100 | } else { 101 | 102 | button.addClass('working'); 103 | 104 | //------------ данные для ajax-запроса 105 | var data = {}; 106 | 107 | //--------------- передать тип на сервер! 108 | data.action = type; 109 | 110 | //------------ { ДОПИСАНО БЭКЭНД-ДЕВЕЛОПЕРОМ 111 | //------------ если это edit, нужно передать id 112 | if (type == 'editarticle'){ 113 | data.id = $('.id-info').attr('id'); 114 | } 115 | //------------ } 116 | 117 | data.title = $('#title').val(); 118 | data.alias = $('#alias').val(); 119 | data.shortdescription = $('#shortdescription').val(); 120 | data.htmltitle = $('#htmltitle').val(); 121 | data.htmldescription = $('#htmldescription').val(); 122 | data.htmlkeywords = $('#htmlkeywords').val(); 123 | data.moderator = $('#moderator').text(); 124 | 125 | //------------ { ДОПИСАНО БЭКЭНД-ДЕВЕЛОПЕРОМ 126 | //------------ передаем статью-оригинал на русском языке и сам язык 127 | data.parent = $('.id-info').attr('data-parent'); 128 | data.lang = lang; 129 | //------------ } 130 | 131 | var catArray = []; 132 | $('#article-categories .active').each(function(){ 133 | //------------ { ДОПИСАНО БЭКЭНД-ДЕВЕЛОПЕРОМ 134 | //------------ собираем не текст с категорий, а id 135 | catArray.push($(this).attr('id')); 136 | //------------ } 137 | }); 138 | data.categories = catArray; 139 | 140 | var tagArray = []; 141 | $('#article-tags .t-item').each(function(){ 142 | tagArray.push($(this).text()); 143 | }); 144 | data.tags = tagArray; 145 | 146 | data.description = mediaElement.saveDescription(); 147 | 148 | $('.loader').fadeIn('fast'); 149 | 150 | //------------ { ДОПИСАНО БЭКЭНД-ДЕВЕЛОПЕРОМ 151 | $.ajax({ 152 | url: '/admin/article/', 153 | type: 'POST', 154 | dataType: 'json', 155 | data: data, 156 | error: function(data){ 157 | showError(data.responseText, errorTopMargin); 158 | button.removeClass('working'); 159 | $('.loader').fadeOut('fast'); 160 | } 161 | }).done(function(data){ 162 | 163 | var articleid = data.id; 164 | 165 | if (globalArticleMainImageFileNameToUpload){ 166 | var fd = new FormData(); 167 | 168 | fd.append('fileUpload', globalArticleMainImageFileNameToUpload); 169 | fd.append('action', 'savemainimage'); 170 | fd.append('id', articleid); 171 | 172 | var xhr = new XMLHttpRequest(); 173 | xhr.open('post', '/admin/article/', true); 174 | xhr.send(fd); 175 | 176 | xhr.onreadystatechange = function() { 177 | if (this.readyState == 4) { 178 | if (xhr.responseText != 'Success'){ 179 | showError(xhr.responseText, errorTopMargin); 180 | button.removeClass('working'); 181 | $('.loader').fadeOut('fast'); 182 | } else { 183 | afterSave(); 184 | } 185 | } 186 | } 187 | } else { 188 | afterSave(); 189 | } 190 | }); 191 | 192 | function afterSave() { 193 | 194 | button.removeClass('working'); 195 | $('.loader').fadeOut('fast'); 196 | var href = window.location.href; 197 | href = href.substr(0, href.lastIndexOf('/')); 198 | if ($('.id-info').attr('data-lang') == 'default'){ 199 | href = href.replace('article', 'articles'); 200 | } 201 | window.location.href = href; 202 | } 203 | //------------ } 204 | } 205 | 206 | } else { 207 | showError('Выберите хотя бы одну категорию!', errorTopMargin); 208 | } 209 | } 210 | } 211 | 212 | //================================================================// 213 | //*********** Редактирование статьи ***********// 214 | //================================================================// 215 | 216 | $('#edit-article').on('click', function(){ 217 | 218 | var button = $(this); 219 | 220 | //----------- проверяем нужно ли удалить старое изображение 221 | if (button.hasClass('todelete')){ 222 | 223 | var data = {}; 224 | 225 | //------------ { ДОПИСАНО БЭКЭНД-ДЕВЕЛОПЕРОМ 226 | data.id = $('.id-info').attr('id'); 227 | data.action = 'deletemainimg'; 228 | data.file = '..' + button.attr('data-src'); 229 | 230 | $.ajax({ 231 | url: '/admin/article/', 232 | type: 'POST', 233 | dataType: 'json', 234 | data: data, 235 | error: function(data){ 236 | showError(data.responseText, errorTopMargin); 237 | } 238 | }).done(function(){ 239 | saveArticle('editarticle', button); 240 | }); 241 | //------------ } 242 | } else { 243 | saveArticle('editarticle', button); 244 | } 245 | 246 | }); 247 | 248 | //================================================================// 249 | //*********** Новая статья ***********// 250 | //================================================================// 251 | 252 | $('#new-article').on('click', function(){ 253 | 254 | var button = $(this); 255 | saveArticle('newarticle', button); 256 | 257 | }); 258 | 259 | //================================================================// 260 | //*********** Удаление статьи ***********// 261 | //================================================================// 262 | 263 | $('#delete-article').on('click', function(){ 264 | 265 | $('body').append('' 276 | ); 277 | 278 | $('.popup-holder.dynamic').fadeIn(); 279 | 280 | }); 281 | 282 | $('body').on('click', '#yes-delete-article', function(){ 283 | 284 | var button = $(this); 285 | 286 | if (!button.hasClass('working')){ 287 | 288 | button.addClass('working'); 289 | 290 | //------------ { ДОПИСАНО БЭКЭНД-ДЕВЕЛОПЕРОМ 291 | var data = {}; 292 | data.action = 'deletearticle'; 293 | data.id = $('.id-info').attr('id'); 294 | 295 | $.ajax({ 296 | url: "/admin/article", 297 | type: 'POST', 298 | dataType: 'json', 299 | data: data, 300 | error: function(){ 301 | showError('Невозможно удалить статью!', errorTopMargin); 302 | button.removeClass('working'); 303 | } 304 | }).done(function(data){ 305 | 306 | window.location.href = "/admin/categories"; 307 | var href = window.location.href; 308 | href = href.substr(0, href.lastIndexOf('/')); 309 | 310 | if ($('.id-info').attr('data-lang') == 'default'){ 311 | $('.id-info').attr('id', 'deleted'); 312 | href = "/admin/categories"; 313 | } 314 | window.location.href = href; 315 | }); 316 | //------------ } 317 | } 318 | }); 319 | 320 | //================================================================// 321 | //*********** Публикация статьи ***********// 322 | //================================================================// 323 | 324 | $('.articles').on('click', '.published span', function(){ 325 | 326 | var button = $(this); 327 | 328 | //------------ данные для ajax-запроса 329 | var data = {}; 330 | var flag = 'yes'; 331 | if (button.hasClass('yes')){ 332 | flag = 'no'; 333 | } 334 | data.flag = flag; 335 | 336 | //------------ { ДОПИСАНО БЭКЭНД-ДЕВЕЛОПЕРОМ 337 | data.id = button.parent().parent().attr('id'); 338 | data.action = 'setpublished'; 339 | 340 | $.ajax({ 341 | url: '/admin/article/', 342 | type: 'POST', 343 | dataType: 'json', 344 | data: data, 345 | error: function(){ 346 | showError('Ошибка базы данных', errorTopMargin); 347 | } 348 | }).done(function(){ 349 | 350 | if (flag == 'yes'){ 351 | button.removeClass('no').addClass('yes').text('Опубликована'); 352 | showSuccess('Статья опубликована', errorTopMargin); 353 | } else { 354 | button.removeClass('yes').addClass('no').text('Не опубликована'); 355 | showSuccess('Статья снята с публикации', errorTopMargin); 356 | } 357 | }); 358 | //------------ } 359 | }); 360 | }); 361 | -------------------------------------------------------------------------------- /public/scripts/admin/categories.js: -------------------------------------------------------------------------------- 1 | 2 | //================================================================// 3 | //*********** categories.js ***********// 4 | //*********** © Aida Drogan - #BlondieCode 5 | //*********** Логика работы с категориями 6 | //*********** Создание новой категории 7 | //*********** Сортировка категорий drag-n-drop (ui-sortable) 8 | //*********** Удаление категории 9 | //*********** Редактирование категории 10 | //================================================================// 11 | 12 | $(document).ready(function(){ 13 | 14 | var errorTopMargin = 50; 15 | 16 | //================================================================// 17 | //*********** Создание новой категории ***********// 18 | //================================================================// 19 | 20 | $('#b-new-category').on('click', function(){ 21 | 22 | //------------ pop-up форма для ввода данных 23 | 24 | $('body').append('' 39 | ); 40 | 41 | $('.popup-holder.dynamic').fadeIn(); 42 | 43 | }); 44 | 45 | //------------ клик по кнопке Сохранить на pop-up форме 46 | 47 | $('body').on('click', '#save-category', function(){ 48 | 49 | var button = $(this); 50 | 51 | if (!button.hasClass('working')){ 52 | 53 | button.addClass('working'); 54 | 55 | //------------ данные для ajax-запроса 56 | var data = {}; 57 | data.title = $('#title').val(); // заголовок категории 58 | data.alias = $('#alias').val(); // алиас (url страницы) 59 | data.pos = $('#sortable li').length; // позиция в слайдере 60 | 61 | //------------ { ДОПИСАНО БЭКЭНД-ДЕВЕЛОПЕРОМ 62 | data.action = 'newcategory'; 63 | data.moderator = $('#moderator').text(); 64 | //------------ } 65 | 66 | if (data.title.length == 0){ 67 | showError('Пожалуйста введите заголовок категории!', errorTopMargin); 68 | button.removeClass('working'); 69 | } else if (data.alias.length == 0){ 70 | showError('Пожалуйста введите роут!', errorTopMargin); 71 | button.removeClass('working'); 72 | } else { 73 | 74 | //------------ { ДОПИСАНО БЭКЭНД-ДЕВЕЛОПЕРОМ 75 | $.ajax({ 76 | url: '/admin/categories', 77 | type: 'POST', 78 | dataType: 'json', 79 | data: data, 80 | error: function(data){ 81 | showError(data.responseText, errorTopMargin); 82 | button.removeClass('working'); 83 | } 84 | }).done(function(data){ 85 | 86 | button.removeClass('working'); 87 | $('.popup-holder.dynamic').fadeOut('fast', function(){ 88 | $(this).remove(); 89 | }); 90 | 91 | $('#sortable').append(data.result); 92 | }); 93 | //------------ } 94 | } 95 | } 96 | }); 97 | 98 | //================================================================// 99 | //*********** Сортировка категорий ***********// 100 | //================================================================// 101 | 102 | function sortItems(){ 103 | $('.ui-sortable-handle').each( function(){ 104 | 105 | //------------ данные для ajax-запроса 106 | var data = {}; 107 | data.pos = $(this).prevAll().length; // позиция в слайдере 108 | 109 | //------------ ajax-запрос на смену позиции категории (сортировка) 110 | //------------ { ДОПИСАНО БЭКЭНД-ДЕВЕЛОПЕРОМ 111 | data.id = $(this).attr('id'); 112 | data.action = 'posupdate'; 113 | 114 | $.ajax({ 115 | url: "/admin/categories", 116 | type: 'POST', 117 | dataType: 'json', 118 | data: data 119 | }); 120 | //------------ } 121 | 122 | }) 123 | } 124 | 125 | if ($('#sortable').length){ 126 | 127 | //------------ объявляем ui-sortable 128 | 129 | $('#sortable').sortable({ 130 | deactivate: function(){ 131 | sortItems(); 132 | }, 133 | placeholder: "ui-state-highlight" 134 | }); 135 | 136 | //------------ отключаем выделение текста 137 | $('#sortable').disableSelection(); 138 | } 139 | 140 | 141 | //================================================================// 142 | //*********** Удаление категории ***********// 143 | //================================================================// 144 | 145 | $('#delete-category').on('click', function(){ 146 | 147 | $('body').append('' 158 | ); 159 | 160 | $('.popup-holder.dynamic').fadeIn(); 161 | 162 | }); 163 | 164 | $('body').on('click', '#yes-delete-category', function(){ 165 | 166 | var button = $(this); 167 | 168 | if (!button.hasClass('working')){ 169 | 170 | button.addClass('working'); 171 | 172 | //------------ ajax-запрос на удаление категории 173 | //------------ { ДОПИСАНО БЭКЭНД-ДЕВЕЛОПЕРОМ 174 | var data = {}; 175 | data.id = $('.id-info').attr('id'); 176 | data.action = 'deletecategory'; 177 | 178 | $.ajax({ 179 | url: "/admin/categories", 180 | type: 'POST', 181 | dataType: 'json', 182 | data: data, 183 | error: function(){ 184 | showError('Невозможно удалить категорию!', errorTopMargin); 185 | button.removeClass('working'); 186 | } 187 | }).done(function(){ 188 | 189 | $('.id-info').attr('id', 'deleted'); 190 | window.location.href = "/admin/categories"; 191 | }); 192 | //------------ } 193 | } 194 | }); 195 | 196 | 197 | //================================================================// 198 | //*********** Редактирование категории ***********// 199 | //================================================================// 200 | 201 | $('#edit-category').on('click', function(){ 202 | 203 | var button = $(this); 204 | 205 | if (!button.hasClass('working')){ 206 | 207 | button.addClass('working'); 208 | 209 | //------------ данные для ajax-запроса 210 | var data = {}; 211 | data.title = $('#title').val(); 212 | data.shortdescription = $('#shortdescription').val(); 213 | data.htmltitle = $('#htmltitle').val(); 214 | data.htmldescription = $('#htmldescription').val(); 215 | data.htmlkeywords = $('#htmlkeywords').val(); 216 | data.alias = $('#alias').val(); 217 | data.menutitle = $('#menutitle').val(); 218 | data.moderator = $('#moderator').text(); 219 | 220 | var ismain = 'false'; 221 | if ($('.ismain').hasClass('active')){ 222 | ismain = 'true'; 223 | } 224 | 225 | data.ismain = ismain; 226 | data.description = mediaElement.saveDescription(); 227 | 228 | //------------ { ДОПИСАНО БЭКЭНД-ДЕВЕЛОПЕРОМ 229 | data.id = $('.id-info').attr('id'); 230 | data.parent = $('.id-info').attr('data-parent'); 231 | var lang = $('.id-info').attr('data-lang'); 232 | data.lang = lang; 233 | data.action = 'editcategory'; 234 | //------------ } 235 | 236 | $('.loader').fadeIn('fast'); 237 | 238 | //------------ ajax-запрос на сохраение категории 239 | //------------ { ДОПИСАНО БЭКЭНД-ДЕВЕЛОПЕРОМ 240 | $.ajax({ 241 | url: '/admin/categories', 242 | type: 'POST', 243 | dataType: 'json', 244 | data: data, 245 | error: function(data){ 246 | showError(data.responseText, errorTopMargin); 247 | button.removeClass('working'); 248 | $('.loader').fadeOut('fast'); 249 | } 250 | }).done(function(data){ 251 | 252 | var id = data.id; 253 | 254 | button.removeClass('working'); 255 | $('.loader').fadeOut('fast'); 256 | 257 | if ($('.id-info').attr('data-lang') == 'default'){ 258 | if ($('.crumbs').find('a').length){ 259 | window.location.href = $('.crumbs').find('a').attr('href'); 260 | } else { 261 | window.location.href = '/admin/categories'; 262 | } 263 | } else { 264 | window.location.href = window.location.href.substr(0, window.location.href.lastIndexOf('/')); 265 | } 266 | }); 267 | //------------ } 268 | } 269 | }); 270 | }); 271 | -------------------------------------------------------------------------------- /public/scripts/admin/click-actions.js: -------------------------------------------------------------------------------- 1 | 2 | //================================================================// 3 | //*********** click-action.js ***********// 4 | //*********** © Aida Drogan - #BlondieCode 5 | //*********** Описывает клики по общим элементам админ-панели 6 | //*********** Кнопка ОТМЕНА на pop-up окнах (class="p-cancel") 7 | //*********** Элементы-чекеры с кастомным интерфейсом (checkbox, radio button) 8 | //*********** Раздвижной элемент "баян" 9 | //================================================================// 10 | 11 | $(document).ready(function(){ 12 | 13 | //================================================================// 14 | //*********** ОТМЕНА на pop-up окнах ***********// 15 | //================================================================// 16 | 17 | $('body').on('click', '.p-cancel', function(){ 18 | $('.popup-holder.dynamic').fadeOut('fast', function(){ 19 | $(this).remove(); 20 | }); 21 | }); 22 | 23 | //================================================================// 24 | //*********** CHECKERS ***********// 25 | // --- атрибут data-relna с именем класса элемента, 26 | // --- который нужно переключить в неактивное состоянии (стилевой класс .na) 27 | // --- при выборе элемента-чекера 28 | 29 | // --- класс .single назначается родительскому элементу, 30 | // --- в котором может быть только один активный чекер (поведение радио-кнопок) 31 | //================================================================// 32 | 33 | $('body').on('click', '.checker', function(){ 34 | $(this).toggleClass('active'); 35 | var relative = $(this).attr('data-relna'); 36 | if (relative){ 37 | $('.' + relative).toggleClass('na'); 38 | } 39 | }); 40 | 41 | $('.check-holder').on('click', '.check', function(){ 42 | if (!$(this).parent('.check-holder').hasClass('na')){ 43 | if ($(this).parents('.check-holder').hasClass('single')){ 44 | $(this).parents('.check-holder').find('.check').removeClass('active'); 45 | $(this).addClass('active'); 46 | } else { 47 | $(this).toggleClass('active'); 48 | } 49 | } 50 | }); 51 | 52 | //================================================================// 53 | //*********** БАЯН ***********// 54 | // --- класс элемента open-bayan 55 | // --- тело баяна следующий за .open-bayan элемент 56 | // --- класс up - стилевое оформление открытого баяна 57 | //================================================================// 58 | 59 | $('body').on('click', '.open-bayan', function(){ 60 | 61 | if ($(this).hasClass('up')){ 62 | $(this).next().slideUp('fast'); 63 | $(this).removeClass('up'); 64 | } else { 65 | $(this).addClass('up'); 66 | $(this).next().slideDown('fast'); 67 | } 68 | }); 69 | }); -------------------------------------------------------------------------------- /public/scripts/admin/error.js: -------------------------------------------------------------------------------- 1 | 2 | //================================================================// 3 | //*********** error.js 4 | //*********** © Aida Drogan - #BlondieCode 5 | //*********** Функционал окна с сообщением об ошибке (
) 6 | //*********** Присутствует на всех страницах админ-панели 7 | //=========== ВЫЗОВ 8 | //*********** showError(text, top) - сообщеие об ошибке 9 | //*********** showSuccess(text, top) - сообщеие об успешном завершении 10 | //=========== ПАРАМЕТРЫ 11 | //*********** text - сообщение в окне 12 | //*********** top - отступ от верха страницы (px) 13 | //================================================================// 14 | 15 | //==============================================================// 16 | //*********** hideError - скрыть сообщение об ошибке ***********// 17 | //==============================================================// 18 | 19 | var timer = null; 20 | 21 | function hideError(){ 22 | 23 | //------------ очищаем таймер 24 | if (timer != null){ 25 | window.clearTimeout(timer); 26 | } 27 | 28 | //------------ таймаут на 5 секунд 29 | timer = window.setTimeout(function(){ 30 | $('.error-holder').fadeOut(); 31 | }, 5000); 32 | } 33 | 34 | //---------- если success, то форма имеет класс error-holder-success 35 | function showPopup(text, top, success) { 36 | 37 | if (success) { 38 | $('.error-holder').addClass('error-holder-success'); 39 | } else { 40 | $('.error-holder').removeClass('error-holder-success'); 41 | } 42 | $('.error-holder').css({"top":"" + top + "px", "z-index":"999999"}); 43 | $('.error-holder span').text(text); 44 | 45 | $('.error-holder').fadeIn(function(){ 46 | hideError(); 47 | }); 48 | } 49 | 50 | //==============================================================// 51 | //********** showError - показать сообщение об ошибке **********// 52 | //==============================================================// 53 | function showError(text, top){ 54 | 55 | showPopup(text, top, false); 56 | } 57 | 58 | //==============================================================// 59 | //******* showSuccess - сообщение об успешном завершении *******// 60 | //==============================================================// 61 | function showSuccess(text, top){ 62 | 63 | showPopup(text, top, true); 64 | } 65 | 66 | $(document).ready(function(){ 67 | 68 | //=============== Скрыть сообщение по клику =================// 69 | $('.error-holder').on("click", function(){ 70 | $(this).fadeOut(); 71 | }); 72 | 73 | }); -------------------------------------------------------------------------------- /public/scripts/admin/login.js: -------------------------------------------------------------------------------- 1 | 2 | //================================================================// 3 | //*********** login.js 4 | //*********** © Aida Drogan - #BlondieCode 5 | //*********** Описание поведения формы логина (страница login.html) 6 | //*********** Проверка полей формы логина на валидность 7 | //*********** ajax-запрос на авторизацию 8 | //================================================================// 9 | 10 | $(document).ready(function(){ 11 | 12 | var errorTopMargin = 50; 13 | 14 | //---------------------------- фильтры для проверки полей на недопустимые символы 15 | //---------------------------- https://www.sitepoint.com/expressions-javascript/ 16 | var filterUsername = /^([a-zA-Z0-9_\-])+$/; 17 | var filterPassword = /^[a-zA-Z0-9!%&@#$\^*?_~+]+$/; 18 | 19 | $('#pass').on('keyup', function(e){ 20 | //---------------------------- если пользователь нажал enter 21 | if (e.keyCode == 13){ 22 | $('.b-login').click(); 23 | } 24 | }); 25 | 26 | //=========================== Кнопка войти ==========================// 27 | 28 | $('.b-login').on("click", function(){ 29 | 30 | //---------------------------- параметры для авторизации 31 | var data = {}; 32 | data.username = $('#username').val(); 33 | data.password = $('#pass').val(); 34 | 35 | if (data.username == ''){ 36 | //-------------------- showError(text, top) функция для отображения ошибки 37 | //-------------------- text - текст сообщения 38 | //-------------------- top - отступ от верха страницы 39 | showError('Пожалуйста введите свое имя!', errorTopMargin); 40 | } else if (data.password == ''){ 41 | showError('Пожалуйста введите свой пароль!', errorTopMargin); 42 | } else if (!filterUsername.test(data.username)){ 43 | showError('Недопустимые символы в имени', errorTopMargin); 44 | } else if(!filterPassword.test(data.password)) { 45 | showError('Недопустимые символы в пароле', errorTopMargin); 46 | } else { 47 | 48 | //------------ { ДОПИСАНО БЭКЭНД-ДЕВЕЛОПЕРОМ 49 | $.ajax({ 50 | url: '/login', 51 | type: 'POST', 52 | dataType: 'json', 53 | data: data, 54 | error: function(){ 55 | showError('Неверное имя или пароль!', errorTopMargin); 56 | } 57 | }).done(function(data){ 58 | 59 | if (data.link){ 60 | window.location.href = data.link; 61 | } else { 62 | showError('Неверное имя или пароль!', errorTopMargin); 63 | } 64 | 65 | }) 66 | } 67 | //------------ } 68 | }) 69 | }); -------------------------------------------------------------------------------- /public/scripts/admin/media-actions.js: -------------------------------------------------------------------------------- 1 | 2 | //================================================================// 3 | //*********** media-actions.js ***********// 4 | //*********** © Aida Drogan - #BlondieCode 5 | //*********** Логика работы с плагином mediadescription и изображениями 6 | //*********** Подключение плагина mediadescription 7 | //*********** Работа с изображениями 8 | //*********** Запись файла в глобальную переменную globalArticleMainImageFileNameToUpload 9 | //*********** Предпросмотр изображения 10 | //*********** Удаление изображения 11 | //================================================================// 12 | 13 | //====================== Переменные ===========================// 14 | 15 | var globalArticleMainImageFileNameToUpload = null; 16 | var mediaElement = null; 17 | 18 | var errorTopMargin = 50; 19 | 20 | //====================== Предпросмотр изображения ===========================// 21 | 22 | function uploadMainArticleImg(file){ 23 | 24 | //------- передаем файл в глобальную переменную 25 | globalArticleMainImageFileNameToUpload = file; 26 | 27 | //------- если файл есть, передаем его в FileReader 28 | if (file != undefined){ 29 | if (!file.type.match('image.*')) { 30 | showError('File type should be an image', errorTopMargin); 31 | return; 32 | } 33 | 34 | var reader = new FileReader(); 35 | reader.onload = (function(){ 36 | 37 | return function(e){ 38 | 39 | //------- после того, как FileReader распознал файл отображаем его в элементе .imgarticle 40 | if ($('.logo-holder.imgarticle img').length){ 41 | 42 | $('.logo-holder.imgarticle img').fadeOut('fast', function(){ 43 | $('.logo-holder.imgarticle').append(''); 44 | $(this).remove(); 45 | }); 46 | } else { 47 | $('.logo-holder.imgarticle').append(''); 48 | } 49 | }; 50 | })(file); 51 | reader.readAsDataURL(file); 52 | } 53 | } 54 | 55 | 56 | $(document).ready(function(){ 57 | 58 | //------- клик по кнопке выбора изображения (инициируем клик по скрытому инпуту) 59 | $('body').on('click', '#main-article-img', function(){ 60 | $('#imgarticle').click(); 61 | }); 62 | 63 | //------- выбор изображение при помощи инпута с типом "файл" 64 | $('body').on('change', '#imgarticle', function(f){ 65 | 66 | var files = f.target.files; 67 | var file = files[0]; 68 | 69 | uploadMainArticleImg(file); 70 | 71 | }); 72 | 73 | //====================== Удаление изображения ===========================// 74 | 75 | $('#del-main-img').on('click', function(){ 76 | 77 | //------- проверяем, есть ли что удалять (изображение в виджете и это не noimage) 78 | if ($('.logo-holder img').length && ($('.logo-holder img').attr('src').indexOf('noimage') == -1)){ 79 | 80 | $('.logo-holder img').fadeOut('fast', function(){ 81 | 82 | globalArticleMainImageFileNameToUpload = null; 83 | //------- добавляем класс на кнопку edit-article, отмечаем, что старое изображение нужно удалить 84 | $('#edit-article').addClass('todelete').attr('data-src', $(this).attr('src')); 85 | $(this).remove(); 86 | }); 87 | 88 | $('.logo-holder').append('noimage'); 89 | } 90 | }); 91 | 92 | //====================== Подключение плагина mediadescription ===========================// 93 | 94 | if ($('.mediaelement').length) { 95 | 96 | var insidedescription = $('.mediaelement').find('.mediacontent').html(); 97 | 98 | mediaElement = $('.mediaelement').makemediadescription({ 99 | insidedescription: insidedescription 100 | }); 101 | 102 | //-------------- подтвердить перезагрузку окна\ уход со страницы 103 | 104 | window.onbeforeunload = function () { 105 | 106 | if (!$('#deleted').length) { 107 | return 'Ты сохранил все изменения?'; 108 | } 109 | }; 110 | } 111 | }); 112 | -------------------------------------------------------------------------------- /public/scripts/admin/mediadescription.js: -------------------------------------------------------------------------------- 1 | //*********************************************************************************************// 2 | //======================== MEDIADESCRIPTION PLUGIN V 1.0 /02-2017/ ============================// 3 | //================================== © #BLONDIECODE © =========================================// 4 | //================================== © Aida Drogan © ==========================================// 5 | //*********************************************************************************************// 6 | 7 | //======================= ОПЦИИ =====================// 8 | // insidedescription - type:STRING - текущее описание статьи из БД (HTML) 9 | 10 | //======================= Функции =====================// 11 | // destroyPluginEvent - очистить плагин (при повторном вызове без перезагрузки страницы) 12 | // saveDescription - экспорт контекста в формате HTML (String) 13 | 14 | (function($){ 15 | 16 | $.fn.makemediadescription = function(options){ 17 | 18 | var container = $(this); 19 | 20 | var options = $.extend( { 21 | insidedescription: '' 22 | }, options); 23 | 24 | 25 | var insidedescription = options.insidedescription; 26 | 27 | container.append( 28 | '
' + 29 | insidedescription + 30 | '
'); 31 | 32 | var id = new Date(); 33 | id = id.getTime(); 34 | 35 | $('body').append('
' + 36 | '
' + 37 | '
' + 38 | '
' + 39 | '
H2
' + 40 | '
H3
' + 41 | '
H4
' + 42 | '
' + 43 | '
' + 44 | '' + 45 | '' + 46 | '
'); 47 | 48 | var popupmenu = $('#' + id); 49 | 50 | var contentHolder = container.find('.article-description'); 51 | contentHolder.attr('id', 'article-descr-' + id); 52 | 53 | 54 | //============================ TEXT HIGHLIGHTER ===============================// 55 | 56 | $.textHighlighter.createWrapper = function() { 57 | return $(''); 58 | }; 59 | contentHolder.textHighlighter(); 60 | 61 | // ---- а еще можно определить так =) 62 | // contentHolder.textHighlighter({highlightedClass: 'temp', color: 'none'}); 63 | 64 | //============================ ФУНКЦИОНАЛЬНОЕ МЕНЮ ===============================// 65 | 66 | function parseMenu(show){ 67 | 68 | if (popupmenu.find('.link-holder:visible').length){ 69 | popupmenu.find('.link-holder').hide(); 70 | popupmenu.find('.mitem').show(); 71 | } 72 | 73 | if (container.find('.temp').length && (show == 'first')){ 74 | 75 | var top = (container.find('.temp').offset().top) - 46; 76 | var left =container.find('.temp').offset().left; 77 | 78 | popupmenu.css({top: top, left: left}).fadeIn('fast'); 79 | 80 | } else if ($('.md-menu-panel:visible').length && (show == 'change')) { 81 | 82 | if (container.find('.temp').length ){ 83 | 84 | var top = container.find('.temp').offset().top - 46; 85 | var left =container.find('.temp').offset().left; 86 | 87 | popupmenu.css({top: top, left: left}); 88 | 89 | } else { 90 | popupmenu.fadeOut('fast'); 91 | } 92 | 93 | } else { 94 | popupmenu.fadeOut('fast'); 95 | } 96 | } 97 | 98 | $(document).on( 'scroll', function(){ 99 | parseMenu('change'); 100 | }); 101 | 102 | contentHolder.on( 'scroll', function(){ 103 | parseMenu('change'); 104 | }); 105 | 106 | container.on('mouseup', function(){ 107 | parseMenu('first'); 108 | }); 109 | 110 | popupmenu.on('click', '.mitem', function(){ 111 | if (!$(this).hasClass('adescr-addlink')){ 112 | parseMenu('hide'); 113 | } 114 | }); 115 | 116 | //============================ KEYPRESS ===============================// 117 | 118 | container.on('keydown', function(e){ 119 | handleDeleteActions(e, $(this)); 120 | }); 121 | 122 | container.on('keypress', function(e){ 123 | handleDeleteActions(e, $(this)); 124 | }); 125 | 126 | function handleDeleteActions(e, obj){ 127 | 128 | //=================== Обработка кнопок backspace и delete ==================// 129 | 130 | if ((e.keyCode == 8) || (e.keyCode == 46)){ 131 | 132 | if (obj.find('.temp').length > 0){ 133 | 134 | e.preventDefault(); 135 | 136 | obj.find('.temp').first().before(''); 137 | 138 | var node = document.getElementById('cursorpos'); 139 | var range = document.createRange(); 140 | range.setStartAfter(node); 141 | range.collapse(true); 142 | var selection = window.getSelection(); 143 | selection.removeAllRanges(); 144 | selection.addRange(range); 145 | 146 | obj.find('.temp').remove(); 147 | obj.find('#cursorpos').remove(); 148 | 149 | $('p:empty').remove(); 150 | $('span:empty').remove(); 151 | $('h2:empty').remove(); 152 | $('h3:empty').remove(); 153 | $('h4:empty').remove(); 154 | $('a:empty').remove(); 155 | } 156 | } 157 | } 158 | 159 | //====================== КНОПКИ МЕНЮ ===========================// 160 | 161 | //-------- по клику на контейнер сбрасываем выделение 162 | 163 | container.on('mousedown', contentHolder, function(){ 164 | 165 | container.find('.temp').each(function(){ 166 | $(this).contents().unwrap(); 167 | }) 168 | 169 | }); 170 | 171 | //-------- функция вставки элемента на место выделения 172 | 173 | function wrapSelection(type, cclass){ 174 | 175 | var str = ''; 176 | var counter = 0; 177 | var length = container.find('.temp').length; 178 | 179 | container.find('.temp').each(function(){ 180 | 181 | counter++; 182 | str = str + $(this).text(); 183 | 184 | if ((type == 'a') && ($(this).parent().is('a'))){ 185 | $(this).unwrap(); 186 | } 187 | 188 | if (counter == length){ 189 | 190 | if (cclass != ''){ 191 | 192 | if (type == 'span'){ 193 | 194 | var span = $(this).after('<' + type + ' class="' + cclass + '">' + str + ''); 195 | // BUG - откомментируй, и познаешь его силу ;) 196 | // span.parent().addClass(cclass).text(span.text()); 197 | // span.remove(); 198 | 199 | } else if (type == 'a'){ 200 | 201 | var href; 202 | 203 | if (popupmenu.find('.open-link').hasClass('active')){ 204 | href = $(this).after('<' + type + ' href="' + cclass + '" target="_blank">' + str + ''); 205 | } else { 206 | href = $(this).after('<' + type + ' href="' + cclass + '" rel="nofollow" target="_blank">' + str + ''); 207 | } 208 | 209 | if (href.parent().is('a')){ 210 | href.unwrap(); 211 | } 212 | } 213 | 214 | } else { 215 | 216 | var h = $(this).after('<' + type + '>' + str + ''); 217 | if (h.parent().attr('id') != contentHolder.attr('id')){ 218 | h.unwrap(); 219 | } 220 | } 221 | } 222 | $(this).remove(); 223 | }) 224 | } 225 | 226 | popupmenu.on('click', '.adescr-bold', function(){ 227 | wrapSelection('b', ''); 228 | }); 229 | 230 | popupmenu.on('click', '.adescr-italic', function(){ 231 | wrapSelection('i', ''); 232 | }); 233 | 234 | popupmenu.on('click', '.adescr-h2', function(){ 235 | wrapSelection('h2', ''); 236 | }); 237 | 238 | popupmenu.on('click', '.adescr-h3', function(){ 239 | wrapSelection('h3', ''); 240 | }); 241 | 242 | popupmenu.on('click', '.adescr-h4', function(){ 243 | wrapSelection('h4', ''); 244 | }); 245 | 246 | popupmenu.on('click', '.adescr-strong', function(){ 247 | wrapSelection('strong', ''); 248 | }); 249 | 250 | popupmenu.on('click', '.adescr-centered', function(){ 251 | wrapSelection('span', 'a-centered'); 252 | }); 253 | 254 | popupmenu.on('click', '.adescr-reset', function(){ 255 | 256 | container.find('.temp').each(function () { 257 | if ($(this).parent().attr('id') != contentHolder.attr('id')){ 258 | $(this).unwrap(); 259 | } 260 | }); 261 | 262 | }); 263 | 264 | //========================= Добавить ссылку ========================// 265 | 266 | popupmenu.on('click', '.adescr-addlink', function(){ 267 | $(this).siblings().fadeOut('fast'); 268 | $(this).fadeOut('fast', function(){ 269 | $(this).next().fadeIn('fast'); 270 | $('.href-val').focus(); 271 | }); 272 | }); 273 | 274 | popupmenu.on('keyup', '.href-val', function(e){ 275 | 276 | if (e.keyCode == 13){ 277 | 278 | if ($(this).val() != ''){ 279 | var link = $(this).val(); 280 | wrapSelection('a', link); 281 | } 282 | 283 | $(this).val(''); 284 | $(this).parent().fadeOut('fast', function(){ 285 | popupmenu.find('.mitem').fadeIn('fast'); 286 | popupmenu.find('.open-link').removeClass('active'); 287 | }); 288 | } 289 | }); 290 | 291 | popupmenu.on('click', '.open-link', function(){ 292 | $(this).toggleClass('active'); 293 | popupmenu.find('.href-val').focus(); 294 | }); 295 | 296 | 297 | //====================== Сохранить описание внутри плагина ===========================// 298 | function saveDescription(){ 299 | 300 | contentHolder.find('br').each(function(){ 301 | if (($(this).parent().is('div'))&& ($(this).parent().attr('id')) != contentHolder.attr('id')){ 302 | $(this).unwrap(); 303 | } 304 | }); 305 | 306 | contentHolder.find('.temp').each(function(){ 307 | $(this).contents().unwrap(); 308 | }); 309 | 310 | contentHolder.find('div').each(function(){ 311 | if ($(this).text().length == 0){ 312 | $(this).remove(); 313 | } else { 314 | $(this).before('
'); 315 | $(this).contents().unwrap(); 316 | } 317 | }); 318 | 319 | contentHolder.find('h2:empty').remove(); 320 | contentHolder.find('h3:empty').remove(); 321 | contentHolder.find('h4:empty').remove(); 322 | contentHolder.find('a:empty').remove(); 323 | 324 | var html = contentHolder.html().replace(/ /g, ''); 325 | return html; 326 | } 327 | 328 | //====================== Экспортные функции ===========================// 329 | 330 | return { 331 | destroyPluginEvent: function(){ 332 | container.children().die(); 333 | popupmenu.children.die(); 334 | }, 335 | saveDescription: function(){ 336 | return saveDescription(); 337 | } 338 | }; 339 | 340 | return container; 341 | }; 342 | 343 | })(jQuery); -------------------------------------------------------------------------------- /public/scripts/admin/tags.js: -------------------------------------------------------------------------------- 1 | 2 | //================================================================// 3 | //*********** tags.js ***********// 4 | //*********** © Aida Drogan - #BlondieCode 5 | //*********** Виджет с автоподстановкой и заполнением тегов 6 | //================================================================// 7 | 8 | //======================= ПАРАМЕТРЫ =====================// 9 | // iInput - элемент для ввода тега 10 | // iContainer - панель для вывода результатов подстановки 11 | // iContainerClass - класс панели для вывода результатов подстановки (String) 12 | // iTagHolder - контейнер для выгрузки выбранных тегов 13 | // iItemClass - класс элемента с тегом в контейнере 14 | // iRoute - роут для ajax-запроса к серверу (совпадение тегов для постановки) 15 | 16 | function tagMeBabe(iInput, iContainer, iContainerClass, iTagHolder, iItemClass, iRoute){ 17 | 18 | var errorTopMargin = 50; 19 | 20 | //------------ функция добавления тега в контейнер 21 | function appendTag(tag){ 22 | 23 | tag = tag.trim().toLowerCase(); 24 | 25 | var toAppend = true; 26 | iInput.val(''); 27 | 28 | iTagHolder.find('.' + iItemClass).each(function(){ 29 | 30 | //------------ проверяем теги на дубли 31 | if ($(this).text() == tag){ 32 | $(this).addClass('double'); 33 | toAppend = false; 34 | } else { 35 | $(this).removeClass('double'); 36 | } 37 | }); 38 | 39 | if (toAppend) { 40 | iTagHolder.prepend('
' + tag + '
'); 41 | } 42 | 43 | } 44 | 45 | function checkTag(input){ 46 | 47 | if (input.val().length > 1){ 48 | 49 | //------------ данные для ajax-запроса 50 | var data = {}; 51 | data.key = input.val(); 52 | 53 | //------------ { ДОПИСАНО БЭКЭНД-ДЕВЕЛОПЕРОМ 54 | data.action = 'searchtag'; 55 | 56 | $.ajax({ 57 | url: iRoute, 58 | type: 'POST', 59 | dataType: 'json', 60 | data: data, 61 | error: function(){ 62 | showError('Ошибка базы данных!', errorTopMargin); 63 | } 64 | }).done(function(data){ 65 | 66 | iContainer.children().remove(); 67 | for (var i=0; i' + data.result[i].name + '') 69 | } 70 | if (iContainer.children().length > 0){ 71 | iContainer.slideDown(200); 72 | } else { 73 | iContainer.slideUp(10); 74 | } 75 | }); 76 | //------------ } 77 | } 78 | } 79 | 80 | iInput.on('focus', function(){ 81 | checkTag($(this)); 82 | }); 83 | 84 | iInput.on('keyup', function(e){ 85 | 86 | var tag = $(this).val(); 87 | var input = $(this); 88 | 89 | //------------ навигация по панели подстановки 90 | if (e.keyCode == 40) { 91 | 92 | if (iContainer.find('.active').length > 0){ 93 | iContainer.find('.active').removeClass('active').next().addClass('active'); 94 | } else { 95 | iContainer.find('span:first').addClass('active'); 96 | } 97 | 98 | input.val(iContainer.find('.active').text()); 99 | 100 | } else if (e.keyCode == 38) { 101 | 102 | if (iContainer.find('.active').length > 0){ 103 | iContainer.find('.active').removeClass('active').prev().addClass('active'); 104 | } else { 105 | iContainer.find('span:last').addClass('active'); 106 | } 107 | 108 | input.val(iContainer.find('.active').text()); 109 | 110 | } else if (e.keyCode == 13){ 111 | 112 | appendTag(tag); 113 | iContainer.slideUp(10); 114 | 115 | } else { 116 | 117 | checkTag(input); 118 | } 119 | }); 120 | 121 | //------------ удаляем тег из контейнера по клику 122 | iTagHolder.on('click', '.' + iItemClass, function(){ 123 | $(this).fadeOut(100, function(){ 124 | $(this).remove(); 125 | }); 126 | }); 127 | 128 | //------------ переносим тег в контейнер из панели подстановки 129 | iContainer.on('click', 'span', function(){ 130 | appendTag($(this).text()); 131 | iContainer.slideUp(10); 132 | }); 133 | 134 | //------------ закрываем панель подстановки по клику в любое место документа 135 | $(document).mousedown(function(e){ 136 | 137 | if (!$(e.target).is('span') && (!$(e.target).parent().hasClass(iContainerClass))){ 138 | iContainer.slideUp(10, function(){ 139 | iContainer.children().remove(); 140 | }); 141 | } 142 | }); 143 | } -------------------------------------------------------------------------------- /public/scripts/client/main.js: -------------------------------------------------------------------------------- 1 | 2 | $(document).ready(function () { 3 | 4 | $('body').on('click', '.more', function () { 5 | 6 | var button = $(this); 7 | var container = $('.article-holder'); 8 | var postLink = '/'; 9 | 10 | if (!button.hasClass('working')){ 11 | button.addClass('working'); 12 | var data = {}; 13 | data.action = 'more'; 14 | data.last = button.attr('data-last'); 15 | data.language = button.attr('data-language'); 16 | 17 | if (button.attr('data-tag')){ 18 | postLink = '/tag'; 19 | data.tag = button.attr('data-tag'); 20 | } else { 21 | data.category = button.attr('data-category'); 22 | data.ismain = button.attr('data-ismain'); 23 | } 24 | } 25 | $.ajax({ 26 | url: postLink, 27 | type: 'POST', 28 | dataType: 'json', 29 | data: data, 30 | error: function(){ 31 | button.removeClass('working'); 32 | } 33 | }).done(function(data){ 34 | container.append(data.html); 35 | if (data.last && (data.last != 'none')){ 36 | button.attr('data-last', data.last); 37 | button.removeClass('working'); 38 | } else { 39 | button.remove(); 40 | } 41 | }); 42 | }); 43 | }); 44 | 45 | $(window).load(function(){ 46 | 47 | var winHeight = $(document).height(); 48 | var step = 4; 49 | var timeToScroll = winHeight/step; 50 | 51 | $('.scrolltop').on('click', function(){ 52 | 53 | $('html, body').animate({ 54 | scrollTop: 0 55 | }, timeToScroll); 56 | }); 57 | 58 | }); 59 | 60 | //********************** {БЛАГОДАРНОСТЬ} ********************// 61 | //----------- Кнопка-скроллер скрыта до момента скролла в 100px от верха страницы 62 | //----------- Автор правки Ванильный Ниндзя 63 | //----------- https://www.youtube.com/channel/UCe-yBRRbvK2NNyIOrE-UTIA 64 | //****************** {БЛАГОДАРНОСТЬ КОНЕЦ} *****************// 65 | $(window).scroll(function(){ 66 | 67 | if ($(document).scrollTop() > 100) { 68 | $('.scrolltop').fadeIn('fast'); 69 | } else { 70 | $('.scrolltop').fadeOut('fast'); 71 | } 72 | }); -------------------------------------------------------------------------------- /public/scripts/client/main.min.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){$("body").on("click",".more",function(){var c=$(this); 2 | var b=$(".article-holder");var a="/";if(!c.hasClass("working")){c.addClass("working"); 3 | var d={};d.action="more";d.last=c.attr("data-last");d.language=c.attr("data-language"); 4 | if(c.attr("data-tag")){a="/tag";d.tag=c.attr("data-tag")}else{d.category=c.attr("data-category"); 5 | d.ismain=c.attr("data-ismain")}}$.ajax({url:a,type:"POST",dataType:"json",data:d,error:function(){c.removeClass("working") 6 | }}).done(function(e){b.append(e.html);if(e.last&&(e.last!="none")){c.attr("data-last",e.last); 7 | c.removeClass("working")}else{c.remove()}})})});$(window).load(function(){var a=$(document).height(); 8 | var c=4;var b=a/c;$(".scrolltop").on("click",function(){$("html, body").animate({scrollTop:0},b) 9 | })});$(window).scroll(function(){if($(document).scrollTop()>100){$(".scrolltop").fadeIn("fast") 10 | }else{$(".scrolltop").fadeOut("fast")}}); -------------------------------------------------------------------------------- /public/styles/admin.min.css: -------------------------------------------------------------------------------- 1 | abbr,address,article,aside,audio,b,blockquote,body,canvas,caption,cite,code,dd,del,details,dfn,div,dl,dt,em,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,p,pre,q,samp,section,small,span,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,ul,var,video{margin:0;padding:0;border:0;outline:0;font-size:100%;background:0 0}body{line-height:1}article,aside,details,figcaption,figure,footer,header,menu,nav,section{display:block}nav ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:''}a{margin:0;padding:0;font-size:100%;vertical-align:baseline;background:0 0}input,select{vertical-align:middle}.clear{display:block;clear:both}.clear:after{content:".";display:block;height:0;clear:both;visibility:hidden;line-height:0}.centered{display:table;margin:0 auto}.fl_left{float:left}.fl_right{float:right}.hidden{overflow:hidden}.half{width:50%}.full{width:100%}.flex-container{display:flex;flex:auto}.flex-col{flex-direction:column;display:flex}.flex-row{flex-direction:row;display:flex}.flex-row-wrap{flex-flow:row wrap}.flex-centered{display:flex;align-content:center;align-items:center;justify-content:center;margin:0 auto}.flex-start{flex-wrap:wrap;align-content:flex-start;align-items:flex-start;justify-content:flex-start}.flex-full{flex-grow:1;flex-basis:100%}.flex-full.mini{max-width:500px}.flex-dominant{flex-grow:1}.flex-half{flex-basis:calc(50% - 10px);margin:0 5px}.flex-tht{flex-basis:calc(33% - 10px);margin:0 5px}.flex-goo{justify-content:space-between}.padding20{box-sizing:border-box;padding:20px}.padding10-h{padding:0 10px}.padding20-h{padding:0 20px}html{min-height:100%;display:flex;flex-direction:column}body{display:flex;flex-direction:column;flex:auto;background:#efefef;font-family:Helvetica Neue,Helvetica,Arial;color:#222;font-size:16px;line-height:1.6}footer,header{flex-grow:0;display:flex;flex-direction:row;width:100%;padding:10px 20px;box-sizing:border-box;background:#222;color:#fff}header{flex-direction:column;padding:0}header .flex-full{padding:10px 20px}header .subheader{background:#777}footer{justify-content:center}footer small{font-size:12px}header a{color:#fff;border-radius:5px;border:1px solid #fff;padding:4px 10px;margin:0 6px 0 0;transition:.2s;text-decoration:none}header a:first-child{margin:0 0 0 26px}header a.active,header a:hover{color:#222;background:#fff;transition:.2s}header .buttons{flex-grow:1;display:flex;flex-direction:row-reverse;flex:auto}header .title{padding:4px;margin:4px 0 0 0}.label,label{color:#999;font-size:14px;margin:0 0 4px 0}.whois{font-size:12px;position:absolute;left:20px;top:0;color:#dadada}.content{flex-grow:1;display:flex;flex-direction:column;flex:auto;width:100%}.button{border-radius:5px;padding:10px 20px;margin:10px 10px 0 0;background:#999;color:#fff;cursor:pointer;line-height:1;height:auto;transition:.2s}.button.nomargin{margin:0}.button.active,.button:hover{transition:.2s;background:#666}.button.limited{max-height:1em}.button.green{background:#3b9739}.button.green:hover{background:#358039}.button.red{background:#973323}.button.red:hover{background:#802725}a.button{text-decoration:none}.link{padding:0 0 4px 0;border-bottom:1px dashed #999;color:#999;cursor:pointer;transition:.2s}.open-bayan{position:relative}.open-bayan:after{content:"⥥";position:absolute;color:#999;top:6px;right:20px}.open-bayan.up:after{content:"⥣"}.open-bayan:hover:after{color:#666}.link:hover{color:#666;border-bottom:1px dashed #666;transition:.2s}.bayan{display:none}.holder{flex-grow:1;display:flex;flex-direction:column;justify-content:center;flex-flow:row wrap;width:100%;min-width:1000px;max-width:1300px;margin:0 auto;background:#fff;box-sizing:border-box}.holder.type{align-items:flex-start;justify-content:flex-start;align-content:flex-start}.holder .holder{padding:20px;align-items:flex-start}.options-header{background:#dadada;width:100%;height:40px;padding:10px 20px;flex-grow:0;display:flex;flex-direction:row-reverse;align-items:center}.options-header .button{margin:0 10px 0 0;padding:10px 20px}.options-header .crumbs a{color:#222}.blog-content,.blog-content ul.ui-sortable{width:100%;display:flex;flex-wrap:wrap;flex-direction:row;align-content:flex-start;list-style-type:none;align-items:flex-start}.blog-content ul.ui-sortable li{flex-basis:calc(25% - 10px);border:1px solid #a8a8a8;margin:10px 10px 0 0;padding:10px;box-sizing:border-box;border-radius:5px}.blog-content ul.ui-sortable li.ismain{background:#f6f6f6}.blog-content ul.ui-sortable li.root{background:#e4fdce}.blog-content ul.ui-sortable li.ui-state-highlight{border:1px dashed #a7a7a7}.blog-content ul.ui-sortable li .image,.blog-content ul.ui-sortable li .image img{width:100%}.blog-content ul.ui-sortable li .title{width:100%;display:block}.blog-content ul.ui-sortable li .small{font-size:12px;color:#999}.blog-content ul.ui-sortable li .buttons{display:flex;flex-direction:row;align-content:flex-start;align-items:flex-start}.blog-content ul.ui-sortable li .buttons a{background:#999;color:#fff;padding:4px 10px;border-radius:5px;margin:10px 10px 0 0;text-decoration:none;transition:.2s}.blog-content ul.ui-sortable li .buttons a:hover{background:#666;transition:.2s}.blog-content ul.ui-sortable li .buttons a.article{background:#3b9739}.blog-content ul.ui-sortable li .buttons a.article:hover{background:#358039}.v-list{align-items:flex-start}.v-list.moder-list{align-items:stretch}.v-list .v-item{justify-content:space-between;align-items:center;padding:10px;margin:0 0 6px 0;border:1px dashed #efefef}.v-list .v-item .title{max-width:600px;min-width:600px}.v-list .v-item .title.full{max-width:100%}.v-list .v-item .image{background:#efefef;border-radius:6px;width:50px;height:50px;overflow:hidden}.v-list .v-item .image img{width:100%;height:100%}.v-list .v-item .created{color:#a7a7a7;font-size:14px}.v-list .v-item .avaliable,.v-list .v-item .published{width:120px;cursor:pointer;font-size:14px}.v-list .v-item .avaliable .yes,.v-list .v-item .published .yes{color:#3b9739}.v-list .v-item .avaliable .no,.v-list .v-item .published .no{color:#973323}.work-holder .v-list .v-item a{color:#222}.search{max-width:400px;margin:0 0 10px 0;width:100%}input,select,textarea{border-radius:5px;display:block;background:#fff;outline:0;border:1px solid #d5d5d5;padding:6px 12px;font-size:16px;box-sizing:border-box;width:100%}input:focus,textarea:focus{border:1px solid #98e25d}textarea{resize:none}.input-row{margin:10px auto;display:flex;flex-direction:column;flex-wrap:wrap;width:100%}.input-row.flex-row{flex-direction:row}.cat-holder,.icon-holder{border:1px dashed #d5d5d5;border-radius:6px;min-height:40px;box-sizing:border-box;padding:20px}.cat-holder.na{opacity:.5}.cat-holder .cat,.cat-holder.na .cat:hover{padding:4px 10px;background:#999;color:#fff;margin:0 6px 6px 0;border-radius:5px;cursor:pointer;transition:.2s}.cat-holder .cat.active,.cat-holder .cat:hover{background:#49a37c;transition:.2s}.cat-holder.na .cat,.cat-holder.na .cat:hover{cursor:default}.icon-holder .icon{font-size:32px;padding:0 10px;border:2px solid #999;border-radius:50%;cursor:pointer;min-width:30px;margin:0 10px 10px 0;transition:.3s}.icon-holder .icon.active,.icon-holder .icon:hover{background:#c0f720;transition:.3s}.checker{display:block;width:16px;height:16px;float:left;margin:1px 6px 0 0;background:#efefef;border-radius:50%;border:2px solid #999;transition:.3s;cursor:pointer}.checker.active,.checker:hover{background:#c0f720;transition:.3s}.logo-holder{max-width:250px;overflow:hidden;margin:10px 0;min-height:50px;border-radius:6px;background:#efefef}.logo-holder img{width:100%;height:auto;border-radius:6px}.infos{max-width:500px}.dateitem input{padding-left:40px;width:140px}.startdate{background:url(../images/general/calendar-green.png) no-repeat 4px 2px}.enddate{background:url(../images/general/calendar-red.png) no-repeat 4px 2px}.ui-widget-content{border:1px solid #ddd;border-radius:3px;background:#fff;box-shadow:rgba(0,0,0,.2) 1px 1px 1px}.ui-widget-header{background-color:#95b6b9;border:none;border-radius:3px;color:#fff;font-weight:700;position:relative}.ui-datepicker{width:250px;padding:.2em}.ui-datepicker .ui-datepicker-prev{left:0;top:0;position:absolute;width:1.7em;height:1.7em}.ui-datepicker .ui-datepicker-next{right:0;top:0;position:absolute;width:1.7em;height:1.7em}.ui-widget-header .ui-icon-circle-triangle-w{background:url(../images/general/prev-ws.png) no-repeat center center}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-widget-header .ui-icon-circle-triangle-e{background:url(../images/general/next-ws.png) no-repeat center center}.ui-datepicker .ui-datepicker-next span,.ui-datepicker .ui-datepicker-prev span{cursor:pointer;display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-next:hover,.ui-datepicker .ui-datepicker-prev:hover{background-color:#3e939b}.ui-datepicker .ui-datepicker-next:hover{border-radius:0 3px 3px 0}.ui-datepicker .ui-datepicker-prev:hover{border-radius:3px 0 0 3px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse;clear:both}.ui-icon{width:16px;height:16px;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker table thead{border-bottom:1px solid #a7a7a7}.ui-datepicker th{padding:.7em .3em;text-align:center;border:0}.ui-datepicker td{padding:1px}.ui-state-default.ui-state-hover{background:#95b6b9;color:#fff}#ui-datepicker-div .ui-widget-content .ui-state-default,#ui-datepicker-div .ui-widget-header .ui-state-default,.ui-state-default{border:none;background:0 0;font-weight:700;color:#000}.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-state-default.ui-state-active{background:#ffe7c7}.ui-state-default.ui-state-active:hover{background:#95b6b9;color:#fff}.tags-holder{border:1px dashed #d5d5d5;border-radius:5px;box-sizing:border-box;padding:10px;min-height:140px}.tags-holder .t-item{display:inline-block;background:#a5a5a5;transition:.3s;cursor:pointer;color:#fff;padding:4px 10px;margin:4px 4px 0 0;font-size:16px;border-radius:5px}.tags-holder .t-item{background:#39a090;padding:4px 24px 4px 10px;position:relative}.tags-holder .t-item.double{background:#a03213}.tags-holder .t-item:after{content:'✕';transition:.2s;position:absolute;right:4px;top:7px;font-size:10px;width:14px;text-align:center;display:inline-block;padding:1px;border-radius:50%}.tags-holder .t-item:hover:after{background:#fff;color:#39a090;transition:.2s}.tags-holder .t-item.double:hover:after{color:#a03213}.tag-holder{position:relative}.autofill-bar{position:absolute;top:60px;background:#fff;display:none;width:100%;border-radius:5px;overflow:hidden}.autofill-bar span{display:block;box-sizing:border-box;padding:6px 10px;border-bottom:1px solid #d5d5d5;cursor:pointer}.autofill-bar span.active,.autofill-bar span:hover{background:#ff9}.autofill-bar span:last-child{border-bottom:none}.tag-holder .autofill-bar,.tag-holder input{max-width:540px}.tag-holder .autofill-bar{border:1px solid #d5d5d5}.loader{display:none;height:15px;width:128px;margin:20px 50px 0 0;background:url(../images/general/ajax-loader.gif) no-repeat}.button.working{opacity:.5}.popup-holder{display:none;position:fixed;top:0;left:0;z-index:99999;background:rgba(96,96,96,.8);overflow:hidden;width:100%;height:100%}.popup-content{position:fixed;z-index:99999;width:1100px;height:600px;background-color:#f9f9f9;left:50%;margin-left:-550px;top:50%;margin-top:-300px;border-radius:6px;box-sizing:border-box}.popup-header{background:#5d5d5d;width:100%;color:#fff;box-sizing:border-box;padding:6px 12px;overflow:hidden}.popup-content .button{margin:0 10px 0 0}.popup-content .full{box-sizing:border-box;padding:20px}.popup-content .full.centered{text-align:center}.popup-content .full input{margin-bottom:20px}.popup-content .button{float:left;padding:10px 20px}.error-holder{position:fixed;top:0;left:35%;min-height:14px;width:28%;padding:16px 1%;display:table;text-align:center;cursor:pointer;background-color:#e96161;border-radius:10px;color:#fff;z-index:999999;display:none}.error-holder.error-holder-success{background-color:#35a63b}.login{border:2px solid #dadada;box-sizing:border-box;padding:20px;border-radius:5px;margin:auto;display:flex;flex-direction:column;justify-content:center;flex-wrap:wrap}.login .button{padding:10px 20px;text-align:center;max-width:100px;margin:20px auto 0}.login input{min-width:400px}.article-mediadescr{width:100%;margin:30px 0}.temp{background:#b1d7fc}.article-description{border:1px dashed #d5d5d5;position:relative;border-radius:6px;width:calc(100% - 42px);padding:20px;margin:4px 0 0 0}.article-description .a-bold{font-weight:700}.article-description .a-italic{font-style:italic}.article-description .a-centered{text-align:center;display:block}.article-description h2{font-weight:700;font-size:22px;margin:14px 0}.article-description h3{font-weight:700;font-size:18px;margin:14px 0}.article-description h4{font-weight:700;font-size:16px;margin:14px 0}.article-description a{color:#b38cc9}.md-menu-panel{position:absolute;overflow:hidden;display:none}.md-menu-panel .mitem{background-repeat:no-repeat;background-position:center;background-repeat:no-repeat;background-position:center;font-weight:700;background-color:rgba(0,0,0,.9);display:inline-block;float:left;color:#fff;font-size:18px;padding:5px;min-width:30px;height:30px;margin:0 2px 0 0;cursor:pointer;text-align:center;line-height:30px;border-radius:50%;font-size:16px;transition:.3s;position:relative}.md-menu-panel .mitem:hover{background-color:#4BA7AF;transition:.3s}.md-menu-panel .link-holder{overflow:hidden;display:none}.md-menu-panel .link-holder .open-link{display:block;width:16px;height:16px;float:left;margin:2px 2px 0 0;background:rgba(0,0,0,.75);border-radius:50%;border:5px solid rgba(0,0,0,.75);transition:.3s;cursor:pointer}.md-menu-panel .link-holder .open-link.active,.md-menu-panel .link-holder .open-link:hover{background:#4BA7AF;transition:.3s}.md-menu-panel .link-holder input{width:200px;background:rgba(0,0,0,.75);color:#fff;font-size:18px;float:left;border:none;padding:4px 8px;border-radius:20px;margin:0}.md-menu-panel .mitem.adescr-reset{background-image:url(../images/mediadescription/i-reset.png);background-size:25px}.md-menu-panel .mitem.adescr-bold{background-image:url(../images/mediadescription/i-bold.png);background-size:25px}.md-menu-panel .mitem.adescr-italic{background-image:url(../images/mediadescription/i-italic.png);background-size:25px}.md-menu-panel .mitem.adescr-strong{background-image:url(../images/mediadescription/i-strong.png);background-size:25px}.md-menu-panel .mitem.adescr-addlink{background-image:url(../images/mediadescription/i-link.png);background-size:25px}.md-menu-panel .mitem.adescr-centered{background-image:url(../images/mediadescription/i-center.png);background-size:25px} -------------------------------------------------------------------------------- /public/styles/main.css: -------------------------------------------------------------------------------- 1 | /*************************** Reset *****************************/ 2 | 3 | html, body, div, span, object, iframe, 4 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 5 | abbr, address, cite, code, 6 | del, dfn, em, img, ins, kbd, q, samp, 7 | small, strong, sub, sup, var, 8 | b, i, 9 | dl, dt, dd, ol, ul, li, 10 | fieldset, form, label, legend, 11 | table, caption, tbody, tfoot, thead, tr, th, td, 12 | article, aside, canvas, details, figcaption, figure, 13 | footer, header, menu, nav, section, summary, 14 | time, mark, audio, video { 15 | margin:0; 16 | padding:0; 17 | border:0; 18 | outline:0; 19 | font-size:100%; 20 | background:transparent; 21 | } 22 | 23 | body { 24 | line-height:1; 25 | } 26 | 27 | article,aside,details,figcaption,figure, 28 | footer,header,menu,nav,section { 29 | display:block; 30 | } 31 | 32 | nav ul { 33 | list-style:none; 34 | } 35 | 36 | blockquote, q { 37 | quotes:none; 38 | } 39 | 40 | blockquote:before, blockquote:after, 41 | q:before, q:after {content:'';} 42 | 43 | a { 44 | margin:0; 45 | padding:0; 46 | font-size:100%; 47 | vertical-align:baseline; 48 | background:transparent; 49 | } 50 | 51 | /************************* Basic styles ***************************/ 52 | 53 | body, html {padding: 0;margin: 0; height: 100%} 54 | body { 55 | background: #f6f6f6; 56 | color: #2d2d2d; 57 | font-family: 'Arial', 'Helvetica'; 58 | font-size: 16px; 59 | line-height: 1.5; 60 | } 61 | 62 | h1 {font-size: 24px;} 63 | h2 {font-size: 22px;} 64 | h3 {font-size: 20px;} 65 | h4, h5, h6 {font-size: 18px;} 66 | 67 | a {color: #ca6a96} 68 | a:hover {color: #e889b4} 69 | 70 | header, footer, main { 71 | width: 100%; 72 | } 73 | 74 | header { 75 | padding: 50px 0 30px; 76 | display: flex; 77 | flex-grow: 0; 78 | flex-direction: row; 79 | justify-content: space-between; 80 | align-items: center; 81 | } 82 | header ul li, footer ul li { 83 | display: inline-block; 84 | margin: 0 20px 0 0; 85 | text-transform: uppercase; 86 | font-weight: 600; 87 | font-size: 11px; 88 | letter-spacing: 1px; 89 | } 90 | header ul a, footer ul a { 91 | color: #2d2d2d; 92 | text-decoration: none; 93 | display: inline-block; 94 | border-bottom: 2px solid transparent; 95 | transition: .2s; 96 | } 97 | header ul a:hover, header ul a.active, footer ul a:hover, footer ul a.active { 98 | color: #2d2d2d; 99 | border-bottom: 2px solid #2d2d2d; 100 | transition: .2s; 101 | } 102 | header nav#language { 103 | margin: 0 0 10px 0; 104 | display: flex; 105 | flex-direction: row-reverse; 106 | } 107 | 108 | header nav#language ul li { 109 | background: #2d2d2d; 110 | color: #f6f6f6; 111 | padding: 4px 6px; 112 | border-radius: 50%; 113 | transition: .2s; 114 | font-size: 10px; 115 | } 116 | header nav#language ul li:hover, header nav#language ul li.active {background: #ca6a96; transition: .2s;} 117 | header nav#language ul li a {color: #f6f6f6; border-bottom: none;} 118 | 119 | main { 120 | flex-grow: 1; 121 | line-height: 1.8; 122 | } 123 | 124 | footer { 125 | flex-grow: 0; 126 | display: flex; 127 | align-items: center; 128 | justify-content: space-between; 129 | padding: 40px 0; 130 | border-top: 1px solid #d4d4d4; 131 | } 132 | 133 | footer small { 134 | font-size: 11px; 135 | text-align: center; 136 | text-transform: uppercase; 137 | } 138 | 139 | main h1, main h2, main h3, main h4 { 140 | text-transform: uppercase; 141 | letter-spacing: 1px; 142 | color: #2d2d2d; 143 | margin: 0 0 20px 0; 144 | } 145 | main h1 { 146 | text-align: center; 147 | margin: 30px 0 50px; 148 | } 149 | 150 | .page-wrapper { 151 | width: 100%; 152 | height: 100%; 153 | margin: 0 auto; 154 | max-width: 1200px; 155 | padding: 0 10px; 156 | box-sizing: border-box; 157 | display: flex; 158 | flex-direction: column; 159 | } 160 | 161 | .scrolltop { 162 | position: fixed; 163 | bottom: 40px; 164 | right: 40px; 165 | width: 40px; 166 | height: 40px; 167 | border-radius: 50%; 168 | background: rgba(0,0,0,.75); 169 | opacity: .75; 170 | cursor: pointer; 171 | transition: .3s; 172 | display: none; 173 | } 174 | .scrolltop:hover { 175 | opacity: 1; 176 | transition: .3s; 177 | } 178 | .scrolltop:after { 179 | content: "⇪"; 180 | width: 100%; 181 | text-align: center; 182 | line-height: 40px; 183 | position: absolute; 184 | top: 0; 185 | left: 0; 186 | color: #ffffff; 187 | } 188 | 189 | /************************* Category page ***************************/ 190 | 191 | .article-holder { 192 | display: flex; 193 | flex-flow: row wrap; 194 | justify-content: space-between; 195 | padding: 10px 0; 196 | margin: 30px 0 0 0; 197 | } 198 | 199 | .article-holder article { 200 | background: #ffffff; 201 | margin: 10px 0 0 0; 202 | flex-basis: calc(33% - 4px); 203 | } 204 | .article-holder article a { 205 | text-decoration: none; 206 | display: flex; 207 | flex-direction: column; 208 | height: 100%; 209 | } 210 | .article-holder article .details { 211 | padding: 30px; 212 | box-sizing: border-box; 213 | font-size: 14px; 214 | color: #7d7d7d; 215 | flex-grow: 1; 216 | } 217 | .article-holder article h2 { 218 | font-weight: 400; 219 | font-size: 18px; 220 | color: #2d2d2d; 221 | } 222 | .article-holder article img { 223 | width: 100%; 224 | height: auto; 225 | opacity: 1; 226 | transition: .3s 227 | } 228 | .article-holder article:hover img { 229 | opacity: .75; 230 | transition: .3s 231 | } 232 | .article-holder article .secondary { 233 | display: flex; 234 | flex-direction: row; 235 | align-items: center; 236 | justify-content: space-between; 237 | padding: 0 20px 30px; 238 | box-sizing: border-box; 239 | font-size: 12px; 240 | } 241 | .article-holder article .secondary .views { 242 | font-size: 16px; 243 | font-weight: 600; 244 | display: block; 245 | position: relative; 246 | padding: 0 0 0 16px; 247 | } 248 | .article-holder article .secondary .views:after { 249 | display: block; 250 | position: absolute; 251 | content: '☋'; 252 | top: 0; 253 | left: 0; 254 | } 255 | 256 | .more { 257 | margin: 20px auto 10px; 258 | background: #ca6a96; 259 | color: #ffffff; 260 | border-radius: 5px; 261 | padding: 4px 10px; 262 | transition: .2s; 263 | cursor: pointer; 264 | display: table; 265 | } 266 | .more:hover { 267 | background: #e889b4; 268 | transition: .2s; 269 | } 270 | 271 | /************************* Article page ***************************/ 272 | 273 | .article-wrapper { 274 | display: flex; 275 | flex-flow: row wrap; 276 | height: 100%; 277 | width: 100%; 278 | margin: 40px 0 0 0; 279 | } 280 | .article-wrapper .details { 281 | padding: 10px; 282 | } 283 | .article-wrapper article .details { 284 | padding: 20px; 285 | } 286 | .article-wrapper article { 287 | display: flex; 288 | flex-direction: column; 289 | flex-wrap: wrap; 290 | background: #ffffff; 291 | margin: 0 20px 20px 0; 292 | width: 70%; 293 | max-width: calc(100% - 320px); 294 | flex-grow: 1; 295 | } 296 | .article-wrapper article h1 { 297 | margin: 30px 0; 298 | } 299 | .article-wrapper article .secondary { 300 | display: flex; 301 | flex-direction: row; 302 | justify-content: space-between; 303 | font-size: 14px; 304 | color: #999999; 305 | } 306 | .article-wrapper article img { 307 | margin: 0 0 20px 0; 308 | width: 100%; 309 | height: auto; 310 | } 311 | .article-wrapper aside { 312 | display: flex; 313 | flex-direction: column; 314 | width: 30%; 315 | max-width: 300px; 316 | } 317 | .article-wrapper aside a { 318 | text-decoration: none; 319 | display: block; 320 | background: #ffffff; 321 | margin: 0 0 20px 0; 322 | } 323 | .article-wrapper aside .tags { 324 | display: flex; 325 | flex-flow: row wrap; 326 | background: #ffffff; 327 | padding: 20px; 328 | margin: 0 0 20px 0; 329 | } 330 | .article-wrapper aside .tags a { 331 | background: #ca6a96; 332 | color: #ffffff; 333 | border-radius: 5px; 334 | padding: 4px 10px; 335 | margin: 0 10px 10px 0; 336 | transition: .2s; 337 | } 338 | .article-wrapper aside .tags a:hover { 339 | background: #e889b4; 340 | transition: .2s; 341 | } 342 | .article-wrapper aside p { 343 | font-size: 14px; 344 | line-height: 1.4; 345 | color: #666666; 346 | } 347 | .article-wrapper aside h2 { 348 | text-transform: none; 349 | font-size: 18px; 350 | line-height: 1.3; 351 | margin: 0 0 12px 0; 352 | } 353 | .article-wrapper aside img { 354 | width: 100%; 355 | height: auto; 356 | } 357 | .a-centered { 358 | display: block; 359 | text-align: center; 360 | } 361 | 362 | /************************* Page 404 ***************************/ 363 | 364 | .notfoundpage { 365 | text-align: center; 366 | font-size: 32px; 367 | margin: 30px 0; 368 | } 369 | .notfoundpage p { 370 | margin: 0 0 20px 0; 371 | } 372 | .notfoundpage .small { 373 | font-size: 22px; 374 | } 375 | .notfoundpage h1 { 376 | font-size: 66px; 377 | } 378 | .notfoundpage .sad { 379 | position: relative; 380 | min-height: 120px; 381 | } 382 | .notfoundpage .sad:after { 383 | position: absolute; 384 | content: "☹"; 385 | color: #999999; 386 | font-size: 120px; 387 | line-height: 1; 388 | width: 100%; 389 | top: 0; 390 | left: 0; 391 | } 392 | 393 | /************************* Responsive styles ***************************/ 394 | 395 | @media (max-width: 800px) { 396 | 397 | header { 398 | display: block; 399 | padding: 10px 0 0 0; 400 | } 401 | header nav#language { 402 | margin: 20px 0 10px; 403 | flex-direction: row; 404 | } 405 | header ul li { 406 | margin: 0 20px 20px 0; 407 | } 408 | main h1 { 409 | margin: 20px 0 30px; 410 | } 411 | .article-holder { 412 | margin: 20px 0 0 0; 413 | } 414 | .article-holder article { 415 | flex-basis: calc(50% - 5px); 416 | } 417 | .scrolltop { 418 | bottom: 20px; 419 | right: 20px; 420 | } 421 | .article-wrapper article { 422 | display: block; 423 | margin: 0 0 20px 0; 424 | } 425 | .article-wrapper { 426 | flex-direction: column; 427 | } 428 | .article-wrapper aside, .article-wrapper article { 429 | max-width: 100%; 430 | width: 100%; 431 | } 432 | .article-wrapper aside img { 433 | width: 50px; 434 | border-radius: 50%; 435 | margin-left: 10px; 436 | } 437 | .article-wrapper aside a { 438 | display: flex; 439 | flex-direction: row; 440 | align-items: center; 441 | margin: 0 0 10px 0; 442 | } 443 | } 444 | 445 | @media (max-width: 600px) { 446 | 447 | .article-holder article { 448 | flex-basis: 100%; 449 | } 450 | 451 | .scrolltop { 452 | bottom: 10px; 453 | right: 10px; 454 | } 455 | } -------------------------------------------------------------------------------- /public/styles/main.min.css: -------------------------------------------------------------------------------- 1 | abbr,address,article,aside,audio,b,blockquote,body,canvas,caption,cite,code,dd,del,details,dfn,div,dl,dt,em,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,p,pre,q,samp,section,small,span,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,ul,var,video{margin:0;padding:0;border:0;outline:0;font-size:100%;background:0 0}body{line-height:1}article,aside,details,figcaption,figure,footer,header,menu,nav,section{display:block}nav ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:''}a{margin:0;padding:0;font-size:100%;vertical-align:baseline;background:0 0}body,html{padding:0;margin:0;height:100%}body{background:#f6f6f6;color:#2d2d2d;font-family:Arial,Helvetica;font-size:16px;line-height:1.5}h1{font-size:24px}h2{font-size:22px}h3{font-size:20px}h4,h5,h6{font-size:18px}a{color:#ca6a96}a:hover{color:#e889b4}footer,header,main{width:100%}header{padding:50px 0 30px;display:flex;flex-grow:0;flex-direction:row;justify-content:space-between;align-items:center}footer ul li,header ul li{display:inline-block;margin:0 20px 0 0;text-transform:uppercase;font-weight:600;font-size:11px;letter-spacing:1px}footer ul a,header ul a{color:#2d2d2d;text-decoration:none;display:inline-block;border-bottom:2px solid transparent;transition:.2s}footer ul a.active,footer ul a:hover,header ul a.active,header ul a:hover{color:#2d2d2d;border-bottom:2px solid #2d2d2d;transition:.2s}header nav#language{margin:0 0 10px 0;display:flex;flex-direction:row-reverse}header nav#language ul li{background:#2d2d2d;color:#f6f6f6;padding:4px 6px;border-radius:50%;transition:.2s;font-size:10px}header nav#language ul li.active,header nav#language ul li:hover{background:#ca6a96;transition:.2s}header nav#language ul li a{color:#f6f6f6;border-bottom:none}main{flex-grow:1;line-height:1.8}footer{flex-grow:0;display:flex;align-items:center;justify-content:space-between;padding:40px 0;border-top:1px solid #d4d4d4}footer small{font-size:11px;text-align:center;text-transform:uppercase}main h1,main h2,main h3,main h4{text-transform:uppercase;letter-spacing:1px;color:#2d2d2d;margin:0 0 20px 0}main h1{text-align:center;margin:30px 0 50px}.page-wrapper{width:100%;height:100%;margin:0 auto;max-width:1200px;padding:0 10px;box-sizing:border-box;display:flex;flex-direction:column}.scrolltop{position:fixed;bottom:40px;right:40px;width:40px;height:40px;border-radius:50%;background:rgba(0,0,0,.75);opacity:.75;cursor:pointer;transition:.3s;display:none}.scrolltop:hover{opacity:1;transition:.3s}.scrolltop:after{content:"⇪";width:100%;text-align:center;line-height:40px;position:absolute;top:0;left:0;color:#fff}.article-holder{display:flex;flex-flow:row wrap;justify-content:space-between;padding:10px 0;margin:30px 0 0 0}.article-holder article{background:#fff;margin:10px 0 0 0;flex-basis:calc(33% - 4px)}.article-holder article a{text-decoration:none;display:flex;flex-direction:column;height:100%}.article-holder article .details{padding:30px;box-sizing:border-box;font-size:14px;color:#7d7d7d;flex-grow:1}.article-holder article h2{font-weight:400;font-size:18px;color:#2d2d2d}.article-holder article img{width:100%;height:auto;opacity:1;transition:.3s}.article-holder article:hover img{opacity:.75;transition:.3s}.article-holder article .secondary{display:flex;flex-direction:row;align-items:center;justify-content:space-between;padding:0 20px 30px;box-sizing:border-box;font-size:12px}.article-holder article .secondary .views{font-size:16px;font-weight:600;display:block;position:relative;padding:0 0 0 16px}.article-holder article .secondary .views:after{display:block;position:absolute;content:'☋';top:0;left:0}.more{margin:20px auto 10px;background:#ca6a96;color:#fff;border-radius:5px;padding:4px 10px;transition:.2s;cursor:pointer;display:table}.more:hover{background:#e889b4;transition:.2s}.article-wrapper{display:flex;flex-flow:row wrap;height:100%;width:100%;margin:40px 0 0 0}.article-wrapper .details{padding:10px}.article-wrapper article .details{padding:20px}.article-wrapper article{display:flex;flex-direction:column;flex-wrap:wrap;background:#fff;margin:0 20px 20px 0;width:70%;max-width:calc(100% - 320px);flex-grow:1}.article-wrapper article h1{margin:30px 0}.article-wrapper article .secondary{display:flex;flex-direction:row;justify-content:space-between;font-size:14px;color:#999}.article-wrapper article img{margin:0 0 20px 0;width:100%;height:auto}.article-wrapper aside{display:flex;flex-direction:column;width:30%;max-width:300px}.article-wrapper aside a{text-decoration:none;display:block;background:#fff;margin:0 0 20px 0}.article-wrapper aside .tags{display:flex;flex-flow:row wrap;background:#fff;padding:20px;margin:0 0 20px 0}.article-wrapper aside .tags a{background:#ca6a96;color:#fff;border-radius:5px;padding:4px 10px;margin:0 10px 10px 0;transition:.2s}.article-wrapper aside .tags a:hover{background:#e889b4;transition:.2s}.article-wrapper aside p{font-size:14px;line-height:1.4;color:#666}.article-wrapper aside h2{text-transform:none;font-size:18px;line-height:1.3;margin:0 0 12px 0}.article-wrapper aside img{width:100%;height:auto}.a-centered{display:block;text-align:center}.notfoundpage{text-align:center;font-size:32px;margin:30px 0}.notfoundpage p{margin:0 0 20px 0}.notfoundpage .small{font-size:22px}.notfoundpage h1{font-size:66px}.notfoundpage .sad{position:relative;min-height:120px}.notfoundpage .sad:after{position:absolute;content:"☹";color:#999;font-size:120px;line-height:1;width:100%;top:0;left:0}@media (max-width:800px){header{display:block;padding:10px 0 0 0}header nav#language{margin:20px 0 10px;flex-direction:row}header ul li{margin:0 20px 20px 0}main h1{margin:20px 0 30px}.article-holder{margin:20px 0 0 0}.article-holder article{flex-basis:calc(50% - 5px)}.scrolltop{bottom:20px;right:20px}.article-wrapper article{display:block;margin:0 0 20px 0}.article-wrapper{flex-direction:column}.article-wrapper article,.article-wrapper aside{max-width:100%;width:100%}.article-wrapper aside img{width:50px;border-radius:50%;margin-left:10px}.article-wrapper aside a{display:flex;flex-direction:row;align-items:center;margin:0 0 10px 0}}@media (max-width:600px){.article-holder article{flex-basis:100%}.scrolltop{bottom:10px;right:10px}} -------------------------------------------------------------------------------- /routes/admin/admin.js: -------------------------------------------------------------------------------- 1 | 2 | var log = require('../../libs/log')(module); 3 | var Admin = require('../../models/admins').Admin; 4 | 5 | exports.get = function(req, res){ 6 | 7 | res.locals.title = "Вход"; 8 | res.locals.page = "login"; 9 | 10 | if (req.session.user){ 11 | 12 | Admin.findOne({_id:req.session.user}, function(err){ 13 | if (err){ 14 | res.render('./admin/login/login'); 15 | } else { 16 | res.redirect('/admin/categories'); 17 | } 18 | }) 19 | } else { 20 | res.render('./admin/login/login'); 21 | } 22 | 23 | }; -------------------------------------------------------------------------------- /routes/admin/articles.js: -------------------------------------------------------------------------------- 1 | 2 | var log = require('../../libs/log')(module); 3 | var Articles = require('../../models/articles').Article; 4 | var Categories = require('../../models/categories').Category; 5 | 6 | exports.get = function(req, res){ 7 | 8 | if (req.session.user) { 9 | 10 | res.locals.page = 'articles'; 11 | 12 | Categories.findOne({_id: req.params.id}, function(err, category){ 13 | 14 | if (category){ 15 | 16 | res.locals.category = category; 17 | res.locals.title = "Статьи в категории " + category.title; 18 | 19 | Articles.find({categories: req.params.id}).sort({moderated: -1}).exec(function(err, articles){ 20 | if (articles &&(articles.length > 0)){ 21 | res.locals.articles = articles; 22 | } else { 23 | res.locals.articles = []; 24 | } 25 | res.render('./admin/articles/articles'); 26 | }); 27 | 28 | } else { 29 | 30 | res.locals.title = '404 Ничего не найдено'; 31 | res.status(404).render('./client/error/error', {errorCode: 404, errorText: 'Страница не найдена'}); 32 | } 33 | }); 34 | 35 | 36 | } else { 37 | res.render('./admin/login/login'); 38 | } 39 | }; 40 | 41 | exports.post = function(req, res){ 42 | 43 | if (req.body.action == 'searcharticle'){ 44 | 45 | Articles.find({title: {$regex: req.body.key, $options: 'im'}}).limit(10).exec(function(err, articles){ 46 | 47 | if (articles && (articles.length > 0)){ 48 | 49 | var category = {}; 50 | category._id = req.body.catid; 51 | 52 | res.render('./admin/articles/item', {articles:articles, category:category}, function(err, html){ 53 | if (err){ 54 | log.error('---------- Error: ' + err); 55 | res.send({result: ''}); 56 | } else { 57 | res.send({result: html}); 58 | } 59 | }); 60 | 61 | } else { 62 | res.send({result : ''}); 63 | } 64 | 65 | }); 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /routes/admin/categories.js: -------------------------------------------------------------------------------- 1 | 2 | var log = require('../../libs/log')(module); 3 | var Categories = require('../../models/categories').Category; 4 | var Articles = require('../../models/articles').Article; 5 | 6 | exports.get = function(req, res){ 7 | 8 | if (req.session.user) { 9 | 10 | res.locals.page = 'categories'; 11 | res.locals.title = 'Категории блог'; 12 | 13 | var indexArray = []; 14 | 15 | function getArticlesCount(category, callback){ 16 | 17 | Articles.count({categories: (category._id).toString()}, function (err, count) { 18 | var articleCount = 0; 19 | if (count){ 20 | articleCount = count; 21 | } 22 | category.articlecount = articleCount; 23 | var pos = indexArray.indexOf(category.id); 24 | callback(category, pos); 25 | }); 26 | } 27 | 28 | Categories.find({lang: "default"}).sort({position: 1}).exec(function(err, categories){ 29 | 30 | if (categories && (categories.length > 0)){ 31 | 32 | var catArray = []; 33 | var counter = 0; 34 | 35 | for (var i=0; i 0)){ 21 | 22 | res.locals.categories = categories; 23 | 24 | if (req.params.label != 'new'){ 25 | 26 | Articles.findOne({_id: req.params.label}, function(err, article){ 27 | if (article){ 28 | 29 | res.locals.title = 'Редактор: ' + article.title; 30 | 31 | if (req.params.lang){ 32 | 33 | Articles.findOne({parent: req.params.label, lang: req.params.lang}, function(err, lproduct){ 34 | 35 | res.locals.lang = req.params.lang; 36 | res.locals.parent = req.params.label; 37 | 38 | if (lproduct){ 39 | 40 | res.locals.article = lproduct; 41 | 42 | } else { 43 | 44 | res.locals.article = 'new' 45 | } 46 | 47 | res.render('./admin/editarticle/editarticle'); 48 | }) 49 | 50 | } else { 51 | 52 | res.locals.article = article; 53 | res.render('./admin/editarticle/editarticle'); 54 | } 55 | 56 | } else { 57 | res.locals.title = '404 Ничего не найдено'; 58 | res.status(404).render('./client/error/error', {errorCode: 404, errorText: 'Страница не найдена'}); 59 | } 60 | }) 61 | } else { 62 | res.locals.article = 'new'; 63 | res.locals.title = 'Новая статья'; 64 | res.render('./admin/editarticle/editarticle'); 65 | } 66 | } else { 67 | res.locals.title = '404 Ничего не найдено'; 68 | res.status(404).render('./client/error/error', {errorCode: 404, errorText: 'Страница не найдена'}); 69 | } 70 | }); 71 | 72 | } else{ 73 | res.render('./admin/login/login'); 74 | } 75 | }; 76 | 77 | exports.post = function(req, res){ 78 | 79 | function checkTags(callback){ 80 | 81 | if (req.body.tags && (req.body.tags.length > 0)){ 82 | 83 | var counter = 0; 84 | for (var i=0; i 0)){ 167 | 168 | var dir = 'blog/' + req.body.id; 169 | 170 | // Можно сделать saveFile, если компрессия не нужна 171 | fileupload.saveWithResizeFile(dir, req.files[0], 'mainimage', 860, function(err, filelink){ 172 | // fileupload.saveFile(dir, req.files[0], 'mainimage', function(err, filelink){ 173 | if (err){ 174 | res.send(err); 175 | } else { 176 | 177 | Articles.saveMainImage(req.body.id, filelink, function(err){ 178 | 179 | if (err){ 180 | res.send(err); 181 | } else { 182 | fileupload.slideCrop('..' + filelink, 385, 205, function(err){ 183 | if (err){ 184 | res.send('Невозможно создать превью!'); 185 | } else { 186 | 187 | fileupload.sqCrop('..' + filelink, 300, function(err){ 188 | if (err){ 189 | res.send('Невозможно создать превью!'); 190 | } else { 191 | res.send('Success'); 192 | } 193 | }); 194 | } 195 | }); 196 | } 197 | }); 198 | } 199 | }); 200 | } else { 201 | res.send('Не выбран файл для загрузки!'); 202 | } 203 | 204 | } else if (req.body.action == 'deletemainimg'){ 205 | 206 | var filename = req.body.file; 207 | var ext = filename.substr(filename.lastIndexOf('.')); 208 | filename = filename.substr(0, filename.lastIndexOf('.')); 209 | var minfile = filename + '-min' + ext; 210 | var slidefile = filename + '-slide' + ext; 211 | 212 | fileupload.fileUnlink(req.body.file, function(err){ 213 | 214 | if (err){ 215 | res.send('Невозможно удалить файл!'); 216 | } else { 217 | 218 | fileupload.fileUnlink(minfile, function(err){ 219 | if (err){ 220 | res.send('Невозможно удалить файл!'); 221 | } else { 222 | 223 | fileupload.fileUnlink(slidefile, function(err){ 224 | if (err){ 225 | res.send('Невозможно удалить файл!'); 226 | } else { 227 | 228 | Articles.saveMainImage(req.body.id, '', function(err){ 229 | if (err){ 230 | res.send('Невозможно удалить файл!') 231 | } else { 232 | res.send({ok: "ok"}); 233 | } 234 | 235 | }); 236 | } 237 | }); 238 | } 239 | }); 240 | } 241 | }) 242 | 243 | } else if (req.body.action == 'setpublished'){ 244 | 245 | var flag = true; 246 | if (req.body.flag == 'no'){ 247 | flag = false; 248 | } 249 | 250 | Articles.setPublished(req.body.id, flag, function(err){ 251 | if (err){ 252 | res.send(err); 253 | } else { 254 | res.send({ok: 'ok'}); 255 | } 256 | }) 257 | 258 | } else if (req.body.action == 'deletearticle') { 259 | 260 | function removeProduct(id, callback){ 261 | 262 | Articles.deleteArticle(id, function(err){ 263 | if (err){ 264 | callback(err); 265 | } else { 266 | fileupload.removeFolder('../files/blog/' + id, function(err){ 267 | if (err) { 268 | log.error('------removeFolder error: ' + err); 269 | } 270 | callback(); 271 | }) 272 | 273 | } 274 | }) 275 | } 276 | 277 | Articles.findOne({_id: req.body.id}, function(err, product){ 278 | 279 | if (product){ 280 | 281 | if (product.lang == 'default'){ 282 | 283 | Articles.find({parent: product._id}, function(err, products){ 284 | if (products && (products.length > 0)){ 285 | 286 | var counter = 0; 287 | for (var i=0; i 0)){ 343 | res.send({result: tags}); 344 | } else { 345 | res.send({result : []}); 346 | } 347 | 348 | }) 349 | 350 | } 351 | 352 | }; -------------------------------------------------------------------------------- /routes/admin/editcategory.js: -------------------------------------------------------------------------------- 1 | var log = require('../../libs/log')(module); 2 | var MyConfig = require('../../libs/myconfig'); 3 | var Categories = require('../../models/categories').Category; 4 | var fileupload = require('../../libs/fileupload'); 5 | 6 | exports.get = function(req, res){ 7 | 8 | if (req.session.user) { 9 | 10 | res.locals.languages = MyConfig.languages; 11 | 12 | Categories.findOne({_id: req.params.id}, function(err, category){ 13 | 14 | if (category){ 15 | 16 | res.locals.page = "editcategory"; 17 | res.locals.title = "Редактор категории " + category.title; 18 | 19 | if (req.params.lang){ 20 | 21 | Categories.findOne({parent: req.params.id, lang: req.params.lang}, function(err, lcategory){ 22 | 23 | res.locals.lang = req.params.lang; 24 | res.locals.parent = req.params.id; 25 | 26 | if (lcategory){ 27 | 28 | res.locals.category = lcategory; 29 | 30 | } else { 31 | 32 | var tempCat = {}; 33 | tempCat.title = ''; 34 | tempCat.alias = ''; 35 | tempCat.shortdescription = ''; 36 | tempCat.htmltitle = ''; 37 | tempCat.htmldescription = ''; 38 | tempCat.htmlkeywords = ''; 39 | tempCat.menutitle = ''; 40 | tempCat.cattype = category.cattype; 41 | 42 | res.locals.category = tempCat; 43 | } 44 | 45 | res.render('./admin/editcategory/editcategory'); 46 | }) 47 | 48 | } else { 49 | 50 | res.locals.category = category; 51 | res.locals.lang = "default"; 52 | res.locals.parent = 'me'; 53 | res.render('./admin/editcategory/editcategory'); 54 | } 55 | 56 | } else { 57 | res.locals.title = '404 Ничего не найдено'; 58 | res.status(404).render('./client/error/error', {errorCode: 404, errorText: 'Страница не найдена'}); 59 | } 60 | }); 61 | } else { 62 | res.render('./admin/login/login'); 63 | } 64 | }; 65 | 66 | exports.post = function(req, res){ 67 | 68 | if (req.body.action == 'newcategory'){ 69 | 70 | Categories.createCategory(req.body.title, req.body.alias, req.body.pos, req.body.moderator, function(err, category){ 71 | 72 | if (category){ 73 | var page = 'categories'; 74 | 75 | category.subcount = 0; 76 | res.render('./admin/categories/item', {item:category, page:page}, function(err, html){ 77 | if (err){ 78 | log.error('------------- Error: ' + err); 79 | res.send({result: ''}); 80 | } else { 81 | res.send({result: html}); 82 | } 83 | }); 84 | } else { 85 | res.send(err); 86 | } 87 | }); 88 | 89 | } else if (req.body.action == 'posupdate'){ 90 | 91 | Categories.posUpdate(req.body.id, req.body.pos, function(err){ 92 | if (err){ 93 | res.send(err); 94 | } else { 95 | res.send({ok: "Ok"}); 96 | } 97 | }) 98 | 99 | } else if (req.body.action == 'editcategory'){ 100 | 101 | var ismain = null; 102 | var alias = null; 103 | var isLocal = false; 104 | 105 | if (req.body.lang == 'default'){ 106 | 107 | ismain = false; 108 | alias = req.body.alias; 109 | 110 | if (req.body.ismain == 'true'){ 111 | ismain = true; 112 | 113 | Categories.setMain('all', false, function (err) { 114 | 115 | if (err){ 116 | res.send(err); 117 | } else { 118 | 119 | editCategory(isLocal, function (err, id) { 120 | if (err){ 121 | res.send(err); 122 | } else { 123 | 124 | Categories.setMain(req.body.id, true, function (err) { 125 | if (err){ 126 | res.send(err); 127 | } else { 128 | res.send({id: id}); 129 | } 130 | }); 131 | } 132 | }); 133 | } 134 | }); 135 | 136 | } else { 137 | 138 | editCategory(isLocal, function (err, id) { 139 | if (err){ 140 | res.send(err); 141 | } else { 142 | res.send({id: id}); 143 | } 144 | }); 145 | } 146 | 147 | } else { 148 | 149 | Categories.findOne({_id: req.body.id}, function(err, category){ 150 | 151 | if (!category){ 152 | isLocal = true; 153 | } 154 | 155 | editCategory(isLocal, function (err, id) { 156 | if (err){ 157 | res.send(err); 158 | } else { 159 | res.send({id: id}); 160 | } 161 | }); 162 | }); 163 | } 164 | 165 | function editCategory(isLocal, callback) { 166 | 167 | if (isLocal) { 168 | Categories.createLocal( 169 | req.body.parent, 170 | req.body.lang, 171 | req.body.title, 172 | req.body.shortdescription, 173 | req.body.description, 174 | req.body.htmltitle, 175 | req.body.htmldescription, 176 | req.body.htmlkeywords, 177 | req.body.menutitle, 178 | req.body.moderator, 179 | 180 | function(err, result){ 181 | 182 | if (err){ 183 | callback(err); 184 | } else { 185 | callback(null, result._id); 186 | } 187 | }); 188 | } else { 189 | Categories.editCategory( 190 | req.body.lang, 191 | req.body.id, 192 | req.body.title, 193 | req.body.shortdescription, 194 | req.body.description, 195 | req.body.htmltitle, 196 | req.body.htmldescription, 197 | req.body.htmlkeywords, 198 | alias, 199 | req.body.menutitle, 200 | ismain, 201 | req.body.moderator, 202 | 203 | function(err){ 204 | 205 | if (err){ 206 | callback(err); 207 | } else { 208 | callback(null, req.body.id); 209 | } 210 | }); 211 | } 212 | } 213 | } else if (req.body.action == 'deletecategory'){ 214 | 215 | function removeCategory(id, callback){ 216 | 217 | Categories.deleteCategory(id, function(err){ 218 | if (err){ 219 | callback(err); 220 | } else { 221 | callback(null); 222 | } 223 | }) 224 | } 225 | 226 | Categories.findOne({_id: req.body.id}, function(err, category){ 227 | 228 | if (category){ 229 | 230 | if (category.lang == 'default'){ 231 | 232 | Categories.find({parent: category._id}, function(err, categories){ 233 | if (categories && (categories.length > 0)){ 234 | 235 | var counter = 0; 236 | for (var i=0; i 0)){ 16 | 17 | Admin.authorize(username, password, function(err, admin){ 18 | 19 | if (err){ 20 | if (err === 403){ 21 | res.send(Config.messages.error.auth); 22 | }else{ 23 | log.error(err); 24 | res.send(Config.messages.error.db); 25 | } 26 | } else { 27 | 28 | req.session.user = admin._id; 29 | var link = "/admin/categories"; 30 | res.send({link: link}); 31 | } 32 | }); 33 | 34 | } else { 35 | 36 | Admin.createAdmin(username, password, "superadmin", function(){ 37 | checkAdmin(); 38 | }); 39 | } 40 | }) 41 | } 42 | 43 | checkAdmin(); 44 | 45 | }; -------------------------------------------------------------------------------- /routes/admin/logout.js: -------------------------------------------------------------------------------- 1 | 2 | var log = require('../../libs/log')(module); 3 | 4 | exports.get = function(req, res, next){ 5 | 6 | req.session.destroy(function(err) { 7 | if (err) return next(err); 8 | res.redirect('/admin'); 9 | }); 10 | 11 | }; -------------------------------------------------------------------------------- /routes/client/article.js: -------------------------------------------------------------------------------- 1 | 2 | var log = require('../../libs/log')(module); 3 | var MyConfig = require('../../libs/myconfig'); 4 | var Articles = require('../../models/articles').Article; 5 | 6 | 7 | exports.get = function(req, res){ 8 | 9 | 10 | Articles.findOne({alias: req.params.alias}, function(err, article){ 11 | 12 | if (article){ 13 | 14 | if (res.locals.language == 'default'){ 15 | 16 | parseArticle(article, article.categories[0]); 17 | 18 | } else { 19 | 20 | findLocal(article, res.locals.language, true, function(resArticle){ 21 | 22 | if (resArticle){ 23 | 24 | parseArticle(resArticle, article.categories[0]); 25 | 26 | } else { 27 | parseArticle(article, article.categories[0]); 28 | } 29 | }) 30 | } 31 | 32 | } else { 33 | res.status = 404; 34 | res.locals.pagenoindex = 'yes'; 35 | res.locals.metatitle = '404 Ничего не найдено'; 36 | res.render('./client/error/error'); 37 | } 38 | }); 39 | 40 | function parseArticle(article, relCategory){ 41 | 42 | Articles.addView(article._id, function(){ 43 | 44 | res.locals.article = article; 45 | res.locals.metatitle = article.htmltitle; 46 | res.locals.metadescription = article.htmldescription; 47 | res.locals.metakeywords = article.htmlkeywords; 48 | 49 | Articles.find({_id: {$ne: article._id}, lang: 'default', categories: relCategory}) 50 | .sort({moderated: -1}) 51 | .limit(MyConfig.limits.relArticles) 52 | .exec(function(err, relarticles){ 53 | 54 | if (res.locals.language == 'default'){ 55 | 56 | res.locals.relarticles = relarticles; 57 | res.render('./client/article/article'); 58 | 59 | } else { 60 | 61 | localizeArticles(relarticles, res.locals.language, article, true, function(relArray){ 62 | res.locals.relarticles = relArray; 63 | res.render('./client/article/article'); 64 | }); 65 | } 66 | }); 67 | }); 68 | } 69 | }; 70 | 71 | function localizeArticles(articles, language, mainArticle, needTranslation, callback){ 72 | 73 | var relArray = []; 74 | var counter = 0; 75 | 76 | for (var i=0; i 2 | 3 | <%- include ../common/head %> 4 | 5 | <%- include ../common/header %> 6 | <%- include ../common/error %> 7 | <%- include ./body %> 8 | <%- include ../common/footer %> 9 | 10 | 11 | -------------------------------------------------------------------------------- /templates/admin/articles/body.ejs: -------------------------------------------------------------------------------- 1 |
2 | 5 | 8 |
9 | <% include ./item %> 10 |
11 |
-------------------------------------------------------------------------------- /templates/admin/articles/item.ejs: -------------------------------------------------------------------------------- 1 | <%-include ../modules/dateParser%> 2 | <% articles.forEach(function(item) { %> 3 |
4 |
5 | <% 6 | var img = noimage 7 | if (item.image && (item.image.length > 0)) { 8 | img = item.image 9 | } 10 | var ext = img.substr(img.indexOf('.')) 11 | img = img.substr(0, img.indexOf('.')) 12 | %> 13 | <%=item.title%> 14 |
15 |
<%=item.title%>
16 |
17 | Создана: <%=dateParser(item.created)%> 18 | <% if (item.moderated) { %> 19 | Обновлена: <%=dateParser(item.moderated)%> 20 | <% } %> 21 |
22 |
23 | <% if (item.published) { %> 24 | Опубликована 25 | <% } else { %> 26 | Не опубликована 27 | <% } %> 28 |
29 |
30 | Редактор 31 |
32 |
33 | <% })%> -------------------------------------------------------------------------------- /templates/admin/categories/body.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Добавить категорию +
4 |
5 |
6 |
7 |
    8 | <% categories.forEach(function(item){ 9 | %> 10 | <% include ./item %> 11 | <% }) %> 12 |
13 |
14 |
15 |
-------------------------------------------------------------------------------- /templates/admin/categories/categories.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include ../common/head %> 4 | 5 | <%- include ../common/header %> 6 | <%- include ../common/error %> 7 | <%- include ./body %> 8 | <%- include ../common/footer %> 9 | 10 | 11 | -------------------------------------------------------------------------------- /templates/admin/categories/item.ejs: -------------------------------------------------------------------------------- 1 | <% 2 | var mainClass = "" 3 | var hideMainArticles = false 4 | if (item.ismain) { 5 | mainClass = "ismain" 6 | if (item.articlecount == 0){ 7 | hideMainArticles = true 8 | } 9 | } 10 | %> 11 |
  • 12 | <% 13 | function SpeakRussian(artCount){ 14 | 15 | artCount = artCount.toString(); 16 | var countWord = ""; 17 | var variants = ['статья', 'статьи', 'статей']; 18 | 19 | switch (artCount[artCount.length - 1]){ 20 | case '1': 21 | if ((artCount.length == 2) && (artCount[0] == '1')) { 22 | countWord = variants[2]; 23 | } else { 24 | countWord = variants[0]; 25 | } 26 | break; 27 | case '2': 28 | case '3': 29 | case '4': 30 | if ((artCount.length == 2) && (artCount[0] == '1')) { 31 | countWord = variants[2]; 32 | } else { 33 | countWord = variants[1]; 34 | } 35 | break; 36 | default : 37 | countWord = variants[2]; 38 | } 39 | 40 | return countWord; 41 | 42 | } 43 | 44 | var articlecount = item.articlecount; 45 | if (!articlecount) { 46 | articlecount = 0; 47 | } 48 | 49 | var countWord = SpeakRussian(articlecount); 50 | var names = [countWord, 'Статьи']; 51 | 52 | %> 53 |
    54 | <%=item.title%> 55 | <% 56 | var subtitle = articlecount + " " + names[0] 57 | if (item.ismain) { 58 | subtitle = "Главная страница (" + subtitle + ")" 59 | } %> 60 | <%=subtitle%> 61 |
    62 |
    63 | <% if (!hideMainArticles) { %> 64 | <%=names[1]%> 65 | <% } %> 66 | Редактор 67 |
    68 |
  • -------------------------------------------------------------------------------- /templates/admin/common/error.ejs: -------------------------------------------------------------------------------- 1 |
    2 | Error 3 |
    -------------------------------------------------------------------------------- /templates/admin/common/footer.ejs: -------------------------------------------------------------------------------- 1 |
    2 | <% 3 | var year = new Date(); 4 | year = year.getFullYear(); 5 | %> 6 | #BlondieCode © <%=year%> 7 |
    -------------------------------------------------------------------------------- /templates/admin/common/head.ejs: -------------------------------------------------------------------------------- 1 | 2 | Админ-панель: <%=title%> 3 | 4 | <% 5 | var tstamp = new Date(); 6 | tstamp = tstamp.getMilliseconds(); 7 | %> 8 | <% if (env == 'production') { %> 9 | 10 | <% } else { %> 11 | 12 | <% } %> 13 | 14 | 15 | 16 | 17 | <% if (env == 'production') { %> 18 | <%- include ./minscriptpack %> 19 | <% } else { %> 20 | <%- include ./scriptpack %> 21 | <% } %> 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /templates/admin/common/header.ejs: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    Вход в админ-панель
    4 | <% if (page != "login") { %> 5 |
    Вы вошли как <%=adminname%>
    6 |
    7 | Выход 8 | <%=companyname%> 9 | <% 10 | var catActive = "" 11 | switch (page){ 12 | case "categories": 13 | catActive = "active" 14 | break 15 | } 16 | %> 17 | Разделы 18 |
    19 |
    20 | <% } %> 21 |
    -------------------------------------------------------------------------------- /templates/admin/common/minscriptpack.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/admin/common/scriptpack.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /templates/admin/editarticle/body.ejs: -------------------------------------------------------------------------------- 1 | <%-include ../modules/dateParser%> 2 |
    3 | <% if ((article != 'new') || (lang != 'default')) { %> 4 |
    5 | <% 6 | var cparent = parent; 7 | 8 | if (lang == "default") { 9 | cparent = article._id; 10 | } 11 | %> 12 | <% 13 | languages.forEach(function(langitem){ 14 | 15 | var activelang = ""; 16 | if ((langitem.name == lang) || (langitem.default && (lang == "default"))){ 17 | activelang = "active"; 18 | } 19 | var urlPostfix = "" 20 | if (!langitem.default){ 21 | urlPostfix = "/" + langitem.name 22 | } 23 | %> 24 | <%=langitem.name%> 25 | <% }) %> 26 |
    27 | <% } %> 28 | <% if (lang == "default") { %> 29 |
    30 | 31 |
    32 | <% 33 | var img = noimage 34 | if (article.image && (article.image.length > 0)) { 35 | img = article.image 36 | } else { 37 | var ext = img.substr(img.indexOf('.')) 38 | img = img.substr(0, img.indexOf('.')) 39 | img = img + "-slide" + ext 40 | } 41 | %> 42 | 43 |
    44 |
    45 |
    + Добавить
    46 |
    Удалить
    47 |
    48 | 49 |
    50 |
    51 |
    52 | <% } %> 53 |
    54 |
    55 |
    56 | 57 | 58 |
    59 | <% if (lang == "default") { %> 60 |
    61 | 62 | 63 |
    64 |
    65 | Категории статьи 66 |
    67 | <% categories.forEach(function(category) { 68 | var catActive = '' 69 | if ((category._id == currentcat) || (article.categories && (article.categories.indexOf(category._id) != -1))){ 70 | catActive = 'active' 71 | } 72 | %> 73 |
    <%=category.title%>
    74 | <% }) %> 75 |
    76 |
    77 | <% } %> 78 |
    79 |
    80 |
    81 | 82 | 83 |
    84 |
    85 | 86 | 87 |
    88 |
    89 | 90 | 91 |
    92 |
    93 |
    94 | 95 | 96 |
    97 |
    98 |
    99 | 100 |
    101 | <% if (article.tags && article.tags.length) { 102 | article.tags.forEach(function(tag){ 103 | %> 104 |
    <%=tag%>
    105 | <% })} %> 106 |
    107 |
    108 |
    109 |
    110 |
    111 |
    112 | 113 | 114 |
    115 |
    116 |
    117 | 118 | 119 |
    120 |
    121 | <% if (article != 'new') { %> 122 |
    123 | 124 |
    125 |
    126 |
    <%=article.creator%>
    127 |
    128 | Создана: <%-dateParser(article.created)%> 129 |
    130 |
    131 | <% if (article.moderatedhistory && (article.moderatedhistory.length > 0)) { 132 | article.moderatedhistory.forEach(function(item){ %> 133 |
    134 |
    <%=item.moderator%>
    135 |
    136 | Отмодерирована: <%-dateParser(item.date)%> 137 |
    138 |
    139 | <% }) 140 | } 141 | %> 142 |
    143 |
    144 | <% } %> 145 |
    146 |
    147 | <% if (article == 'new') { %> 148 |
    Сохранить
    149 | <% } else { %> 150 |
    Сохранить
    151 |
    Удалить
    152 | <% } %> 153 | Отмена 154 |
    155 |
    156 |
    157 |
    -------------------------------------------------------------------------------- /templates/admin/editarticle/editarticle.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include ../common/head %> 4 | 5 | <%- include ../common/header %> 6 | <%- include ../common/error %> 7 | <%- include ./body %> 8 | <%- include ../common/footer %> 9 | 10 | 11 | -------------------------------------------------------------------------------- /templates/admin/editcategory/body.ejs: -------------------------------------------------------------------------------- 1 | <%-include ../modules/dateParser%> 2 |
    3 |
    4 | <% 5 | var cparent = parent; 6 | 7 | if (lang == "default") { 8 | cparent = category._id; 9 | } 10 | %> 11 | <% 12 | languages.forEach(function(langitem){ 13 | 14 | var activelang = ""; 15 | if ((langitem.name == lang) || (langitem.default && (lang == "default"))){ 16 | activelang = "active"; 17 | } 18 | var urlPostfix = "" 19 | if (!langitem.default){ 20 | urlPostfix = "/" + langitem.name 21 | } 22 | %> 23 | <%=langitem.name%> 24 | <% }) %> 25 |
    26 |
    27 |
    28 |
    29 | 30 | 31 |
    32 | <% if (lang == "default") { %> 33 |
    34 | 35 | 36 |
    37 | <% } %> 38 |
    39 | 40 | 41 |
    42 |
    43 |
    44 |
    45 | 46 | 47 |
    48 |
    49 | 50 | 51 |
    52 |
    53 | 54 | 55 |
    56 |
    57 |
    58 | <% if ((parent == 'me') && (page == 'editcategory')){ %> 59 |
    60 |
    61 | <% 62 | var ismainclass = "" 63 | if (category.ismain){ 64 | ismainclass = "active" 65 | } 66 | %> 67 | 68 | Главная страница 69 |
    70 |
    71 | <% } %> 72 |
    73 | 74 | 75 |
    76 |
    77 |
    78 | 79 | 80 |
    81 |
    82 |
    83 | 84 |
    85 |
    86 |
    <%=category.creator%>
    87 |
    88 | Создана: <%=dateParser(category.created)%> 89 |
    90 |
    91 | <% if (category.moderatedhistory && (category.moderatedhistory.length > 0)) { 92 | category.moderatedhistory.forEach(function(item){ %> 93 |
    94 |
    <%=item.moderator%>
    95 |
    96 | Отмодерирована: <%=dateParser(item.date)%> 97 |
    98 |
    99 | <% }) 100 | } 101 | %> 102 |
    103 |
    104 |
    105 |
    106 |
    Сохранить
    107 | <% if (adminrights == "superadmin") { %> 108 |
    Удалить
    109 | <% } %> 110 | Отмена 111 |
    112 |
    113 |
    114 | -------------------------------------------------------------------------------- /templates/admin/editcategory/editcategory.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include ../common/head %> 4 | 5 | <%- include ../common/header %> 6 | <%- include ../common/error %> 7 | <%- include ./body %> 8 | <%- include ../common/footer %> 9 | 10 | 11 | -------------------------------------------------------------------------------- /templates/admin/login/body.ejs: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 16 |
    17 |
    -------------------------------------------------------------------------------- /templates/admin/login/login.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include ../common/head %> 4 | 5 | <%- include ../common/header %> 6 | <%- include ../common/error %> 7 | <%- include ./body %> 8 | <%- include ../common/footer %> 9 | 10 | 11 | -------------------------------------------------------------------------------- /templates/admin/modules/dateParser.ejs: -------------------------------------------------------------------------------- 1 | <% dateParser = function(date) { 2 | var startDate = new Date(date); 3 | var startDay = (startDate.getDate() < 10 ? '0' + startDate.getDate() : startDate.getDate()); 4 | var startMonth = '' 5 | var weekDay = '' 6 | switch (startDate.getMonth()){ 7 | case 0: 8 | startMonth = 'Янв' 9 | break; 10 | case 1: 11 | startMonth = 'Фев' 12 | break; 13 | case 2: 14 | startMonth = 'Март' 15 | break; 16 | case 3: 17 | startMonth = 'Апр' 18 | break; 19 | case 4: 20 | startMonth = 'Май' 21 | break; 22 | case 5: 23 | startMonth = 'Июнь' 24 | break; 25 | case 6: 26 | startMonth = 'Июль' 27 | break; 28 | case 7: 29 | startMonth = 'Авг' 30 | break; 31 | case 8: 32 | startMonth = 'Сен' 33 | break; 34 | case 9: 35 | startMonth = 'Окт' 36 | break; 37 | case 10: 38 | startMonth = 'Нояб' 39 | break; 40 | case 11: 41 | startMonth = 'Дек' 42 | break; 43 | } 44 | 45 | switch (startDate.getDay()){ 46 | case 0: 47 | weekDay = 'Вс'; 48 | break; 49 | case 1: 50 | weekDay = 'Пн'; 51 | break; 52 | case 2: 53 | weekDay = 'Вт'; 54 | break; 55 | case 3: 56 | weekDay = 'Ср'; 57 | break; 58 | case 4: 59 | weekDay = 'Чт'; 60 | break; 61 | case 5: 62 | weekDay = 'Пт'; 63 | break; 64 | case 6: 65 | weekDay = 'Сб'; 66 | break; 67 | } 68 | startDate = weekDay + ', ' + startDay + '. ' + startMonth + ' ' + startDate.getFullYear() + ', в ' + startDate.getHours() + ':' + startDate.getMinutes() 69 | return startDate; 70 | } 71 | %> -------------------------------------------------------------------------------- /templates/client/article/article.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include ../common/head %> 4 | 5 |
    6 | <%- include ../common/header %> 7 | <%- include ./body %> 8 | <%- include ../common/footer %> 9 | <%- include ../common/scrolltop%> 10 |
    11 | <%- include ../common/scriptpack %> 12 | 13 | -------------------------------------------------------------------------------- /templates/client/article/body.ejs: -------------------------------------------------------------------------------- 1 | <%-include ../modules/dateParser%> 2 |
    3 |
    4 |
    5 | <% if (article.image && (article.image.length > 0)){%> 6 | <%=article.title%> 7 | <% } %> 8 |
    9 |
    10 | <%=locals.navigations.published[language]%><%=dateParser(article.moderated)%> 11 | <%=locals.navigations.views[language]%><%=article.views%> 12 |
    13 |

    <%=article.title%>

    14 | <%-article.description%> 15 |
    16 |
    17 | 23 |
    24 |
    25 | -------------------------------------------------------------------------------- /templates/client/category/body.ejs: -------------------------------------------------------------------------------- 1 |
    2 |

    <%=category.title%>

    3 | <%-category.description%> 4 |
    5 | <%-include ../modules/articleItemArray%> 6 |
    7 | <% if (showMore){ %> 8 | <%-include ../modules/moreButton%> 9 | <% } %> 10 |
    11 | -------------------------------------------------------------------------------- /templates/client/category/category.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include ../common/head %> 4 | 5 |
    6 | <%- include ../common/header %> 7 | <%- include ./body %> 8 | <%- include ../common/footer %> 9 | <%- include ../common/scrolltop%> 10 |
    11 | <%- include ../common/scriptpack %> 12 | 13 | -------------------------------------------------------------------------------- /templates/client/common/footer.ejs: -------------------------------------------------------------------------------- 1 |
    2 | 10 | #BlondieCode © <%=fullyear%> 11 |
    -------------------------------------------------------------------------------- /templates/client/common/head.ejs: -------------------------------------------------------------------------------- 1 | 2 | <%=metatitle%> 3 | 4 | 5 | 6 | 7 | 8 | <% if (pagenoindex == 'yes') { %> 9 | 10 | <% } else { %> 11 | 12 | <% } %> 13 | 14 | <% languages.forEach(function(lang){ 15 | var langUrl = "" 16 | if (!lang.default){ 17 | langUrl = "?language=" + lang.name 18 | } 19 | %> 20 | 21 | <% }) %> 22 | <% if (env == 'production') { %> 23 | 24 | <% } else { %> 25 | 26 | <% } %> 27 | -------------------------------------------------------------------------------- /templates/client/common/header.ejs: -------------------------------------------------------------------------------- 1 |
    2 | 3 | <%=companyname%> 4 | 5 |
    6 | 26 | 44 |
    45 |
    -------------------------------------------------------------------------------- /templates/client/common/scriptpack.ejs: -------------------------------------------------------------------------------- 1 | 2 | <% if (env == 'production') { %> 3 | 4 | <% } else { %> 5 | 6 | <% } %> 7 | -------------------------------------------------------------------------------- /templates/client/common/scrolltop.ejs: -------------------------------------------------------------------------------- 1 |
    -------------------------------------------------------------------------------- /templates/client/error/body.ejs: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    404

    4 |

    5 | <%=locals.messages.page404.text[language]%> 6 |

    7 |
    8 |
    -------------------------------------------------------------------------------- /templates/client/error/error.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include ../common/head %> 4 | 5 |
    6 | <%- include ../common/header %> 7 | <%- include ./body %> 8 | <%- include ../common/footer %> 9 | <%- include ../common/scrolltop%> 10 |
    11 | <%- include ../common/scriptpack %> 12 | 13 | -------------------------------------------------------------------------------- /templates/client/modules/articleItem.ejs: -------------------------------------------------------------------------------- 1 | <%-include ./dateParser%> 2 |
    3 | 4 | <% var image = "/images/noimage.jpg" 5 | if (article.image){ 6 | image = article.image 7 | } 8 | image = image.substr(0, image.lastIndexOf('.')) + "-slide" + 9 | image.substr(image.lastIndexOf('.')) 10 | %> 11 | <%=article.title%> 12 |
    13 |

    <%=article.title%>

    14 | <% var shortdescription = article.shortdescription 15 | if (shortdescription.length > 170){ 16 | shortdescription = shortdescription.substr(0, 170) + "..." 17 | } 18 | %> 19 |

    <%=shortdescription%>

    20 |
    21 |
    22 | <%=article.views%> 23 | <%=dateParser(article.moderated)%> 24 |
    25 |
    26 |
    27 | -------------------------------------------------------------------------------- /templates/client/modules/articleItemArray.ejs: -------------------------------------------------------------------------------- 1 | <% articles.forEach(function(article) { %> 2 | <%-include ../modules/articleItem%> 3 | <% }) %> -------------------------------------------------------------------------------- /templates/client/modules/dateParser.ejs: -------------------------------------------------------------------------------- 1 | <% dateParser = function(date) { 2 | var startDate = new Date(date); 3 | var startDay = (startDate.getDate() < 10 ? '0' + startDate.getDate() : startDate.getDate()); 4 | var startYear = startDate.getFullYear() 5 | var startMonth = '' 6 | var weekDay = '' 7 | switch (startDate.getMonth()){ 8 | case 0: 9 | startMonth = locals.month[language][0] 10 | break; 11 | case 1: 12 | startMonth = locals.month[language][1] 13 | break; 14 | case 2: 15 | startMonth = locals.month[language][2] 16 | break; 17 | case 3: 18 | startMonth = locals.month[language][3] 19 | break; 20 | case 4: 21 | startMonth = locals.month[language][4] 22 | break; 23 | case 5: 24 | startMonth = locals.month[language][5] 25 | break; 26 | case 6: 27 | startMonth = locals.month[language][6] 28 | break; 29 | case 7: 30 | startMonth = locals.month[language][7] 31 | break; 32 | case 8: 33 | startMonth = locals.month[language][8] 34 | break; 35 | case 9: 36 | startMonth = locals.month[language][9] 37 | break; 38 | case 10: 39 | startMonth = locals.month[language][10] 40 | break; 41 | case 11: 42 | startMonth = locals.month[language][11] 43 | break; 44 | } 45 | 46 | switch (startDate.getDay()){ 47 | case 0: 48 | weekDay = locals.days[language][0]; 49 | break; 50 | case 1: 51 | weekDay = locals.days[language][1]; 52 | break; 53 | case 2: 54 | weekDay = locals.days[language][2]; 55 | break; 56 | case 3: 57 | weekDay = locals.days[language][3]; 58 | break; 59 | case 4: 60 | weekDay = locals.days[language][4]; 61 | break; 62 | case 5: 63 | weekDay = locals.days[language][5]; 64 | break; 65 | case 6: 66 | weekDay = locals.days[language][6]; 67 | break; 68 | } 69 | startDate = weekDay + ', ' + startDay + '. ' + startMonth + ' ' + startYear; 70 | return startDate; 71 | } 72 | %> -------------------------------------------------------------------------------- /templates/client/modules/moreButton.ejs: -------------------------------------------------------------------------------- 1 | <% if (page == 'category') { %> 2 |
    <%=locals.navigations.more[language]%>
    3 | <% } else if (page == 'tags') { %> 4 |
    <%=locals.navigations.more[language]%>
    5 | <% } %> -------------------------------------------------------------------------------- /templates/client/modules/sideArticleItem.ejs: -------------------------------------------------------------------------------- 1 | 2 | <% var image = "/images/noimage.jpg" 3 | if (article.image){ 4 | image = article.image 5 | } 6 | image = image.substr(0, image.lastIndexOf('.')) + "-min" + 7 | image.substr(image.lastIndexOf('.')) 8 | %> 9 | <%=article.title%> 10 |
    11 |

    <%=article.title%>

    12 | <% var shortdescription = article.shortdescription 13 | if (shortdescription.length > 100){ 14 | shortdescription = shortdescription.substr(0, 100) + "..." 15 | } 16 | %> 17 |

    <%=shortdescription%>

    18 |
    19 |
    20 | -------------------------------------------------------------------------------- /templates/client/modules/tags.ejs: -------------------------------------------------------------------------------- 1 | <% if (article.tags && (article.tags.length > 0)) { %> 2 |
    3 | <% article.tags.forEach(function(tag){ %> 4 | #<%=tag%> 5 | <% }) %> 6 |
    7 | <% } %> 8 | -------------------------------------------------------------------------------- /templates/client/tag/body.ejs: -------------------------------------------------------------------------------- 1 |
    2 | <% if (notfound) { %> 3 |
    4 |

    <%=locals.messages.notfound.title[language]%><%=tag%>

    5 |

    <%=locals.messages.notfound.text[language]%>

    6 |
    7 |
    8 | <% } else { %> 9 |

    <%=locals.messages.found[language]%><%=tag%>

    10 |
    11 | <% articles.forEach(function(article) { %> 12 | <%-include ../modules/articleItem%> 13 | <% }) %> 14 |
    15 | <% if (showMore){ %> 16 | <%-include ../modules/moreButton%> 17 | <% } %> 18 | <% } %> 19 |
    -------------------------------------------------------------------------------- /templates/client/tag/tag.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include ../common/head %> 4 | 5 |
    6 | <%- include ../common/header %> 7 | <%- include ./body %> 8 | <%- include ../common/footer %> 9 | <%- include ../common/scrolltop%> 10 |
    11 | <%- include ../common/scriptpack %> 12 | 13 | --------------------------------------------------------------------------------