Skip to content

Commit d64a931

Browse files
committed
WIP
1 parent a6b7f30 commit d64a931

22 files changed

Lines changed: 318 additions & 234 deletions

File tree

handlers/multipartParser.js

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
const PathListCheck = require('pathListCheck');
22
const multiparty = require('multiparty');
3-
const thunkify = require('thunkify');
43

54
var log = require('log')();
65

@@ -9,48 +8,50 @@ function MultipartParser() {
98
}
109

1110

12-
MultipartParser.prototype.parse = thunkify(function(req, callback) {
11+
MultipartParser.prototype.parse = function(req) {
1312

14-
var form = new multiparty.Form();
13+
return new Promise((resolve, reject) => {
14+
var form = new multiparty.Form();
1515

16-
var hadError = false;
17-
var fields = {};
16+
var hadError = false;
17+
var fields = {};
1818

19-
form.on('field', function(name, value) {
20-
fields[name] = value;
21-
});
19+
form.on('field', function(name, value) {
20+
fields[name] = value;
21+
});
2222

23-
// multipart file must be the last
24-
form.on('part', function(part) {
25-
if (part.filename !== null) {
26-
// error is made the same way as multiparty uses
27-
callback(createError(400, 'Files are not allowed here'));
28-
} else {
29-
throw new Error("Must never reach this line (field event parses all fields)");
30-
}
31-
part.on('error', onError);
32-
});
23+
// multipart file must be the last
24+
form.on('part', function(part) {
25+
if (part.filename !== null) {
26+
// error is made the same way as multiparty uses
27+
callback(createError(400, 'Files are not allowed here'));
28+
} else {
29+
throw new Error("Must never reach this line (field event parses all fields)");
30+
}
31+
part.on('error', onError);
32+
});
3333

34-
form.on('error', onError);
34+
form.on('error', onError);
3535

36-
form.on('close', onDone);
36+
form.on('close', onDone);
3737

38-
form.parse(req);
38+
form.parse(req);
3939

40-
function onDone() {
41-
log.debug("multipart parse done", fields);
42-
if (hadError) return;
43-
callback(null, fields);
44-
}
40+
function onDone() {
41+
log.debug("multipart parse done", fields);
42+
if (hadError) return;
43+
resolve(fields);
44+
}
4545

46-
function onError(err) {
47-
log.debug("multipart error", err);
48-
if (hadError) return;
49-
hadError = true;
50-
callback(err);
51-
}
46+
function onError(err) {
47+
log.debug("multipart error", err);
48+
if (hadError) return;
49+
hadError = true;
50+
reject(err);
51+
}
52+
});
5253

53-
});
54+
};
5455

5556

