├── user-bootstrap
├── .gitignore
├── views
│ ├── README.md
│ ├── clean.html
│ └── index.html
├── assets
│ └── css
│ │ └── overrides.css
├── data
│ └── bootstrap
│ │ ├── fonts
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.ttf
│ │ ├── glyphicons-halflings-regular.woff
│ │ └── glyphicons-halflings-regular.svg
│ │ └── js
│ │ └── bootstrap.min.js
└── options.js
├── user-source-ok-demo
├── .gitignore
├── assets
│ └── css
│ │ ├── user.css
│ │ └── hacks.css
├── views
│ ├── README.md
│ ├── clean.html
│ ├── index.html
│ ├── lego-layouts.html
│ └── lego-layer.html
├── README.md
├── core
│ └── routes
│ │ └── index.js
└── options.js
├── assets
├── i
│ ├── close.png
│ ├── close_h.png
│ ├── layout_1col.png
│ ├── layout_2col.png
│ ├── layout_3col.png
│ └── layout_default.png
├── js
│ ├── global.js
│ ├── virtualblock.js
│ ├── client-templates.js
│ ├── components.js
│ ├── search.js
│ ├── main.js
│ └── modifiers.js
└── css
│ └── lego.css
├── .gitignore
├── core
├── routes
│ ├── index.js
│ └── redirects.js
├── getView.js
├── loadOptions.js
└── css-mod
│ └── index.js
├── views
├── clean.html
├── index.html
├── lego-layer.html
├── lego-layouts.html
├── client-templates.html
└── lego-сontainer.html
├── options.js
├── parser
├── package.json
└── bootstrap.js
├── package.json
├── README.md
└── lego.js
/user-bootstrap/.gitignore:
--------------------------------------------------------------------------------
1 | /saved
--------------------------------------------------------------------------------
/user-source-ok-demo/.gitignore:
--------------------------------------------------------------------------------
1 | /saved
--------------------------------------------------------------------------------
/user-source-ok-demo/assets/css/user.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/user-bootstrap/views/README.md:
--------------------------------------------------------------------------------
1 | Overrides `public/views`
--------------------------------------------------------------------------------
/user-source-ok-demo/views/README.md:
--------------------------------------------------------------------------------
1 | Overrides `public/views`
--------------------------------------------------------------------------------
/user-bootstrap/assets/css/overrides.css:
--------------------------------------------------------------------------------
1 | .lego_content_w {
2 | margin-top: 0;
3 | }
--------------------------------------------------------------------------------
/assets/i/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sourcejs/lego/HEAD/assets/i/close.png
--------------------------------------------------------------------------------
/assets/i/close_h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sourcejs/lego/HEAD/assets/i/close_h.png
--------------------------------------------------------------------------------
/assets/i/layout_1col.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sourcejs/lego/HEAD/assets/i/layout_1col.png
--------------------------------------------------------------------------------
/assets/i/layout_2col.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sourcejs/lego/HEAD/assets/i/layout_2col.png
--------------------------------------------------------------------------------
/assets/i/layout_3col.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sourcejs/lego/HEAD/assets/i/layout_3col.png
--------------------------------------------------------------------------------
/assets/i/layout_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sourcejs/lego/HEAD/assets/i/layout_default.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .idea
3 | node_modules
4 |
5 | # App data
6 | /data
7 |
8 | # User custom settings
9 | # /user
--------------------------------------------------------------------------------
/user-source-ok-demo/README.md:
--------------------------------------------------------------------------------
1 | To point the app into this content folder run it with special flag:
2 |
3 | ```
4 | node lego -u user-source-ok-demo
5 | ```
6 |
--------------------------------------------------------------------------------
/core/routes/index.js:
--------------------------------------------------------------------------------
1 | // Core routes
2 | require("./redirects.js");
3 |
4 | // User custom routes
5 | try {
6 | require(global.app.get('user') + "/core/routes");
7 | } catch(e){}
--------------------------------------------------------------------------------
/user-bootstrap/data/bootstrap/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sourcejs/lego/HEAD/user-bootstrap/data/bootstrap/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/user-bootstrap/data/bootstrap/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sourcejs/lego/HEAD/user-bootstrap/data/bootstrap/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/user-bootstrap/data/bootstrap/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sourcejs/lego/HEAD/user-bootstrap/data/bootstrap/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/user-source-ok-demo/core/routes/index.js:
--------------------------------------------------------------------------------
1 | var staticDirs = ['/res/*','/data/res/*'];
2 | staticDirs.map(function(item) {
3 | global.app.get(item, function(req, res) {
4 | res.redirect(301, opts.specsMaster.current + req.url);
5 | });
6 | });
--------------------------------------------------------------------------------
/views/clean.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Design Lego
6 |
7 |
8 |
9 | {{{legoLayer}}}
10 |
11 |
--------------------------------------------------------------------------------
/views/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Design-Lego
6 |
7 |
8 |
9 | {{{legoContainer}}}
10 |
11 |
--------------------------------------------------------------------------------
/assets/js/global.js:
--------------------------------------------------------------------------------
1 | var lego = lego || {};
2 |
3 | lego.elementList = {}; // Будет хранить список всех виртуальных элементов
4 | lego.specList = {}; // Будет кешировать данные о спеках по мере их подгрузки
5 | lego.parsedTree = {}; // Хранит структуру спецификаций
--------------------------------------------------------------------------------
/options.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | common: {
3 | port: 7777,
4 |
5 | // Path to `user` folder with settings could be only overridden from app start arguments
6 | pathToUser: "user-bootstrap"
7 | },
8 |
9 | modifyInnerBlocks: true
10 | };
--------------------------------------------------------------------------------
/user-source-ok-demo/views/clean.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Design Lego
6 |
7 |
8 |
9 |
10 | {{{legoLayer}}}
11 |
12 |
--------------------------------------------------------------------------------
/parser/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Odnoklassniki front-end team",
3 | "name": "design-lego-parser",
4 | "version": "0.0.1",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/sourcejs/lego.git"
8 | },
9 | "dependencies": {
10 | "jsdom": "~1.0.1",
11 | "fs-extra": "~0.12.0"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/user-bootstrap/views/clean.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Design Lego
6 |
7 |
8 |
9 |
10 | {{{legoLayer}}}
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/user-bootstrap/options.js:
--------------------------------------------------------------------------------
1 | // Overrides /options.js
2 | module.exports = {
3 | specsMaster: {
4 | customDataUrl: '/data/bootstrap/bootstrap.json'
5 | },
6 |
7 | cssMod: {
8 | files: [
9 | "http://127.0.0.1:7777/data/bootstrap/css/bootstrap.css"
10 | ],
11 | rules: {
12 | blockRule: "^[a-zA-Z0-9]+$",
13 | modifierRule: "-",
14 | startModifierRule: "^-"
15 | }
16 | }
17 | };
--------------------------------------------------------------------------------
/user-source-ok-demo/views/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Design-Lego
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {{{legoContainer}}}
14 |
15 |
--------------------------------------------------------------------------------
/user-bootstrap/views/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Design-Lego
6 |
7 |
8 |
9 |
10 |
11 | {{{legoContainer}}}
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/views/lego-layer.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "SourceJS team",
3 | "name": "design-lego",
4 | "version": "0.1.0-alpha",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/sourcejs/lego.git"
8 | },
9 | "dependencies": {
10 | "body-parser": "^1.6.4",
11 | "colors": "0.6.x",
12 | "css": "^2.1.0",
13 | "express": "~4.8.3",
14 | "gzippo": "~0.2.0",
15 | "mustache": "~0.7.3",
16 | "shorthash": "0.0.2",
17 | "request": "~2.44.0",
18 | "deep-extend": "~0.2.11",
19 | "fs-extra": "~0.12.0",
20 | "commander": "~2.3.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/user-source-ok-demo/assets/css/hacks.css:
--------------------------------------------------------------------------------
1 | /* Cosmetic styles just for demo
2 | ---------------------------------------------------------------------------------- */
3 |
4 | .toolbar_search.__redesign {
5 | right: 5px;
6 | }
7 |
8 | .toolbar .layer_ovr,
9 | .toolbar + .layer_ovr {
10 | display: none;
11 | }
12 |
13 | .lego_content .toolbar {
14 | margin-left: -288px;
15 | left: 16px;
16 | }
17 |
18 | .cardsList_li {
19 | margin-left: 22px;
20 | }
21 |
22 | .compactProfile {
23 | margin-bottom: 34px;
24 | }
25 |
26 | /* /Cosmetic styles just for demo
27 | ---------------------------------------------------------------------------------- */
--------------------------------------------------------------------------------
/core/getView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var pathToApp = path.dirname(require.main.filename);
5 | var fs = require('fs');
6 |
7 | module.exports = function(requestedView){
8 | var output;
9 |
10 | // Working Sync, waiting for async refactoring
11 | try {
12 | // Try reading user view
13 | output = fs.readFileSync(path.join(pathToApp, global.opts.common.pathToUser, 'views', requestedView), 'utf8');
14 | } catch (e) {
15 | // If no user view, get regular view
16 | output = fs.readFileSync(path.join(pathToApp, 'views', requestedView), 'utf8');
17 | }
18 |
19 | return output;
20 | };
--------------------------------------------------------------------------------
/user-source-ok-demo/options.js:
--------------------------------------------------------------------------------
1 | // Overrides /options.js
2 | module.exports = {
3 | common: {
4 | port: 7777
5 | },
6 |
7 | specsMaster: {
8 | dev: "http://127.0.0.1:8080",
9 | prod: "http://okp.me:8080"
10 | },
11 |
12 | cssMod: {
13 | files: [
14 | "http://127.0.0.1:8080/res/css/prod/core/ncore.css",
15 | "http://127.0.0.1:8080/res/css/prod/core/ncore_postponed.css",
16 | "http://127.0.0.1:8080/res/css/prod/main/nmain_postponed.css",
17 | "http://127.0.0.1:8080/res/css/prod/main/nmain.css"
18 | ],
19 | rules: {
20 | blockRule: "^[a-zA-Z0-9\-]+$",
21 | modifierRule: "__",
22 | startModifierRule: "^__"
23 | },
24 | debug: false
25 | }
26 | };
--------------------------------------------------------------------------------
/core/loadOptions.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var path = require('path');
3 | var pathToApp = path.dirname(require.main.filename);
4 | var deepExtend = require('deep-extend');
5 | var fs = require('fs');
6 |
7 | module.exports = function(){
8 |
9 | var requireUncached = function (module) {
10 | delete require.cache[require.resolve(module)];
11 | return require(module);
12 | };
13 | var coreSettings = requireUncached(path.join(pathToApp,'options'));
14 |
15 | var pathToUser = global.commander.user ? global.commander.user : coreSettings.common.pathToUser;
16 | var userSettingsFile = path.join(pathToApp, pathToUser, 'options.js');
17 |
18 | // If user settings file is present, override core settings
19 | if(fs.existsSync(userSettingsFile)) {
20 | deepExtend(coreSettings, requireUncached(userSettingsFile));
21 | }
22 |
23 | return coreSettings;
24 | };
--------------------------------------------------------------------------------
/views/lego-layouts.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Default
4 |
5 |
6 |
7 | One column
8 |
9 |
10 |
11 | 2 columns
12 |
13 |
14 |
15 | 3 columns
16 |
--------------------------------------------------------------------------------
/user-source-ok-demo/views/lego-layouts.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | По умолчанию
4 |
5 |
6 |
7 | 1 колонка
8 |
9 |
10 |
11 | 2 колонки
12 |
13 |
14 |
15 | 3 колонки
16 |
--------------------------------------------------------------------------------
/core/routes/redirects.js:
--------------------------------------------------------------------------------
1 | /*
2 | This file contains default redirects, to add custom redirects user /user/core/routes/index.js
3 | */
4 |
5 | var path = require('path');
6 | var express = require('express');
7 | var pathToApp = path.dirname(require.main.filename);
8 |
9 | if (global.opts.specsMaster.current) {
10 | // Redirecting calls for master-app
11 | global.app.get('/master-app/*', function (req, res) {
12 | var processUrl = req.url.replace('/master-app/', '');
13 |
14 | res.redirect(301, global.opts.specsMaster.current + '/' + processUrl);
15 | });
16 | }
17 |
18 | // First, check if there's minified assets
19 | global.app.use('/lego/assets', express.static(pathToApp + '/build/assets'));
20 | global.app.use('/lego/views', express.static(pathToApp + '/build/views'));
21 |
22 | // Redirecting core client-side file requests to app root paths
23 | global.app.use('/lego/assets', express.static(pathToApp + '/assets'));
24 | global.app.use('/lego/views', express.static(pathToApp + '/views'));
--------------------------------------------------------------------------------
/assets/js/virtualblock.js:
--------------------------------------------------------------------------------
1 | /*global window */
2 |
3 | (function (global) {
4 | "use strict";
5 | /**
6 | * конструктор виртуального блока
7 | *
8 | * @param specId String
9 | * @returns {VirtualBlock}
10 | * @constructor
11 | */
12 | var VirtualBlock = global.lego.VirtualBlock = function (specId) {
13 | this.id = 'block-' + Math.round(Math.random() * 10000);
14 | this.specId = specId;
15 | this.modifiers = {};
16 | this.variation = 0;
17 | this.changed = false;
18 |
19 | global.lego.elementList[this.id] = this;
20 |
21 | return this;
22 | };
23 |
24 | /**
25 | * Сохранить данные о виртуальном блоке
26 | *
27 | * @param p Object
28 | * @param p.variation Number
29 | * @param p.modifiers Object { block: [mod, mod, mod] }
30 | */
31 | VirtualBlock.prototype.save = function (p) {
32 | if (p.variation) {
33 | this.variation = p.variation;
34 | }
35 |
36 | if (p.modifiers) {
37 | this.modifiers = JSON.parse(JSON.stringify(p.modifiers));
38 | }
39 |
40 | if (p.changed) {
41 | this.changed = true;
42 | }
43 | };
44 | }(window));
--------------------------------------------------------------------------------
/assets/js/client-templates.js:
--------------------------------------------------------------------------------
1 | /*global window */
2 |
3 | (function (global) {
4 | "use strict";
5 |
6 | var $ = global.jQuery;
7 | var Handlebars = global.Handlebars;
8 |
9 | var clientTemplates = global.lego.clientTemplates = {
10 | get: function () {
11 | var that = this;
12 |
13 | return new Promise(function (resolve, reject) {
14 | $.ajax({
15 | url: '/lego/views/client-templates.html',
16 | dataType: 'html',
17 | success: function (rawTemplates) {
18 | var $rawTemplates = $(rawTemplates);
19 |
20 | $rawTemplates.each(function () {
21 | var templateId = $(this).attr('id');
22 | if (templateId) {
23 | that[templateId] = Handlebars.compile($(this).html());
24 | }
25 | });
26 |
27 | resolve();
28 | },
29 | error: function () {
30 | reject();
31 | }
32 | });
33 | });
34 | }
35 | };
36 | }(window));
--------------------------------------------------------------------------------
/views/client-templates.html:
--------------------------------------------------------------------------------
1 |
12 |
13 |
23 |
24 |
32 |
33 |
--------------------------------------------------------------------------------
/assets/js/components.js:
--------------------------------------------------------------------------------
1 | /*global window */
2 |
3 | (function (global) {
4 | "use strict";
5 | var $ = global.jQuery;
6 | var component = global.lego.component = {};
7 |
8 | component.expand = (function () {
9 |
10 | var expandContainerClassName = 'lego_expand';
11 | var expandTitleClassName = 'lego_expand_h';
12 | var expandBodyClassName = 'lego_expand_cnt';
13 | var expandIsClosed = '__closed';
14 |
15 |
16 | $('body').on('click', '.' + expandTitleClassName, function () {
17 | $(this).closest('.' + expandContainerClassName).toggleClass(expandIsClosed);
18 | });
19 |
20 | return {
21 |
22 | create: function ($parentContainer, title, isClosed) {
23 | var template = ' \
24 |
' + title + '
\
25 |
\
26 |
';
27 | var $template = $(template);
28 |
29 | if (isClosed) {
30 | $template.addClass(expandIsClosed);
31 | }
32 |
33 | $parentContainer.append($template);
34 |
35 | return $template;
36 | },
37 |
38 | append: function ($expandContainer, html) {
39 | var $addedElem = $(html);
40 | $expandContainer.find('.' + expandBodyClassName).append($addedElem);
41 | return $addedElem;
42 | },
43 |
44 | remove: function ($expandContainer) {
45 | $expandContainer.remove();
46 | }
47 | };
48 | }());
49 | }(window));
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Design-lego
2 |
3 | [](https://gitter.im/sourcejs/Source)
4 |
5 | **v0.1.0-alpha (first concept)**
6 |
7 | [SourceJS](http://sourcejs.com) companion app, for layout prototyping based on own blocks library. Could be also used separately, without SourceJS, check `/user-bootstrap` demo (runs by default).
8 |
9 | 
10 |
11 | **[Demo video](https://www.youtube.com/watch?v=cefy_U5NU4o)**
12 |
13 | If you're already using [SourceJS](http://sourcejs.com) engine for organising your front-end component library, check demo configuration folder for integration `/user-source-ok-demo`. As from 0.4.0 SourceJS has own Content API, that lets you request any block resources from your library, all you need is to set some basic configuration for Design-Lego app.
14 |
15 | For running your own block library with Design-Lego app, all you need is to generate JSON data file with [similar structure](https://github.com/sourcejs/lego/blob/master/user-bootstrap/data/bootstrap/bootstrap.json). For bootstrap integration, we created [simple parser](https://github.com/sourcejs/lego/blob/master/parser/bootstrap.js) for required data generation.
16 |
17 | ## Features
18 |
19 | * Load your own component library (based on any frameworks and templating engines)
20 | * Review your existing blocks through search and switch between different variations (block examples)
21 | * If you're seperating CSS modifiers, Design-Lego will parse your CSS and suggest existing modifiers for your currently selected block
22 | * Build lite prototypes with different components combination and share
23 | * Block layout drop predictions
24 |
25 | ## Setup
26 |
27 | ```
28 | git clone https://github.com/sourcejs/lego.git
29 | cd lego
30 | npm i
31 | node lego
32 | ```
33 |
34 | NodeJS will run local server, with pre-build bootstrap library for playing in Design-lego app.
35 |
36 | ## Upcoming features
37 |
38 | * Configurable layouts
39 | * Improved UI
40 | * Improved prototyping features with drag n drop
41 | * Easy to setup configuration with SourceJS
42 | * Component export to tools for designing in the browser
43 |
--------------------------------------------------------------------------------
/parser/bootstrap.js:
--------------------------------------------------------------------------------
1 | var jsdom = require('jsdom');
2 | var fs = require('fs-extra');
3 | var path = require('path');
4 | var jquery = fs.readFileSync("./lib/jquery.js", "utf-8");
5 |
6 | var generateJSON = function(window){
7 | var $ = window.$;
8 | var output = {};
9 |
10 | $('.bs-docs-section').each(function(){
11 | var $t = $(this);
12 | var $header = $t.find('.page-header');
13 | var id = $header.attr('id');
14 | var title = $header.text();
15 |
16 | var contents = [];
17 | var exampleID = 1;
18 |
19 | $t.find('.bs-example').each(function(){
20 | var html = [];
21 | html.push($(this).children()[0].outerHTML);
22 |
23 | var header = $(this).prevAll('h4').html();
24 |
25 | if (!header) header = $(this).prevAll('h3').html();
26 | if (!header) header = $(this).prevAll('h2').html();
27 | if (!header) header = $(this).prevAll('h1').html();
28 |
29 | contents.push({
30 | header: header,
31 | id: exampleID,
32 | html: html
33 | });
34 |
35 | exampleID++;
36 | });
37 |
38 | output[id] = {
39 | id: id,
40 | url: 'http://getbootstrap.com/components#' + id,
41 | title: title,
42 | contents: contents
43 | };
44 | });
45 |
46 | return output;
47 | };
48 |
49 | jsdom.env({
50 | file: 'raw/bootstrap.html',
51 | // url: "http://getbootstrap.com/components",
52 | src: [jquery],
53 | done: function (errors, window) {
54 | var dataPath = '../user-bootstrap/data/bootstrap';
55 | var fileName = 'bootstrap.json';
56 | var fullPath = path.join(dataPath, fileName);
57 |
58 | var data = generateJSON(window);
59 |
60 | // Preparing path for data write
61 | fs.mkdirp(dataPath, function (err) {
62 | if (err) return console.error(err);
63 |
64 | fs.writeFile(fullPath, JSON.stringify(data, null, 4), function(err) {
65 | if (err) return console.error(err);
66 |
67 | console.log('JSON saved to: ' + fullPath);
68 | });
69 | });
70 | }
71 | });
--------------------------------------------------------------------------------
/views/lego-сontainer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
Layouts
11 |
12 | {{{legoLayouts}}}
13 |
14 |
15 |
16 |
17 |
18 |
Loading components...
19 |
20 |
21 |
22 |
23 | {{{legoLayer}}}
24 |
25 |
26 |
27 |
28 |
35 |
36 |
40 |
41 |
48 |
49 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/user-source-ok-demo/views/lego-layer.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/js/search.js:
--------------------------------------------------------------------------------
1 | /*global window */
2 |
3 | (function (global) {
4 | "use strict";
5 |
6 | var $ = global.jQuery;
7 | var Handlebars = global.Handlebars;
8 | var globalOptions = global.lego.globalOptions;
9 | var component = global.lego.component;
10 | var clientTemplates = global.lego.clientTemplates;
11 |
12 | var parsed = false;
13 |
14 | var processSpecsData = function (specsTree, callback) {
15 | var resultTree = {
16 | root: []
17 | };
18 | var closedSection = false;
19 | var cb = callback || function () {};
20 | var specsPath;
21 | var specsSection;
22 |
23 | // Separating spec cats
24 | for (specsPath in specsTree) {
25 | if (specsTree.hasOwnProperty(specsPath)) {
26 | var specPathParts = specsPath.split('/');
27 |
28 | if (specPathParts.length === 1) {
29 | resultTree.root.push(specsTree[specsPath]);
30 | } else {
31 | if (!resultTree[specPathParts[0]]) {
32 | resultTree[specPathParts[0]] = [];
33 | }
34 | resultTree[specPathParts[0]].push(specsTree[specsPath]);
35 | }
36 | }
37 | }
38 |
39 | if (resultTree.root.length === 0) {
40 | delete resultTree.root;
41 | }
42 |
43 | for (specsSection in resultTree) {
44 | if (resultTree.hasOwnProperty(specsSection)) {
45 | resultTree[specsSection].sort(function (a, b) {
46 | if (a.title > b.title) {
47 | return 1;
48 | }
49 | if (a.title < b.title) {
50 | return -1;
51 | }
52 | return 0;
53 | });
54 |
55 | var newNode = component.expand.create($('#lego_search-result'), specsSection, closedSection);
56 | component.expand.append(newNode, clientTemplates['search-item'](resultTree[specsSection]));
57 |
58 | closedSection = true;
59 | }
60 | }
61 |
62 | cb();
63 | };
64 |
65 | var prepareSpecsData = function (callback) {
66 | var specsMaster = globalOptions.specsMaster.current;
67 | var customDataUrl = globalOptions.specsMaster.customDataUrl;
68 |
69 | var drawNavigation = function (data) {
70 | global.lego.parsedTree = data;
71 |
72 | clientTemplates.get().then(function () {
73 | parsed = true;
74 | processSpecsData(global.lego.parsedTree, callback);
75 | });
76 | };
77 |
78 | if (customDataUrl) {
79 | $.ajax(customDataUrl, {
80 | method: 'GET',
81 | contentType: 'application/json',
82 |
83 | success: drawNavigation
84 | });
85 | } else {
86 | $.ajax(specsMaster + '/api/specs', {
87 | method: 'GET',
88 | crossDomain: true,
89 | data: {
90 | filter: {
91 | cats: ['base'],
92 | forceTags: ['lego']
93 | },
94 | filterOut: {
95 | tags: ['html', 'lego-hide', 'hidden', 'deprecated']
96 | }
97 | },
98 | contentType: 'application/json',
99 |
100 | success: drawNavigation
101 | });
102 | }
103 |
104 | Handlebars.registerHelper("imageUrl", function (url) {
105 | url = url.toString();
106 |
107 | return specsMaster + url + "/thumbnail.png";
108 | });
109 |
110 | Handlebars.registerHelper("fullUrl", function (url) {
111 | url = url.toString();
112 |
113 | return specsMaster ? specsMaster + url : url;
114 | });
115 | };
116 |
117 | var fuzzySearch = function (q, allData) {
118 | var result = {};
119 | var query = q.toLowerCase().trim();
120 | var qRegExp = new RegExp(query);
121 | var cat;
122 |
123 | for (cat in allData) {
124 | if (allData.hasOwnProperty(cat)) {
125 | var lowerCat = cat.toLowerCase();
126 | var title = allData[cat].title || '';
127 | var info = allData[cat].info || '';
128 | var keywords = allData[cat].keywords || '';
129 |
130 | title = title.toString().toLowerCase(); // Защита от массивов
131 | info = info.toString().toLowerCase();
132 | keywords = keywords.toString().toLowerCase();
133 |
134 | // if query matches current category, all category's articles are considered a match
135 |
136 | if (qRegExp.test(lowerCat.match(query)) ||
137 | qRegExp.test(info.match(query)) ||
138 | qRegExp.test(keywords.match(query)) ||
139 | qRegExp.test(title.match(query))) {
140 |
141 | if (!result[cat]) {
142 | result[cat] = {};
143 | }
144 | result[cat] = allData[cat];
145 | }
146 | }
147 | }
148 | return result;
149 | };
150 |
151 | var renderLiveSearchResults = function (value) {
152 | var $searchResult = $("#lego_search-result");
153 |
154 | $searchResult.empty();
155 |
156 | if (parsed) {
157 | var result = fuzzySearch(value, global.lego.parsedTree);
158 | if ($.isEmptyObject(result)) {
159 | $searchResult.html("No search results");
160 | } else {
161 | processSpecsData(result);
162 | }
163 | } else {
164 | $searchResult.html("Loading components...");
165 |
166 | global.setTimeout(function () {
167 | renderLiveSearchResults(value);
168 | }, 500);
169 | }
170 | };
171 |
172 | $(function () {
173 | var $searchInput = $('#search');
174 |
175 | // Загрузить дерево спецификаций и шаблон поисковой выдачи
176 | // После завершения обработки отрендерить значение фильтра
177 | prepareSpecsData(function () {
178 | renderLiveSearchResults($searchInput.val());
179 | });
180 |
181 | $searchInput.on("keyup search", function () {
182 |
183 | // Нет смысла дергать поиск, если нет данных и шаблона
184 | if (parsed) {
185 | renderLiveSearchResults($searchInput.val());
186 | }
187 | });
188 | });
189 | }(window));
--------------------------------------------------------------------------------
/lego.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var fs = require('fs-extra');
3 | var path = require('path');
4 | var mustache = require('mustache');
5 | var gzippo = require('gzippo');
6 | var sh = require('shorthash');
7 | var colors = require('colors');
8 | var commander = require('commander');
9 | var cssMod = require('./core/css-mod');
10 | var bodyParser = require('body-parser');
11 | var getView = require('./core/getView');
12 | var defOpts = require('./options');
13 |
14 |
15 | /* Globals */
16 |
17 | // Parameters
18 | commander
19 | .option('-u, --user [string]', 'Path to user folder (default: ' + defOpts.common.pathToUser + ')', defOpts.common.pathToUser)
20 | .option('-p, --port [number]', 'Server port (default: ' + defOpts.common.port + ')', defOpts.common.port)
21 | .parse(process.argv);
22 |
23 | global.commander = commander;
24 |
25 | var app = global.app = express();
26 | var opts = global.opts = require('./core/loadOptions')();
27 |
28 | var MODE = global.MODE = process.env.NODE_ENV || 'development';
29 |
30 | // Preparing environment
31 | if (global.opts.specsMaster.prod && global.opts.specsMaster.dev) {
32 | opts.specsMaster.current = global.MODE === 'production' ? global.opts.specsMaster.prod : global.opts.specsMaster.dev;
33 | }
34 |
35 | // Overriding options from specified arguments
36 | if (commander.port) global.opts.common.port = commander.port;
37 | if (commander.user) global.opts.common.pathToUser = commander.user;
38 | /* /Globals */
39 |
40 |
41 | /* Express settings */
42 | app.set('user', path.join(__dirname, opts.common.pathToUser));
43 |
44 | app.use(bodyParser.json());
45 | app.use(logErrors);
46 | app.use(clientErrorHandler);
47 | app.use(errorHandler);
48 | /* /Express settings */
49 |
50 |
51 |
52 | /* Includes */
53 |
54 | // Routes
55 | require('./core/routes');
56 |
57 | /* /Includes */
58 |
59 |
60 |
61 | /* Route for static files */
62 | app.use(gzippo.staticGzip(app.get('user')));
63 | app.use(gzippo.compress());
64 |
65 | // Main page
66 | var arr = ['/','/index','/index.html','/home'];
67 | arr.map(function(item) {
68 | app.get(item, function(req, res) {
69 | var legoContainer = mustache.to_html(getView('lego-сontainer.html'), {
70 | legoLayer: getView('lego-layer.html'),
71 | legoLayouts: getView('lego-layouts.html'),
72 | globalOptions: JSON.stringify(opts)
73 | });
74 |
75 | var htmlToSend = mustache.to_html(getView('index.html'), {
76 | legoContainer: legoContainer
77 | });
78 |
79 | res.send(htmlToSend);
80 | });
81 | });
82 |
83 | // Share link
84 | app.get('/s/:page', function (req, res) {
85 | var page = req.params.page;
86 |
87 | fs.readFile(path.join(app.get('user'), 'saved', page + '.html'), "utf8", function (err, data) {
88 | if(err) {
89 | console.log(err);
90 | res.send('No saved data with this id.');
91 | return;
92 | }
93 |
94 | var legoContainer = mustache.to_html(getView('lego-сontainer.html'), {
95 | legoLayer: data,
96 | legoLayouts: getView('lego-layouts.html'),
97 | globalOptions: JSON.stringify(opts)
98 | });
99 |
100 | var htmlToSend = mustache.to_html(getView('index.html'), {
101 | legoContainer: legoContainer
102 | });
103 |
104 | res.send(htmlToSend);
105 | });
106 | });
107 |
108 | // Clean html link
109 | app.get('/clean/:page', function (req, res) {
110 | var page = req.params.page;
111 | var view = getView('clean.html');
112 |
113 | fs.readFile(path.join(app.get('user'), 'saved', page + '.html'), "utf8", function (err, data) {
114 | if (err) {
115 | console.log(err);
116 | res.send('No saved data with this id.');
117 | return;
118 | }
119 |
120 | var htmlToSend = mustache.to_html(view, {
121 | legoLayer: data,
122 | globalOptions: JSON.stringify(opts)
123 | });
124 |
125 | res.send(htmlToSend);
126 | });
127 | });
128 | /* /Route for static files */
129 |
130 |
131 |
132 | /* API */
133 | // Get Css modifiers
134 | app.post('/cssmod', function (req, res) {
135 | // Preparing config for cssMod parser from options file and received info
136 | var config = JSON.parse(JSON.stringify(opts.cssMod));
137 | config.files = req.body.files;
138 |
139 | cssMod.getCssMod(config, function(err, parsedCss) {
140 | if (err) {
141 | console.log('getCssMod error: ', err);
142 |
143 | res.status(500).send('Error getting specified CSS files for modifier analysis, check options.js.');
144 | return;
145 | }
146 |
147 | res.send(parsedCss);
148 | });
149 | });
150 |
151 | // Save working html
152 | app.get('/save', function (req, res) {
153 | var html = req.query.html;
154 | var outputDir = path.join(app.get('user'), 'saved');
155 | var name = sh.unique(html);
156 |
157 | fs.mkdirp(outputDir, function (err) {
158 | if (err) {
159 | return console.error(err);
160 | }
161 |
162 | fs.writeFile(path.join(outputDir, name + ".html"), html, function(err) {
163 | if (err) {
164 | console.log(err);
165 | res.send({
166 | success: false
167 | });
168 | return;
169 | }
170 |
171 | res.send({
172 | success: true,
173 | name: name
174 | });
175 | });
176 | });
177 | });
178 | /* /API */
179 |
180 |
181 |
182 | /* Error handling */
183 | function logErrors(err, req, res, next) {
184 | console.error(("Error: " + err.stack).red);
185 | next(err);
186 | }
187 |
188 | function clientErrorHandler(err, req, res, next) {
189 | if (req.xhr) {
190 | res.send(500, { error: 'Something blew up!' });
191 | } else {
192 | next(err);
193 | }
194 | }
195 |
196 | function errorHandler(err, req, res, next) {
197 | res.status(500);
198 | res.render('error', { error: err });
199 | }
200 | /* /Error handling */
201 |
202 |
203 | // Starting server
204 | if (!module.parent) {
205 | var port = opts.common.port;
206 | var portString = port.toString();
207 |
208 | app.listen(port);
209 |
210 | var d = new Date();
211 | var dateArr = [d.getHours(), d.getMinutes(), d.getSeconds()];
212 | dateArr = dateArr.map(function (el) {
213 | return (el > 9) ? el : '0' + el;
214 | });
215 |
216 | var dateString = dateArr.join(':').red;
217 |
218 | console.log('[LEGO] '.blue + dateString + ' Server started on http://localhost:'.blue + portString.red + ', from '.blue + global.opts.common.pathToUser.red + ' folder in '.blue + MODE.blue + ' mode...'.blue);
219 | }
--------------------------------------------------------------------------------
/core/css-mod/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * CSS modifiers analyzer
3 | */
4 |
5 | var fs = require('fs');
6 | var css = require('css');
7 | var request = require('request');
8 |
9 | module.exports = function cssMod() {
10 | var debug = false;
11 | var modifierDetect = /__/;
12 | var startModifierDetect = /^__/;
13 | var lastProcessedObj = {};
14 | var lastEtags = {};
15 | var cssFilesCache = {};
16 |
17 | // CSS Files collection
18 | var cssFiles = [];
19 |
20 | // Output tree with block, element, modifier structure
21 | var outputTree = {};
22 |
23 |
24 | // Download all styles and parse CSS modifiers
25 | function parseCSS(callback) {
26 | var _callback = callback || function () {};
27 |
28 | // String with all concatenated CSS files
29 | var importCss = '';
30 |
31 | for (var i = 0, allReqComplete = 0, chachedCount = 0, reqErrors = 0; i < cssFiles.length; i++) {
32 | var currentFile = cssFiles[i];
33 | var requrestOptions = {
34 | url: cssFiles[i],
35 | headers: {
36 | }
37 | };
38 | var currentFileEtags = lastEtags[currentFile];
39 | if (currentFileEtags) {
40 | // Setting etag for cache
41 | requrestOptions.headers['If-None-Match'] = currentFileEtags;
42 | }
43 |
44 | request(requrestOptions, function(err, response, body) {
45 | if (err) {
46 | console.log('Error loading stylesheet for modifiers parsing:', requrestOptions.url);
47 |
48 | reqErrors++;
49 | } else {
50 | var currentFile = response.request.uri.href;
51 |
52 | // Saving etag in memory
53 | lastEtags[currentFile] = response.headers.etag;
54 |
55 | // If file new
56 | if (response.statusCode == 200) {
57 | importCss += body;
58 | cssFilesCache[currentFile] = body;
59 |
60 | // If file is cached
61 | } else if (response.statusCode == 304) {
62 | importCss += cssFilesCache[currentFile];
63 | chachedCount++;
64 | }
65 | }
66 |
67 | allReqComplete++;
68 |
69 | // When loop is done
70 | if (allReqComplete === cssFiles.length) {
71 |
72 | // Check we have at lest some downloaded files
73 | if (reqErrors === cssFiles.length) {
74 | _callback('All CSS files for modifier parsing are unreachable.', null);
75 | } else if (chachedCount === cssFiles.length) {
76 | // If all files are cached, return last parsed CSS
77 | _callback(null, lastProcessedObj);
78 | } else {
79 | // Or parse it again
80 | _callback(null, processCssList(css.parse(importCss).stylesheet.rules))
81 | }
82 | }
83 | });
84 | }
85 | }
86 |
87 | // Добавление селектора в итоговый список
88 | function addToList(block, modifier) {
89 | if ( outputTree[block] === undefined ) {
90 | outputTree[block] = [];
91 | }
92 |
93 | if (outputTree[block].indexOf(modifier) === -1) {
94 | outputTree[block].push(modifier);
95 | }
96 | }
97 |
98 | // Диагностика селекторов
99 | function dropException(context, selector, cause) {
100 | if (debug) {
101 | console.log('Ignored selector: ' + selector);
102 | console.log('Context: ' + context);
103 | console.log('Cause: ' + cause)
104 | console.log('');
105 | }
106 | }
107 |
108 | // Разбор правил
109 | function processCssList(parsedCss) {
110 |
111 | function parseOldModifier(rule) {
112 | var separatorPos = stringList[sel].search(modifierDetect);
113 |
114 | return {
115 | block: rule.substring(0, separatorPos),
116 | modifier: rule
117 | }
118 | }
119 |
120 | for (var rule = 0; rule < parsedCss.length; rule++) {
121 | var selectors = parsedCss[rule].selectors;
122 |
123 | if (selectors !== undefined) {
124 | for (var selector = 0; selector < selectors.length; selectors++) {
125 |
126 | if (modifierDetect.test( selectors[selector] )) {
127 |
128 | var words = selectors[selector]
129 |
130 | // зачищаем псевдоэлементы и псевдоклассы
131 | .replace(/::before/g, '')
132 | .replace(/::after/g, '')
133 | .replace(/::disabled/g, '')
134 |
135 | .replace(/:before/g, '')
136 | .replace(/:after/g, '')
137 | .replace(/:hover/g, '')
138 | .replace(/:focus/g, '')
139 | .replace(/:target/g, '')
140 | .replace(/:active/g, '')
141 | .replace(/:disabled/g, '')
142 | .replace(/:first-child/g, '')
143 | .replace(/:last-child/g, '')
144 | .replace(/:empty/g, '')
145 |
146 | .replace(/::-moz-placeholder/g, '')
147 | .replace(/:-ms-input-placeholder/g, '')
148 | .replace(/::-webkit-input-placeholder/g, '')
149 |
150 | // из :not(.__class) можно извлечь пользу
151 | .replace(/:not\(/g, '')
152 | .replace(/\)/g, '')
153 |
154 | // удаляем атрибуты
155 | .replace(/\[.*?\]/ig, '')
156 |
157 | // сложные правила конвертируем в каскад
158 | .replace(/\+/g, ' ')
159 | .replace(/~/g, ' ')
160 | .replace(/>/g, ' ')
161 | .replace(/\*/, ' ')
162 | .split(' ');
163 |
164 | for (var word = 0; word < words.length; word++) {
165 | if ( modifierDetect.test(words[word]) ) {
166 |
167 | // Разбиваем селектор, содержащий class и id, и удаляем первый пустой элемент
168 | var stringList = words[word].split(/\.|#/g);
169 | stringList.shift();
170 |
171 | var block = 0, // флаг неопределенности блока
172 | modifier = []; // массив для модификаторов в новом стиле
173 |
174 | for (var sel = 0; sel < stringList.length; sel++) {
175 | var currentClass = stringList[sel];
176 |
177 | if (!modifierDetect.test(currentClass)) {
178 | // Это блок (элемент)
179 | if (block !== false && block !== currentClass) {
180 | if (block === 0) {
181 | block = currentClass
182 | } else {
183 | block = false;
184 | }
185 | }
186 | } else if (startModifierDetect.test(currentClass)) {
187 | // Это модификатор в новом стиле
188 | modifier.push(currentClass);
189 | } else {
190 | // Это модификатор в старом стиле
191 | var result = parseOldModifier(currentClass);
192 |
193 | if (block !== false && block !== result.block) {
194 | if (block === 0) {
195 | block = result.block
196 | } else {
197 | block = false;
198 | }
199 | }
200 |
201 | addToList(result.block, result.modifier);
202 | }
203 | }
204 |
205 | // Если строго один блок (элемент) и модификаторы в новом стиле, нет неопределенности
206 | if (block && modifier.length) {
207 | for (var i = 0; i < modifier.length; i++) {
208 | addToList(block, modifier[i]);
209 | }
210 | } else if (!block && modifier.length) {
211 | dropException(selectors[selector], words[word], 'Принадлежность модификатора не определена');
212 | }
213 | }
214 | }
215 | }
216 | }
217 | }
218 | }
219 |
220 | // Caching parse output
221 | lastProcessedObj = outputTree;
222 | return outputTree;
223 | }
224 |
225 | return {
226 | getCssMod: function (config, callback) {
227 | var _callback = callback || function () {};
228 |
229 | // TODO: Add automatic style parsing, based on page-content
230 | cssFiles = config.files;
231 | modifierDetect = new RegExp(config.rules.modifierRule) || modifierDetect;
232 | startModifierDetect = new RegExp(config.rules.startModifierRule) || startModifierDetect;
233 | debug = config.debug || debug;
234 |
235 | parseCSS(_callback);
236 | }
237 | }
238 | }();
239 |
--------------------------------------------------------------------------------
/assets/css/lego.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | position: relative;
4 | height: 100%;
5 | }
6 |
7 | /* Base
8 | ---------------------------------------------------------------------------------- */
9 |
10 | .lego_lk:link,
11 | .lego_lk:visited {
12 | color: #ddd;
13 | text-decoration: none;
14 | }
15 |
16 | .lego_lk:hover {
17 | text-decoration: underline;
18 | }
19 |
20 | .lego_lk.__active {
21 | color:#EB722E;
22 | }
23 |
24 | /* Clearfix */
25 | .lego_cf:before,
26 | .lego_cf:after {
27 | content: " ";
28 | display: table;
29 | }
30 |
31 | .lego_cf:after {
32 | clear: both;
33 | }
34 |
35 | /* Expand */
36 | .lego_expand {
37 | }
38 |
39 | .lego_expand_h {
40 | position: relative;
41 | margin-bottom: 10px;
42 | color: #bababa;
43 | font-size: 14px;
44 | cursor: pointer;
45 | display: block;
46 | }
47 | .lego_expand_h:before{
48 | display: inline-block;
49 | content: '▼';
50 | margin-right: 5px;
51 | font-size: 12px;
52 | transform: rotate(0deg);
53 | transition: transform 0.2s;
54 | }
55 | .lego_expand.__closed .lego_expand_h:before {
56 | transform: rotate(-90deg);
57 | }
58 |
59 | .lego_expand_cnt {
60 | padding-left: 0;
61 | }
62 | .lego_search-result.__list .lego_expand_cnt {
63 | padding-left: 17px;
64 | }
65 | .lego_expand.__closed .lego_expand_cnt {
66 | display: none;
67 | }
68 |
69 | /* /Base
70 | ---------------------------------------------------------------------------------- */
71 |
72 |
73 |
74 | /* Project
75 | ---------------------------------------------------------------------------------- */
76 |
77 | /* Layout */
78 |
79 | .lego_container {
80 | position: relative;
81 | height: 100%;
82 | display: flex;
83 | }
84 |
85 | .lego_aside,
86 | .lego_main {
87 |
88 | }
89 |
90 | .lego_aside {
91 | position: relative;
92 | flex: 1;
93 | min-width: 120px;
94 | }
95 | .lego_aside.__right {
96 | flex: initial;
97 | width: 200px;
98 | }
99 |
100 | .lego_aside_panel {
101 | position: absolute;
102 | top: 0;
103 | left: 0;
104 | overflow-y: auto;
105 | overflow-x: hidden;
106 | width: 100%;
107 | min-width: 100%;
108 | height: 100%;
109 | background: #444;
110 | z-index: 1000;
111 | box-shadow: none;
112 | transition: width 0.3s 0.15s, box-shadow 0.3s 0.15s;
113 | }
114 | .lego_aside_panel:hover {
115 | width: 215px;
116 | box-shadow: 0 0 10px #000;
117 | }
118 | .lego_aside_panel:hover:after {
119 | box-shadow: none;
120 | }
121 |
122 | .lego_aside_panel:after {
123 | content: '';
124 | position: absolute;
125 | top: 0;
126 | right: 0;
127 | height: 100%;
128 | box-shadow: 0 0 10px 1px #000;
129 | transition: box-shadow 0.3s;
130 | }
131 |
132 | .lego_aside.__right .lego_aside_panel {
133 | left: auto;
134 | right: 0;
135 | }
136 | .lego_aside.__right .lego_aside_panel:after {
137 | right: auto;
138 | left: 0;
139 | }
140 |
141 | .lego_aside_cnt {
142 | width: 215px;
143 | padding-right: 10px;
144 | box-sizing: border-box;
145 | }
146 |
147 | .lego_main {
148 | position: relative;
149 | height: 100%;
150 | overflow: auto;
151 | background: #999; /* color of cover */
152 | width: 1015px; /* 1000 + scroll width */
153 | }
154 |
155 | .lego_toggler {
156 | margin: 10px;
157 | }
158 | .lego_toggle_i {
159 | display: inline-block;
160 | padding: 3px;
161 | font-size: 12px;
162 | }
163 | .lego_toggle_i.__active {
164 | background: #FF7F00;
165 | color: #fff;
166 | }
167 |
168 | .lego_aside_search {
169 | width: 85%;
170 | margin-top: 20px;
171 | margin-left: 10px;
172 | padding: 5px;
173 |
174 | border: 1px solid #bababa;
175 | }
176 | input[type="search"].lego_aside_search {
177 | -webkit-appearance: searchfield;
178 | }
179 | input[type="search"].lego_aside_search::-webkit-search-cancel-button {
180 | -webkit-appearance: searchfield-cancel-button;
181 | }
182 |
183 | .lego_search-result {
184 | padding: 10px;
185 | color: #fff;
186 | }
187 |
188 | .lego_search-result.__list .lego_search-result_i {
189 | display: block;
190 | width: auto;
191 |
192 | }
193 | .lego_search-result.__list .lego_search-result_img {
194 | display: none;
195 |
196 | }
197 |
198 | .lego_search-result_img {
199 | display: inline-block;
200 | width: 85px;
201 | height: 85px;
202 | background-color: #fff !important;
203 | background-position: 50% !important;
204 | background-repeat: no-repeat !important;
205 | background-size: contain !important;
206 | }
207 |
208 | .lego_search-result_w {
209 |
210 | }
211 |
212 | .lego_search-result_i {
213 | display: inline-block;
214 | width: 85px;
215 | vertical-align: top;
216 | margin-bottom: 10px;
217 | margin-right: 2px;
218 | }
219 |
220 | .lego_search-result_n {
221 | display: inline-block;
222 | vertical-align: middle;
223 | font-size: 12px;
224 | color: #fff;
225 | }
226 |
227 | .lego_export-select {
228 | display: block;
229 | margin: 0 auto;
230 | }
231 |
232 | .lego_widget:nth-of-type(1) {
233 | margin-top: 20px;
234 | }
235 |
236 | .lego_widget {
237 | color: #bababa;
238 | padding: 0 10px 20px;
239 |
240 | font-size: 12px;
241 | }
242 |
243 | .lego_widget_h {
244 | border-bottom: 1px solid #bababa;
245 | margin-bottom: 15px;
246 | font-size: 16px;
247 | }
248 |
249 | .lego_widget_ul {
250 | padding: 0;
251 | margin: 0;
252 | list-style-type: none;
253 | }
254 |
255 | .lego_widget_ul-i {
256 | position: relative;
257 | padding-right: 20px;
258 | padding-bottom: 5px;
259 | font-size: 12px;
260 | }
261 |
262 | .lego_widget_ul-i .lego_lk {
263 | display: inline-block;
264 | }
265 |
266 | .lego_widget_ul-i .lego_ic {
267 | display: none;
268 | position: absolute;
269 | top: -1px; right: 0;
270 | }
271 |
272 | .lego_widget_ul-i:hover .lego_ic_close {
273 | display: block;
274 | }
275 |
276 | .lego_widget_form {
277 |
278 | }
279 |
280 | .lego_form-i {
281 | margin-bottom: 15px;
282 | }
283 |
284 | .lego_form-i_w {
285 | clear: left;
286 | margin-bottom: 10px;
287 | }
288 |
289 | .lego_form-i_w .lego_it,
290 | .lego_form-i_w .lego_form-i_txt {
291 | display: inline-block;
292 | }
293 |
294 | .lego_form-i_w .lego_form-i_txt {
295 |
296 | }
297 |
298 | .lego_form-i .lego_checkbox {
299 |
300 | }
301 |
302 | .lego_label {
303 | display: block;
304 | margin-bottom: 5px;
305 | color: #ddd;
306 | }
307 |
308 | .lego_it {
309 | box-sizing: border-box;
310 | padding: 2px 3px;
311 | font-size: 13px;
312 | color: #555;
313 | }
314 |
315 | .lego_it.__short {
316 | width: 80px;
317 | }
318 |
319 | .lego_it.__medium {
320 | width: 155px;
321 | }
322 |
323 |
324 |
325 | .lego_ic {
326 | width: 16px;
327 | height: 16px;
328 | background-repeat: no-repeat;
329 | }
330 |
331 | .lego_ic.lego_ic_close {
332 | background: url(/lego/assets/i/close.png);
333 | }
334 | .lego_ic.lego_ic_close:hover {
335 | background: url(/lego/assets/i/close_h.png);
336 | }
337 |
338 |
339 | /* Layers */
340 |
341 | /* default styles for all layers */
342 | .lego_layer {
343 | display: flex;
344 | position: relative;
345 | overflow: hidden;
346 | margin: 10px;
347 | min-height: calc(100% - 20px);
348 | }
349 | .lego_layer.__one-column,
350 | .lego_layer.__two-column,
351 | .lego_layer.__three-column {
352 | width: 1000px;
353 | margin: 0;
354 | margin-right: 20px;
355 | }
356 |
357 |
358 | /* without background of grid */
359 | .lego_layer.__hide-bg .lego_right-column,
360 | .lego_layer.__hide-bg .lego_sidebar,
361 | .lego_layer.__hide-bg .lego_toolbar,
362 | .lego_layer.__hide-bg .lego_navbar,
363 | .lego_layer.__hide-bg .lego_content {
364 | background: #fff;
365 | }
366 |
367 |
368 | /* by default all block exclude content_w are hidden */
369 | .lego_right-column,
370 | .lego_sidebar,
371 | .lego_toolbar,
372 | .lego_navbar,
373 | .lego_content[data-target="overlay"],
374 | .lego_content[data-target="primary"] {
375 | display: none;
376 | background: #FFF4F8;
377 | }
378 |
379 | .lego_toolbar {
380 | width: 100%;
381 | height: 48px;
382 | }
383 |
384 |
385 | .lego_sidebar {
386 | float: left;
387 | margin: 15px 0 0 15px;
388 | width: 210px;
389 | min-height: 685px;
390 | }
391 |
392 | .lego_content_w {
393 | width: 100%;
394 | margin-top: 45px;
395 | background: #fff;
396 | }
397 |
398 | .lego_content[data-target="primary"],
399 | .lego_content[data-target="overlay"]{
400 | width: 1000px;
401 | background: #FFF4F8;
402 | }
403 |
404 | .lego_navbar {
405 | float: right;
406 | min-height: 40px;
407 | width: 730px;
408 | margin-right: 15px;
409 | margin-top: 15px;
410 | }
411 |
412 | .lego_right-column {
413 | float: right;
414 | width: 215px;
415 | min-height: 600px;
416 | margin: 0 15px;
417 | }
418 |
419 | /* default */
420 |
421 | .lego_layer.__default .lego_content[data-target="overlay"] {
422 | display: inline-block;
423 | width: auto;
424 | min-width: 150px;
425 | padding: 10px;
426 | text-align: left;
427 | background: #fff;
428 | }
429 |
430 | .lego_layer.__default .lego_content_dec {
431 | display: flex;
432 | align-items: center;
433 | justify-content: center;
434 | text-align: center;
435 | padding-top: 50px;
436 | }
437 |
438 | .lego_layer.__default .lego_content_w {
439 | display: flex;
440 | justify-content: center;
441 | margin-top: 0;
442 | }
443 |
444 | /* one column */
445 | .lego_layer.__one-column .lego_toolbar,
446 | .lego_layer.__one-column .lego_content[data-target="primary"] {
447 | display: block;
448 | }
449 |
450 | .lego_layer.__one-column .lego_content[data-target="primary"] {
451 | width: 970px;
452 | margin: 15px;
453 | min-height: 40px;
454 | }
455 |
456 | .lego_layer.__one-column .lego_navbar {
457 | display: block;
458 | width: auto;
459 | float: none;
460 | margin-left: 15px;
461 | }
462 |
463 | /* two column layout */
464 | .lego_layer.__two-column .lego_navbar,
465 | .lego_layer.__two-column .lego_sidebar,
466 | .lego_layer.__two-column .lego_content[data-target="primary"],
467 | .lego_layer.__two-column .lego_toolbar { display: block; }
468 |
469 | .lego_layer.__two-column .lego_sidebar {
470 | width: 230px;
471 | }
472 |
473 | .lego_layer.__two-column .lego_navbar {
474 | width: 715px;
475 | margin-bottom: 15px;
476 | }
477 |
478 | .lego_layer.__two-column .lego_content[data-target="primary"] {
479 | float: right;
480 | width: 715px;
481 | margin-right: 15px;
482 | min-height: 40px;
483 | text-align: left;
484 | }
485 |
486 | /* three column */
487 | .lego_layer.__three-column .lego_navbar,
488 | .lego_layer.__three-column .lego_sidebar,
489 | .lego_layer.__three-column .lego_content[data-target="primary"],
490 | .lego_layer.__three-column .lego_toolbar,
491 | .lego_layer.__three-column .lego_right-column { display: block; }
492 |
493 | .lego_layer.__three-column .lego_content[data-target="primary"] {
494 | float: right;
495 | width: 500px;
496 | min-height: 40px;
497 | }
498 |
499 | .lego_layer.__three-column .lego_navbar {
500 | margin-bottom: 15px;
501 | }
502 |
503 | /* /Project
504 | ---------------------------------------------------------------------------------- */
505 |
506 | /* temp */
507 |
508 | .editable {
509 | box-shadow: 0 0 10px rgba(255,0,0,.5);
510 | }
511 |
512 | [data-active="true"] {
513 | outline: 1px solid #0CF;
514 | }
515 | [data-target="overlay"] [data-active="true"] {
516 | outline: 0 none;
517 | }
518 |
519 | [data-url]:before,
520 | [data-url]:after {
521 | content: " ";
522 | display: table;
523 | }
524 |
525 | [data-url]:after {
526 | clear: both;
527 | }
528 |
529 | [data-target="overlay"] > *:not([data-active="true"]) {
530 | position: absolute;
531 | top: -9999px;
532 | left: -9999px;
533 | }
534 |
535 | .__moved {
536 | position: absolute;
537 | opacity: 0.5;
538 | }
--------------------------------------------------------------------------------
/assets/js/main.js:
--------------------------------------------------------------------------------
1 | /*global window */
2 |
3 | (function (global) {
4 | "use strict";
5 |
6 | var $ = global.jQuery;
7 | var console = global.console;
8 | var modifiers = global.lego.modifiers;
9 | var clientTemplates = global.lego.clientTemplates;
10 |
11 | var $body = $('body');
12 |
13 | var possibleTargets = ['other', 'navbar', 'tertiary', 'secondary', 'primary'];
14 | var activeTargets = [];
15 | var acceptsHTML = false;
16 |
17 | // State variables
18 | var chosenNavigation;
19 | var $dragged = false;
20 |
21 | /* --- Служебные функции --- */
22 |
23 | // 0 или пусто - overlay (добавляется автоматически)
24 | // 1 - primary
25 | // 2 - secondary
26 | // 4 - tertiary
27 | // 8 - navbar
28 | // 16 - other
29 | var parseTargets = function (targets) {
30 | var result = [];
31 | var mask, i;
32 | for (mask = 16, i = 0; mask > 0; mask >>= 1, i++) {
33 | if (mask && targets) {
34 | result.push(possibleTargets[i]);
35 | }
36 | }
37 |
38 | // Предполагаем, что если цель явно не задана, элемент может упасть в произвольный контейнер
39 | if (!result.length) {
40 | result = possibleTargets.slice(0);
41 | }
42 |
43 | result.push('overlay');
44 | return result;
45 | };
46 |
47 | //Clearing chosen
48 | var clearChosen = function () {
49 | chosenNavigation = false;
50 | $('.lego_layer *').removeClass('editable');
51 | };
52 |
53 | // Вставка нового блока
54 | var insertChosen = function ($targetContainer) {
55 |
56 | if (chosenNavigation) {
57 | var name = chosenNavigation.text();
58 | var shortUrl = chosenNavigation.attr('data-spec-id');
59 | var fullUrl = global.lego.parsedTree[shortUrl].url;
60 |
61 | var currentHTML = $('').attr('data-container', acceptsHTML);
62 | var menuItem;
63 |
64 | // Создадим новый виртуальный блок
65 | var virtualBlock = new global.lego.VirtualBlock(shortUrl);
66 | var specId = virtualBlock.specId;
67 |
68 | // Добавляем новый элемент, сбросим признак активности
69 | modifiers.clearActiveNode();
70 |
71 | $targetContainer.append(currentHTML);
72 |
73 | // Добавить ссылку на элемент в правое меню
74 | menuItem = clientTemplates['element-item']({
75 | blockId: virtualBlock.id,
76 | specLink: fullUrl,
77 | specName: name
78 | });
79 | $("#current-elements").append(menuItem);
80 |
81 | // Работа с виртуальным блоком и рендер
82 | modifiers.getSpecHTML(specId, function () {
83 | modifiers
84 | .generateVariationsList(specId)
85 | .generateModificatorsList(virtualBlock.id)
86 | .setupVariationsList(virtualBlock.id)
87 | .setupModificatorsList(virtualBlock.id)
88 | .render(virtualBlock.id);
89 | });
90 |
91 | // После добавления элемента скрыть сетку
92 | $(".lego_layer").addClass('__hide-bg');
93 | }
94 |
95 | // Сбросить информацию для добавления блока в контейнер
96 | clearChosen();
97 | };
98 |
99 | /* --- Инициализация загрузки модификаторов --- */
100 | modifiers.init();
101 |
102 | /* --- Обработчики кликов --- */
103 |
104 | $('#export-img').on('click', function (e) {
105 | e.preventDefault();
106 |
107 | console.log('In development...');
108 | });
109 |
110 | $('#save-html').on('click', function (e) {
111 | e.preventDefault();
112 |
113 | var html = $('#working-space')[0].outerHTML;
114 | var $saveHtmlLk = $('#save-html-lk');
115 |
116 | var data = {
117 | html: html
118 | };
119 |
120 | var hostUrl = global.location.host;
121 |
122 | $.ajax({
123 | url: '/save',
124 | cache: false,
125 | data: data,
126 | success: function (data) {
127 | if (data.success) {
128 | if ($saveHtmlLk.length === 0) {
129 | $('#save-html').after('');
130 | } else {
131 | $saveHtmlLk.val(hostUrl + '/clean/' + data.name);
132 | }
133 |
134 | $saveHtmlLk.trigger('select');
135 | }
136 | }
137 | });
138 | });
139 |
140 | // Компонент тогглер-переключатель
141 | $(".lego_toggler").on("click", ".lego_toggle_i", function () {
142 | var targetNode = $('.lego_search-result.__layout');
143 |
144 | $(this)
145 | .addClass("__active")
146 | .siblings()
147 | .removeClass('__active');
148 |
149 | if ($(this).attr('data-target') === 'list') {
150 | targetNode.addClass("__list");
151 | } else {
152 | targetNode.removeClass("__list");
153 | }
154 |
155 | });
156 |
157 | // Вставить элемент в подсвеченную область, доступную для работы
158 | $body.on("click", ".editable", function (e) {
159 | e.stopPropagation(); // Не нужно сбрасывать фокус, это происходит при всплытии клика на контейнер
160 | insertChosen($(this));
161 | });
162 |
163 | // Клик по результатам поиска — выбор блока для вставки
164 | $("#lego_search-result").on("click", ".lego_search-result_i", function (e) {
165 | if (e.metaKey || e.ctrlKey) {
166 | return;
167 | }
168 |
169 | e.preventDefault();
170 |
171 | chosenNavigation = $(this);
172 | acceptsHTML = chosenNavigation.data('accepts');
173 |
174 | var i, j;
175 |
176 | // Инициализация областей, принимающих контент
177 | if (activeTargets.length) {
178 | for (i = 0; i < activeTargets.length; i++) {
179 | activeTargets[i].removeClass('editable');
180 | }
181 | activeTargets = [];
182 | }
183 |
184 | var targets = $(this)[0].dataset.target;
185 | var results = parseTargets(targets);
186 |
187 | for (j = 0; j < results.length; j++) {
188 | var elem = $('[data-target="' + results[j] + '"]');
189 | activeTargets.push(elem);
190 | elem.addClass('editable');
191 | }
192 |
193 | // Обходим все блоки, способные принимать в себя контент
194 | $('[data-container="true"]').each(function () {
195 | activeTargets.push($(this));
196 | $(this).addClass('editable');
197 | $(this).find('div, span').each(function () {
198 | activeTargets.push($(this));
199 | $(this).addClass('editable');
200 | });
201 | });
202 |
203 | // Вставка выбранного блока
204 | if (activeTargets.length < 3) {
205 | for (i = 0; i < activeTargets.length; i++) {
206 | if (activeTargets[i].is(':visible')) {
207 | insertChosen(activeTargets[i]);
208 | }
209 | }
210 | } else if ($('[data-target="overlay"]:visible').length !== 0 || $('[data-target="container"]:visible').length !== 0) {
211 | for (i = 0; i < activeTargets.length; i++) {
212 | if (activeTargets[i].selector === '[data-target="overlay"]') {
213 | insertChosen(activeTargets[i]);
214 | }
215 | }
216 | }
217 | });
218 |
219 | // Переключение режимов сетки
220 | $body.on('click', '.js-layouts-list .lego_search-result_i', function (e) {
221 | e.preventDefault();
222 |
223 | var layerMode = $(this).find('.lego_search-result_n').data('layout');
224 |
225 | $('.lego_layer')
226 | .removeClass('__default __one-column __two-column __three-column __hide-bg')
227 | .addClass('__' + layerMode);
228 | });
229 |
230 | // Подсветить блоки по клику
231 | $body.on('click', '.lego_main [data-id]', function (e) {
232 | if (e.metaKey || e.ctrlKey) {
233 | return;
234 | }
235 |
236 | var virtualBlockId = $(this).attr('data-id') || $(this).closest('.lego_main [data-id]').attr('data-id');
237 |
238 | modifiers
239 | .generateVariationsList(global.lego.elementList[virtualBlockId].specId)
240 | .generateModificatorsList(virtualBlockId)
241 | .setupVariationsList(virtualBlockId)
242 | .setupModificatorsList(virtualBlockId)
243 | .setActiveNode(virtualBlockId); // Перерендер по клику не нужен
244 |
245 | return false;
246 | });
247 |
248 | // Обработка кликов по вариациям и модификаторам в сайдбаре
249 | $body.on('click', '.lego_checkbox', function () {
250 |
251 | var $activeNode = modifiers.getActiveNode();
252 | var activeVirtualBlockId = $activeNode.attr('data-id');
253 | var activeVirtualBlock = global.lego.elementList[activeVirtualBlockId];
254 |
255 | if ($(this).attr('name') === 'variations') {
256 |
257 | // Получить номер выбранной вариации
258 | var variationValue = $(this).attr('data-variation');
259 |
260 | // Сохранить номер в модели блока
261 | activeVirtualBlock.save({
262 | variation: variationValue
263 | });
264 |
265 | // Перерендерить список модификаторов в соответствии с новой вариацией
266 | modifiers
267 | .generateModificatorsList(activeVirtualBlockId)
268 | .setupModificatorsList(activeVirtualBlockId);
269 |
270 | } else {
271 | var variationExport = {};
272 |
273 | // Собрать новый набор модификаторов
274 | $('.js-modificators .lego_expand').each(function () {
275 | var blockTitle = $(this).children('.lego_expand_h').text();
276 |
277 | variationExport[blockTitle] = [];
278 |
279 | $(this).find('.lego_checkbox').each(function () {
280 | if ($(this).is(':checked')) {
281 | variationExport[blockTitle].push($(this).attr('data-mod'));
282 | }
283 | });
284 | });
285 |
286 | // Сохранить новый набор модификаторов
287 | activeVirtualBlock.save({
288 | modifiers: variationExport,
289 | changed: true
290 | });
291 | }
292 |
293 | // Перерендерить с учетом изменений
294 | modifiers.render();
295 | });
296 |
297 | // Обработка кликов переключения блоков
298 | $body.on('click', '#current-elements .lego_lk', function (e) {
299 | if (e.metaKey || e.ctrlKey) {
300 | return;
301 | }
302 |
303 | e.preventDefault();
304 |
305 | var virtualBlockId = $(this).parent().attr('data-id');
306 | var $allLegoLk = $("#current-elements").find(".lego_lk");
307 |
308 | if (virtualBlockId) {
309 | $allLegoLk.removeClass("__active");
310 | $(this).addClass('__active');
311 |
312 | modifiers
313 | .generateVariationsList(global.lego.elementList[virtualBlockId].specId)
314 | .generateModificatorsList(virtualBlockId)
315 | .setupVariationsList(virtualBlockId)
316 | .setupModificatorsList(virtualBlockId)
317 | .render(virtualBlockId);
318 | }
319 | });
320 |
321 | // Обработка кликов по иконке удаления блока
322 | $body.on('click', '.lego_ic_close', function () {
323 |
324 | var activeBlockId = modifiers.getActiveNode().attr('data-id');
325 | var $listItem = $(this).parent('.lego_widget_ul-i');
326 | var virtualBlockId = $listItem.attr('data-id');
327 | var $blockNode = $('.lego_main [data-id="' + virtualBlockId + '"]');
328 | var $candidatListItem = $listItem.prev();
329 | var candidatVirtualBlockId = false;
330 |
331 | // По умолчанию при удалении переключаемся на предыдущий элемент,
332 | // Но если его нет, то пытаемся на следующий
333 | if (!$candidatListItem.length) {
334 | $candidatListItem = $listItem.next();
335 | }
336 |
337 | if (delete global.lego.elementList[virtualBlockId]) {
338 |
339 | $blockNode.remove(); // удалить блок с холста
340 | $listItem.remove(); // удалить элемент в списке
341 |
342 | // Переключиться на новый блок и отрендерить его в том случае,
343 | // если на холсте вообще остаются какие-либо элементы
344 | // и при этом удаляемый элемент является активным
345 | if ($candidatListItem.length && activeBlockId === virtualBlockId) {
346 | candidatVirtualBlockId = $candidatListItem.attr('data-id');
347 |
348 | modifiers
349 | .generateVariationsList(global.lego.elementList[candidatVirtualBlockId].specId)
350 | .generateModificatorsList(candidatVirtualBlockId)
351 | .setupVariationsList(candidatVirtualBlockId)
352 | .setupModificatorsList(candidatVirtualBlockId)
353 | .render(candidatVirtualBlockId);
354 | } else {
355 |
356 | // Если элементов нет, очистить сайдбар
357 | if (!Object.keys(global.lego.elementList).length) {
358 | modifiers
359 | .generateVariationsList()
360 | .generateModificatorsList();
361 | }
362 | }
363 | }
364 | });
365 |
366 | $body.on('click', '.lego_main', function () {
367 | // Очищать выделение только при наличии сетки
368 | if (!$('.lego_layer.__default').length) {
369 | $('.lego_layer').addClass('__hide-bg');
370 | modifiers.clearActiveNode();
371 | }
372 | });
373 |
374 | /* Drag and drop */
375 | $body.on('dragstart', function (e) {
376 | $dragged = $(e.target);
377 |
378 | // Не запускать в режиме по умолчанию
379 | if ($('.lego_layer.__default').length) {
380 | return false;
381 | }
382 |
383 | // Перетаскивание блока на холсте
384 | if ($dragged.attr('data-id') !== undefined) {
385 | $('.lego_layer').removeClass('__hide-bg');
386 | e.target.style.opacity = 0.5;
387 | } else {
388 | // Перетаскивание блока из нав.меню
389 | if ($dragged.attr('data-spec-id') !== undefined) {
390 | $('.lego_layer').removeClass('__hide-bg');
391 | chosenNavigation = $dragged;
392 | } else {
393 | $dragged = false;
394 | e.preventDefault();
395 | }
396 | }
397 | });
398 |
399 | $body.on('dragend', function (e) {
400 | if ($dragged) {
401 | $('.lego_layer').addClass('__hide-bg');
402 | e.target.style.opacity = "";
403 | }
404 | });
405 |
406 | $body.on('dragover', function (e) {
407 | // prevent default to allow drop
408 | e.preventDefault();
409 | });
410 |
411 | $body.on('dragenter', function (e) {
412 | e.preventDefault();
413 |
414 | if ($dragged) {
415 | var $closestDataTarget = $(e.target).closest('[data-target]');
416 |
417 | if ($closestDataTarget.length) {
418 | $closestDataTarget.addClass('editable');
419 | }
420 | }
421 | });
422 |
423 | $body.on('dragleave', function (e) {
424 | if ($dragged) {
425 | var $closestDataTarget = $(e.target).closest('[data-target]');
426 |
427 | if ($closestDataTarget.length) {
428 | $closestDataTarget.removeClass('editable');
429 | }
430 | }
431 | });
432 |
433 | $body.on('drop', function (e) {
434 | e.preventDefault();
435 | if ($dragged) {
436 | var $closestDataTarget = $(e.target).closest('[data-target]');
437 | if ($closestDataTarget.length) {
438 | if ($dragged.attr('data-id') !== undefined) {
439 | $closestDataTarget
440 | .append($dragged)
441 | .removeClass('editable');
442 | } else if ($dragged.attr('data-spec-id')) {
443 | insertChosen($closestDataTarget);
444 | $closestDataTarget
445 | .removeClass('editable');
446 | }
447 | }
448 | }
449 | });
450 |
451 | }(window));
--------------------------------------------------------------------------------
/assets/js/modifiers.js:
--------------------------------------------------------------------------------
1 | /*global window */
2 |
3 | (function (global) {
4 | "use strict";
5 |
6 | var allModifiers = false; // Хранит объект всех блоков, элементов и модификаторов
7 | var $ = global.jQuery;
8 | var globalOptions = global.lego.globalOptions;
9 | var component = global.lego.component;
10 | var clientTemplates = global.lego.clientTemplates;
11 |
12 | // Получает все доступные модификаторы для всех блоков и эдементов
13 | function getCSSMod(callback) {
14 | var cb = callback || function () {};
15 |
16 | if (!allModifiers) {
17 | $.ajax({
18 | url: "/cssmod",
19 | type: 'POST',
20 | data: JSON.stringify({
21 | // Массив в перечислением css-файлов для анализа
22 | // Может быть импортирован из глобальных настроек или задан вручную
23 | files: globalOptions.cssMod.files
24 | }),
25 | dataType: 'json',
26 | contentType: "application/json", // нужен для обработки параметров POST-запроса в express
27 | success: function (data) {
28 | allModifiers = $.extend({}, data, true);
29 | cb();
30 | }
31 | });
32 | } else {
33 | cb();
34 | }
35 | }
36 |
37 | // Получает HTML-шаблон блока
38 | function getHTMLpart(specId, callback) {
39 | var cb = callback || function () {};
40 |
41 | // Если в глобальном объекте спек нет экземпляра с этим id, выкачать и сохранить, иначе отдать готовый
42 | if (!global.lego.specList[specId]) {
43 | var specsMaster = globalOptions.specsMaster.current;
44 | var customDataUrl = globalOptions.specsMaster.customDataUrl;
45 |
46 | var processData = function (data) {
47 | var flatSections = [];
48 | var tempNode;
49 | var i;
50 |
51 | // Нам нужно развернуть древовидную структуру «секция[]»-«подсекция[]»—...—«примеры[]» в плоский массив
52 | // Все html хранятся в глобальном объекте спецификаций, индекс — из параметра
53 | (function flatten(target) {
54 |
55 | for (i = 0; i < target.length; i++) {
56 |
57 | // Выбросим блоки, в которых нет html-кода
58 | if (target[i].html.length) {
59 |
60 | // Для хорошей вставки шаблона нужно, чтобы был только один корневой узел
61 | tempNode = '' + target[i].html[0] + '
';
62 | if ($(tempNode).children().length > 1) {
63 | target[i].html = [tempNode];
64 | }
65 |
66 | flatSections.push(target[i]);
67 | }
68 |
69 | if (target[i].nested && target[i].nested.length) {
70 | flatten(target[i].nested);
71 | }
72 | }
73 | }(data.contents));
74 |
75 | global.lego.specList[specId] = flatSections;
76 | cb(global.lego.specList[specId]);
77 | };
78 |
79 | if (customDataUrl) {
80 | processData(global.lego.parsedTree[specId]);
81 | } else {
82 | $.ajax(specsMaster + '/api/specs/html', {
83 | data: {
84 | id: specId
85 | },
86 | dataType: "json",
87 | type: 'GET',
88 | success: processData
89 | });
90 | }
91 | } else {
92 | cb(global.lego.specList[specId]);
93 | }
94 | }
95 |
96 |
97 | // Применяет атрибуты виртуального блока к DOM-узлу
98 | function applyAttributes(virtualBlockId, $node) {
99 | var blockModifiers = global.lego.elementList[virtualBlockId].modifiers;
100 | var currentBlock;
101 | var currentModifier;
102 |
103 | for (currentBlock in blockModifiers) {
104 |
105 | if (blockModifiers.hasOwnProperty(currentBlock)) {
106 | var childBlocks = $node.find('.' + currentBlock);
107 |
108 | var allBlocksModifiers = allModifiers[currentBlock];
109 | var usedModifiers = blockModifiers[currentBlock];
110 |
111 | // Применяем к детям в неограниченном количестве
112 | // Эксперимент: сбросим исходные модификаторы
113 | childBlocks.each(function () {
114 |
115 | // TODO: remove mods removal
116 | for (currentModifier = 0; currentModifier < allBlocksModifiers.length; currentModifier++) {
117 | $(this).removeClass(allBlocksModifiers[currentModifier]);
118 | }
119 | });
120 |
121 | // Эксперимент: применять модификатор не ко всем подходящим элементам, а только к одному — первому
122 | for (currentModifier = 0; currentModifier < usedModifiers.length; currentModifier++) {
123 | childBlocks.eq(0).addClass(usedModifiers[currentModifier]);
124 | }
125 | }
126 | }
127 | }
128 |
129 |
130 | // Просматриваем структуру на предмет вариаций, и на основе полученного
131 | // генерируем список вариаций в правом сайдбаре
132 | function generateVariationList(specId) {
133 |
134 | var $wrap = $('.js-variations .lego_form-i');
135 | var template;
136 |
137 | $wrap.empty();
138 |
139 | if (specId === undefined) {
140 | return;
141 | }
142 |
143 | // Рендер секции в сайдбаре
144 | template = clientTemplates['variation-item'](global.lego.specList[specId]);
145 | $wrap.append(template);
146 | }
147 |
148 |
149 | // Определение проектного класса на основе сопоставления иерархии разметки и присутствия
150 | // классов в дереве модификаторов (рассматриваются все классы в блоке)
151 | function detectBlockClassName(allSelectors) {
152 | var blockDetect = new RegExp(globalOptions.cssMod.rules.blockRule);
153 | var result = '';
154 | var currentSelector;
155 | var curClassList;
156 |
157 | // По всем селекторам, содержащим класс
158 | for (currentSelector = 0; currentSelector < allSelectors.length; currentSelector++) {
159 | var currentSelectorClassList = allSelectors[currentSelector].classList;
160 |
161 | if (result) {
162 | break;
163 | }
164 |
165 | // По всем классам в полученных селекторах
166 | for (curClassList = 0; curClassList < currentSelectorClassList.length; curClassList++) {
167 | var currElem = currentSelectorClassList[curClassList];
168 |
169 | if (blockDetect.test(currElem)) {
170 | result = currElem;
171 | break;
172 | }
173 | }
174 | }
175 |
176 | return result;
177 | }
178 |
179 | // Поиск доступных для блока+элементов модификаторов и рендер в правом сайдбаре
180 | function generateModificatorsList(virtualBlockId) {
181 |
182 | var $wrap = $('.js-modificators .lego_form-i');
183 | var usedModifiers = [];
184 | var linksBlockToExpand = {};
185 | var template;
186 |
187 | $wrap.empty();
188 |
189 | if (virtualBlockId === undefined) {
190 | return;
191 | }
192 |
193 | var virtualBlockSpecId = global.lego.elementList[virtualBlockId].specId;
194 | var virtualBlockVariation = global.lego.elementList[virtualBlockId].variation;
195 | var virtualBlockHTML = global.lego.specList[virtualBlockSpecId][virtualBlockVariation].html[0]; // только первый source_example
196 |
197 | // Создадим временный узел для работы с классами через DOM
198 | $('body').append('' + virtualBlockHTML + '
');
199 |
200 | // Дальше будет удобно работать с массивом классов, переходим на DOM ClassList
201 | var allSelectors = global.document.querySelectorAll('.temp-node [class]');
202 |
203 | // Если работа со вложенными блоками выключена,
204 | // попробуем определить проектный класс
205 | var projectClass = detectBlockClassName(allSelectors);
206 | var baseClass = !globalOptions.modifyInnerBlocks
207 | ? projectClass
208 | : '';
209 |
210 | var currentSelector;
211 |
212 | // По всем селекторам, содержащим класс
213 | for (currentSelector = 0; currentSelector < allSelectors.length; currentSelector++) {
214 | var currentSelectorClassList = allSelectors[currentSelector].classList;
215 | var curClassList;
216 |
217 | // перебор классов в класслисте
218 | for (curClassList = 0; curClassList < currentSelectorClassList.length; curClassList++) {
219 |
220 | // Выбираются только те, которые принадлежат подмножеству проектного класса
221 | if (currentSelectorClassList[curClassList].indexOf(baseClass) !== -1) {
222 |
223 | var currElem = currentSelectorClassList[curClassList];
224 |
225 | // Если блок обладает модификаторами
226 | if ((allModifiers[currElem])) {
227 | var currentModifier;
228 |
229 | for (currentModifier = 0; currentModifier < allModifiers[currElem].length; currentModifier++) {
230 |
231 | // если такой блок+модификатор уже использовался, выйти
232 | if (usedModifiers.indexOf(currElem + allModifiers[currElem][currentModifier]) !== -1) {
233 | continue;
234 | }
235 | usedModifiers.push(currElem + allModifiers[currElem][currentModifier]);
236 |
237 | if (!linksBlockToExpand[currElem]) {
238 | var isClosed = false;
239 | if (Object.keys(linksBlockToExpand).length) {
240 | isClosed = true;
241 | }
242 | linksBlockToExpand[currElem] = component.expand.create($wrap, currElem, isClosed);
243 | }
244 |
245 | template = clientTemplates['modifier-item']({
246 | element: currElem,
247 | modifier: allModifiers[currentSelectorClassList[curClassList]][currentModifier],
248 | modifierName: allModifiers[currElem][currentModifier]
249 | });
250 | component.expand.append(linksBlockToExpand[currElem], template);
251 | }
252 | }
253 | }
254 | }
255 | }
256 |
257 | // Удаляем временный блок
258 | $('.temp-node').remove();
259 | }
260 |
261 | // Перещелкнуть пункт в меню
262 | function setupBlockList(virtualBlockId) {
263 | // Перещелкнуть активную ссылку на блок в меню
264 | var $currentElements = $('#current-elements');
265 |
266 | $currentElements.find('.lego_lk').removeClass('__active');
267 |
268 | if (virtualBlockId) {
269 | $currentElements.find('.lego_widget_ul-i[data-id="' + virtualBlockId + '"] .lego_lk').addClass('__active');
270 | }
271 | }
272 |
273 | // Проставляет активную вариацию в сайдбаре
274 | function setupVariationsList(virtualBlockId) {
275 |
276 | if (virtualBlockId === undefined) {
277 | return;
278 | }
279 |
280 | $('.js-variations .lego_form-i_w')
281 | .eq(global.lego.elementList[virtualBlockId].variation)
282 | .find('input')
283 | .prop('checked', true);
284 |
285 | }
286 |
287 | // Проставляет галочки модификаторов для хтмл из вариации
288 | function setupModificatorsList(virtualBlockId) {
289 | var modifiersData = {};
290 |
291 | if (virtualBlockId === undefined) {
292 | return;
293 | }
294 |
295 | var virtualBlock = global.lego.elementList[virtualBlockId];
296 | var virtualBlockSpecId = virtualBlock.specId;
297 | var virtualBlockVariation = virtualBlock.variation;
298 | var virtualBlockHTML = '' + global.lego.specList[virtualBlockSpecId][virtualBlockVariation].html[0] + '
'; // только первый source_example
299 | var $virtualBlockHTML = $(virtualBlockHTML);
300 | var block;
301 |
302 | $('input[name="modificators"]').each(function () {
303 |
304 | var modValue = $(this).attr('data-mod');
305 | var elemValue = $(this).attr('data-elem');
306 | var $affectedNodes = $virtualBlockHTML.find('.' + elemValue + '.' + modValue);
307 |
308 | $(this).prop('checked', false);
309 |
310 | if ($affectedNodes.length) {
311 | $(this).prop('checked', true);
312 |
313 | if (!modifiersData[elemValue]) {
314 | modifiersData[elemValue] = [];
315 | }
316 |
317 | modifiersData[elemValue].push(modValue);
318 | }
319 | });
320 |
321 | // Восстановим данные из виртуального блока, если они там существуют
322 | if (Object.keys(virtualBlock.modifiers).length) {
323 | for (block in virtualBlock.modifiers) {
324 | if (virtualBlock.modifiers.hasOwnProperty(block)) {
325 | var modifier;
326 | $('input[name="modificators"][data-elem="' + block + '"]').prop('checked', false);
327 |
328 | for (modifier = 0; modifier < virtualBlock.modifiers[block].length; modifier++) {
329 | $('input[name="modificators"][data-elem="' + block + '"][data-mod="' + virtualBlock.modifiers[block][modifier] + '"]').prop('checked', true);
330 | }
331 | }
332 | }
333 |
334 | modifiersData = virtualBlock.modifiers;
335 | }
336 |
337 | // Сохраним полученные в результате анализа вариации модификаторы в модели
338 | virtualBlock.save({
339 | modifiers: modifiersData
340 | });
341 | }
342 |
343 | // Получить активную ноду
344 | function getActiveNode() {
345 | return $('.lego_main [data-active="true"]').eq(0);
346 | }
347 |
348 | // Сбросить фокус с активной ноды
349 | function clearActiveNode() {
350 | getActiveNode().removeAttr('data-active');
351 | }
352 |
353 | // Поставить фокус на узел
354 | function setActiveNode(virtualBlockId) {
355 | clearActiveNode();
356 |
357 | if (!virtualBlockId) {
358 | return;
359 | }
360 |
361 | $('.lego_main [data-id="' + virtualBlockId + '"]').attr('data-active', true);
362 |
363 | // Подсветить в списке блоков
364 | setupBlockList(virtualBlockId);
365 | }
366 |
367 | // Отрисовывает блок с учетом диффа
368 | function render(virtualBlockId) {
369 | var $activeElement = '';
370 |
371 | // Если блок для отрисовки не указан явно, накатываем изменения на текущий активный блок
372 | if (!virtualBlockId) {
373 | $activeElement = getActiveNode();
374 | virtualBlockId = $activeElement.attr('data-id');
375 | } else {
376 | // Приоритет узла за блоком с заданным id, однако при инициализации такого атрибута может еще не быть
377 | $activeElement = $('.lego_main [data-id="' + virtualBlockId + '"]');
378 |
379 | if (!$activeElement.length) {
380 | $activeElement = getActiveNode();
381 | }
382 | }
383 |
384 | // Может оказаться, что рендерить нечего
385 | if (!$activeElement.length) {
386 | return;
387 | }
388 |
389 | // Мы знаем, с каким элементом мы работаем, можно удалить признак активности
390 | clearActiveNode();
391 |
392 | var virtualBlock = global.lego.elementList[virtualBlockId];
393 | var virtualBlockSpecId = virtualBlock.specId;
394 | var virtualBlockVariation = virtualBlock.variation;
395 | var virtualBlockOriginHTML = global.lego.specList[virtualBlockSpecId][virtualBlockVariation].html[0]; // Только первый source_example
396 |
397 | // Создадим временный блок и применим к нему дифф из виртуального блока
398 | var $tempHTML = $('' + virtualBlockOriginHTML + '
');
399 |
400 | // Накладывать будем только на измененный блок
401 | if (virtualBlock.changed) {
402 | applyAttributes(virtualBlockId, $tempHTML);
403 | }
404 |
405 | // На самом деле нам интересно только содержимое временного блока
406 | $tempHTML = $tempHTML.children();
407 |
408 | $tempHTML
409 | .attr('draggable', true)
410 | .attr('data-active', true)
411 | .attr('data-url', virtualBlockSpecId)
412 | .attr('data-id', virtualBlockId);
413 |
414 | $activeElement.replaceWith($tempHTML);
415 |
416 | // Перещелкнуть список блоков
417 | setupBlockList(virtualBlockId);
418 | }
419 |
420 | var modifiers = global.lego.modifiers = {
421 | init: function (callback) {
422 | getCSSMod(callback);
423 | return this;
424 | },
425 |
426 | getSpecHTML: function (specId, callback) {
427 | var cb = callback || function () {};
428 |
429 | // Если модификаторы не загружены, загрузить и работать дальше
430 | getCSSMod(function () {
431 | // Получить вариации спецификации
432 | getHTMLpart(specId, function () {
433 | cb();
434 | });
435 | });
436 |
437 | return this;
438 | },
439 |
440 | generateVariationsList: function (specId) {
441 | generateVariationList(specId);
442 |
443 | return this;
444 | },
445 |
446 | generateModificatorsList: function (virtualBlockId) {
447 | generateModificatorsList(virtualBlockId);
448 |
449 | return this;
450 | },
451 |
452 | setupVariationsList: function (virtualBlockId) {
453 | setupVariationsList(virtualBlockId);
454 |
455 | return this;
456 | },
457 |
458 | setupModificatorsList: function (virtualBlockId) {
459 | setupModificatorsList(virtualBlockId);
460 |
461 | return this;
462 | },
463 |
464 |
465 | getActiveNode: function () {
466 |
467 | return getActiveNode();
468 | },
469 |
470 | clearActiveNode: function () {
471 | // Для публичного метода делаем больше работы:
472 | // сбрасываем фокус, очищаем правый сайдбар и сбрасываем выделение в списке блоков
473 |
474 | clearActiveNode();
475 | generateVariationList();
476 | generateModificatorsList();
477 | setupBlockList();
478 |
479 | return this;
480 | },
481 |
482 | setActiveNode: function (virtualBlockId) {
483 | setActiveNode(virtualBlockId);
484 |
485 | return this;
486 | },
487 |
488 | render: function (virtualBlockId) {
489 | render(virtualBlockId);
490 |
491 | return this;
492 | }
493 | };
494 | }(window));
--------------------------------------------------------------------------------
/user-bootstrap/data/bootstrap/js/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.2.0 (http://getbootstrap.com)
3 | * Copyright 2011-2014 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.2.0",d.prototype.close=function(b){function c(){f.detach().trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",c).emulateTransitionEnd(150):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.2.0",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),d[e](null==f[b]?this.options[b]:f[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b).on("keydown.bs.carousel",a.proxy(this.keydown,this)),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.2.0",c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},c.prototype.keydown=function(a){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.to=function(b){var c=this,d=this.getItemIndex(this.$active=this.$element.find(".item.active"));return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=e[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:g});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,f&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(e)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:g});return a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one("bsTransitionEnd",function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger(m)),f&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(b=!b),e||d.data("bs.collapse",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};c.VERSION="3.2.0",c.DEFAULTS={toggle:!0},c.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},c.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var c=a.Event("show.bs.collapse");if(this.$element.trigger(c),!c.isDefaultPrevented()){var d=this.$parent&&this.$parent.find("> .panel > .in");if(d&&d.length){var e=d.data("bs.collapse");if(e&&e.transitioning)return;b.call(d,"hide"),e||d.data("bs.collapse",null)}var f=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[f](0),this.transitioning=1;var g=function(){this.$element.removeClass("collapsing").addClass("collapse in")[f](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return g.call(this);var h=a.camelCase(["scroll",f].join("-"));this.$element.one("bsTransitionEnd",a.proxy(g,this)).emulateTransitionEnd(350)[f](this.$element[0][h])}}},c.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},c.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var d=a.fn.collapse;a.fn.collapse=b,a.fn.collapse.Constructor=c,a.fn.collapse.noConflict=function(){return a.fn.collapse=d,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(c){var d,e=a(this),f=e.attr("data-target")||c.preventDefault()||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),g=a(f),h=g.data("bs.collapse"),i=h?"toggle":e.data(),j=e.attr("data-parent"),k=j&&a(j);h&&h.transitioning||(k&&k.find('[data-toggle="collapse"][data-parent="'+j+'"]').not(e).addClass("collapsed"),e[g.hasClass("in")?"addClass":"removeClass"]("collapsed")),b.call(g,i)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.2.0",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('').insertAfter(a(this)).on("click",b);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(b){if(/(38|40|27)/.test(b.keyCode)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var e=c(d),g=e.hasClass("open");if(!g||g&&27==b.keyCode)return 27==b.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.divider):visible a",i=e.find('[role="menu"]'+h+', [role="listbox"]'+h);if(i.length){var j=i.index(i.filter(":focus"));38==b.keyCode&&j>0&&j--,40==b.keyCode&&j').appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus.call(this.$element[0]):this.hide.call(this))},this)),e&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;e?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(150):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var f=function(){c.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",f).emulateTransitionEnd(150):f()}else b&&b()},c.prototype.checkScrollbar=function(){document.body.clientWidth>=window.innerWidth||(this.scrollbarWidth=this.scrollbarWidth||this.measureScrollbar())},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.scrollbarWidth&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right","")},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;(e||"destroy"!=b)&&(e||d.data("bs.tooltip",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",a,b)};c.VERSION="3.2.0",c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport);for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show()},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var c=a.contains(document.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!c)return;var d=this,e=this.tip(),f=this.getUID(this.type);this.setContent(),e.attr("id",f),this.$element.attr("aria-describedby",f),this.options.animation&&e.addClass("fade");var g="function"==typeof this.options.placement?this.options.placement.call(this,e[0],this.$element[0]):this.options.placement,h=/\s?auto?\s?/i,i=h.test(g);i&&(g=g.replace(h,"")||"top"),e.detach().css({top:0,left:0,display:"block"}).addClass(g).data("bs."+this.type,this),this.options.container?e.appendTo(this.options.container):e.insertAfter(this.$element);var j=this.getPosition(),k=e[0].offsetWidth,l=e[0].offsetHeight;if(i){var m=g,n=this.$element.parent(),o=this.getPosition(n);g="bottom"==g&&j.top+j.height+l-o.scroll>o.height?"top":"top"==g&&j.top-o.scroll-l<0?"bottom":"right"==g&&j.right+k>o.width?"left":"left"==g&&j.left-kg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.validate=function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){clearTimeout(this.timeout),this.hide().$element.off("."+this.type).removeData("bs."+this.type)};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||"destroy"!=b)&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.2.0",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").empty()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},c.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){var e=a.proxy(this.process,this);this.$body=a("body"),this.$scrollElement=a(a(c).is("body")?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",e),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.2.0",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b="offset",c=0;a.isWindow(this.$scrollElement[0])||(b="position",c=this.$scrollElement.scrollTop()),this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight();var d=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+c,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){d.offsets.push(this[0]),d.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<=e[0])return g!=(a=f[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parentsUntil(this.options.target,".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.2.0",c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.closest("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},c.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one("bsTransitionEnd",e).emulateTransitionEnd(150):e(),f.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(c){c.preventDefault(),b.call(a(this),"show")})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.2.0",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=a(document).height(),d=this.$target.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top(this.$element)),"function"==typeof h&&(h=f.bottom(this.$element));var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=b-h?"bottom":null!=g&&g>=d?"top":!1;if(this.affixed!==i){null!=this.unpin&&this.$element.css("top","");var j="affix"+(i?"-"+i:""),k=a.Event(j+".bs.affix");this.$element.trigger(k),k.isDefaultPrevented()||(this.affixed=i,this.unpin="bottom"==i?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(j).trigger(a.Event(j.replace("affix","affixed"))),"bottom"==i&&this.$element.offset({top:b-this.$element.height()-h}))}}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},d.offsetBottom&&(d.offset.bottom=d.offsetBottom),d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);
--------------------------------------------------------------------------------
/user-bootstrap/data/bootstrap/fonts/glyphicons-halflings-regular.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------