diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..610047c --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,19 @@ + + + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://db03.millslan.net:5432/www_1 + $ProjectFileDir$ + + + mongo + true + com.dbschema.MongoJdbcDriver + mongodb://db03.millslan.net:27017 + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/extollo.iml b/.idea/extollo.iml index cb5ff22..9e388b7 100644 --- a/.idea/extollo.iml +++ b/.idea/extollo.iml @@ -1,7 +1,9 @@ - + + + diff --git a/package.json b/package.json index f7ee3dc..e1b7c90 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "lib": "lib" }, "dependencies": { - "@extollo/lib": "^0.9.0", + "@extollo/lib": "^0.9.1", "copyfiles": "^2.4.1", "rimraf": "^3.0.2", "ts-expose-internals": "^4.5.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d0be490..e4e37cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,7 +2,7 @@ lockfileVersion: 5.3 specifiers: '@extollo/cc': ^0.6.0 - '@extollo/lib': ^0.9.0 + '@extollo/lib': ^0.9.1 copyfiles: ^2.4.1 rimraf: ^3.0.2 ts-expose-internals: ^4.5.4 @@ -12,7 +12,7 @@ specifiers: zod: ^3.11.6 dependencies: - '@extollo/lib': 0.9.0 + '@extollo/lib': 0.9.1 copyfiles: 2.4.1 rimraf: 3.0.2 ts-expose-internals: 4.5.4 @@ -110,8 +110,8 @@ packages: - supports-color dev: true - /@extollo/lib/0.9.0: - resolution: {integrity: sha512-Gu9qwjwjDWPfXrp1ThyjFIROWXD8jXqwz4c7JxjWoWbGnY0dLGAZHv6MOF1tqNGU9zTW399jpGS/7lyazApMpQ==} + /@extollo/lib/0.9.1: + resolution: {integrity: sha512-HPYwlKO0SHZsTQBQh5zTA8oErIEncdFWKr3LGs3LmL7Dq8rWN7dQ1qK2qk809Fc7FBqWeiZKuOsRA8yLkhr6Kg==} dependencies: '@atao60/fse-cli': 0.1.7 '@extollo/ui': 0.1.0_@types+node@14.18.12 diff --git a/src/app/configs/app.config.ts b/src/app/configs/app.config.ts index 57042be..d78a24d 100644 --- a/src/app/configs/app.config.ts +++ b/src/app/configs/app.config.ts @@ -1,5 +1,5 @@ import { env } from '@extollo/lib' export default { - name: env('APP_NAME', 'Extollo'), + name: env('APP_NAME', 'Garrett Mills'), } diff --git a/src/app/configs/database.config.ts b/src/app/configs/database.config.ts index 0a4f070..e8873e2 100644 --- a/src/app/configs/database.config.ts +++ b/src/app/configs/database.config.ts @@ -3,7 +3,7 @@ import { env } from "@extollo/lib"; export default { connections: { default: { - user: env('DATABASE_USERNAME', 'extollo'), + user: env('DATABASE_USERNAME', 'www'), password: env('DATABASE_PASSWORD'), host: env('DATABASE_HOST', 'localhost'), port: env('DATABASE_PORT', 5432), diff --git a/src/app/http/controllers/Home.controller.ts b/src/app/http/controllers/Home.controller.ts new file mode 100644 index 0000000..3946bc4 --- /dev/null +++ b/src/app/http/controllers/Home.controller.ts @@ -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> { + const query = FeedPost.query() + .orderByDescending('posted_at') + + if ( !all ) { + query.limit(6) + } + + if ( !this.security.hasUser() ) { + query.where('visible', '=', true) + } + + return query.get().collect() + } + + protected async getWorkItems(): Promise> { + const query = WorkItem.query() + .orderByDescending('start_date') + + if ( !this.security.hasUser() ) { + query.where('visible', '=', true) + } + + return query.get().collect() + } +} diff --git a/src/app/http/controllers/main/Home.controller.ts b/src/app/http/controllers/main/Home.controller.ts deleted file mode 100644 index 14275ba..0000000 --- a/src/app/http/controllers/main/Home.controller.ts +++ /dev/null @@ -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(() => {}) - - return view('@extollo:welcome', { - app_visits: this.session.get('app_visits'), - locale: this.locale.helper(), - }) - } - - public test() { - return new Validator() - } -} diff --git a/src/app/http/routes/app.routes.ts b/src/app/http/routes/app.routes.ts index c7f578d..0aed250 100644 --- a/src/app/http/routes/app.routes.ts +++ b/src/app/http/routes/app.routes.ts @@ -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.welcome) -}) -Route.group('', () => { - Route.get('/dash') - .pre(AuthRequiredMiddleware) - .calls(Home, home => home.welcome) + Route.get('/feed') + .calls(Home, home => home.feed) + .alias('feed') }).pre(SessionAuthMiddleware) + +// Route.group('', () => { +// Route.get('/dash') +// .pre(AuthRequiredMiddleware) +// .calls(Home, home => home.welcome) +// }).pre(SessionAuthMiddleware) diff --git a/src/app/migrations/2022-03-29T14:53:08.624Z_CreateWorkItemsTableMigration.migration.ts b/src/app/migrations/2022-03-29T14:53:08.624Z_CreateWorkItemsTableMigration.migration.ts new file mode 100644 index 0000000..a58ce4b --- /dev/null +++ b/src/app/migrations/2022-03-29T14:53:08.624Z_CreateWorkItemsTableMigration.migration.ts @@ -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 { + 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 { + const schema = this.db.get().schema() + const table = await schema.table('work_items') + + table.dropIfExists() + + await schema.commit(table) + } +} diff --git a/src/app/migrations/2022-03-29T17:04:30.346Z_CreateFeedPostsTableMigration.migration.ts b/src/app/migrations/2022-03-29T17:04:30.346Z_CreateFeedPostsTableMigration.migration.ts new file mode 100644 index 0000000..fc82630 --- /dev/null +++ b/src/app/migrations/2022-03-29T17:04:30.346Z_CreateFeedPostsTableMigration.migration.ts @@ -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 { + 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 { + const schema = this.db.get().schema() + const table = await schema.table('feed_posts') + + table.dropIfExists() + + await schema.commit(table) + } +} diff --git a/src/app/models/FeedPost.model.ts b/src/app/models/FeedPost.model.ts new file mode 100644 index 0000000..32a6037 --- /dev/null +++ b/src/app/models/FeedPost.model.ts @@ -0,0 +1,21 @@ +import {Field, FieldType, Model} from '@extollo/lib' + +export class FeedPost extends Model { + 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 = '' +} diff --git a/src/app/models/WorkItem.model.ts b/src/app/models/WorkItem.model.ts new file mode 100644 index 0000000..c521729 --- /dev/null +++ b/src/app/models/WorkItem.model.ts @@ -0,0 +1,44 @@ +import {Field, FieldType, Model} from '@extollo/lib' + +export class WorkItem extends Model { + 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] + } +} diff --git a/src/app/resources/assets/ffox.png b/src/app/resources/assets/ffox.png new file mode 100644 index 0000000..4d54735 Binary files /dev/null and b/src/app/resources/assets/ffox.png differ diff --git a/src/app/resources/assets/font/fira/FiraCode-Bold.ttf b/src/app/resources/assets/font/fira/FiraCode-Bold.ttf new file mode 100644 index 0000000..fe2dd41 Binary files /dev/null and b/src/app/resources/assets/font/fira/FiraCode-Bold.ttf differ diff --git a/src/app/resources/assets/font/fira/FiraCode-Light.ttf b/src/app/resources/assets/font/fira/FiraCode-Light.ttf new file mode 100644 index 0000000..d4a1efe Binary files /dev/null and b/src/app/resources/assets/font/fira/FiraCode-Light.ttf differ diff --git a/src/app/resources/assets/font/fira/FiraCode-Medium.ttf b/src/app/resources/assets/font/fira/FiraCode-Medium.ttf new file mode 100644 index 0000000..459d586 Binary files /dev/null and b/src/app/resources/assets/font/fira/FiraCode-Medium.ttf differ diff --git a/src/app/resources/assets/font/fira/FiraCode-Regular.ttf b/src/app/resources/assets/font/fira/FiraCode-Regular.ttf new file mode 100644 index 0000000..f60e430 Binary files /dev/null and b/src/app/resources/assets/font/fira/FiraCode-Regular.ttf differ diff --git a/src/app/resources/assets/font/fira/FiraCode-SemiBold.ttf b/src/app/resources/assets/font/fira/FiraCode-SemiBold.ttf new file mode 100644 index 0000000..62557fe Binary files /dev/null and b/src/app/resources/assets/font/fira/FiraCode-SemiBold.ttf differ diff --git a/src/app/resources/assets/font/playfair/PlayfairDisplay-Black.ttf b/src/app/resources/assets/font/playfair/PlayfairDisplay-Black.ttf new file mode 100644 index 0000000..4bc5afd Binary files /dev/null and b/src/app/resources/assets/font/playfair/PlayfairDisplay-Black.ttf differ diff --git a/src/app/resources/assets/font/playfair/PlayfairDisplay-BlackItalic.ttf b/src/app/resources/assets/font/playfair/PlayfairDisplay-BlackItalic.ttf new file mode 100644 index 0000000..7f2e6ee Binary files /dev/null and b/src/app/resources/assets/font/playfair/PlayfairDisplay-BlackItalic.ttf differ diff --git a/src/app/resources/assets/font/playfair/PlayfairDisplay-Bold.ttf b/src/app/resources/assets/font/playfair/PlayfairDisplay-Bold.ttf new file mode 100644 index 0000000..70668c1 Binary files /dev/null and b/src/app/resources/assets/font/playfair/PlayfairDisplay-Bold.ttf differ diff --git a/src/app/resources/assets/font/playfair/PlayfairDisplay-BoldItalic.ttf b/src/app/resources/assets/font/playfair/PlayfairDisplay-BoldItalic.ttf new file mode 100644 index 0000000..d25f49e Binary files /dev/null and b/src/app/resources/assets/font/playfair/PlayfairDisplay-BoldItalic.ttf differ diff --git a/src/app/resources/assets/font/playfair/PlayfairDisplay-Italic.ttf b/src/app/resources/assets/font/playfair/PlayfairDisplay-Italic.ttf new file mode 100644 index 0000000..58b8104 Binary files /dev/null and b/src/app/resources/assets/font/playfair/PlayfairDisplay-Italic.ttf differ diff --git a/src/app/resources/assets/font/playfair/PlayfairDisplay-Regular.ttf b/src/app/resources/assets/font/playfair/PlayfairDisplay-Regular.ttf new file mode 100644 index 0000000..782c1a4 Binary files /dev/null and b/src/app/resources/assets/font/playfair/PlayfairDisplay-Regular.ttf differ diff --git a/src/app/resources/assets/font/rajdhani/Rajdhani-Bold.ttf b/src/app/resources/assets/font/rajdhani/Rajdhani-Bold.ttf new file mode 100644 index 0000000..6c0454c Binary files /dev/null and b/src/app/resources/assets/font/rajdhani/Rajdhani-Bold.ttf differ diff --git a/src/app/resources/assets/font/rajdhani/Rajdhani-Light.ttf b/src/app/resources/assets/font/rajdhani/Rajdhani-Light.ttf new file mode 100644 index 0000000..1c7a361 Binary files /dev/null and b/src/app/resources/assets/font/rajdhani/Rajdhani-Light.ttf differ diff --git a/src/app/resources/assets/font/rajdhani/Rajdhani-Medium.ttf b/src/app/resources/assets/font/rajdhani/Rajdhani-Medium.ttf new file mode 100644 index 0000000..e52b403 Binary files /dev/null and b/src/app/resources/assets/font/rajdhani/Rajdhani-Medium.ttf differ diff --git a/src/app/resources/assets/font/rajdhani/Rajdhani-Regular.ttf b/src/app/resources/assets/font/rajdhani/Rajdhani-Regular.ttf new file mode 100644 index 0000000..37663a5 Binary files /dev/null and b/src/app/resources/assets/font/rajdhani/Rajdhani-Regular.ttf differ diff --git a/src/app/resources/assets/font/rajdhani/Rajdhani-SemiBold.ttf b/src/app/resources/assets/font/rajdhani/Rajdhani-SemiBold.ttf new file mode 100644 index 0000000..f7ec9bb Binary files /dev/null and b/src/app/resources/assets/font/rajdhani/Rajdhani-SemiBold.ttf differ diff --git a/src/app/resources/assets/img/profile.jpg b/src/app/resources/assets/img/profile.jpg new file mode 100644 index 0000000..b48af6d Binary files /dev/null and b/src/app/resources/assets/img/profile.jpg differ diff --git a/src/app/resources/assets/img/profile_wide.jpg b/src/app/resources/assets/img/profile_wide.jpg new file mode 100644 index 0000000..af2ccdb Binary files /dev/null and b/src/app/resources/assets/img/profile_wide.jpg differ diff --git a/src/app/resources/assets/img/profile_wide.jpg~ b/src/app/resources/assets/img/profile_wide.jpg~ new file mode 100644 index 0000000..b48af6d Binary files /dev/null and b/src/app/resources/assets/img/profile_wide.jpg~ differ diff --git a/src/app/resources/assets/main.css b/src/app/resources/assets/main.css new file mode 100644 index 0000000..63bf051 --- /dev/null +++ b/src/app/resources/assets/main.css @@ -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; + } +} diff --git a/src/app/resources/assets/welcome.js b/src/app/resources/assets/welcome.js new file mode 100644 index 0000000..69590a6 --- /dev/null +++ b/src/app/resources/assets/welcome.js @@ -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) diff --git a/src/app/resources/views/feed.pug b/src/app/resources/views/feed.pug new file mode 100644 index 0000000..3d28631 --- /dev/null +++ b/src/app/resources/views/feed.pug @@ -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 permalink  |  #{item.postedAt.toLocaleString()} diff --git a/src/app/resources/views/template_raj.pug b/src/app/resources/views/template_raj.pug new file mode 100644 index 0000000..13e1463 --- /dev/null +++ b/src/app/resources/views/template_raj.pug @@ -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}  |  Log out + .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')) diff --git a/src/app/resources/views/welcome.pug b/src/app/resources/views/welcome.pug index 7649fb1..ebd88d0 100644 --- a/src/app/resources/views/welcome.pug +++ b/src/app/resources/views/welcome.pug @@ -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 Extollo framework, Noded, CoreID 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 blog. + p E-mail: shout@garrettmills.dev + .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 permalink  |  #{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 edit | delete + // .stamp permalink  |  #{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")