';
+ }
+ };
+
+ var $ = cheerio.load(marked(input));
+
+ // Spec description
+ var $H1 = $('h1');
+ var $afterH1 = $H1.nextUntil('h2');
+ $afterH1.remove();
+ $H1.after('
'+ $afterH1 +'
');
+
+ // Spec sections
+ $('h2').each(function(){
+ var $this = $(this);
+ var $sectionElems = $this.nextUntil('h2');
+ var id = $this.attr('id');
+ $this.removeAttr('id');
+ $sectionElems.remove();
+
+ $(this).replaceWith([
+ '
',
+ $this + $sectionElems,
+ '
'
+ ].join(''));
+
+ });
+
+ req.specData.renderedHtml = $.html();
+
+ var end = process.hrtime(start);
+ global.log.debug('Markdown processing took: ', prettyHrtime(end));
+
+ next();
+ } else {
+ next();
+ }
+};
\ No newline at end of file
diff --git a/core/middleware/markdown.js b/core/middleware/mdTag.js
similarity index 100%
rename from core/middleware/markdown.js
rename to core/middleware/mdTag.js
diff --git a/core/middleware/read.js b/core/middleware/read.js
index 1935bd5..c08b117 100644
--- a/core/middleware/read.js
+++ b/core/middleware/read.js
@@ -5,134 +5,123 @@ var path = require('path');
var pathToApp = path.dirname(require.main.filename);
/**
- * Get spec content and write it to request
+ * Handling Spec request
*
* @param {object} req - Request object
* @param {object} res - Response object
* @param {function} next - The callback function
* */
-var handleRequest = function(req, res, next) {
+var handleSpec = function(req, res, next) {
+ // Filled during middleware processing
req.specData = {};
- // get the physical path of a requested file
- var physicalPath = global.app.get('user') + req.path;
+ // Get the physical path of a requested file
+ var physicalPath = path.join(global.app.get('user'), req.path);
// TODO: move to config with array of exclusions
if (req.path.lastIndexOf('/docs/', 0) === 0) {
physicalPath = pathToApp + req.path;
}
+ // Extension of a requested file
+ var extension = path.extname(physicalPath).replace(".", "");
var directory = path.dirname(physicalPath); // get the dir of a requested file
- //var filename = path.basename(physicalPath); // filename of a requested file
- var extension = path.extname(physicalPath).replace(".", ""); // extension of a requested file
- var supportedExtensions = global.opts.core.common.extensions;
- var extIndex = supportedExtensions.indexOf(extension);
var infoJson = directory + '/' + global.opts.core.common.infoFile;
- // in case if one of supported filetypes is requested
- if (extIndex >= 0) {
- fs.exists(physicalPath, function(exists) {
-
- if (exists) {
- fs.readFile(physicalPath, 'utf8', function (err, data) {
- data = data.replace(/^\s+|\s+$/g, '');
- if (err) {
- res.send(err);
- } else {
-
- fs.readFile(infoJson, 'utf8', function (err, info) {
- if (err) {
- info = {
- title: "New spec",
- author: "Anonymous",
- keywords: ""
- };
- } else {
- info = JSON.parse(info);
- }
-
- // if requested file is one of supported filetypes, then write proper flag to request. f.e. req.specData.isJade; // true
- if (extension === supportedExtensions[extIndex]) {
- var capitalizedExtension = extension.charAt(0).toUpperCase() + extension.slice(1);
- req.specData["is" + capitalizedExtension] = true;
- }
-
- req.specData.info = info; // add spec info object to request
- req.specData.renderedHtml = data; // add spec content to request
-
- next();
- });
- }
-
- });
+ fs.readFile(physicalPath, 'utf8', function (err, data) {
+ if (err) {
+ res.send(err);
+ return;
+ }
+
+ data = data.replace(/^\s+|\s+$/g, '');
+ fs.readFile(infoJson, 'utf8', function (err, info) {
+ if (err) {
+ info = {
+ title: "New spec",
+ author: "Anonymous",
+ keywords: ""
+ };
} else {
- next();
+ info = JSON.parse(info);
}
+
+ var capitalizedExtension = extension.charAt(0).toUpperCase() + extension.slice(1);
+
+ req.specData["is" + capitalizedExtension] = true;
+ req.specData.info = info; // add spec info object to request
+ req.specData.renderedHtml = data; // add spec content to request
+
+ next();
});
+ });
+};
+
+/**
+ * Checking if Spec is requested
+ *
+ * @param {object} req - Request object
+ * @param {object} res - Response object
+ * @param {function} next - The callback function
+ * */
+exports.process = function(req, res, next) {
+ // Get the physical path of a requested file
+ var physicalPath = global.app.get('user') + req.path;
+
+ // TODO: move to config with array of exclusions
+ if (req.path.lastIndexOf('/docs/', 0) === 0) {
+ physicalPath = pathToApp + req.path;
}
- // if directory is requested
- else if (extension === "") {
+
+ // Extension of a requested file
+ var extension = path.extname(physicalPath).replace(".", "");
+
+ // Check if folder is requested
+ if (extension === "") {
var requestedDir = req.path;
+ var supportedExtensions = global.opts.core.common.extensions;
- // append trailing slash
+ // Append trailing slash
if (requestedDir.slice(-1) !== '/') {
requestedDir += '/';
}
- var oneOfExtensionsFound = false;
-
- for (var j = 0; j < supportedExtensions.length; j++) {
- var fileName = "index." + supportedExtensions[j];
-
- if (fs.existsSync(physicalPath + fileName)) {
+ var specNotFileFound = true;
+ var checkingSpecFile = function(supportedIndexFormat){
+ if (specNotFileFound && fs.existsSync(physicalPath + supportedIndexFormat)) {
+ // Passing req params
var urlParams = req.url.split('?')[1];
var paramsString = urlParams ? '?' + urlParams : '';
- req.url = requestedDir + fileName + paramsString;
- // recursive call
- handleRequest(req, res, next);
+ // Modifying url and saving params string
+ req.url = requestedDir + supportedIndexFormat + paramsString;
- oneOfExtensionsFound = true;
- break;
+ // Recursive call
+ handleSpec(req, res, next);
+
+ specNotFileFound = false;
}
- }
+ };
- if (!oneOfExtensionsFound) {
- next();
+ // First check if any supported file exists in dir
+ for (var j = 0; j < supportedExtensions.length; j++) {
+ if (specNotFileFound) {
+ var supportedIndexFormat = "index." + supportedExtensions[j];
+
+ checkingSpecFile(supportedIndexFormat);
+ } else {
+ break;
+ }
}
- } else {
- next();
- }
-};
+ // Then check if component have readme.md
+ checkingSpecFile('readme.md');
-/**
- * check if requested file is *.src and render
- *
- * @param {object} req - Request object
- * @param {object} res - Response object
- * @param {function} next - The callback function
- * */
-exports.process = function (req, res, next) {
- handleRequest(req, res, next);
-};
+ if (specNotFileFound) next();
-/**
- * if URL ends with "index.src" => redirect to trailing slash
- *
- * @param {object} req - Request object
- * @param {object} res - Response object
- * @param {function} next - The callback function
- * */
-exports.handleIndex = function (req, res, next) {
- if (req.path.slice(-9) === 'index.src') {
- // Keeping params on redirect
- var urlParams = req.url.split('?')[1];
- var paramsSting = urlParams ? '?' + urlParams : '';
- res.redirect(301, req.path.slice(0, -9) + paramsSting);
} else {
next();
}
-};
+};
\ No newline at end of file
diff --git a/core/middleware/wrap.js b/core/middleware/wrap.js
index b6de3fc..26b1f4a 100644
--- a/core/middleware/wrap.js
+++ b/core/middleware/wrap.js
@@ -17,15 +17,17 @@ var coreTemplatesDir = pathToApp + "/core/views/";
* @returns {string}
* */
function getTemplateFullPath (name) {
- var pathToTemplate;
+ var output;
if (fs.existsSync(userTemplatesDir + name)) {
- pathToTemplate = userTemplatesDir + name;
+ output = userTemplatesDir + name;
+ } else if (fs.existsSync(coreTemplatesDir + name)) {
+ output = coreTemplatesDir + name;
} else {
- pathToTemplate = coreTemplatesDir + name;
+ output = name;
}
- return pathToTemplate;
+ return output;
}
/**
@@ -45,9 +47,6 @@ exports.process = function (req, res, next) {
// get spec info
var info = req.specData.info;
- // get header and footer
- var headerFooterHTML = getHeaderAndFooter();
-
// choose the proper template, depending on page type
var template, templatePath;
if (info.template) {
@@ -57,43 +56,54 @@ exports.process = function (req, res, next) {
} else {
templatePath = getTemplateFullPath("spec.ejs");
}
- template = fs.readFileSync(templatePath, "utf-8");
- // if the following fields are not set, set them to defaults
- info.title = info.title ? info.title : "New spec";
- info.author = info.author ? info.author : "Anonymous";
- info.keywords = info.keywords ? info.keywords : "";
+ template = fs.readFile(templatePath, "utf-8", function(err, template){
+ if (err) {
+ res.send('EJS template "' + templatePath + '" not found in `core/views` and `user/core/views`.');
- jsdom.env(
- ''+data+'',
- function (errors, window) {
- // get head contents from spec file source
- var headHook = window.document.getElementsByTagName('head')[0];
- var specData = window.document.getElementById('data');
- var head = '';
+ return;
+ }
- if (headHook) {
- head = headHook.innerHTML;
+ // get header and footer
- specData.removeChild(headHook);
- }
+ // if the following fields are not set, set them to defaults
+ info.title = info.title ? info.title : "New spec";
+ info.author = info.author ? info.author : "Anonymous";
+ info.keywords = info.keywords ? info.keywords : "";
- // final data object for the template
- var templateJSON = {
- content: specData.innerHTML,
- head: head,
- header: headerFooterHTML.header,
- footer: headerFooterHTML.footer,
- info: info,
- filename: templatePath
- };
+ jsdom.env(
+ ''+data+'',
+ function (errors, window) {
+ var headerFooterHTML = getHeaderAndFooter();
- // render page and send it as response
- req.specData.renderedHtml = ejs.render(template, templateJSON);
+ // get head contents from spec file source
+ var headHook = window.document.getElementsByTagName('head')[0];
+ var specData = window.document.getElementById('data');
+ var head = '';
- next();
- }
- );
+ if (headHook) {
+ head = headHook.innerHTML;
+
+ specData.removeChild(headHook);
+ }
+
+ // final data object for the template
+ var templateJSON = {
+ content: specData.innerHTML,
+ head: head,
+ header: headerFooterHTML.header,
+ footer: headerFooterHTML.footer,
+ info: info,
+ filename: templatePath
+ };
+
+ // render page and send it as response
+ req.specData.renderedHtml = ejs.render(template, templateJSON);
+
+ next();
+ }
+ );
+ });
} else {
// proceed to next middleware
next();
diff --git a/core/views/doc.ejs b/core/views/doc.ejs
new file mode 100644
index 0000000..fa91b7f
--- /dev/null
+++ b/core/views/doc.ejs
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+ <%= info.title %>
+
+ <% if (info.keywords !== '') {%>
+
+ <% } %>
+
+
+
+
+
+
+
+
+
+
+ <%- head %>
+
+
+
+ <%- header %>
+
<%- content %>
+ <%- footer %>
+
+
+
\ No newline at end of file
diff --git a/docs/api/load-events/index.src b/docs/api/load-events/readme.md
similarity index 81%
rename from docs/api/load-events/index.src
rename to docs/api/load-events/readme.md
index 98b1be5..f282ad1 100644
--- a/docs/api/load-events/index.src
+++ b/docs/api/load-events/readme.md
@@ -1,11 +1,7 @@
-
Spec Load Event
+# Spec Load Event
-
-
Spec load event API helps to organize Spec loading flow, and define the final load event of all content, modules and plugins.
-
+Spec load event API helps to organize Spec loading flow, and define the final load event of all content, modules and plugins.
-
-
## The Problem
As SourceJS is modular engine, any Spec page could be featured with many client-side plugins, which will operate with DOM during the page load. To resolve those cases, when modules or plugins need to interact with each other, or wait till all DOM operations are done, we made Spec Load Event.
@@ -15,18 +11,14 @@ Load event is recommended to use in any SourceJS plugins that make changes to DO
Example use cases:
* When Specs page is loaded through PhantomJS, we must know when page is done building, considering all DOM interactions from plugins
* When we open Spec page with link to section, we must be sure, that page scroll will remain in the right position after load
-
-
-
-
## Basics
Each plugin that interacts with DOM must be registered at Load Event API global object `window.source.loadEvents`.
Recommended plugin definition:
-
+```js
window.source = window.source || {};
window.source.loadEvents = window.source.loadEvents || {};
window.source.loadEvents.moduleName = window.source.loadEvents.moduleName || {
@@ -34,23 +26,19 @@ window.source.loadEvents.moduleName = window.source.loadEvents.moduleName || {
finishEvent: 'moduleNameFinished',
updateEvent: 'moduleNameFinishedUpdated'
};
-
+```
As module load sequence could be random, first we init main data object `window.source.loadEvent`, then define options for our module.
Note: if you are using RequireJS, then be sure that module is registered before any `define`/`require` call.
-
+```js
window.source = window.source || {};
...
define([], function(){ ... });
-
-
-
+```
-
-
## Options
Registering new module to Load Event API we pass these options:
@@ -64,30 +52,23 @@ Registering new module to Load Event API we pass these options:
Emit `finishEvent`, when you know that your module done working with the DOM, if it won't be called, API will rely on timeout. The default timeout is always used for any registered module, when it's passed, we consider that module is done his job.
If the module is working with DOM quite actively with few iterations, it's recommended to emit `updateEvent`, to reset timeout counter on each iteration.
-
-
-
-
+
## Emitting Events
Recommended way of emitting `finishEvent` or `updateEvent` is:
-
+```js
if (window.CustomEvent) {
new CustomEvent('moduleNameFinished');
} else {
var event = document.createEvent('CustomEvent');
event.initCustomEvent('moduleNameFinished', true, true);
}
-
+```
Where event name `moduleNameFinished` is the same as we defined during module registration.
-
-
-
-
## Global Finish Event
When all registered modules to Load Event API are loaded, or timeout is passed, API emits event named `allPluginsFinish`.
@@ -96,25 +77,19 @@ When all registered modules to Load Event API are loaded, or timeout is passed,
If event was shouted before you subscribe, you can check global storage object for any status of registered plugins.
-
+```js
window.source.loadEvents.moduleName.finish
window.source.loadEvents.pluginName.finish
-
-
-
+```
-
-
## Grouping modules
To define module bundles, and control their load state, you can also define a group with array of module names:
-
+```js
window.source = window.source || {};
window.source.loadEvents = window.source.loadEvents || {};
window.source.loadEvents.groupName1 = ['moduleName1', 'pluginName2'];
-
+```
-When all modules and plugins in group are done their job, we emit `groupName1GroupFinish` event, where `groupName1` is your defined name.
-
-
\ No newline at end of file
+When all modules and plugins in group are done their job, we emit `groupName1GroupFinish` event, where `groupName1` is your defined name.
\ No newline at end of file
diff --git a/docs/api/plugins/index.src b/docs/api/plugins/readme.md
similarity index 88%
rename from docs/api/plugins/index.src
rename to docs/api/plugins/readme.md
index b180c74..854f596 100644
--- a/docs/api/plugins/index.src
+++ b/docs/api/plugins/readme.md
@@ -1,19 +1,15 @@
-
Writing SourceJS Plugins and Middlewares
+# Writing SourceJS Plugins and Middlewares
-
- SourceJS core contains only default APIs for most common use cases, all specific features we move to plugins, that could contain back-end and client-side improvements.
-
+SourceJS core contains only default APIs for most common use cases, all specific features we move to plugins, that could contain back-end and client-side improvements.
-
-
## Starting Templates
To easily bootstrap new SourceJS items, we extended our official [SourceJS Generator](https://github.com/sourcejs/generator-sourcejs) with default templates for all types of engine components:
-
+```html
$ yo sourcejs:plugin
$ yo sourcejs:middleware
-
+```
Besides generators, we also have abstract plugin demos, which will be always updated to current engine APIs:
@@ -21,25 +17,21 @@ Besides generators, we also have abstract plugin demos, which will be always upd
* [sourcejs-tpl-middleware](https://github.com/sourcejs/sourcejs-tpl-middleware)
We are continuously improving mentioned demos, adding new best practices and API usage examples.
-
-
-
-
## SourceJS Plugins and Middleware Structure
The recommended way of structuring SourceJS plugins is to define them as [NPM packages](https://docs.npmjs.com/misc/developers). All public plugins must be named by corresponding prefix `sourcejs-*`, so they could be easily searchable through global repository.
All plugins must be installed in `sourcejs/user` directory:
-
+```html
/source
user
package.json
node_modules
sourcejs-plugin1
sourcejs-plugin2
-
+```
### Internal plugins
@@ -50,11 +42,7 @@ SourceJS is an open source project, and we expect our community to be open as we
* Using `sourcejs/user/plugins` folder for your custom client-side dependencies and `sourcejs/user/app.js` to extend SourceJS back-end
Last mentioned option is deprecated, and not recommended to use, yet you can still find some mentions of this approach in `options.js` and `moduleLoader.js`.
-
-
-
-
## Plugins
### Client-side
@@ -65,22 +53,22 @@ Each plugin works the same as any internal JavaScript module and is loaded throu
From your plugin, you can call any libraries and internal APIs, defining your dependencies in AMD module:
-
+```js
define([
'jquery',
'sourceModules/module',
'sourceModules/css'
], function ($, module, css) {});
-
+```
To access other plugin assets, you can use direct path to your component:
-
+```js
define([
'node_modules/sourcejs-plugin/assets/js/innerDep',
'text!node_modules/sourcejs-plugin/assets/templates/tpl.html'
], function (innerDep, tpl) {});
-
+```
#### Connecting Plugin JS Modules
@@ -90,7 +78,7 @@ To achieve this, we generate custom RequireJS configuration through default Grun
To disable client-side module of any installed npm Plugins, you can edit the module definition in `user/options.js`:
-
+```js
{
core: {},
assets: {
@@ -99,7 +87,7 @@ To disable client-side module of any installed npm Plugins, you can edit the mod
}
}
}
-
+```
### Back-end
@@ -114,11 +102,6 @@ As SourceJS back-end uses [ExpressJS](http://expressjs.com), it's recommended to
* [sourcejs-spec-status](https://github.com/sourcejs/sourcejs-spec-status)
* [sourcejs-specs-linting](https://github.com/sourcejs/sourcejs-specs-linting)
-
-
-
-
-
## Middlewares
SourceJS Middleware is basically the same as Plugin item, but it has only back-end part and its entry point must be defined in `core/middleware/index.js` path.
@@ -146,6 +129,4 @@ In 0.4.0 middlewares from plugins are connected one by one, sorted by alphabet.
### Examples
* [sourcejs-jade](https://github.com/sourcejs/sourcejs-jade)
-* [sourcejs-smiles](https://github.com/sourcejs/sourcejs-smiles)
-
-
\ No newline at end of file
+* [sourcejs-smiles](https://github.com/sourcejs/sourcejs-smiles)
\ No newline at end of file
diff --git a/docs/api/rest-api/index.src b/docs/api/rest-api/readme.md
similarity index 79%
rename from docs/api/rest-api/index.src
rename to docs/api/rest-api/readme.md
index a359e2f..8be30fc 100644
--- a/docs/api/rest-api/index.src
+++ b/docs/api/rest-api/readme.md
@@ -1,16 +1,12 @@
-
SourceJS REST API
+# SourceJS REST API
-
- From 0.4.0 version, SourceJS started to grow own REST API for flexible plugins development and easy side services integration. API provides full access to Spec contents, navigation tree and other useful features.
-
+From 0.4.0 version, SourceJS started to grow own REST API for flexible plugins development and easy side services integration. API provides full access to Spec contents, navigation tree and other useful features.
-
-
## GET Specs
[Get](/api/specs) access to Specs navigation tree with filtering by various options. This Navigation tree is used to build search, catalog pages and digests, here you can also read all `info.json` configuration of each Spec.
-```source_wide-code
+```
GET http://localhost:8080/api/specs
```
@@ -26,7 +22,7 @@ All parameters must be passed as JSON with request, except `id` that accepts reg
Possible combinations:
-```source_wide-code
+```
[id|filter|filterOut], [filter|filterOut]
```
@@ -52,7 +48,7 @@ And another custom param for filter:
Get raw, nested Navigation tree:
-```source_wide-code
+```
GET http://localhost:8080/api/specs/raw
```
@@ -60,25 +56,21 @@ GET http://localhost:8080/api/specs/raw
Return all specs that has info field:
-```source_wide-code
+```
$ curl -H "Content-Type: application/json" -X GET -d '{"filter":{"fields":["info"]}}' http://localhost:8080/api/specs
```
Return all specs except those, which have info field:
-```source_wide-code
+```
$ curl -H "Content-Type: application/json" -X GET -d '{"filterOut":{"fields":["info"]}}' http://localhost:8080/api/specs
```
-
-
-
-
## GET HTML
[Get](/api/specs/html) access to Spec examples HTML content.
-```source_wide-code
+```
GET http://localhost:8080/api/specs/html
```
@@ -87,14 +79,10 @@ GET http://localhost:8080/api/specs/html
| Param | Value | Description |
|---|---|---|
| id | String | Get specific Spec examples HTML information by ID - `base/btn`, `docs/spec` ([example](/api/specs/html?id=docs/spec)). |
-
-
-
-
## POST HTML
-```source_wide-code
+```
POST http://localhost:8080/api/specs/html
```
@@ -106,14 +94,10 @@ Params must be passed as JSON. List of possible params:
|---|---|---|
| data | Object | Data to post, will be extended on existing data. |
| unflatten | Boolean | Set true, to unflatten object from `some/spec` before extending on current data. |
-
-
-
-
## DELETE HTML
-```source_wide-code
+```
DELETE http://localhost:8080/api/specs/html
```
@@ -121,6 +105,4 @@ DELETE http://localhost:8080/api/specs/html
| Param | Value | Description |
|---|---|---|
-| id | String | Spec ID (`some/spec`), that will be deleted from current data.|
-
-
\ No newline at end of file
+| id | String | Spec ID (`some/spec`), that will be deleted from current data.|
\ No newline at end of file
diff --git a/docs/markdown/README.md b/docs/markdown/README.md
new file mode 100644
index 0000000..26eeed3
--- /dev/null
+++ b/docs/markdown/README.md
@@ -0,0 +1,85 @@
+# Markdown Support
+
+SourceJS supports markdown as pure `.md` files and renders it in any other extension files using `` HTML tag.
+
+## Basics
+
+Engine treats `index.md` and `readme.md` as main Spec files. Readme file is taken with lowest priority after `index.src`, `index.md` and others.
+
+In case if Spec folder contains `index.src` and `index.md`, first one will have higher priority.
+
+## Markup examples
+
+[Check out source code of this file](/docs/markdown/readme.md) and compare with this rendered Markdown page.
+
+* List 1
+* List 2
+
+
+1. Ordered list 1
+2. Ordered list 2
+
+| Table | Value |
+|---|---|
+| Td | value |
+
+
+```example
+Mark code with `example` keywork to define `.source_example` with your renderen component view.
+
+Example button
+```
+
+## Code examples
+
+List of demo code examples
+
+```
+Some random code
+.css {
+ color:red;
+}
+```
+
+```css
+.css {
+ color:red;
+}
+```
+
+```js
+var test = function(){};
+```
+
+```html
+
+```
+
+```example
+code example
+```
+
+## h2
+
+```example
+code example in h2
+```
+
+### h3
+
+Text
+
+```example
+code example in h3
+```
+
+* list
+* list
+
+#### h4
+
+Text
+
+```example
+code example in h4
+```
\ No newline at end of file
diff --git a/docs/markdown/info.json b/docs/markdown/info.json
new file mode 100644
index 0000000..c580f3f
--- /dev/null
+++ b/docs/markdown/info.json
@@ -0,0 +1,4 @@
+{
+ "title": "Markdown support",
+ "template": "doc"
+}
\ No newline at end of file
diff --git a/docs/migration/info.json b/docs/migration/info.json
new file mode 100644
index 0000000..5feaa66
--- /dev/null
+++ b/docs/migration/info.json
@@ -0,0 +1,3 @@
+{
+ "title": "Migration notes"
+}
\ No newline at end of file
diff --git a/docs/spec/index.src b/docs/spec/index.src
index c53df76..a7cfeb6 100755
--- a/docs/spec/index.src
+++ b/docs/spec/index.src
@@ -1,8 +1,4 @@
-
-
-
-
-