Merge pull request #7 from RealOrangeOne/contact-page
Add basic contact page
This commit is contained in:
commit
34cab118cf
11 changed files with 124 additions and 39 deletions
|
@ -49,13 +49,13 @@ accounts:
|
||||||
- fa-trello
|
- fa-trello
|
||||||
|
|
||||||
freenode:
|
freenode:
|
||||||
- Freenode IRC
|
- Freenode
|
||||||
- TheOrangeOne
|
- TheOrangeOne
|
||||||
- https://webchat.freenode.net/
|
- https://webchat.freenode.net/
|
||||||
- fa-rss
|
- fa-rss
|
||||||
|
|
||||||
atomio:
|
atomio:
|
||||||
- AtomIO Slack
|
- Atom Slack
|
||||||
- TheOrangeOne
|
- TheOrangeOne
|
||||||
- https://atomio.slack.com/
|
- https://atomio.slack.com/
|
||||||
- fa-slack
|
- fa-slack
|
||||||
|
@ -69,14 +69,14 @@ accounts:
|
||||||
codepen:
|
codepen:
|
||||||
- CodePen
|
- CodePen
|
||||||
- TheOrangeOne
|
- TheOrangeOne
|
||||||
- https://codepen.io/~{0}/
|
- https://codepen.io/{0}/
|
||||||
- fa-codepen
|
- fa-codepen
|
||||||
|
|
||||||
npm:
|
npm:
|
||||||
- npm
|
- npm
|
||||||
- TheOrangeOne
|
- TheOrangeOne
|
||||||
- https://www.npmjs.com/~{0}/
|
- https://www.npmjs.com/~{0}/
|
||||||
- fa-file-code-io
|
- fa-file-code-o
|
||||||
|
|
||||||
footer_accounts:
|
footer_accounts:
|
||||||
- github
|
- github
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<head>
|
<head>
|
||||||
<meta name="slug" content="about" />
|
<meta name="title" content="About Me" />
|
||||||
<meta name="title" content="About" />
|
|
||||||
<meta name="no_container" content="true" />
|
<meta name="no_container" content="true" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -14,36 +13,40 @@
|
||||||
</div>
|
</div>
|
||||||
<script async defer src="//cdn.jsdelivr.net/github-cards/latest/widget.js"></script>
|
<script async defer src="//cdn.jsdelivr.net/github-cards/latest/widget.js"></script>
|
||||||
</section>
|
</section>
|
||||||
<section class="bg-primary" id="website">
|
<section class="bg-primary">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>Website</h2>
|
<h2 class="section-heading">Personal Data</h2>
|
||||||
<p>
|
<p>
|
||||||
My website is the culmination of all my knowledge, compiled into 1 place. It not only contains all my projects, but is itself is a project.
|
In the interest of privacy, there's very little personal information here.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The site is primarily built with <a href="http://getpelican.com">Pelican</a>, a static site generator. This allows me to write nice clean, <i>DRY</i> content, and have it come out as clean, minified HTML.
|
The information that is here is eitther not personal enough to bother protecting, or has been selectively chosen as nothing bad.
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
The Javascript is built using <a href="http://browserify.org/">Browserify</a>, and the CSS is built using <a href="https://github.com/sass/node-sass">node-SCSS</a>. Both are run as a build step when pelican builds.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section id="server">
|
<section>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>Server</h2>
|
<h2 class="section-heading">Accounts</h2>
|
||||||
<p>
|
<p>
|
||||||
The website is hosted on part of my dedicated server from <a href="https://www.soyoustart.com/en/">SoYouStart</a>, running an Ubuntu Server VM with <a href="http://dokku.viewdocs.io/dokku/">Dokku</a> installed.
|
These are all the accounts I run, all to do with various things. Take a look!
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
The prebuilt static files are served using a <a href="https://github.com/RealOrangeOne/tstatic">custom Express server</a>, to make the site as fast and effective as possible.
|
|
||||||
</p>
|
</p>
|
||||||
|
<div class="row text-center">
|
||||||
|
{% for key, account in ACCOUNTS.items() %}
|
||||||
|
<div class="col-sm-3 col-xs-6">
|
||||||
|
<div class="service-box account">
|
||||||
|
<a href="{{ account.url }}" class="no-underline">
|
||||||
|
<i class="fa fa-4x {{ account.icon }}"></i>
|
||||||
|
<h3>{{ account.site }}</h3>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="text-center">
|
<section class="bg-primary text-center">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="btn-group">
|
<a class="btn btn-primary-dark btn-xl" href="/contact/">Contact Me</a>
|
||||||
<a class="btn btn-github btn-xl" href="https://github.com/RealOrangeOne/theorangeone.net"><i class="fa fa-github fa-lg"></i> View Source</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</body>
|
</body>
|
||||||
|
|
27
content/pages/contact.html
Normal file
27
content/pages/contact.html
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<head>
|
||||||
|
<meta name="title" content="Contact Me" />
|
||||||
|
<meta name="no_container" content="true" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section>
|
||||||
|
<div class="container">
|
||||||
|
<p>
|
||||||
|
The fastest way to contact me is through twitter. Just tag me or send me a message and I'll respond as soon as possible!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="bg-primary">
|
||||||
|
<div class="container">
|
||||||
|
<h2 class="section-heading">Need something more formal?</h2>
|
||||||
|
<p>
|
||||||
|
If you need to contact me in a more formal capacity, send me an email! I aim to respond to all emails within 3 days.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="text-center">
|
||||||
|
<div class="container">
|
||||||
|
<a class="btn btn-primary btn-xl margin protected-mailto" data-value="{{ CONTACT_EMAIL|encode_text }}">Send me an email!</a>
|
||||||
|
<a class="btn btn-primary btn-xl margin" href="{{ ACCOUNTS.twitter.url }}">View Twitter</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</body>
|
|
@ -7,6 +7,7 @@ sys.path.insert(0, os.path.realpath('./'))
|
||||||
AUTHOR = "Jake Howard"
|
AUTHOR = "Jake Howard"
|
||||||
SITENAME = "TheOrangeOne"
|
SITENAME = "TheOrangeOne"
|
||||||
SITEURL = "https://theorangeone.net"
|
SITEURL = "https://theorangeone.net"
|
||||||
|
CONTACT_EMAIL = "info@theorangeone.net"
|
||||||
PATH = 'content'
|
PATH = 'content'
|
||||||
TIMEZONE = "Europe/London"
|
TIMEZONE = "Europe/London"
|
||||||
DEFAULT_LANG = "en"
|
DEFAULT_LANG = "en"
|
||||||
|
@ -111,7 +112,8 @@ JINJA_FILTERS = {
|
||||||
"limit": filters.limit,
|
"limit": filters.limit,
|
||||||
"get_title": filters.get_title,
|
"get_title": filters.get_title,
|
||||||
"get_html_title": filters.get_html_title,
|
"get_html_title": filters.get_html_title,
|
||||||
"get_image": filters.get_image
|
"get_image": filters.get_image,
|
||||||
|
"encode_text": filters.encode_text
|
||||||
}
|
}
|
||||||
|
|
||||||
JINJA_ENVIRONMENT = {
|
JINJA_ENVIRONMENT = {
|
||||||
|
|
|
@ -39,3 +39,7 @@ def get_html_title(instance):
|
||||||
|
|
||||||
def get_image(instance):
|
def get_image(instance):
|
||||||
return get_attribute(instance, 'image') or (hasattr(instance, 'page') and get_attribute(instance.page, 'name')) or ''
|
return get_attribute(instance, 'image') or (hasattr(instance, 'page') and get_attribute(instance.page, 'name')) or ''
|
||||||
|
|
||||||
|
|
||||||
|
def encode_text(text):
|
||||||
|
return " ".join([str(ord(c)) for c in text])
|
||||||
|
|
|
@ -9,7 +9,7 @@ class TestClient:
|
||||||
def get(self, path, JS=True):
|
def get(self, path, JS=True):
|
||||||
file_path = self.build_path(path)
|
file_path = self.build_path(path)
|
||||||
content = "".join(open(file_path).readlines())
|
content = "".join(open(file_path).readlines())
|
||||||
if path.endswith('html'):
|
if file_path.endswith('html'):
|
||||||
content = BeautifulSoup(content, 'html.parser')
|
content = BeautifulSoup(content, 'html.parser')
|
||||||
if JS:
|
if JS:
|
||||||
for script in content(["noscript"]): # Remove noscript tags
|
for script in content(["noscript"]): # Remove noscript tags
|
||||||
|
@ -19,6 +19,8 @@ class TestClient:
|
||||||
def build_path(self, path):
|
def build_path(self, path):
|
||||||
if path.startswith('/'):
|
if path.startswith('/'):
|
||||||
path = path[1:]
|
path = path[1:]
|
||||||
|
if path.endswith('/'):
|
||||||
|
path += 'index.html'
|
||||||
return os.path.join(self.output_path, path)
|
return os.path.join(self.output_path, path)
|
||||||
|
|
||||||
def exists(self, path):
|
def exists(self, path):
|
||||||
|
|
|
@ -96,5 +96,11 @@ class TestClientTestCase(TestCase):
|
||||||
def test_file_exists(self):
|
def test_file_exists(self):
|
||||||
self.assertTrue(self.client.exists('index.html'))
|
self.assertTrue(self.client.exists('index.html'))
|
||||||
|
|
||||||
|
def test_build_path_without_index(self):
|
||||||
|
self.assertEqual(
|
||||||
|
self.client.build_path('foo/'),
|
||||||
|
self.client.build_path('foo/index.html')
|
||||||
|
)
|
||||||
|
|
||||||
def test_file_doesnt_exist(self):
|
def test_file_doesnt_exist(self):
|
||||||
self.assertFalse(self.client.exists('foo.bar'))
|
self.assertFalse(self.client.exists('foo.bar'))
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from tests import TestCase
|
from tests import TestCase
|
||||||
from config import social as social_settings
|
from config import social as social_settings
|
||||||
|
import pelicanconf as settings
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,18 +48,6 @@ class AboutPageTestCase(TestCase):
|
||||||
self.assertHeaderTitle(content, 'About')
|
self.assertHeaderTitle(content, 'About')
|
||||||
self.assertTitle(content, 'About')
|
self.assertTitle(content, 'About')
|
||||||
|
|
||||||
def test_website_section(self):
|
|
||||||
content = self.client.get('about/index.html')
|
|
||||||
section = content.find('section', id='website')
|
|
||||||
subtitle = section.find('h2')
|
|
||||||
self.assertEqual('Website', self.get_children(subtitle))
|
|
||||||
|
|
||||||
def test_server_section(self):
|
|
||||||
content = self.client.get('about/index.html')
|
|
||||||
section = content.find('section', id='server')
|
|
||||||
subtitle = section.find('h2')
|
|
||||||
self.assertEqual('Server', self.get_children(subtitle))
|
|
||||||
|
|
||||||
def test_github_card(self):
|
def test_github_card(self):
|
||||||
content = self.client.get('about/index.html')
|
content = self.client.get('about/index.html')
|
||||||
tags = content.find_all('div', class_='github-card')
|
tags = content.find_all('div', class_='github-card')
|
||||||
|
@ -67,6 +56,33 @@ class AboutPageTestCase(TestCase):
|
||||||
self.assertEqual('medium', tag.attrs['data-theme'])
|
self.assertEqual('medium', tag.attrs['data-theme'])
|
||||||
self.assertEqual(social_settings['accounts']['github'][1], tag.attrs['data-github'])
|
self.assertEqual(social_settings['accounts']['github'][1], tag.attrs['data-github'])
|
||||||
|
|
||||||
|
def test_accounts(self):
|
||||||
|
content = self.client.get('about/index.html')
|
||||||
|
accounts = content.find_all('div', class_='account')
|
||||||
|
defined_accounts = [s for k, s in settings.ACCOUNTS.items()]
|
||||||
|
self.assertEqual(len(accounts), len(defined_accounts))
|
||||||
|
site_names = [s['site'] for s in defined_accounts]
|
||||||
|
urls = [s['url'] for s in defined_accounts]
|
||||||
|
icons = [s['icon'] for s in defined_accounts]
|
||||||
|
for account in accounts:
|
||||||
|
self.assertIn(account.find('a').attrs['href'], urls)
|
||||||
|
self.assertIn(account.find('i').attrs['class'][-1], icons)
|
||||||
|
self.assertIn(self.get_children(account.find('h3')), site_names)
|
||||||
|
|
||||||
|
|
||||||
|
class ContactPageTestCase(TestCase):
|
||||||
|
def test_title(self):
|
||||||
|
content = self.client.get('contact/')
|
||||||
|
self.assertHeaderTitle(content, 'Contact Me')
|
||||||
|
self.assertTitle(content, 'Contact Me')
|
||||||
|
|
||||||
|
def test_contact_links(self):
|
||||||
|
content = self.client.get('contact/')
|
||||||
|
links = content.find_all('section')[2].find_all('a')
|
||||||
|
self.assertEqual(links[1].attrs['href'], settings.ACCOUNTS['twitter']['url'])
|
||||||
|
decoded_value = ''.join([chr(int(c)) for c in links[0].attrs['data-value'].split(' ')])
|
||||||
|
self.assertEqual(decoded_value, settings.CONTACT_EMAIL)
|
||||||
|
|
||||||
|
|
||||||
class Page404TestCase(TestCase):
|
class Page404TestCase(TestCase):
|
||||||
def test_title(self):
|
def test_title(self):
|
||||||
|
|
|
@ -30,3 +30,14 @@ $('.navbar-brand').bind('click', function (event) {
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$('.protected-mailto').bind('click', function (evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
var char_codes = $(this).data('value').split(' ');
|
||||||
|
var plain_text = [];
|
||||||
|
for (var i = 0; i < char_codes.length; i++) {
|
||||||
|
plain_text.push(String.fromCharCode(parseInt(char_codes[i], 10)));
|
||||||
|
}
|
||||||
|
window.location = 'mailto:' + plain_text.join('');
|
||||||
|
});
|
||||||
|
|
|
@ -16,3 +16,7 @@
|
||||||
|
|
||||||
padding: 0 $grid-gutter-width / 2;
|
padding: 0 $grid-gutter-width / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.no-underline:hover {
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
|
|
@ -110,3 +110,13 @@ header#header {
|
||||||
.github-card-container > iframe {
|
.github-card-container > iframe {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.protected-mailto {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-box.account {
|
||||||
|
a:hover {
|
||||||
|
color: $brand-orange-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Reference in a new issue