├── .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('

');
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('');
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 + '' + type + '>');
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 + '' + type + '>');
205 | } else {
206 | href = $(this).after('<' + type + ' href="' + cclass + '" rel="nofollow" target="_blank">' + str + '' + type + '>');
207 | }
208 |
209 | if (href.parent().is('a')){
210 | href.unwrap();
211 | }
212 | }
213 |
214 | } else {
215 |
216 | var h = $(this).after('<' + type + '>' + str + '' + type + '>');
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 0)){
151 |
152 | if (articles.length == MyConfig.limits.pageArticles){
153 |
154 | var lastDate = articles[articles.length - 1].moderated;
155 | params.moderated = {$lt: lastDate};
156 | Articles.findOne(params, function(err, nextArticle){
157 |
158 | if (nextArticle){
159 | isMore = true;
160 | last = lastDate;
161 | } else {
162 | isMore = false;
163 | }
164 | getResLocals();
165 | })
166 | } else {
167 | getResLocals();
168 | }
169 |
170 | function getResLocals(){
171 |
172 | localizeArticles(articles, lang, mainArticle, needTranslation, function(localizedArticles){
173 | callback(localizedArticles, isMore, last, notFound);
174 | });
175 | }
176 |
177 | } else {
178 | notFound = true;
179 | callback(articleArray, isMore, last, notFound);
180 | }
181 | });
182 | };
183 |
--------------------------------------------------------------------------------
/routes/client/category.js:
--------------------------------------------------------------------------------
1 |
2 | var log = require('../../libs/log')(module);
3 | var async = require('async');
4 | var Categories = require('../../models/categories').Category;
5 | var Localizer = require('./article');
6 |
7 | exports.get = function(req, res){
8 |
9 | var language = res.locals.language;
10 | var isMain = false;
11 |
12 | var categorySearchParams = {};
13 | var articleSearchParams = {};
14 |
15 | if (req.params.alias){
16 | categorySearchParams.alias = req.params.alias;
17 | } else {
18 | categorySearchParams.ismain = true;
19 | isMain = true;
20 | }
21 |
22 | articleSearchParams.published = true;
23 | articleSearchParams.lang = 'default';
24 | if (language != 'default'){
25 | articleSearchParams.locales = language;
26 | }
27 |
28 | res.locals.showMore = false;
29 | res.locals.page = 'category';
30 |
31 | async.parallel([
32 | getCategory,
33 | getArticles
34 | ], function(err){
35 | if (err){
36 | log.info('------ Ошибка: ' + err);
37 | res.status = 404;
38 | res.locals.pagenoindex = 'yes';
39 | res.locals.metatitle = '404 Ничего не найдено';
40 | res.render('./client/error/error');
41 | } else {
42 | res.render('./client/category/category');
43 | }
44 | });
45 |
46 | function getCategory(callback){
47 |
48 | Categories.findOne(categorySearchParams, function(err, category){
49 |
50 | if (category && ((category.ismain && isMain) || (!category.ismain && !isMain))){
51 |
52 | if (language == "default"){
53 |
54 | setLocalCategory(category, category, function(){
55 | callback(null);
56 | });
57 |
58 | } else {
59 |
60 | Categories.findOne({parent: category._id, lang: language}, function(err, localCategory){
61 |
62 | if (localCategory){
63 |
64 | setLocalCategory(localCategory, category, function(){
65 | callback(null);
66 | });
67 |
68 | } else {
69 |
70 | setLocalCategory(category, category, function(){
71 | callback(null);
72 | });
73 | }
74 | })
75 | }
76 |
77 | } else {
78 |
79 | callback("Нет такой категории");
80 | }
81 | })
82 | }
83 |
84 | function setLocalCategory(category, parent, callback){
85 |
86 | res.locals.category = category;
87 | res.locals.metatitle = category.htmltitle;
88 | res.locals.metadescription = category.htmldescription;
89 | res.locals.metakeywords = category.htmlkeywords;
90 |
91 | if (!isMain){
92 |
93 | articleSearchParams.categories = parent._id.toString();
94 |
95 | Localizer.findArticles(articleSearchParams, res.locals.language, null, true, function(articles, isMore, last, notFound){
96 |
97 | if (isMore){
98 | res.locals.showMore = true;
99 | res.locals.from = last;
100 | }
101 | res.locals.articles = articles;
102 | callback();
103 | });
104 | } else {
105 | callback();
106 | }
107 | }
108 |
109 | function getArticles(callback){
110 |
111 | if (isMain){
112 | Localizer.findArticles(articleSearchParams, res.locals.language, null, true, function(articles, isMore, last, notFound){
113 |
114 | if (isMore){
115 | res.locals.showMore = true;
116 | res.locals.from = last;
117 | }
118 | res.locals.articles = articles;
119 | callback(null);
120 | });
121 | } else {
122 | callback(null);
123 | }
124 | }
125 |
126 | };
127 |
128 | exports.post = function(req, res){
129 |
130 | if (req.body.action == 'more'){
131 |
132 | var params = {};
133 | if (req.body.ismain != 'true'){
134 | params.categories = req.body.category;
135 | }
136 | if (req.body.language != 'default'){
137 | params.locales = req.body.language;
138 | } else {
139 | params.lang = 'default';
140 | }
141 | var date = new Date(req.body.last);
142 | params.moderated = {$lt: date};
143 | params.published = true;
144 |
145 | Localizer.findArticles(params, req.body.language, null, true, function(articles, isMore, last, notFound){
146 |
147 | res.render('./client/modules/articleItemArray', {articles: articles}, function(err, html){
148 | var data = {};
149 | if (isMore){
150 | data.last = last;
151 | }
152 | if (html){
153 | data.html = html;
154 | } else {
155 | data.html = '';
156 | }
157 | res.send(data);
158 | });
159 | });
160 | }
161 | };
162 |
--------------------------------------------------------------------------------
/routes/client/tag.js:
--------------------------------------------------------------------------------
1 |
2 | var log = require('../../libs/log')(module);
3 | var Localizer = require('./article');
4 |
5 | exports.get = function(req, res){
6 |
7 | res.locals.pagenoindex = 'yes';
8 | res.locals.tag = req.params.tag;
9 | res.locals.notfound = false;
10 | res.locals.showMore = false;
11 | res.locals.page = 'tags';
12 |
13 | var params = {};
14 | params.tags = req.params.tag;
15 | params.published = true;
16 |
17 | Localizer.findArticles(params, 'default', null, false, function(articles, isMore, last, notFound){
18 |
19 | res.locals.articles = articles;
20 |
21 | if (isMore){
22 | res.locals.showMore = true;
23 | res.locals.from = last;
24 | }
25 |
26 | if (notFound){
27 | res.locals.notfound = true;
28 | }
29 |
30 | res.render('./client/tag/tag');
31 | });
32 |
33 | };
34 |
35 | exports.post = function(req, res){
36 |
37 | if (req.body.action == 'more'){
38 |
39 | var params = {};
40 | params.tags = req.body.tag;
41 |
42 | var date = new Date(req.body.last);
43 | params.moderated = {$lt: date};
44 | params.published = true;
45 |
46 | Localizer.findArticles(params, 'default', null, false, function(articles, isMore, last, notFound){
47 |
48 | res.render('./client/modules/articleItemArray', {articles: articles}, function(err, html){
49 | var data = {};
50 | if (isMore){
51 | data.last = last;
52 | }
53 | if (html){
54 | data.html = html;
55 | } else {
56 | data.html = '';
57 | }
58 | res.send(data);
59 | });
60 | });
61 | }
62 | };
63 |
--------------------------------------------------------------------------------
/routes/index.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = function(app){
3 |
4 | //=====================================================//
5 | //************ Admin ************//
6 | //=====================================================//
7 |
8 | var startAdminRoute = require('./admin/admin');
9 | app.get('/admin', startAdminRoute.get);
10 | var loginRoute = require('./admin/login');
11 | app.post('/login', loginRoute.post);
12 | var logoutRoute = require('./admin/logout');
13 | app.get('/admin/logout', logoutRoute.get);
14 |
15 | //====================== Categories ==========================//
16 | var categoriesListRoute = require('./admin/categories');
17 | app.get('/admin/categories', categoriesListRoute.get);
18 |
19 | var editCategoryRoute = require('./admin/editcategory');
20 | app.post('/admin/categories', editCategoryRoute.post);
21 |
22 | app.get('/admin/editcategory/:id', editCategoryRoute.get);
23 | app.get('/admin/editcategory/:id/:lang', editCategoryRoute.get);
24 |
25 | //====================== Articles ==========================//
26 | var articlesListRoute = require('./admin/articles');
27 | app.get('/admin/articles/:id', articlesListRoute.get);
28 | app.post('/admin/articles', articlesListRoute.post);
29 |
30 | var editArticleRoute = require('./admin/editarticle');
31 | app.get('/admin/article/:id/:label', editArticleRoute.get);
32 | app.get('/admin/article/:id/:label/:lang', editArticleRoute.get);
33 | app.post('/admin/article', editArticleRoute.post);
34 |
35 | //=====================================================//
36 | //************ Client ************//
37 | //=====================================================//
38 |
39 | //====================== Categories ==========================//
40 | var categoryRoute = require('./client/category');
41 | app.get('/', categoryRoute.get);
42 | app.get('/:alias', categoryRoute.get);
43 |
44 | app.post('/', categoryRoute.post);
45 |
46 | //====================== Articles ==========================//
47 | var articleRoute = require('./client/article');
48 | app.get('/article/:alias', articleRoute.get);
49 |
50 | //====================== Tags ==========================//
51 | var tagRoute = require('./client/tag');
52 | app.get('/articles/:tag', tagRoute.get);
53 | app.post('/tag', tagRoute.post);
54 |
55 | };
56 |
--------------------------------------------------------------------------------
/templates/admin/articles/articles.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/articles/body.ejs:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
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 |

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 |
32 |
33 | <% })%>
--------------------------------------------------------------------------------
/templates/admin/categories/body.ejs:
--------------------------------------------------------------------------------
1 |
2 |
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 |
68 |
--------------------------------------------------------------------------------
/templates/admin/common/error.ejs:
--------------------------------------------------------------------------------
1 |
2 | Error
3 |
--------------------------------------------------------------------------------
/templates/admin/common/footer.ejs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
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 |
77 | <% } %>
78 |
79 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
<%-article.description%>
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 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | <% if (lang == "default") { %>
33 |
34 |
35 |
36 |
37 | <% } %>
38 |
39 |
40 |
41 |
42 |
43 |
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 |
<%-category.description%>
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 |
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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
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 |
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 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------