5657
MultipartParser.prototype.middleware = function() {

handlers/render.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const log = require('log')();
99
const jade = require('lib/serverJade');
1010
const assert = require('assert');
1111
const t = require('i18n');
12-
const money = require('money');
12+
1313
const url = require('url');
1414
const validate = require('validate');
1515
const pluralize = require('textUtil/pluralize');

handlers/tutorial/controller/map.js

Lines changed: 19 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,87 +2,38 @@
22

33
const Article = require('../models/article');
44
const Task = require('../models/task');
5+
const TutorialTree = require('../models/tutorialTree');
56
const ArticleRenderer = require('../renderer/articleRenderer');
67
const TaskRenderer = require('../renderer/taskRenderer');
78
const _ = require('lodash');
8-
const CacheEntry = require('cache').CacheEntry;
99
const makeAnchor = require('textUtil/makeAnchor');
1010

11-
exports.get = function *get(next) {
11+
const localStorage = require('localStorage');
1212

13-
var renderedMap = yield CacheEntry.getOrGenerate({
14-
key: 'map:rendered',
15-
tags: ['article']
16-
}, renderMap);
13+
const t = require('i18n');
1714

18-
var locals = {
19-
children: renderedMap
20-
};
15+
const LANG = require('config').lang;
2116

22-
var template = this.get('X-Requested-With') ? '_map' : 'map';
23-
24-
this.body = this.render(template, locals);
25-
};
26-
27-
// body
28-
// metadata
29-
// modified
30-
// title
31-
// isFolder
32-
// prev
33-
// next
34-
// path
35-
// siblings
36-
function* renderMap() {
17+
t.requirePhrase('tutorial.map', require('../locales/map/' + LANG + '.yml'));
3718

38-
const tree = yield* Article.findTree();
19+
exports.get = function* get() {
3920

40-
function* renderTree(tree) {
41-
var children = [];
21+
const tutorialTree = TutorialTree.instance();
4222

43-
for (var i = 0; i < tree.children.length; i++) {
44-
var child = tree.children[i];
45-
46-
var childRendered = {
47-
url: Article.getUrlBySlug(child.slug),
48-
title: child.title
49-
};
23+
var template = this.get('X-Requested-With') ? '_map' : 'map';
5024

51-
if (child.isFolder) {
52-
childRendered.children = yield* renderTree(child);
25+
this.body = this.render(template, {
26+
bySlug: tutorialTree.bySlug.bind(tutorialTree),
27+
roots: [
28+
treeRendered[0],
29+
treeRendered[1],
30+
{
31+
title: t('site.additional_articles'),
32+
children: treeRendered.slice(2)
5333
}
34+
]
35+
});
5436

55-
var tasks = yield Task.find({
56-
parent: child._id
57-
}).sort({weight: 1}).select('-_id slug title importance').lean();
58-
59-
tasks = tasks.map(function(task) {
60-
task.url = Task.getUrlBySlug(task.slug);
61-
delete task.slug;
62-
task.anchor = childRendered.url + '#' + makeAnchor(task.title);
63-
return task;
64-
});
65-
66-
childRendered.tasks = tasks;
67-
children.push(childRendered);
68-
69-
}
70-
return children;
71-
}
72-
73-
var treeRendered = yield* renderTree(tree);
74-
75-
76-
return [
77-
treeRendered[0],
78-
treeRendered[1],
79-
{
80-
url: '#',
81-
title: 'Дополнительно',
82-
children: treeRendered.slice(2)
83-
}
84-
];
85-
86-
}
37+
};
8738

8839

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
'use strict';
22

3-
const Plunk = require('plunk').Plunk;
3+
const TutorialView = require('../models/tutorialView');
44

55
exports.get = function*() {
6-
var plunk = yield Plunk.findOne({ plunkId: this.query.plunkId });
6+
let view;
77

8-
if (!plunk) {
9-
this.throw(404);
8+
for(let webpath in TutorialView.storage) {
9+
view = TutorialView.storage[webpath];
10+
if (view.plunkId == this.query.plunkId) {
11+
this.set('Content-Type', 'application/zip');
12+
this.body = view.getZip();
13+
}
1014
}
1115

12-
this.set('Content-Type', 'application/zip');
13-
this.body = plunk.getZip();
14-
1516
};

handlers/tutorial/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ exports.init = function(app) {
1616

1717
exports.Article = require('./models/article');
1818
exports.Task = require('./models/task');
19+
exports.TutorialTree = require('./models/tutorialTree');
20+
exports.TutorialView = require('./models/tutorialView');
1921

2022
exports.TaskRenderer = require('./renderer/taskRenderer');
2123
exports.ArticleRenderer = require('./renderer/articleRenderer');

handlers/tutorial/lib/tutorialImporter.js

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,16 @@ const TutorialView = require('../models/tutorialView');
1818
const TutorialParser = require('./tutorialParser');
1919
const stripTitle = require('markit').stripTitle;
2020
const stripYamlMetadata = require('markit').stripYamlMetadata;
21+
const mime = require('mime');
22+
const stripIndents = require('textUtil/stripIndents');
2123

2224

25+
const t = require('i18n');
26+
27+
const LANG = require('config').lang;
28+
29+
t.requirePhrase('tutorial.importer', require('../locales/importer/' + LANG + '.yml'));
30+
2331
module.exports = class TutorialImporter {
2432
constructor(options) {
2533
this.root = fs.realpathSync(options.root);
@@ -366,7 +374,7 @@ module.exports = class TutorialImporter {
366374
log.debug("Created new plunk (db empty)", view);
367375
}
368376

369-
let filesForPlunk = plunkReadFs(dir);
377+
let filesForPlunk = readFs(dir);
370378
log.debug("Files for plunk", filesForPlunk);
371379

372380
if (!filesForPlunk) return; // had errors
@@ -421,7 +429,7 @@ module.exports = class TutorialImporter {
421429
webPath: sourceWebPath,
422430
description: "Fork from https://" + config.domain.main
423431
});
424-
TutorialView.storage[webbPath] = sourceView;
432+
TutorialView.storage[sourceWebPath] = sourceView;
425433
}
426434

427435
let sourceFilesForView = {
@@ -435,15 +443,15 @@ module.exports = class TutorialImporter {
435443
}
436444
};
437445

438-
log.debug("save plunk for ", webPath);
446+
log.debug("save plunk for ", sourceWebPath);
439447
await sourceView.mergeAndSyncPlunk(sourceFilesForView, this.plunkerToken);
440448

441449
// Solution
442450
let solutionWebPath = task.getResourceWebRoot() + '/solution';
443451

444452
let solution = makeSolution(solutionJs, testJs);
445453

446-
let solutionView = TutorialView.storage[webPath];
454+
let solutionView = TutorialView.storage[solutionWebPath];
447455

448456
if (!solutionView) {
449457
solutionView = new TutorialView({
@@ -544,4 +552,68 @@ function copySync(srcPath, dstPath) {
544552

545553
fse.copySync(srcPath, dstPath);
546554
}
555+
556+
557+
function readFs(dir) {
558+
559+
var files = fs.readdirSync(dir);
560+
561+
var hadErrors = false;
562+
files = files.filter(function(file) {
563+
if (file[0] == ".") return false;
564+
565+
var filePath = path.join(dir, file);
566+
if (fs.statSync(filePath).isDirectory()) {
567+
log.error("Directory not allowed: " + file);
568+
hadErrors = true;
569+
}
570+
571+
var type = mime.lookup(file).split('/');
572+
if (type[0] != 'text' && type[1] != 'json' && type[1] != 'javascript' && type[1] != 'svg+xml') {
573+
log.error("Bad file extension: " + file);
574+
hadErrors = true;
575+
}
576+
577+
return true;
578+
});
579+
580+
if (hadErrors) {
581+
return null;
582+
}
583+
584+
files = files.sort(function(fileA, fileB) {
585+
var extA = fileA.slice(fileA.lastIndexOf('.') + 1);
586+
var extB = fileB.slice(fileB.lastIndexOf('.') + 1);
587+
588+
if (extA == extB) {
589+
return fileA > fileB ? 1 : -1;
590+
}
591+
592+
// html always first
593+
if (extA == 'html') return 1;
594+
if (extB == 'html') return -1;
595+
596+
// then goes CSS
597+
if (extA == 'css') return 1;
598+
if (extB == 'css') return -1;
599+
600+
// then JS
601+
if (extA == 'js') return 1;
602+
if (extB == 'js') return -1;
603+
604+
// then other extensions
605+
return fileA > fileB ? 1 : -1;
606+
});
607+
608+
var filesForPlunk = {};
609+
for (var i = 0; i < files.length; i++) {
610+
var file = files[i];
611+
filesForPlunk[file] = {
612+
filename: file,
613+
content: stripIndents(fs.readFileSync(path.join(dir, file), 'utf-8'))
614+
};
615+
}
616+
617+
return filesForPlunk;
618+
}
547619

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
filter_by_title: Filter by title
3+
show_tasks: Show tasks
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
filter_by_title: Фильтр по заголовку
3+
show_tasks: Показать задачи

handlers/tutorial/models/task.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ module.exports = class Task extends TutorialEntry {
2626
}
2727
});
2828

29-
3029
}
30+
3131
};

handlers/tutorial/models/tutorialTree.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ module.exports = class TutorialTree {
5353
i++;
5454
}
5555
if (i === parentChildren.length) {
56-
parentChildren.push(entry);
56+
parentChildren.push(entry.slug);
5757
} else {
58-
parentChildren.splice(i, 0, entry);
58+
parentChildren.splice(i, 0, entry.slug);
5959
}
6060
}
6161

0 commit comments

Comments
 (0)