parent
0c01712341
commit
2a8571d6dd
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="www_1@db03.millslan.net" uuid="1002a9b7-c04c-43d0-bbbf-bd2c4fc7b6a9">
|
||||
<driver-ref>postgresql</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:postgresql://db03.millslan.net:5432/www_1</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="@db03.millslan.net" uuid="afe39e7a-503d-4718-abe7-8bfb0428771b">
|
||||
<driver-ref>mongo</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>com.dbschema.MongoJdbcDriver</jdbc-driver>
|
||||
<jdbc-url>mongodb://db03.millslan.net:27017</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
@ -1,5 +1,5 @@
|
||||
import { env } from '@extollo/lib'
|
||||
|
||||
export default {
|
||||
name: env('APP_NAME', 'Extollo'),
|
||||
name: env('APP_NAME', 'Garrett Mills'),
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
import {Controller, view, Injectable, SecurityContext, Inject, Collection} from '@extollo/lib'
|
||||
import {WorkItem} from '../../models/WorkItem.model'
|
||||
import {FeedPost} from '../../models/FeedPost.model'
|
||||
|
||||
@Injectable()
|
||||
export class Home extends Controller {
|
||||
@Inject()
|
||||
protected readonly security!: SecurityContext
|
||||
|
||||
public async welcome() {
|
||||
const workItems = await this.getWorkItems()
|
||||
const feedPosts = await this.getFeedPosts()
|
||||
|
||||
return view('welcome', {
|
||||
feedPosts: feedPosts.toArray(),
|
||||
workItemGroups: workItems.groupBy(item => item.startDate.getFullYear()),
|
||||
workItemYears: workItems.map(item => item.startDate.getFullYear())
|
||||
.unique()
|
||||
.toArray(),
|
||||
workItemHiddenYears: workItems.filter(item => item.endDate)
|
||||
.map(item => item.startDate.getFullYear())
|
||||
.unique()
|
||||
.toArray()
|
||||
})
|
||||
}
|
||||
|
||||
public async feed() {
|
||||
const feedPosts = await this.getFeedPosts(true)
|
||||
return view('feed', {
|
||||
feedPosts: feedPosts.toArray(),
|
||||
})
|
||||
}
|
||||
|
||||
protected async getFeedPosts(all = false): Promise<Collection<FeedPost>> {
|
||||
const query = FeedPost.query<FeedPost>()
|
||||
.orderByDescending('posted_at')
|
||||
|
||||
if ( !all ) {
|
||||
query.limit(6)
|
||||
}
|
||||
|
||||
if ( !this.security.hasUser() ) {
|
||||
query.where('visible', '=', true)
|
||||
}
|
||||
|
||||
return query.get().collect()
|
||||
}
|
||||
|
||||
protected async getWorkItems(): Promise<Collection<WorkItem>> {
|
||||
const query = WorkItem.query<WorkItem>()
|
||||
.orderByDescending('start_date')
|
||||
|
||||
if ( !this.security.hasUser() ) {
|
||||
query.where('visible', '=', true)
|
||||
}
|
||||
|
||||
return query.get().collect()
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
import {Controller, view, Session, Inject, Injectable, Locale, Validator} from '@extollo/lib'
|
||||
import {UserLogin} from "../../../types/UserLogin";
|
||||
|
||||
@Injectable()
|
||||
export class Home extends Controller {
|
||||
@Inject()
|
||||
protected readonly session!: Session;
|
||||
|
||||
@Inject()
|
||||
protected readonly locale!: Locale;
|
||||
|
||||
public welcome() {
|
||||
this.session.set('app_visits', this.session.get('app_visits', 0) + 1)
|
||||
|
||||
const valid = new Promise<UserLogin>(() => {})
|
||||
|
||||
return view('@extollo:welcome', {
|
||||
app_visits: this.session.get('app_visits'),
|
||||
locale: this.locale.helper(),
|
||||
})
|
||||
}
|
||||
|
||||
public test() {
|
||||
return new Validator<UserLogin>()
|
||||
}
|
||||
}
|
@ -1,13 +1,17 @@
|
||||
import {Route, SessionAuthMiddleware, AuthRequiredMiddleware} from '@extollo/lib'
|
||||
import {Home} from '../controllers/main/Home.controller'
|
||||
import {Route, SessionAuthMiddleware} from '@extollo/lib'
|
||||
import {Home} from '../controllers/Home.controller'
|
||||
|
||||
Route.group('/', () => {
|
||||
Route.get('/')
|
||||
.calls<Home>(Home, home => home.welcome)
|
||||
})
|
||||
|
||||
Route.group('', () => {
|
||||
Route.get('/dash')
|
||||
.pre(AuthRequiredMiddleware)
|
||||
.calls<Home>(Home, home => home.welcome)
|
||||
Route.get('/feed')
|
||||
.calls<Home>(Home, home => home.feed)
|
||||
.alias('feed')
|
||||
}).pre(SessionAuthMiddleware)
|
||||
|
||||
// Route.group('', () => {
|
||||
// Route.get('/dash')
|
||||
// .pre(AuthRequiredMiddleware)
|
||||
// .calls<Home>(Home, home => home.welcome)
|
||||
// }).pre(SessionAuthMiddleware)
|
||||
|
@ -0,0 +1,58 @@
|
||||
import {DatabaseService, FieldType, Inject, Injectable, Migration} from '@extollo/lib'
|
||||
|
||||
/**
|
||||
* CreateWorkItemsTableMigration
|
||||
* ----------------------------------
|
||||
* Create the work_items table to track project history.
|
||||
*/
|
||||
@Injectable()
|
||||
export default class CreateWorkItemsTableMigration extends Migration {
|
||||
@Inject()
|
||||
protected readonly db!: DatabaseService
|
||||
|
||||
/**
|
||||
* Apply the migration.
|
||||
*/
|
||||
async up(): Promise<void> {
|
||||
const schema = this.db.get().schema()
|
||||
const table = await schema.table('work_items')
|
||||
|
||||
table.primaryKey('work_item_id').required()
|
||||
|
||||
table.column('visible')
|
||||
.type(FieldType.bool)
|
||||
.required()
|
||||
.default(false)
|
||||
|
||||
table.column('name')
|
||||
.type(FieldType.varchar)
|
||||
.required()
|
||||
|
||||
table.column('description')
|
||||
.type(FieldType.text)
|
||||
.required()
|
||||
.default('')
|
||||
|
||||
table.column('start_date')
|
||||
.type(FieldType.timestamp)
|
||||
.required()
|
||||
|
||||
table.column('end_date')
|
||||
.type(FieldType.timestamp)
|
||||
.nullable()
|
||||
|
||||
await schema.commit(table)
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo the migration.
|
||||
*/
|
||||
async down(): Promise<void> {
|
||||
const schema = this.db.get().schema()
|
||||
const table = await schema.table('work_items')
|
||||
|
||||
table.dropIfExists()
|
||||
|
||||
await schema.commit(table)
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
import {DatabaseService, FieldType, Inject, Injectable, Migration, raw} from '@extollo/lib'
|
||||
|
||||
/**
|
||||
* CreateFeedPostsTableMigration
|
||||
* ----------------------------------
|
||||
* Create the table to hold posts to the "updates" feed.
|
||||
*/
|
||||
@Injectable()
|
||||
export default class CreateFeedPostsTableMigration extends Migration {
|
||||
@Inject()
|
||||
protected readonly db!: DatabaseService
|
||||
|
||||
/**
|
||||
* Apply the migration.
|
||||
*/
|
||||
async up(): Promise<void> {
|
||||
const schema = this.db.get().schema()
|
||||
const table = await schema.table('feed_posts')
|
||||
|
||||
table.primaryKey('feed_post_id', FieldType.varchar)
|
||||
.required()
|
||||
|
||||
// date, tag, text, visible
|
||||
|
||||
table.column('posted_at')
|
||||
.type(FieldType.timestamp)
|
||||
.default(raw('NOW()'))
|
||||
.required()
|
||||
|
||||
table.column('tag')
|
||||
.type(FieldType.varchar)
|
||||
.required()
|
||||
|
||||
table.column('visible')
|
||||
.type(FieldType.bool)
|
||||
.default(false)
|
||||
.required()
|
||||
|
||||
table.column('body')
|
||||
.type(FieldType.text)
|
||||
.default('')
|
||||
.required()
|
||||
|
||||
await schema.commit(table)
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo the migration.
|
||||
*/
|
||||
async down(): Promise<void> {
|
||||
const schema = this.db.get().schema()
|
||||
const table = await schema.table('feed_posts')
|
||||
|
||||
table.dropIfExists()
|
||||
|
||||
await schema.commit(table)
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import {Field, FieldType, Model} from '@extollo/lib'
|
||||
|
||||
export class FeedPost extends Model<FeedPost> {
|
||||
protected static table = 'feed_posts'
|
||||
protected static key = 'feed_post_id'
|
||||
|
||||
@Field(FieldType.varchar, 'feed_post_id')
|
||||
public readonly feedPostId?: string
|
||||
|
||||
@Field(FieldType.timestamp, 'posted_at')
|
||||
public postedAt: Date = new Date()
|
||||
|
||||
@Field(FieldType.varchar)
|
||||
public tag!: string
|
||||
|
||||
@Field(FieldType.bool)
|
||||
public visible = false
|
||||
|
||||
@Field(FieldType.text)
|
||||
public body = ''
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
import {Field, FieldType, Model} from '@extollo/lib'
|
||||
|
||||
export class WorkItem extends Model<WorkItem> {
|
||||
protected static table = 'work_items'
|
||||
protected static key = 'work_item_id'
|
||||
|
||||
@Field(FieldType.serial, 'work_item_id')
|
||||
public readonly workItemId!: number
|
||||
|
||||
@Field(FieldType.bool)
|
||||
public visible: boolean = false
|
||||
|
||||
@Field(FieldType.varchar)
|
||||
public name!: string
|
||||
|
||||
@Field(FieldType.text)
|
||||
public description: string = ''
|
||||
|
||||
@Field(FieldType.timestamp, 'start_date')
|
||||
public startDate!: Date
|
||||
|
||||
@Field(FieldType.timestamp, 'end_date')
|
||||
public endDate?: Date
|
||||
|
||||
public startDisplay(): string {
|
||||
return `${this.getMonth(this.startDate.getMonth())} ${this.startDate.getFullYear()}`
|
||||
}
|
||||
|
||||
public endDisplay(): string {
|
||||
if ( !this.endDate ) {
|
||||
return 'Present'
|
||||
}
|
||||
|
||||
return `${this.getMonth(this.endDate.getMonth())} ${this.endDate.getFullYear()}`
|
||||
}
|
||||
|
||||
public rangeDisplay(): string {
|
||||
return `${this.startDisplay()} - ${this.endDisplay()}`
|
||||
}
|
||||
|
||||
protected getMonth(index: number): string {
|
||||
return ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][index]
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 34 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 49 KiB |
@ -0,0 +1,374 @@
|
||||
@font-face {
|
||||
font-family: "Raj";
|
||||
src: url("/assets/font/rajdhani/Rajdhani-Regular.ttf");
|
||||
font-weight: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Raj";
|
||||
src: url("/assets/font/rajdhani/Rajdhani-Bold.ttf");
|
||||
font-weight: bold;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Raj";
|
||||
src: url("/assets/font/rajdhani/Rajdhani-Light.ttf");
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Raj";
|
||||
src: url("/assets/font/rajdhani/Rajdhani-Medium.ttf");
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Raj";
|
||||
src: url("/assets/font/rajdhani/Rajdhani-SemiBold.ttf");
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
}
|
||||
body {
|
||||
background: #5e6572;
|
||||
color: #c9d4e2;
|
||||
font-family: "Raj", sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
h1 {
|
||||
font-size: 64pt;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
h2 {
|
||||
font-size: 36pt;
|
||||
padding-top: 70px;
|
||||
padding-bottom: 0;
|
||||
margin: 0;
|
||||
font-weight: 300;
|
||||
}
|
||||
h3 {
|
||||
font-size: 24pt;
|
||||
text-transform: lowercase;
|
||||
margin: 0;
|
||||
padding: 30px 0 0;
|
||||
font-weight: 300;
|
||||
}
|
||||
h4 {
|
||||
font-size: 18pt;
|
||||
margin: 0;
|
||||
padding: 40px 0 0;
|
||||
font-weight: 300;
|
||||
}
|
||||
p {
|
||||
font-size: 18pt;
|
||||
font-weight: 300;
|
||||
}
|
||||
li {
|
||||
font-size: 16pt;
|
||||
font-weight: 300;
|
||||
}
|
||||
code {
|
||||
font-size: 12pt;
|
||||
background: #3e4552;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
pre code {
|
||||
display: block;
|
||||
}
|
||||
.inner {
|
||||
padding-bottom: 10%;
|
||||
max-width: min(70%, 1000px);
|
||||
min-height: 100vh;
|
||||
}
|
||||
.theme-hide {
|
||||
display: none !important;
|
||||
}
|
||||
button,
|
||||
a.button,
|
||||
a.button-small {
|
||||
border: 1px solid #3e4552;
|
||||
font-family: "Raj", sans-serif;
|
||||
font-weight: 300;
|
||||
font-size: 16pt;
|
||||
padding: 5px 20px;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.1s linear;
|
||||
text-decoration: none;
|
||||
color: #c9d4e2;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
}
|
||||
button:hover,
|
||||
a.button:hover,
|
||||
a.button-small:hover {
|
||||
cursor: pointer;
|
||||
background: #3e4552;
|
||||
}
|
||||
button:active,
|
||||
a.button:active,
|
||||
a.button-small:active {
|
||||
background: #323742;
|
||||
}
|
||||
a {
|
||||
color: #c9d4e2;
|
||||
}
|
||||
a.button-small,
|
||||
button.small {
|
||||
padding: 1px 10px;
|
||||
background: #c9d4e2;
|
||||
border: none;
|
||||
color: #3e4552;
|
||||
margin: 5px;
|
||||
}
|
||||
a.button-small:hover,
|
||||
button.small:hover {
|
||||
color: #c9d4e2;
|
||||
background: #3e4552;
|
||||
}
|
||||
.form-group {
|
||||
margin: 10px;
|
||||
}
|
||||
.form-group label {
|
||||
font-size: 14pt;
|
||||
font-weight: 300;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
.form-group input,
|
||||
.form-group textarea,
|
||||
.form-group select {
|
||||
border: 1px solid #3e4552;
|
||||
border-radius: 5px;
|
||||
padding: 7px;
|
||||
font-size: 14pt;
|
||||
min-width: 400px;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
color: #c9d4e2;
|
||||
}
|
||||
.form-group input::placeholder,
|
||||
.form-group textarea::placeholder,
|
||||
.form-group select::placeholder {
|
||||
color: #c9d4e2;
|
||||
}
|
||||
footer {
|
||||
background: #3e4552;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
footer ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 50px 0 0;
|
||||
}
|
||||
footer ul li {
|
||||
display: inline;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
footer ul li a {
|
||||
color: #a9b4c2;
|
||||
text-decoration: none;
|
||||
font-size: 16pt;
|
||||
font-weight: 300;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
footer ul li a:hover {
|
||||
color: #ffffff;
|
||||
}
|
||||
footer .col {
|
||||
margin: 0 30px;
|
||||
}
|
||||
footer .by-line {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
font-size: 36pt;
|
||||
font-weight: 300;
|
||||
text-align: right;
|
||||
padding-top: 30px;
|
||||
}
|
||||
footer .copy {
|
||||
font-size: 14pt;
|
||||
font-weight: 200;
|
||||
}
|
||||
footer ul li {
|
||||
display: block;
|
||||
margin: 10px 0;
|
||||
}
|
||||
footer #tagline:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
footer .auth-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
footer .auth-container .profile {
|
||||
margin-right: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.button-links {
|
||||
margin: 20px;
|
||||
}
|
||||
.button-links a {
|
||||
margin-right: 10px;
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.container {
|
||||
justify-content: left;
|
||||
padding: 20px;
|
||||
width: calc(100% - 40px);
|
||||
}
|
||||
.inner {
|
||||
width: 100%;
|
||||
max-width: unset;
|
||||
}
|
||||
.form-group input,
|
||||
.form-group textarea,
|
||||
.form-group select {
|
||||
min-width: 100%;
|
||||
}
|
||||
footer {
|
||||
flex-direction: column;
|
||||
}
|
||||
footer .by-line {
|
||||
text-align: left;
|
||||
padding: 30px;
|
||||
}
|
||||
footer .links {
|
||||
padding: 20px 0 0;
|
||||
}
|
||||
}
|
||||
.hero {
|
||||
margin-top: 100px;
|
||||
}
|
||||
.hero h1 {
|
||||
font-weight: 300;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
.hero h2 {
|
||||
font-size: 36pt;
|
||||
padding: 0;
|
||||
font-weight: 300;
|
||||
margin: 10px 0 0;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
.hero p {
|
||||
font-size: 20pt;
|
||||
}
|
||||
section#about .about-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
section#about .img {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
section#about .img img {
|
||||
max-width: 300px;
|
||||
margin-top: 30px;
|
||||
margin-right: 20px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
section#work .timeline-group .timeline-year {
|
||||
font-size: 18pt;
|
||||
margin-top: 40px;
|
||||
}
|
||||
section#work .timeline-group .timeline-content {
|
||||
background: #3e4552;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
margin: 20px;
|
||||
}
|
||||
section#work .timeline-group .timeline-content h3 {
|
||||
padding: 0;
|
||||
}
|
||||
section#work .timeline-group .timeline-content p {
|
||||
margin: 0;
|
||||
font-size: 16pt;
|
||||
}
|
||||
section#contact .contact-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
section#contact .contact-container .message {
|
||||
text-align: right;
|
||||
margin-right: 20px;
|
||||
}
|
||||
section#recent .feed-item {
|
||||
background: #3e4552;
|
||||
padding: 20px;
|
||||
margin: 20px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
section#recent .feed-item .feed-category {
|
||||
display: flex;
|
||||
}
|
||||
section#recent .feed-item .feed-category .tag {
|
||||
background: #5e6572;
|
||||
padding: 2px 5px;
|
||||
border-radius: 2px;
|
||||
font-size: 14pt;
|
||||
}
|
||||
section#recent .feed-item .bottom {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: end;
|
||||
}
|
||||
section#recent .feed-item .bottom .stamp {
|
||||
margin-left: 20px;
|
||||
font-size: 14pt;
|
||||
font-weight: 300;
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
}
|
||||
section#recent a.button {
|
||||
margin-left: 20px;
|
||||
}
|
||||
section#auth {
|
||||
max-width: 500px;
|
||||
}
|
||||
section#auth h3 {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.hero h1 {
|
||||
font-size: 48pt;
|
||||
}
|
||||
section#about .about-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
section#work .timeline-content {
|
||||
margin: 20px 0 !important;
|
||||
}
|
||||
section#contact .contact-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
section#contact .contact-container .message {
|
||||
text-align: left;
|
||||
}
|
||||
section#recent .feed-item {
|
||||
margin: 20px 0;
|
||||
}
|
||||
section#recent a.button {
|
||||
margin-left: 0;
|
||||
}
|
||||
section#recent .bottom {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
section#recent .bottom .stamp {
|
||||
max-width: 100%;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
window.glmdev = window.glmdev || {}
|
||||
|
||||
window.glmdev.taglines = [
|
||||
'...is proud that this site is Google-free',
|
||||
'...is a supporter of FLOSS',
|
||||
'...prefers JavaScript, but Python will do',
|
||||
'...occasionally rants about dependency injection',
|
||||
'...loves GNU/Linux',
|
||||
'(or as I\'ve recently taken to calling it, GNU+Linux)',
|
||||
'...uses spaces, not tabs',
|
||||
'...self-hosts all the things',
|
||||
'...occasionally does contract work',
|
||||
'...reads the terms and conditions',
|
||||
'...enjoys r/sysadmin',
|
||||
'...just lost the game',
|
||||
'...is running out of tag-line ideas',
|
||||
`copyright © ${(new Date()).getFullYear()} garrett mills`,
|
||||
]
|
||||
|
||||
document.querySelector('#tagline')
|
||||
.addEventListener('click', event => {
|
||||
if ( typeof glmdev.tagline_index === 'undefined' ) glmdev.tagline_index = 0
|
||||
else if ( glmdev.tagline_index === glmdev.taglines.length - 1 ) glmdev.tagline_index = 0
|
||||
else glmdev.tagline_index += 1
|
||||
|
||||
document.querySelector('#tagline').innerHTML = glmdev.taglines[glmdev.tagline_index]
|
||||
})
|
||||
|
||||
document.querySelector('#timeline-view-all')
|
||||
.addEventListener('click', event => {
|
||||
const hidden = document.querySelectorAll('.work-container.theme-hide')
|
||||
for ( const item of hidden ) {
|
||||
item.classList.remove('theme-hide')
|
||||
}
|
||||
|
||||
document.querySelector('#timeline-view-all').classList.add('theme-hide')
|
||||
}, false)
|
@ -0,0 +1,23 @@
|
||||
extends template_raj
|
||||
|
||||
block content
|
||||
.container#top
|
||||
.inner
|
||||
.hero
|
||||
h1 garrettmills
|
||||
section#recent
|
||||
h2 posts & updates
|
||||
.button-links
|
||||
a.button-small(href='/feed/rss.xml') rss
|
||||
a.button-small(href='/feed/atom.xml') atom
|
||||
a.button-small(href='/feed/json.json') json
|
||||
each item in feedPosts
|
||||
.feed-item
|
||||
.feed-category(id=item.feedPostId)
|
||||
.tag #{item.tag}
|
||||
span
|
||||
if !item.visible
|
||||
p.text (draft)
|
||||
p.text !{item.body}
|
||||
.bottom
|
||||
.stamp <a href="#{named('feed')}##{item.feedPostId}" class="feed-edit-button">permalink</a> | #{item.postedAt.toLocaleString()}
|
@ -0,0 +1,65 @@
|
||||
doctype html
|
||||
head
|
||||
block meta
|
||||
meta(charset='utf-8')
|
||||
meta(name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no')
|
||||
meta(http-equiv='x-ua-compatible' content='ie=edge')
|
||||
meta(name='description' content='Hi, there! My name is Garrett. I am a self-taught software developer and speaker.')
|
||||
meta(name='keywords' content='garrett mills glmdev developer speaker flitter extollo student')
|
||||
meta(name='author' content=config('app.name', 'Garrett Mills'))
|
||||
meta(name='robots' content='index, follow')
|
||||
|
||||
block title
|
||||
title #{config('app.name', 'Garrett Mills')} | Developer, Speaker, Designer
|
||||
|
||||
block style
|
||||
link(rel='stylesheet' href=asset('main.css'))
|
||||
|
||||
link(rel='author' href='humans.txt')
|
||||
link(rel="alternate" href="/feed/atom.xml" title="Garrett Mills - Posts & Updates" type="application/atom+xml")
|
||||
link(rel="alternate" href="/feed/rss.xml" title="Garrett Mills - Posts & Updates" type="application/rss+xml")
|
||||
|
||||
body
|
||||
block content
|
||||
|
||||
footer
|
||||
.ff-tag(style="margin-right: 80px")
|
||||
a(href="https://www.mozilla.org/en-US/firefox/browsers/" target="_blank")
|
||||
img(src="/assets/ffox.png" width="75" style="margin-top: -1px")
|
||||
.by-line garrettmills
|
||||
.copy#tagline(title="my, aren't you curious...") copyright © #{(new Date()).getFullYear()} garrett mills
|
||||
if false
|
||||
div.auth-container
|
||||
div.profile
|
||||
img(src=user.profile_url width=50 style="border-radius: 50%")
|
||||
p Welcome, #{user.first_name} #{user.last_name} | <a href="/auth/logout">Log out</a>
|
||||
.col
|
||||
ul.links
|
||||
li
|
||||
a(href='/#home') home
|
||||
li
|
||||
a(href='/#about') about me
|
||||
li
|
||||
a(href='/#work') what I'm working on
|
||||
.col
|
||||
ul.links
|
||||
li
|
||||
a(href='/#contact') get in touch
|
||||
li
|
||||
a(href='/#recent') latest updates
|
||||
li
|
||||
a(href='https://static.garrettmills.dev/Resume.pdf' target='_blank') résumé
|
||||
.col
|
||||
ul.links
|
||||
li
|
||||
a(href='/blog') blog
|
||||
li
|
||||
a(href='https://code.garrettmills.dev/garrettmills' target='_blank') my code
|
||||
li
|
||||
a(href='/technical') technical info
|
||||
if false
|
||||
li
|
||||
a(href='/dash/main') dashboard
|
||||
|
||||
block script
|
||||
script(src=asset('welcome.js'))
|
@ -1,6 +1,85 @@
|
||||
html
|
||||
head
|
||||
title !{locale('app_name')}
|
||||
body
|
||||
h1 !{locale('welcome_to_extollo')}
|
||||
h2 !{locale('viewed_page_num_times', { interp: { num: app_visits } })}
|
||||
extends template_raj
|
||||
block content
|
||||
.container#home
|
||||
.inner
|
||||
.hero
|
||||
h1 garrettmills
|
||||
p Hi, there. My name is Garrett, and I'm a self-taught software-developer and speaker.
|
||||
section#about
|
||||
h2 about me
|
||||
.about-container
|
||||
.img
|
||||
img(src="/assets/img/profile.jpg" width=300 height=300)
|
||||
.about
|
||||
p Hi! My name is Garrett. Welcome to my corner of the internet. I'm a self-taught developer and maker. I like to build software to improve the developer/user experience. I created the <a href="https://extollo.garrettmills.dev" target="_blank">Extollo</a> framework, <a href="https://code.garrettmills.dev/Noded" target="_blank">Noded</a>, <a href="https://code.garrettmills.dev/starship/coreid" target="_blank">CoreID</a> authentication server, and a couple other projects. I love to communicate my work, and help others pursue their projects. I write blog posts, create video tutorials, hold talks, and publish code from my projects in the hope that others will find it useful.
|
||||
p A bit more background: I grew up in the rural mid-west, and I got started by teaching myself everything I know. I'm a big fan of learning to code this way. I'm currently studying computer science at the University of Kansas.
|
||||
|
||||
if workItemYears && workItemYears.length
|
||||
section#work
|
||||
h2 what I'm working on
|
||||
.timeline-group
|
||||
each year in workItemYears
|
||||
.work-container(class=(workItemHiddenYears.includes(year) ? 'timeline-item theme-hide' : 'timeline-item'))
|
||||
.timeline-container
|
||||
.timeline-year #{year}
|
||||
|
||||
each item in workItemGroups[year]
|
||||
.work-container(class=(item.endDate ? 'timeline-item theme-hide' : 'timeline-item'))
|
||||
.timeline-container
|
||||
.timeline-content
|
||||
.range-small #{item.rangeDisplay()}
|
||||
h3 !{item.name}
|
||||
p !{item.description}
|
||||
.work-container
|
||||
button#timeline-view-all show past work
|
||||
|
||||
section#contact
|
||||
h2 get in touch
|
||||
.contact-container
|
||||
.message
|
||||
p I'd love to hear from you if you have questions or inquiries related to me or my projects. You can get in touch by text, e-mail, or using this form. I also occasionally share thoughts on my <a href="/blog">blog</a>.
|
||||
p <b>E-mail:</b> <a href="mailto:shout@garrettmills.dev">shout@garrettmills.dev</a>
|
||||
.form
|
||||
form#contact-form
|
||||
.form-group
|
||||
input#contactEmail.form-control(type='email' name='email' placeholder='E-Mail Address' required)
|
||||
.form-group
|
||||
input#contactFirst.form-control(name='first' placeholder='First Name' required)
|
||||
.form-group
|
||||
input#contactLast.form-control(name='last' placeholder='Last Name' required)
|
||||
.form-group
|
||||
textarea.form-control#contactMessage(name='message' placeholder='Message' required rows=6)
|
||||
.form-group
|
||||
button Send
|
||||
|
||||
section#recent
|
||||
h2 latest updates
|
||||
each item in feedPosts
|
||||
.feed-item
|
||||
.feed-category(id='feedPostTag_' + item.feedPostId)
|
||||
.tag #{item.tag}
|
||||
span
|
||||
if !item.visible
|
||||
p.text (draft)
|
||||
p.text !{item.body}
|
||||
.bottom
|
||||
.stamp <a href="#{named('feed')}##{item.feedPostId}" class="feed-edit-button">permalink</a> | #{item.postedAt.toLocaleString()}
|
||||
// each item in feed_items
|
||||
// .feed-item
|
||||
// div.feed-category(id='feedPostTag_' + item.id)
|
||||
// .tag #{item.tag}
|
||||
// span
|
||||
// if item.draft
|
||||
// p.text (draft)
|
||||
// p.text !{item.text}
|
||||
// .bottom
|
||||
// if user && can_access.feed_delete && can_access.feed_edit
|
||||
// .stamp <a href="/dash/c/form/FeedPost?id=#{item.id}" class="feed-edit-button" target="_blank">edit</a> | <a href="#recent" class="feed-delete-button" postid="#{item.id}">delete</a>
|
||||
// .stamp <a href="#{app.url}feed##{item.id}" class="feed-edit-button">permalink</a> | #{item.date.toLocaleString()}
|
||||
if feed_overflow
|
||||
.row.mt-4
|
||||
.col-12.text-center
|
||||
a.button(href="/feed") view more
|
||||
|
||||
block append script
|
||||
script(src="/assets/js/raj/welcome.js")
|
||||
|
Loading…
Reference in new issue