├── .gitignore
├── LICENSE.md
├── README.md
├── app
├── config
│ └── config.json
├── routes
│ └── routes.js
└── views
│ ├── 404.ward.html
│ └── home.ward.html
├── core
├── App.js
├── init
│ ├── config.js
│ └── index.js
├── ressources
│ └── core.css
├── routing
│ ├── Route.js
│ └── Router.js
├── templating
│ ├── Component.js
│ ├── Engine.js
│ ├── View.js
│ └── helpers.js
└── utility
│ └── load.js
├── package-lock.json
├── package.json
├── procfile
├── public
├── assets
│ ├── css
│ │ ├── app.css
│ │ └── app.min.css
│ └── images
│ │ └── logo.png
└── index.html
└── server.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Dependency directories
10 | node_modules/
11 |
12 | # Optional npm cache directory
13 | .npm
14 |
15 | # Yarn Integrity file
16 | .yarn-integrity
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Colin Espinas
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ### The full documentation is not available for now, the project is still in early stages.
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
WARD
12 |
13 |
14 | A fast & simple client side framework for building websites
15 |
16 | Explore the docs »
17 |
18 |
19 | View Demo
20 | ·
21 | Report Bug
22 | ·
23 | Request Feature
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | ## Table of Contents
54 |
55 | * [About](#about)
56 | * [Getting Started](#getting-started)
57 | * [Prerequisites](#prerequisites)
58 | * [Installation](#installation)
59 | * [Usage](#usage)
60 | * [Templating](#templating)
61 | * [Views](#views)
62 | * [Template Engine](#template-engine)
63 | * [Components](#components)
64 | * [Routing](#routing)
65 | * [Contributing](#contributing)
66 | * [License](#license)
67 | * [Contact](#contact)
68 |
69 |
70 |
71 |
72 |
73 | ## About
74 |
75 | [![Product Name Screen Shot][product-screenshot]](https://ward-demo.herokuapp.com/)
76 |
77 | Ward is a simple client sided framework that helps you create fast websites. It is really easy to use and comes with routing and templating.
78 |
79 |
80 |
81 |
82 | ## Getting Started
83 |
84 | Get your Ward project up and ready.
85 |
86 | ### Prerequisites
87 |
88 | Ward is a standalone framework, you don't need anything to make it work but you will need Node.js and a package manager to serve it easely with [ward-server](https://github.com/ward-framework/ward-server).
89 |
90 | ### Installation
91 |
92 | #### CLI
93 |
94 | It is recommended to use the [ward-cli](https://github.com/ward-framework/ward-cli) to create and serve your Ward projects
95 | 1. Install ward-cli globally:
96 | ```sh
97 | npm install ward-cli -g
98 | ```
99 |
100 | 2. Create a new Ward project and serve it:
101 | ```sh
102 | # Create a new project
103 | ward new MyProject
104 | # Move into the project directory
105 | cd MyProject
106 | # Serve the project
107 | ward serve
108 | ```
109 |
110 | #### Git Clone
111 |
112 | 1. This repository can act as a skeleton for Ward projects so you just need to clone it:
113 | ```sh
114 | git clone https://github.com/ColinEspinas/ward.git
115 | ```
116 | 2. Install dependencies ([ward-server](https://github.com/ward-framework/ward-server)):
117 | ```sh
118 | npm install
119 | ```
120 | 3. Now if you want to serve your Ward project use:
121 | ```sh
122 | npm start
123 | ```
124 |
125 | NOTE: Nothing stops you from serving Ward projects with Apache servers by tweeking your `.htaccess` file. If you do so, do not hesitate to share your methods to help the development of the project.
126 |
127 |
128 | ## Usage
129 |
130 | The content below is just explaining basic usages, consider cheking the [documentation]() about more specific use cases.
131 |
132 | ### Templating
133 |
134 | #### Views
135 |
136 | Ward uses view loading to display content on routes. Views uses the `.ward.html` extension.
137 |
138 | Views are defined by a head and a body like normal html pages:
139 | ```html
140 |
141 | View title
142 |
143 |
144 |
145 | View content
146 |
147 | ```
148 |
149 | The name of a view will be the path of the view from the `app/views` folder without the extension:
150 | ```
151 | "app/views/myhomepage.ward.html" => "myhomepage"
152 | "app/views/mypages/myhomepage.ward.html" => "mypages/myhomepage"
153 | ```
154 |
155 | In javascript the View object is constructed with:
156 | ```javascript
157 | View(name, params/*optional*/));
158 | ```
159 |
160 |
161 | #### Template engine
162 |
163 | The Ward templating engine works with `{# expression #}` and allows every javascript expression.
164 |
165 | You can use it to pass parameters to a view:
166 | ```html
167 | {# name #}
168 | ```
169 | NOTE: You can pass any javascript global variable too.
170 |
171 | And you can also use logic structures and functions:
172 | ```html
173 |
174 | {# for(let item of items) { #}
175 | {# item.name #}
176 | {# console.log(item) #}
177 | {# } #}
178 |
179 | ```
180 |
181 |
182 | #### Components
183 |
184 | Components are just like views, they use the extension `ward.html` but are self contained and can be included in views or in other components.
185 |
186 | Like views, the name of a component will be the path of the component from the `app/views` folder without the extension:
187 | ```
188 | "app/views/components/button.ward.html" => "compontents/button"
189 | ```
190 |
191 | In javascript the Component object is constructed with:
192 | ```javascript
193 | Component(tag, name/*optional*/, params/*optional*/));
194 | ```
195 |
196 | To add a component to a view you need to use the `component` helper function:
197 | ```html
198 | {# component("tag", "name", { options : true }) #}
199 | ```
200 |
201 | That helper function shares the same arguments as the Component object constructor.
202 |
203 |
204 | ### Routing
205 | Ward uses hash navigation by default, that means that your URI will look like `/#/this/is/a/route`.
206 |
207 | The routing is done in `app/routes/routes.js`.
208 |
209 | To register a new route you should use:
210 | ```javascript
211 | router.register(new Route(path, callback));
212 | ```
213 | ```javascript
214 | // Exemple:
215 | router.register(new Route("/home", function() {
216 | return new View("home");
217 | }));
218 | ```
219 | Routes that return Views will load that view, you can pass arguments to a view with:
220 | ```javascript
221 | return new View(name, arguments);
222 | ```
223 |
224 | To navigate to a new route use:
225 | ```javascript
226 | router.redirect(path);
227 | ```
228 | ```javascript
229 | // Exemple:
230 | router.redirect("/home");
231 | ```
232 |
233 | To get the right path to a view use:
234 | ```javascript
235 | Route.link(path); // In javascript
236 | link(path) // Helper for templates
237 | ```
238 | ```html
239 |
240 | Home
241 | ```
242 |
243 | To add a name to a route use:
244 | ```javascript
245 | route.name("home");
246 | ```
247 |
248 | To create an alias to a route use:
249 | ```javascript
250 | route.alias("/path");
251 | ```
252 |
253 | You can chain the `name` and `alias` method like this:
254 | ```javascript
255 | router.register(new Route("/home", function() {
256 | return new View("home");
257 | }).name("home").alias("/myhomepage"));
258 | ```
259 |
260 |
261 |
262 | ## Contributing
263 |
264 | This project is developed by a somewhat beginner javascript developer, help is always welcome. Do not hesitate to contribute to the project.
265 |
266 | 1. Fork the Project
267 | 2. Create your Feature or Fix Branch (`git checkout -b feature/Feature` or `git checkout -b fix/Fix`)
268 | 3. Commit your Changes (`git commit -m 'Add some feature or fix'`)
269 | 4. Push to the Branch (`git push origin feature/Feature` or `git push origin fix/Fix`)
270 | 5. Open a Pull Request
271 |
272 |
273 |
274 |
275 | ## License
276 |
277 | Ward is distributed under the MIT License. See `LICENSE` for more information.
278 |
279 |
280 |
281 |
282 | ## Contact
283 |
284 | Colin Espinas - [Website](https://colinespinas.com) - contact@colinespinas.com
285 |
286 | Project link: [https://github.com/ColinEspinas/ward](https://github.com/ward-framework/ward)
287 |
288 |
289 |
290 |
--------------------------------------------------------------------------------
/app/config/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "mode": "DEV",
3 | "route": {
4 | "type": "hash"
5 | },
6 | "path": {
7 | "dir": "/ward/public/",
8 | "app": "../app/",
9 | "public": "../public/",
10 | "views": "../app/views/",
11 | "styles": "../public/assets/css/",
12 | "scripts": "../public/assets/js/"
13 | }
14 | }
--------------------------------------------------------------------------------
/app/routes/routes.js:
--------------------------------------------------------------------------------
1 | import App from "../../core/App.js";
2 | import Route from "../../core/routing/Route.js";
3 | import View from "../../core/templating/View.js";
4 |
5 | const router = App.get.router;
6 |
7 | // Register routes here
8 | router.register(new Route("/", function() { return new View("home"); }).name("home"));
--------------------------------------------------------------------------------
/app/views/404.ward.html:
--------------------------------------------------------------------------------
1 |
2 | Page not found
3 |
4 |
5 |
6 |
7 |
404
8 |
Oops, this route does not exist 😕
9 |
12 |
13 |
--------------------------------------------------------------------------------
/app/views/home.ward.html:
--------------------------------------------------------------------------------
1 |
2 | Welcome!
3 |
4 |
5 |
6 |
7 |
WARD
8 |
You are now ready to start your website 👍
9 |
13 |
14 |
--------------------------------------------------------------------------------
/core/App.js:
--------------------------------------------------------------------------------
1 | import { config } from "./init/config.js";
2 | import Router from "./routing/Router.js";
3 | import { OnConfigLoaded } from "./utility/load.js";
4 |
5 | const app = Symbol();
6 | const singletonEnforcer = Symbol();
7 |
8 | class App {
9 | constructor(enforcer) {
10 | if (enforcer !== singletonEnforcer) {
11 | throw new Error('Cannot construct App');
12 | }
13 |
14 | OnConfigLoaded(function() {
15 | // Define app router
16 | const options = config.mode === "DEV" ? {muted:false} : {muted:true};
17 | this.router = new Router([], options);
18 |
19 | // Send "AppLoaded" event to window
20 | let event = new Event("AppLoaded");
21 | window.dispatchEvent(event);
22 | }.bind(this));
23 | }
24 |
25 | static get get() {
26 | if (!this[app]) {
27 | this[app] = new App(singletonEnforcer);
28 | }
29 |
30 | return this[app];
31 | }
32 | }
33 |
34 | export default App;
--------------------------------------------------------------------------------
/core/init/config.js:
--------------------------------------------------------------------------------
1 | import { loadJSON } from "../utility/load.js";
2 |
3 | // Config location path (from index.html)
4 | const configPath = '../../app/config/config.json';
5 |
6 | // Global config variable
7 | let config;
8 |
9 | // Load config file into config object variable
10 | function loadConfig() {
11 | loadJSON(configPath, function(response) {
12 | config = JSON.parse(response);
13 |
14 | // Send "ConfigLoaded" event to window
15 | let event = new Event("ConfigLoaded");
16 | window.dispatchEvent(event);
17 | });
18 | }
19 |
20 | export { config, loadConfig };
--------------------------------------------------------------------------------
/core/init/index.js:
--------------------------------------------------------------------------------
1 | import { config, loadConfig } from "./config.js";
2 | import App from "../App.js";
3 | import { OnAppLoaded, OnReady } from "../utility/load.js";
4 |
5 | const app = App.get;
6 |
7 | // Init function called Onload
8 | function init() {
9 |
10 | // Load config on start
11 | loadConfig();
12 | // When config is loaded, init routes methods
13 | OnAppLoaded(function() {
14 |
15 | import("../../app/routes/routes.js").then(() => {
16 | // Change route on hash change
17 | if (config.route.type === "hash") {
18 | window.addEventListener('hashchange', function() {
19 | app.router.redirect(app.router.location());
20 | });
21 | }
22 |
23 | // Load page if hash not changed (Direct link)
24 | app.router.redirect(app.router.location());
25 | });
26 | });
27 | }
28 |
29 | // Add init to onload listener
30 | OnReady(init);
--------------------------------------------------------------------------------
/core/ressources/core.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | }
4 |
5 | viewhead {
6 | display: none;
7 | }
8 |
9 | viewbody {
10 | margin: 0;
11 | display: block;
12 | }
--------------------------------------------------------------------------------
/core/routing/Route.js:
--------------------------------------------------------------------------------
1 | import { config } from "../init/config.js";
2 | import App from "../App.js";
3 | import View from "../templating/View.js";
4 |
5 | class Route {
6 | constructor(path, callback) {
7 | this.path = path;
8 | this.params = {};
9 | this.aliases = [];
10 | if (config && config.route.type === "hash") {
11 | if (this.path) {
12 | this.path = this.path.replace("/", "#/");
13 | }
14 |
15 | if (this.path === "#/") {
16 | this.alias("");
17 | }
18 | }
19 | this.callback = callback;
20 | this.router;
21 | this.name;
22 | }
23 |
24 | // Check route callback and handle it
25 | check() {
26 | let routeReturn = this.callback.call();
27 |
28 | // If view is returned load it
29 | if (routeReturn instanceof View) {
30 | routeReturn.load();
31 | }
32 | }
33 |
34 | static link(path) {
35 | if (config && config.route.type === "hash") {
36 | if (path) {
37 | path = path.replace("/", "#/");
38 | }
39 | }
40 |
41 | for (const route of App.get.router.routes) {
42 | if (route.name === path) {
43 | return route.path;
44 | }
45 | if (route.path === path) {
46 | return route.path;
47 | }
48 | else {
49 | for(const alias of route.aliases) {
50 | if (alias === path) {
51 | return alias;
52 | }
53 | }
54 | }
55 | }
56 | return this.link("/notfound");
57 | }
58 |
59 | alias(path) {
60 | if (config && config.route.type === "hash") {
61 | if (path) {
62 | path = path.replace("/", "#/");
63 | }
64 |
65 | if (path === "/") {
66 | this.alias("");
67 | }
68 | }
69 | this.aliases.push(path);
70 | return this; // Return the route for chaining
71 | }
72 |
73 | name(name) {
74 | this.name = name;
75 | return this; // Return the route for chaining
76 | }
77 | }
78 |
79 | export default Route;
--------------------------------------------------------------------------------
/core/routing/Router.js:
--------------------------------------------------------------------------------
1 | import { config } from "../init/config.js";
2 | import Route from "./Route.js";
3 | import View from "../templating/View.js";
4 |
5 | class Router {
6 | constructor(routes, options) {
7 | if (options && Object.entries(options).length >= 0 && options.constructor === Object) {
8 | this.options = options;
9 | } else {
10 | this.options = {
11 | muted: true, // If false message at each redirect.
12 | };
13 | }
14 |
15 | if (routes) {
16 | this.routes = routes;
17 | } else {
18 | this.routes = [];
19 | }
20 |
21 | this.current;
22 |
23 | this.routes.push(new Route("/notfound", function () {
24 | return new View("404");
25 | }));
26 | }
27 |
28 | // Register a route to router
29 | register(route) {
30 | this.routes.push(route);
31 | route.router = this;
32 | }
33 |
34 | // Get current location hash
35 | location() {
36 | if (config && config.route.type === "hash") {
37 | return window.location.hash;
38 | }
39 | }
40 |
41 | setLocation(path) {
42 | if (config && config.route.type === "hash") {
43 | window.location.hash = path;
44 | }
45 | }
46 |
47 | // Check route on path
48 | redirect(path) {
49 | if (!this.current || path !== this.current.path) {
50 | let found = false;
51 | for (let route of this.routes) {
52 | if (route.path === path) {
53 | found = true;
54 |
55 | // Check route
56 | route.check();
57 | this.current = route;
58 | this.setLocation(path);
59 |
60 | // Send "redirected" Event when redirect is successful
61 | var event = new CustomEvent('redirected', {
62 | detail: {
63 | route: this.current
64 | }
65 | });
66 | document.dispatchEvent(event);
67 |
68 | // Send console message if not muted
69 | if (!this.options.muted) {
70 | console.log("Redirected from router :", this);
71 | }
72 | }
73 | else {
74 | for (let alias of route.aliases) {
75 | if (alias === path) {
76 | found = true;
77 |
78 | // Check route
79 | route.check();
80 | this.current = route;
81 | this.setLocation(this.current.path);
82 |
83 | // Send "redirected" Event when redirect is successful
84 | var event = new CustomEvent('redirected', {
85 | detail: {
86 | route: this.current
87 | }
88 | });
89 | document.dispatchEvent(event);
90 |
91 | // Send console message if not muted
92 | if (!this.options.muted) {
93 | console.log("Redirected from router :", this);
94 | }
95 | }
96 | }
97 | }
98 | }
99 |
100 | // Send "not found" message if not muted
101 | if (!found && !this.options.muted) {
102 | console.log("Cannot find route in routes :", path, this.routes);
103 | this.redirect(Route.link("/notfound"));
104 | }
105 | }
106 | }
107 | }
108 |
109 | export default Router;
--------------------------------------------------------------------------------
/core/templating/Component.js:
--------------------------------------------------------------------------------
1 | import { config } from "../init/config.js";
2 | import templateEngine from "./Engine.js";
3 |
4 | class Component {
5 | constructor(tag, name, params) {
6 | this.tag = tag;
7 | this.name = name;
8 | this.path = `${config.path.views}${this.name}.ward.html`;
9 | if (params) {
10 | this.params = params;
11 | }
12 | else {
13 | this.params = {};
14 | }
15 | this.content = "";
16 | }
17 |
18 | // Load parsed template into view
19 | load() {
20 | let params = this.params;
21 | let path = this.path;
22 | let component = this;
23 | let xhr = new XMLHttpRequest();
24 | xhr.overrideMimeType("text/html");
25 | xhr.open('GET', this.path, true);
26 | xhr.onreadystatechange = function () {
27 | if (xhr.readyState == 4 && xhr.status == "200") {
28 |
29 | // Parse view template and adds it to the document
30 | document.querySelector(component.tag).innerHTML = templateEngine.parse(this.responseText, params);
31 | }
32 | if (this.status != 200) {
33 | throw Error("Cannot load component from " + path)
34 | }
35 | };
36 | xhr.send(null);
37 | return document.createElement(this.tag).outerHTML;
38 | }
39 |
40 | // Load raw html into view
41 | loadRaw() {
42 | let xhr = new XMLHttpRequest();
43 | xhr.overrideMimeType("text/html");
44 | xhr.open('GET', this.path, true);
45 | xhr.onreadystatechange = function () {
46 | if (xhr.readyState == 4 && xhr.status == "200") {
47 | // Adds raw content to page
48 | document.querySelector(component.tag).innerHTML = this.responseText;
49 | }
50 | };
51 | xhr.send(null);
52 | }
53 | }
54 |
55 | export default Component;
--------------------------------------------------------------------------------
/core/templating/Engine.js:
--------------------------------------------------------------------------------
1 | import getHelpers from "./helpers.js";
2 |
3 | class templateEngine {
4 |
5 | // Parse template (content) with parameters
6 | static parse(content, params) {
7 | let scope = getHelpers(params);
8 | let re = /{#(.+?)#}/g,
9 | reExp = /(^\s*(if|for|else|switch|case|break|{|})).*/g,
10 | code = 'with(obj) { var r=[];\n',
11 | cursor = 0,
12 | result,
13 | match;
14 | let add = function (line, js) {
15 | js ? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
16 | (code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
17 | return add;
18 | }
19 | while (match = re.exec(content)) {
20 | add(content.slice(cursor, match.index))(match[1], true);
21 | cursor = match.index + match[0].length;
22 | }
23 | add(content.substr(cursor, content.length - cursor));
24 | code = (code + 'return r.join(""); }').replace(/[\r\t\n]/g, ' ');
25 | try {
26 | result = new Function('obj', code).apply(scope, [params]);
27 | }
28 | catch (err) {
29 | console.error(err.message, "falling back to raw content.");
30 | return content;
31 | }
32 | return result;
33 | };
34 | };
35 |
36 | export default templateEngine;
--------------------------------------------------------------------------------
/core/templating/View.js:
--------------------------------------------------------------------------------
1 | import { config } from "../init/config.js";
2 | import templateEngine from "./Engine.js";
3 |
4 | class View {
5 | constructor(name, params) {
6 | this.name = name;
7 | this.path = `${config.path.views}${this.name}.ward.html`;
8 | if (params) {
9 | this.params = params;
10 | }
11 | else {
12 | this.params = {};
13 | }
14 | }
15 |
16 | // Load parsed template view into "viewroot"
17 | load() {
18 | let params = this.params;
19 | let xhr = new XMLHttpRequest();
20 | xhr.overrideMimeType("text/html");
21 | xhr.open('GET', this.path, true);
22 | xhr.onreadystatechange = function () {
23 | if (xhr.readyState == 4 && xhr.status == "200") {
24 |
25 | // Parse view template
26 | let parsedview = templateEngine.parse(this.responseText, params);
27 |
28 | // Load parsed html into viewroot element
29 | document.querySelector("viewroot").innerHTML = parsedview;
30 |
31 | // Change page title with view title
32 | let title = document.querySelector("viewhead").querySelector("title");
33 | document.title = title.text;
34 | }
35 | };
36 | xhr.send(null);
37 | }
38 |
39 | // Load raw html view into "viewroot"
40 | loadRaw() {
41 | let xhr = new XMLHttpRequest();
42 | xhr.overrideMimeType("text/html");
43 | xhr.open('GET', this.path, true);
44 | xhr.onreadystatechange = function () {
45 | if (xhr.readyState == 4 && xhr.status == "200") {
46 |
47 | // Load html into viewroot element
48 | document.querySelector("viewroot").innerHTML = this.responseText;
49 |
50 | // Change page title with view title
51 | let title = document.querySelector("viewhead").querySelector("title");
52 | document.title = title.text;
53 | }
54 | };
55 | xhr.send(null);
56 | }
57 | }
58 |
59 | export default View;
--------------------------------------------------------------------------------
/core/templating/helpers.js:
--------------------------------------------------------------------------------
1 | import Route from "../routing/Route.js";
2 | import Component from "./Component.js";
3 | import App from "../App.js";
4 |
5 | function helpers(params) {
6 |
7 | let scope = params;
8 |
9 | // Helpers are registered here:
10 |
11 |
12 | // Get correct route path from url
13 | scope.link = function(url) {
14 | return Route.link(url);
15 | }
16 |
17 | // Get current location into a string
18 | scope.location = function() {
19 | return App.get.router.location();
20 | }
21 |
22 | // Instantiate a new component
23 | scope.component = function(tag, name, params) {
24 |
25 | let component;
26 |
27 | if (typeof name !== 'string') {
28 | params = name;
29 | name = tag;
30 | }
31 |
32 | if (typeof params === 'object' && params !== null) {
33 | component = new Component(tag, name, params);
34 | }
35 | else {
36 | component = new Component(tag, name);
37 | }
38 |
39 | return component.load();
40 | }
41 |
42 |
43 |
44 |
45 |
46 |
47 | return scope;
48 | }
49 |
50 | export default helpers;
--------------------------------------------------------------------------------
/core/utility/load.js:
--------------------------------------------------------------------------------
1 | // Add func to "DOMContentLoaded" event queue
2 | function OnReady(func) {
3 | window.addEventListener("DOMContentLoaded", func);
4 | }
5 | // Add func to "load" event queue
6 | function OnLoad(func) {
7 | window.addEventListener("load", func);
8 | }
9 |
10 | function OnConfigLoaded(func) {
11 | window.addEventListener('ConfigLoaded', func);
12 | }
13 |
14 | function OnAppLoaded(func) {
15 | window.addEventListener('AppLoaded', func);
16 | }
17 |
18 | function loadJSON(path, callback, err) {
19 | let xhr = new XMLHttpRequest();
20 | xhr.overrideMimeType("application/json");
21 | xhr.open('GET', path, true);
22 | xhr.onreadystatechange = function () {
23 | if (xhr.readyState == 4 && xhr.status == "200") {
24 | callback(xhr.responseText);
25 | }
26 | else {
27 | if (err)
28 | err();
29 | }
30 | };
31 | xhr.send(null);
32 | }
33 |
34 | export { OnReady, OnLoad, OnConfigLoaded, OnAppLoaded, loadJSON };
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ward-project",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@types/color-name": {
8 | "version": "1.1.1",
9 | "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
10 | "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
11 | },
12 | "ansi-styles": {
13 | "version": "4.2.1",
14 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
15 | "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
16 | "requires": {
17 | "@types/color-name": "^1.1.1",
18 | "color-convert": "^2.0.1"
19 | }
20 | },
21 | "chalk": {
22 | "version": "3.0.0",
23 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
24 | "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
25 | "requires": {
26 | "ansi-styles": "^4.1.0",
27 | "supports-color": "^7.1.0"
28 | }
29 | },
30 | "color-convert": {
31 | "version": "2.0.1",
32 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
33 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
34 | "requires": {
35 | "color-name": "~1.1.4"
36 | }
37 | },
38 | "color-name": {
39 | "version": "1.1.4",
40 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
41 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
42 | },
43 | "connect": {
44 | "version": "3.7.0",
45 | "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz",
46 | "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==",
47 | "requires": {
48 | "debug": "2.6.9",
49 | "finalhandler": "1.1.2",
50 | "parseurl": "~1.3.3",
51 | "utils-merge": "1.0.1"
52 | }
53 | },
54 | "debug": {
55 | "version": "2.6.9",
56 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
57 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
58 | "requires": {
59 | "ms": "2.0.0"
60 | }
61 | },
62 | "depd": {
63 | "version": "1.1.2",
64 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
65 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
66 | },
67 | "destroy": {
68 | "version": "1.0.4",
69 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
70 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
71 | },
72 | "ee-first": {
73 | "version": "1.1.1",
74 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
75 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
76 | },
77 | "encodeurl": {
78 | "version": "1.0.2",
79 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
80 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
81 | },
82 | "escape-html": {
83 | "version": "1.0.3",
84 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
85 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
86 | },
87 | "etag": {
88 | "version": "1.8.1",
89 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
90 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
91 | },
92 | "finalhandler": {
93 | "version": "1.1.2",
94 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
95 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
96 | "requires": {
97 | "debug": "2.6.9",
98 | "encodeurl": "~1.0.2",
99 | "escape-html": "~1.0.3",
100 | "on-finished": "~2.3.0",
101 | "parseurl": "~1.3.3",
102 | "statuses": "~1.5.0",
103 | "unpipe": "~1.0.0"
104 | }
105 | },
106 | "fresh": {
107 | "version": "0.5.2",
108 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
109 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
110 | },
111 | "has-flag": {
112 | "version": "4.0.0",
113 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
114 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
115 | },
116 | "http-errors": {
117 | "version": "1.7.3",
118 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
119 | "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==",
120 | "requires": {
121 | "depd": "~1.1.2",
122 | "inherits": "2.0.4",
123 | "setprototypeof": "1.1.1",
124 | "statuses": ">= 1.5.0 < 2",
125 | "toidentifier": "1.0.0"
126 | }
127 | },
128 | "inherits": {
129 | "version": "2.0.4",
130 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
131 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
132 | },
133 | "mime": {
134 | "version": "1.6.0",
135 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
136 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
137 | },
138 | "ms": {
139 | "version": "2.0.0",
140 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
141 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
142 | },
143 | "on-finished": {
144 | "version": "2.3.0",
145 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
146 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
147 | "requires": {
148 | "ee-first": "1.1.1"
149 | }
150 | },
151 | "parseurl": {
152 | "version": "1.3.3",
153 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
154 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
155 | },
156 | "range-parser": {
157 | "version": "1.2.1",
158 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
159 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
160 | },
161 | "send": {
162 | "version": "0.17.1",
163 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
164 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
165 | "requires": {
166 | "debug": "2.6.9",
167 | "depd": "~1.1.2",
168 | "destroy": "~1.0.4",
169 | "encodeurl": "~1.0.2",
170 | "escape-html": "~1.0.3",
171 | "etag": "~1.8.1",
172 | "fresh": "0.5.2",
173 | "http-errors": "~1.7.2",
174 | "mime": "1.6.0",
175 | "ms": "2.1.1",
176 | "on-finished": "~2.3.0",
177 | "range-parser": "~1.2.1",
178 | "statuses": "~1.5.0"
179 | },
180 | "dependencies": {
181 | "ms": {
182 | "version": "2.1.1",
183 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
184 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
185 | }
186 | }
187 | },
188 | "serve-static": {
189 | "version": "1.14.1",
190 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
191 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
192 | "requires": {
193 | "encodeurl": "~1.0.2",
194 | "escape-html": "~1.0.3",
195 | "parseurl": "~1.3.3",
196 | "send": "0.17.1"
197 | }
198 | },
199 | "setprototypeof": {
200 | "version": "1.1.1",
201 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
202 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
203 | },
204 | "statuses": {
205 | "version": "1.5.0",
206 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
207 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
208 | },
209 | "supports-color": {
210 | "version": "7.1.0",
211 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
212 | "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
213 | "requires": {
214 | "has-flag": "^4.0.0"
215 | }
216 | },
217 | "toidentifier": {
218 | "version": "1.0.0",
219 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
220 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
221 | },
222 | "unpipe": {
223 | "version": "1.0.0",
224 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
225 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
226 | },
227 | "utils-merge": {
228 | "version": "1.0.1",
229 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
230 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
231 | },
232 | "ward-server": {
233 | "version": "1.1.1",
234 | "resolved": "https://registry.npmjs.org/ward-server/-/ward-server-1.1.1.tgz",
235 | "integrity": "sha512-gVBLVmCTiUkfndulfQuvhSN6fF1Z9IV4yoOj7bLS5s//5fCwHu4VaSaZxM+a2klTceSfyKzSinry7s3mvqun4g==",
236 | "requires": {
237 | "chalk": "^3.0.0",
238 | "connect": "^3.7.0",
239 | "serve-static": "^1.14.1"
240 | }
241 | }
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ward-project",
3 | "version": "1.0.0",
4 | "main": "server.js",
5 | "scripts": {
6 | "start": "node server.js"
7 | },
8 | "author": "",
9 | "license": "ISC",
10 | "dependencies": {
11 | "ward-server": "^1.1.1"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/procfile:
--------------------------------------------------------------------------------
1 | web: node server.js --port=$PORT
--------------------------------------------------------------------------------
/public/assets/css/app.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Nunito&display=swap');
2 | @import url('https://fonts.googleapis.com/css?family=Playfair+Display&display=swap');
3 |
4 | viewbody {
5 | background-color: #08002e;
6 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 1200 800'%3E%3Cdefs%3E%3CradialGradient id='a' cx='0' cy='800' r='800' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23158295'/%3E%3Cstop offset='1' stop-color='%23158295' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='b' cx='1200' cy='800' r='800' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23020287'/%3E%3Cstop offset='1' stop-color='%23020287' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='c' cx='600' cy='0' r='600' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%232adcd8'/%3E%3Cstop offset='1' stop-color='%232adcd8' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='d' cx='600' cy='800' r='600' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%2308002e'/%3E%3Cstop offset='1' stop-color='%2308002e' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='e' cx='0' cy='0' r='800' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%235ec96c'/%3E%3Cstop offset='1' stop-color='%235ec96c' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='f' cx='1200' cy='0' r='800' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23072dde'/%3E%3Cstop offset='1' stop-color='%23072dde' stop-opacity='0'/%3E%3C/radialGradient%3E%3C/defs%3E%3Crect fill='url(%23a)' width='1200' height='800'/%3E%3Crect fill='url(%23b)' width='1200' height='800'/%3E%3Crect fill='url(%23c)' width='1200' height='800'/%3E%3Crect fill='url(%23d)' width='1200' height='800'/%3E%3Crect fill='url(%23e)' width='1200' height='800'/%3E%3Crect fill='url(%23f)' width='1200' height='800'/%3E%3C/svg%3E");
7 | background-attachment: fixed;
8 | background-size: cover;
9 | width: 100%;
10 | height: 100vh;
11 | display: flex;
12 | align-items: center;
13 | justify-content: center;
14 | }
15 |
16 | .container {
17 | color: white;
18 | }
19 |
20 | .title {
21 | font-size: 5em;
22 | margin: 0;
23 | text-align: center;
24 | opacity: 0.5;
25 | font-family: 'Playfair Display', serif;
26 | letter-spacing: 0.1em;
27 | font-weight: normal;
28 | }
29 |
30 | .description {
31 | text-align: center;
32 | margin: 5px 0 0 0;
33 | font-family: 'Nunito', sans-serif;
34 | }
35 |
36 | .links {
37 | list-style-type: none;
38 | margin: 15px 0 0;
39 | padding: 0;
40 | text-align: center;
41 | font-family: 'Nunito', sans-serif;
42 | }
43 |
44 | .links>li {
45 | display: inline-block;
46 | }
47 |
48 | .links>li>a {
49 | display: block;
50 | padding: 10px;
51 | background: rgba(0, 0, 0, 0.3);
52 | color: white;
53 | text-decoration: none;
54 | border-radius: 5px;
55 | cursor: pointer;
56 | transition: background 300ms ease;
57 | }
58 |
59 | .links>li>a:hover {
60 | background: rgba(0, 0, 0, 0.6);
61 | }
--------------------------------------------------------------------------------
/public/assets/css/app.min.css:
--------------------------------------------------------------------------------
1 | @import url(https://fonts.googleapis.com/css?family=Nunito&display=swap);@import url(https://fonts.googleapis.com/css?family=Playfair+Display&display=swap);viewbody{background-color:#08002e;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 1200 800'%3E%3Cdefs%3E%3CradialGradient id='a' cx='0' cy='800' r='800' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23158295'/%3E%3Cstop offset='1' stop-color='%23158295' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='b' cx='1200' cy='800' r='800' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23020287'/%3E%3Cstop offset='1' stop-color='%23020287' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='c' cx='600' cy='0' r='600' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%232adcd8'/%3E%3Cstop offset='1' stop-color='%232adcd8' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='d' cx='600' cy='800' r='600' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%2308002e'/%3E%3Cstop offset='1' stop-color='%2308002e' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='e' cx='0' cy='0' r='800' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%235ec96c'/%3E%3Cstop offset='1' stop-color='%235ec96c' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='f' cx='1200' cy='0' r='800' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23072dde'/%3E%3Cstop offset='1' stop-color='%23072dde' stop-opacity='0'/%3E%3C/radialGradient%3E%3C/defs%3E%3Crect fill='url(%23a)' width='1200' height='800'/%3E%3Crect fill='url(%23b)' width='1200' height='800'/%3E%3Crect fill='url(%23c)' width='1200' height='800'/%3E%3Crect fill='url(%23d)' width='1200' height='800'/%3E%3Crect fill='url(%23e)' width='1200' height='800'/%3E%3Crect fill='url(%23f)' width='1200' height='800'/%3E%3C/svg%3E");background-attachment:fixed;background-size:cover;width:100%;height:100vh;display:flex;align-items:center;justify-content:center}.container{color:#fff}.title{font-size:5em;margin:0;text-align:center;opacity:.5;font-family:'Playfair Display',serif;letter-spacing:.1em;font-weight:400}.description{text-align:center;margin:5px 0 0 0;font-family:Nunito,sans-serif}.links{list-style-type:none;margin:15px 0 0;padding:0;text-align:center;font-family:Nunito,sans-serif}.links>li{display:inline-block}.links>li>a{display:block;padding:10px;background:rgba(0,0,0,.3);color:#fff;text-decoration:none;border-radius:5px;cursor:pointer;transition:background .3s ease}.links>li>a:hover{background:rgba(0,0,0,.6)}
--------------------------------------------------------------------------------
/public/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ward-framework/ward/4c99295de16e0b155f5e7353f2a987e64d470cec/public/assets/images/logo.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const Server = require('ward-server');
2 |
3 | // Create a Server instance
4 | const server = new Server({
5 | path : __dirname,
6 | port: process.env.PORT || 8000,
7 | });
8 |
9 | // Serve files
10 | server.serve();
--------------------------------------------------------------------------------