Added presentation framework.
This commit is contained in:
parent
92bca6599b
commit
178f5ad135
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -25,3 +25,9 @@ build/Release
|
|||||||
# Dependency directory
|
# Dependency directory
|
||||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||||
node_modules
|
node_modules
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# IDE things
|
||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
dist
|
||||||
|
28
README.md
Normal file
28
README.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
My Convention Presentations
|
||||||
|
===========================
|
||||||
|
|
||||||
|
If you're interested in my presentations, you can go look at them here:
|
||||||
|
https://krotscheck.github.io/presentations/
|
||||||
|
|
||||||
|
|
||||||
|
Building your own presentations
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
If you would like to use this repository as a base from which to build your
|
||||||
|
own presentation website, you can clone the ``starter`` branch and work from
|
||||||
|
there. The following commands will get you started:
|
||||||
|
|
||||||
|
// This will install miscellaneous runtime dependencies.
|
||||||
|
npm install
|
||||||
|
|
||||||
|
// This will start a VERY basic presentation wizard. To modify the
|
||||||
|
// output, make changes in ./src/template/index.hbs
|
||||||
|
npm run new
|
||||||
|
|
||||||
|
// This will create a local webhost, serving all of your presentations.
|
||||||
|
// It will autodetect changes and refresh any applicable pages.
|
||||||
|
npm run serve
|
||||||
|
|
||||||
|
// This will construct your current presentations, and push them to
|
||||||
|
// gh-pages.
|
||||||
|
npm run release
|
33
bower.json
Normal file
33
bower.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "presentations",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"homepage": "https://krotscheck.github.com/presentations",
|
||||||
|
"authors": [
|
||||||
|
"Michael Krotscheck <krotscheck@gmail.com>"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"ignore": [
|
||||||
|
"**/.*",
|
||||||
|
"node_modules",
|
||||||
|
"bower_components",
|
||||||
|
"test",
|
||||||
|
"tests"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"font-awesome": "~4.3.0",
|
||||||
|
"font-mfizz": "~2.0.1",
|
||||||
|
"reveal.js": "~3.1.0",
|
||||||
|
"bootstrap": "~3.3.5"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"font-mfizz": {
|
||||||
|
"main": [
|
||||||
|
"css/font-mfizz.css",
|
||||||
|
"fonts/font-mfizz.eot",
|
||||||
|
"fonts/font-mfizz.svg",
|
||||||
|
"fonts/font-mfizz.ttf",
|
||||||
|
"fonts/font-mfizz.woff"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
234
gulpfile.js
Normal file
234
gulpfile.js
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
(function initializeGulp () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
var cheerio = require('cheerio');
|
||||||
|
var rimraf = require('rimraf');
|
||||||
|
var mainBowerFiles = require('main-bower-files');
|
||||||
|
|
||||||
|
var gulp = require('gulp');
|
||||||
|
var git = require('gulp-git');
|
||||||
|
var filter = require('gulp-filter');
|
||||||
|
var less = require('gulp-less');
|
||||||
|
var webserver = require('gulp-webserver');
|
||||||
|
var streamqueue = require('streamqueue');
|
||||||
|
var ignore = require('gulp-ignore');
|
||||||
|
var prompt = require('gulp-prompt');
|
||||||
|
var handlebars = require('gulp-compile-handlebars');
|
||||||
|
var rename = require('gulp-rename');
|
||||||
|
|
||||||
|
var dir = {
|
||||||
|
'dist': './dist',
|
||||||
|
'src': './src'
|
||||||
|
};
|
||||||
|
|
||||||
|
// List of file paths.
|
||||||
|
var paths = {
|
||||||
|
'html': dir.src + '/**/*.html',
|
||||||
|
'hbs': dir.src + '/index.hbs',
|
||||||
|
'index': dir.src + '/index.hbs'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Contents of all our bower dependencies' main:[] fields.
|
||||||
|
var bowerFiles = mainBowerFiles();
|
||||||
|
|
||||||
|
// The current package.json file.
|
||||||
|
var packageJson = require('./package.json');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The handlebars configuration object.
|
||||||
|
*/
|
||||||
|
var handlebarsConfig = {
|
||||||
|
helpers: {
|
||||||
|
'datetime': function (object) {
|
||||||
|
return object.toLocaleDateString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method parses through discovered presentations, reads their
|
||||||
|
* html metadata, and returns an array of that metadata.
|
||||||
|
*/
|
||||||
|
function buildPresentationManifest () {
|
||||||
|
var presentations = [];
|
||||||
|
var files = fs.readdirSync(dir.src);
|
||||||
|
|
||||||
|
for (var i = 0; i < files.length; i++) {
|
||||||
|
var file = dir.src + '/' + files[i] + '/index.html';
|
||||||
|
try {
|
||||||
|
var stat = fs.statSync(file);
|
||||||
|
var $ = cheerio.load(fs.readFileSync(file));
|
||||||
|
presentations.push({
|
||||||
|
'title': $("head title").text(),
|
||||||
|
'description': $("head meta[name='description']").attr('content'),
|
||||||
|
'author': $('head meta[name="author"]').attr('content'),
|
||||||
|
'mtime': stat.mtime,
|
||||||
|
'path': files[i] + '/index.html'
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
presentations.sort(function (a, b) {
|
||||||
|
return a.mtime >= b.mtime ? 1 : -1;
|
||||||
|
});
|
||||||
|
return presentations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean the output directory.
|
||||||
|
*
|
||||||
|
* @param {Function} cb callback.
|
||||||
|
* @return {*} A gulp stream that performs this action.
|
||||||
|
*/
|
||||||
|
gulp.task('clean', function (cb) {
|
||||||
|
rimraf(dir.dist, cb);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the static file structure from our bower dependencies. Reveal.js
|
||||||
|
* is given a special snowflake status, because it doesn't observe the
|
||||||
|
* standard packaging format that bower files like.
|
||||||
|
*/
|
||||||
|
gulp.task('package:libs', function (cb) {
|
||||||
|
var resolveCSS = gulp.src(bowerFiles)
|
||||||
|
.pipe(filter('*.css'))
|
||||||
|
.pipe(gulp.dest(dir.dist + '/css'));
|
||||||
|
|
||||||
|
var resolveLESS = gulp.src(bowerFiles)
|
||||||
|
.pipe(filter('*.less'))
|
||||||
|
.pipe(less())
|
||||||
|
.pipe(gulp.dest(dir.dist + '/css'));
|
||||||
|
|
||||||
|
var resolveFonts = gulp.src(bowerFiles)
|
||||||
|
.pipe(filter(['*.otf', '*.eot', '*.svg', '*.ttf', '*.woff', '*.woff2']))
|
||||||
|
.pipe(gulp.dest(dir.dist + '/fonts'));
|
||||||
|
|
||||||
|
var resolveLibs = gulp.src(bowerFiles)
|
||||||
|
.pipe(filter('*.js'))
|
||||||
|
.pipe(gulp.dest(dir.dist + '/js'));
|
||||||
|
|
||||||
|
// Reveal.js is a special snowflake.
|
||||||
|
var resolveReveal = gulp.src('./bower_components/reveal.js/*/**/*.*',
|
||||||
|
{'base': './bower_components/reveal.js/'})
|
||||||
|
.pipe(ignore(['**/test/**', '*.js']))
|
||||||
|
.pipe(filter([
|
||||||
|
'**/*.js',
|
||||||
|
'**/*.css',
|
||||||
|
'**/*.eot',
|
||||||
|
'**/*.ttf',
|
||||||
|
'**/*.woff'
|
||||||
|
]))
|
||||||
|
.pipe(gulp.dest(dir.dist));
|
||||||
|
|
||||||
|
return streamqueue({'objectMode': true}, resolveCSS, resolveLESS,
|
||||||
|
resolveReveal, resolveLibs, resolveFonts);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Package the handlebars files.
|
||||||
|
*/
|
||||||
|
gulp.task('package:hbs', function () {
|
||||||
|
|
||||||
|
var templateData = {
|
||||||
|
'presentations': buildPresentationManifest(),
|
||||||
|
'author': packageJson.author
|
||||||
|
};
|
||||||
|
|
||||||
|
// Automatically build the site list.
|
||||||
|
return gulp.src(paths.hbs, {'base': dir.src})
|
||||||
|
.pipe(handlebars(templateData, handlebarsConfig))
|
||||||
|
.pipe(rename(function (path) {
|
||||||
|
path.extname = ".html";
|
||||||
|
}))
|
||||||
|
.pipe(gulp.dest(dir.dist));
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy the HTML files into the dist folder.
|
||||||
|
*/
|
||||||
|
gulp.task('package:html', function () {
|
||||||
|
return gulp.src(paths.html, {'base': dir.src})
|
||||||
|
.pipe(gulp.dest(dir.dist));
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This task builds a new presentation from the base presentation template.
|
||||||
|
*/
|
||||||
|
gulp.task('new', function () {
|
||||||
|
var templateData = {
|
||||||
|
author: packageJson.author
|
||||||
|
};
|
||||||
|
var destinationFolder = '';
|
||||||
|
return gulp.src(dir.src + '/template/index.hbs')
|
||||||
|
.pipe(prompt.prompt([
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'folderName',
|
||||||
|
message: 'Presentation Folder Name (/^[a-z][a-z_]+$/):',
|
||||||
|
validate: function (value) {
|
||||||
|
var result = value.match(/^([a-z][a-z_]+)$/);
|
||||||
|
return result !== null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'title',
|
||||||
|
message: 'Presentation Title:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'description',
|
||||||
|
message: 'Presentation Description:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'event',
|
||||||
|
message: 'First presented at:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'event',
|
||||||
|
message: 'First presented on (date):'
|
||||||
|
}],
|
||||||
|
function (res) {
|
||||||
|
destinationFolder = res.folderName;
|
||||||
|
templateData.presentation = {
|
||||||
|
'title': res.title,
|
||||||
|
'description': res.description,
|
||||||
|
'event': res.event
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.pipe(handlebars(templateData, handlebarsConfig))
|
||||||
|
.pipe(rename(function (path) {
|
||||||
|
path.dirname += '/' + destinationFolder;
|
||||||
|
path.basename = "index";
|
||||||
|
path.extname = ".html";
|
||||||
|
}))
|
||||||
|
.pipe(gulp.dest(dir.src))
|
||||||
|
.pipe(git.add());
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Package the entire site into the dist folder.
|
||||||
|
*/
|
||||||
|
gulp.task('package', ['package:html', 'package:hbs', 'package:libs']);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a local server and serve the application code. This is
|
||||||
|
* equivalent to opening index.html in a browser.
|
||||||
|
*
|
||||||
|
* @return {*} A gulp stream that performs this action.
|
||||||
|
*/
|
||||||
|
gulp.task('serve', function () {
|
||||||
|
gulp.watch(paths.html, ['package:html']);
|
||||||
|
gulp.watch(paths.hbs, ['package:hbs']);
|
||||||
|
|
||||||
|
return gulp.src(dir.dist)
|
||||||
|
.pipe(webserver({
|
||||||
|
'livereload': true,
|
||||||
|
'open': true
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
})();
|
39
package.json
Normal file
39
package.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "presentations",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "My convention presentations.",
|
||||||
|
"main": "index.html",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"postinstall": "bower install",
|
||||||
|
"start": "gulp serve",
|
||||||
|
"prestart": "gulp package",
|
||||||
|
"prepublish": "gulp package",
|
||||||
|
"new": "gulp new"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"name": "Michael Krotscheck",
|
||||||
|
"email": "krotscheck@gmail.com",
|
||||||
|
"url": "http://www.krotscheck.net/"
|
||||||
|
},
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"bower": "^1.4.1",
|
||||||
|
"cheerio": "^0.19.0",
|
||||||
|
"gulp": "^3.9.0",
|
||||||
|
"gulp-compile-handlebars": "^0.5.0",
|
||||||
|
"gulp-filter": "^3.0.0",
|
||||||
|
"gulp-git": "^1.2.4",
|
||||||
|
"gulp-ignore": "^1.2.1",
|
||||||
|
"gulp-less": "^3.0.3",
|
||||||
|
"gulp-prompt": "^0.1.2",
|
||||||
|
"gulp-rename": "^1.2.2",
|
||||||
|
"gulp-util": "^3.0.6",
|
||||||
|
"gulp-webserver": "^0.9.1",
|
||||||
|
"main-bower-files": "^2.9.0",
|
||||||
|
"rimraf": "^2.4.2",
|
||||||
|
"streamqueue": "^1.1.0"
|
||||||
|
},
|
||||||
|
"repository": "git@github.com:krotscheck/presentations.git"
|
||||||
|
}
|
71
src/index.hbs
Normal file
71
src/index.hbs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Convention Presentations</title>
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="css/bootstrap.css">
|
||||||
|
<meta name="description"
|
||||||
|
content="Convention Presentations by {{author.name}}">
|
||||||
|
<meta name="author" content="{{author.name}}">
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body style="margin-top: 70px; margin-bottom: 70px">
|
||||||
|
<nav class="navbar navbar-default navbar-fixed-top">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<span class="navbar-brand">Convention Presentations</span>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
<li><strong>Author:</strong> {{author.name}}</li>
|
||||||
|
<li><strong>Website:</strong>
|
||||||
|
<a href="{{author.url}}">{{author.url}}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<br/>
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="col-xs-2">Last Updated</th>
|
||||||
|
<th>Title</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each presentations}}
|
||||||
|
<tr>
|
||||||
|
<td>{{datetime mtime}}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{path}}" target="_blank">
|
||||||
|
{{title}}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<nav class="navbar navbar-default navbar-fixed-bottom">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<p class="navbar-text">
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by/4.0/">
|
||||||
|
<img alt="Creative Commons License"
|
||||||
|
src="https://i.creativecommons.org/l/by/4.0/88x31.png"/>
|
||||||
|
</a>
|
||||||
|
This work by <span xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
property="cc:attributionName">{{author.name}}</span>
|
||||||
|
is licensed under a <a rel="license"
|
||||||
|
href="http://creativecommons.org/licenses/by/4.0/">Creative
|
||||||
|
Commons Attribution 4.0 International License</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</body>
|
||||||
|
</html>
|
111
src/template/index.hbs
Normal file
111
src/template/index.hbs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
|
||||||
|
<title>{{presentation.title}}</title>
|
||||||
|
|
||||||
|
<meta name="description"
|
||||||
|
content="{{presentation.description}}">
|
||||||
|
<meta name="author" content="{{author.name}}">
|
||||||
|
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes"/>
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style"
|
||||||
|
content="black-translucent"/>
|
||||||
|
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="../css/reveal.css">
|
||||||
|
<link rel="stylesheet" href="../css/font-awesome.css">
|
||||||
|
<link rel="stylesheet" href="../css/font-mfizz.css">
|
||||||
|
<link rel="stylesheet" href="../css/theme/serif.css" id="theme">
|
||||||
|
|
||||||
|
<!-- Code syntax highlighting -->
|
||||||
|
<link rel="stylesheet" href="../lib/css/zenburn.css">
|
||||||
|
|
||||||
|
<!-- Printing and PDF exports -->
|
||||||
|
<script>
|
||||||
|
var link = document.createElement('link');
|
||||||
|
link.rel = 'stylesheet';
|
||||||
|
link.type = 'text/css';
|
||||||
|
link.href =
|
||||||
|
window.location.search.match(/print-pdf/gi) ?
|
||||||
|
'../css/print/pdf.css' :
|
||||||
|
'../css/print/paper.css';
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(link);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="../lib/js/html5shiv.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="reveal" data-transition="slide">
|
||||||
|
|
||||||
|
<div class="slides">
|
||||||
|
<section>
|
||||||
|
<h1>{{presentation.title}}</h1>
|
||||||
|
|
||||||
|
<h3>{{presentation.description}}</h3>
|
||||||
|
|
||||||
|
<p>by {{author.name}}</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../lib/js/head.min.js"></script>
|
||||||
|
<script src="../js/reveal.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
// Full list of configuration options available at:
|
||||||
|
// https://github.com/hakimel/reveal.js#configuration
|
||||||
|
Reveal.initialize({
|
||||||
|
controls: true,
|
||||||
|
progress: true,
|
||||||
|
history: true,
|
||||||
|
center: true,
|
||||||
|
|
||||||
|
transition: 'slide', // none/fade/slide/convex/concave/zoom
|
||||||
|
|
||||||
|
// Optional reveal.js plugins
|
||||||
|
dependencies: [
|
||||||
|
{
|
||||||
|
src: '../lib/js/classList.js', condition: function () {
|
||||||
|
return !document.body.classList;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '../plugin/markdown/marked.js',
|
||||||
|
condition: function () {
|
||||||
|
return !!document.querySelector('[data-markdown]');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '../plugin/markdown/markdown.js',
|
||||||
|
condition: function () {
|
||||||
|
return !!document.querySelector('[data-markdown]');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '../plugin/highlight/highlight.js',
|
||||||
|
async: true,
|
||||||
|
condition: function () {
|
||||||
|
return !!document.querySelector('pre code');
|
||||||
|
},
|
||||||
|
callback: function () {
|
||||||
|
hljs.initHighlightingOnLoad();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{src: '../plugin/zoom-js/zoom.js', async: true},
|
||||||
|
{src: '../plugin/notes/notes.js', async: true}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user