on master help sign in
Merge pull request #134 from urbit/application-form

Application form

commitfc00160aaa1603c32a828d109b719beec879ad78 authorMorgan Sutherland <msutherl@users.noreply.github.com> dateMon 1 Jul 2019, 18:34 (7 years ago) parent2f95ae5c62febf
13 files changed+695−27
content/hoonschool.md+23 sass/_main.scss+29−3 static/assets/back-arrow.svg+3 static/php/airtable/hoonschool.php+3 static/php/airtable/src/Airtable.php+118 static/php/airtable/src/Request.php+155 static/php/airtable/src/Response.php+246 templates/.gitignore+2 templates/index.html+1 templates/page.html−24 templates/partials/navigation-special.html+60 templates/partials/top-alert.html+9 templates/sections/index.html+46
content/hoonschool.mdnew file+23
@@ -0,0 +1,23 @@
1++++
2+title = "Hoon School"
3++++
4+
5+Interested in developing on Urbit? You’ll want to learn [Hoon](https://urbit.org/docs/learn/hoon/_index.md), a functional programming language that’s custom-designed for use with Arvo, the Urbit operating system.
6+
7+We want you to learn Hoon, too. That’s why we’re offering Hoon School: a free, asynchronous program that’s fully supported by an instructor. Hoon School is offered in the form of two courses: Hoon 101 and Hoon 201. Classes are offered four times a year, on a quarterly basis.
8+
9+[Apply below ↓](#apply)
10+
11+## Hoon 101
12+
13+Hoon 101 is a six-week, introductory-level course. It’s appropriate for motivated learners with no programming experience, and for those familiar with other programming languages. You’ll leave Hoon 101 with a strong understanding of the language’s fundamentals. Expect to devote four hours a week to this course.
14+
15+## Hoon 201
16+
17+Hoon 201 is an eight-week, advanced-level course with a heavier workload. It’s appropriate for graduates of Hoon 101, for those otherwise already familiar with Hoon, and for very experienced programmers of other languages. After graduating from Hoon 201, you’ll have enough knowledge to build applications and contribute code to the Arvo operating system. Expect to devote six hours a week to this class.
18+
19+## Which should I take?
20+
21+If Hoon would be your first language, or you have some experience with non-functional languages, apply to Hoon 101. If you’ve already played around with Hoon extensively, or you’re a seasoned professional programmer, apply to Hoon 201. If you’re on the fence, go with 101; the two courses scheduled back-to-back, so you can enroll in the advanced course upon graduating from the introductory one.
22+
23+<script class="src="https://static.airtable.com/js/embed/embed_snippet_v1.js"></script><iframe id="apply" class="airtable-embed airtable-dynamic-height w-full" src="https://airtable.com/embed/shr8bb15QPR3hfQpG?backgroundColor=teal" frameborder="0" height="1300"></iframe>
sass/_main.scss+29−3
@@ -47,6 +47,15 @@ iframe {
4747 margin-top: -(24rem / 4);
4848}
4949
50+// Hack to left-align top-bar text with content
51+.top-bar-inner {
52+ max-width: 960px;
53+ margin: 0 auto;
54+ p {
55+ margin-left: (100% / 6);
56+ }
57+}
58+
5059.content-nav {
5160 width: 20%;
5261 min-width: 200px;
@@ -269,18 +278,35 @@ iframe {
269278 max-width: 320px;
270279}
271280
281+.form--big {
282+ max-width: 390px;
283+ .btn {
284+ width: 100%;
285+ }
286+}
287+
288+.hoonschool-application {
289+ &__experience {
290+ resize: vertical;
291+ }
292+}
293+
272294/*
273295 * styling
274296 */
275297
276−textarea, input[type="text"], input[type="email"], input[type="number"] {
277− border: 2px solid #000;
298+textarea, input[type="text"], input[type="email"], input[type="number"], select {
299+ border: 1px solid #000;
278300 padding: 12px;
279301 width: 100%;
280302 outline: none;
281303 box-sizing: border-box;
282304}
283305
306+textarea {
307+ resize: horizontal;
308+}
309+
284310button, .btn {
285311 font-family: 'Inter UI', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
286312 font-weight: 500;
@@ -416,7 +442,7 @@ pre, code {
416442 font-size: (4rem / 4);
417443}
418444
419−textarea, input[type="text"], input[type="email"], input[type="number"] {
445+textarea, input[type="text"], input[type="email"], input[type="number"], select {
420446 border: 1px solid #B1B2B3;
421447 font-family: "Source Code Pro", mono;
422448 font-size: (4rem / 4);
static/assets/back-arrow.svgnew file+3
@@ -0,0 +1,3 @@
1+<svg width="13" height="12" viewBox="0 0 13 12" fill="none" xmlns="http://www.w3.org/2000/svg">
2+<path d="M5.68537 11.1477L6.85582 9.97727L3.62287 6.75568H12.1399V5.0625H3.62287L6.85582 1.82955L5.68537 0.670454L0.446733 5.90909L5.68537 11.1477Z" fill="black"/>
3+</svg>
static/php/airtable/hoonschool.phpnew file+3
@@ -0,0 +1,3 @@
1+<?php
2+ echo "test";
3+?>
static/php/airtable/src/Airtable.phpnew file+118
@@ -0,0 +1,118 @@
1+<?php
2+
3+namespace TANIOS\Airtable;
4+
5+/**
6+ * Airtable API Class
7+ *
8+ * @author Sleiman Tanios
9+ * @copyright Sleiman Tanios - TANIOS 2017
10+ * @version 1.0
11+ */
12+
13+
14+class Airtable
15+{
16+
17+ const API_URL = "https://api.airtable.com/v0/";
18+
19+ private $_key;
20+
21+ private $_base;
22+
23+ public function __construct($config)
24+ {
25+ if (is_array($config)) {
26+ $this->setKey($config['api_key']);
27+ $this->setBase($config['base']);
28+ } else {
29+ echo 'Error: __construct() - Configuration data is missing.';
30+ }
31+ }
32+
33+ public function setKey($key)
34+ {
35+ $this->_key = $key;
36+ }
37+
38+ public function getKey()
39+ {
40+ return $this->_key;
41+ }
42+
43+ public function setBase($base)
44+ {
45+ $this->_base = $base;
46+ }
47+
48+ public function getBase()
49+ {
50+ return $this->_base;
51+ }
52+
53+ public function getApiUrl($request){
54+ $request = str_replace( ' ', '%20', $request );
55+ $url = self::API_URL.$this->getBase().'/'.$request;
56+ return $url;
57+ }
58+
59+ function getContent($content_type,$params="",$relations=false)
60+ {
61+ return new Request( $this, $content_type, $params, false, $relations );
62+ }
63+
64+ function saveContent($content_type,$fields)
65+ {
66+
67+ $fields = array('fields'=>$fields);
68+
69+ $request = new Request( $this, $content_type, $fields, true );
70+
71+ return $request->getResponse();
72+
73+ }
74+
75+ function updateContent($content_type,$fields)
76+ {
77+
78+ $fields = array('fields'=>$fields);
79+
80+ $request = new Request( $this, $content_type, $fields, 'patch' );
81+
82+ return $request->getResponse();
83+
84+ }
85+
86+ function deleteContent($content_type)
87+ {
88+
89+ $request = new Request( $this, $content_type, [], 'delete' );
90+
91+ return $request->getResponse();
92+
93+ }
94+
95+ function quickCheck($content_type,$field="",$value="")
96+ {
97+ $params = "";
98+
99+ if (!empty($field)&& !empty($value)){
100+
101+ $params = array(
102+ "filterByFormula" => "AND({{$field}} = '$value')",
103+ );
104+ }
105+
106+ $request = new Request( $this, $content_type, $params, false );
107+
108+ $response = $request->getResponse();
109+
110+
111+ $results['count'] = count($response->records);
112+ $results['records'] = $response->records;
113+
114+
115+ return (object)$results;
116+ }
117+
118+}
static/php/airtable/src/Request.phpnew file+155
@@ -0,0 +1,155 @@
1+<?php
2+/**
3+ * Created by PhpStorm.
4+ * User: glali
5+ * Date: 2017-04-27
6+ * Time: 14:55
7+ */
8+
9+namespace TANIOS\Airtable;
10+
11+class Request implements \ArrayAccess
12+{
13+
14+ /**
15+ * @var Airtable Instance of Airtable
16+ */
17+ private $airtable;
18+ /**
19+ * @var resource Instance of CURL
20+ */
21+ private $curl;
22+ /**
23+ * @var string Content type
24+ */
25+ private $content_type;
26+ /**
27+ * @var array Request data
28+ */
29+ private $data = [];
30+ /**
31+ * @var bool Is it a POST request?
32+ */
33+ private $is_post = false;
34+
35+ /**
36+ * @var array|boolean Relations to lazy load
37+ */
38+ private $relations;
39+
40+ /**
41+ * Create a Request to AirTable API
42+ * @param Airtable $airtable Instance of Airtable
43+ * @param string $content_type Content type
44+ * @param array $data Request data
45+ * @param bool|string $is_post Is it a POST request?
46+ */
47+ public function __construct( $airtable, $content_type, $data = [], $is_post = false, $relations = false )
48+ {
49+
50+ $this->airtable = $airtable;
51+ $this->content_type = $content_type;
52+ $this->data = $data;
53+ $this->is_post = $is_post;
54+ $this->relations = $relations;
55+
56+ }
57+
58+ private function init()
59+ {
60+
61+ $headers = array(
62+ 'Content-Type: application/json',
63+ sprintf('Authorization: Bearer %s', $this->airtable->getKey())
64+ );
65+
66+ $request = $this->content_type;
67+
68+ if( ! $this->is_post )
69+ {
70+ if (!empty($this->data)){
71+ $data = http_build_query($this->data);
72+ $request .= "?" . $data;
73+ }
74+ }
75+
76+ $curl = curl_init($this->airtable->getApiUrl($request));
77+
78+ curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
79+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
80+
81+ if( $this->is_post )
82+ {
83+ if( strtolower( $this->is_post ) == 'patch' )
84+ {
85+ curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PATCH');
86+ }
87+ else if( strtolower( $this->is_post ) == 'delete' )
88+ {
89+ curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
90+ }
91+ curl_setopt($curl,CURLOPT_POST, count($this->data));
92+ curl_setopt($curl,CURLOPT_POSTFIELDS, json_encode($this->data));
93+ }
94+
95+ $this->curl = $curl;
96+
97+ }
98+
99+ /**
100+ * @return Response Get response from API
101+ */
102+ public function getResponse()
103+ {
104+
105+ $this->init();
106+
107+ $response_string = curl_exec( $this->curl );
108+
109+ $response = new Response( $this->airtable, $this, $response_string, $this->relations );
110+
111+ return $response;
112+
113+ }
114+
115+ public function __set( $key, $value )
116+ {
117+ if( ! is_array( $this->data ) )
118+ {
119+ $this->data = [];
120+ }
121+
122+ $this->data[ $key ] = $value;
123+ }
124+
125+ public function offsetExists($offset)
126+ {
127+ return is_array( $this->data) && isset( $this->data[ $offset ] );
128+ }
129+
130+ public function offsetGet($offset)
131+ {
132+ return is_array( $this->data ) && isset( $this->data[ $offset ] )
133+ ? $this->data[ $offset ]
134+ : null;
135+ }
136+
137+ public function offsetSet($offset, $value)
138+ {
139+ if( ! is_array( $this->data ) )
140+ {
141+ $this->data = [];
142+ }
143+
144+ $this->data[ $offset ] = $value;
145+ }
146+
147+ public function offsetUnset($offset)
148+ {
149+ if( is_array( $this->data ) && isset( $this->data[ $offset ] ) )
150+ {
151+ unset( $this->data[ $offset ] );
152+ }
153+ }
154+
155+}
static/php/airtable/src/Response.phpnew file+246
@@ -0,0 +1,246 @@
1+<?php
2+/**
3+ * Created by PhpStorm.
4+ * User: glali
5+ * Date: 2017-04-27
6+ * Time: 14:44
7+ */
8+
9+namespace TANIOS\Airtable;
10+
11+
12+class Response implements \ArrayAccess
13+{
14+
15+ /**
16+ * @var Airtable Instance of Airtable
17+ */
18+ private $airtable;
19+
20+ /**
21+ * @var Request Instance or Request
22+ */
23+ private $request;
24+
25+ /**
26+ * @var string Response content
27+ */
28+ private $content = "";
29+
30+ /**
31+ * @var bool|\stdClass Response
32+ */
33+ private $parsedContent = false;
34+
35+ /**
36+ * Response constructor.
37+ * @param Airtable $airtable Instance of Airtable
38+ * @param Request $request Instance of Request
39+ * @param string $content Content string
40+ */
41+ public function __construct( $airtable, $request, $content, $relations = false )
42+ {
43+
44+ $this->airtable = $airtable;
45+
46+ $this->request = $request;
47+
48+ $this->content = $content;
49+
50+ try
51+ {
52+ $this->parsedContent = json_decode( $content );
53+ }
54+ catch ( \Exception $e )
55+ {
56+ $this->parsedContent = false;
57+ }
58+
59+ if( is_array( $relations ) && count( $relations ) > 0 )
60+ {
61+
62+ if( array_keys( $relations ) !== range( 0, count( $relations ) - 1 ) )
63+ {
64+ foreach ( $relations as $related_field => $related_table )
65+ {
66+ $this->processRelatedField( $related_field, $related_table );
67+ }
68+ }
69+ else
70+ {
71+ foreach ( $relations as $related_field )
72+ {
73+ $this->processRelatedField( $related_field );
74+ }
75+ }
76+
77+ }
78+
79+ }
80+
81+ private function processRelatedField( $related_field, $related_table = false )
82+ {
83+
84+ if( isset( $this->parsedContent->records ) && is_array( $this->parsedContent->records ) && count( $this->parsedContent->records ) > 0 )
85+ {
86+ foreach ( $this->parsedContent->records as $record_key => $record )
87+ {
88+ $this->parsedContent->records[ $record_key ] = $this->loadRelatedField( $related_field, $related_table, $record );
89+ }
90+ }
91+ else
92+ {
93+ $this->parsedContent = $this->loadRelatedField( $related_field, $related_table, $this->parsedContent );
94+ }
95+
96+ }
97+
98+ private function loadRelatedField( $related_field, $related_table, $record )
99+ {
100+
101+
102+ if( ! isset( $record->fields ) || ! isset( $record->fields->$related_field ) )
103+ {
104+ return $record;
105+ }
106+
107+ if( empty( $related_table ) )
108+ {
109+ $related_table = $related_field;
110+ }
111+
112+ $relation_ids = $record->fields->$related_field;
113+
114+ if( ! is_array( $relation_ids ) )
115+ {
116+ $relation_ids = [ $relation_ids ];
117+ }
118+
119+ $relation_formula = "OR(";
120+ $relation_formula .= implode( ', ', array_map( function( $id ) { return "RECORD_ID() = '$id'"; }, $relation_ids ) );
121+ $relation_formula .= ")";
122+
123+ if( ! is_array( $related_table ) )
124+ {
125+ $relation_request = $this->airtable->getContent( "$related_table", [
126+ 'filterByFormula' => $relation_formula
127+ ] );
128+ }
129+ else
130+ {
131+ $related_table_relations = isset( $related_table[ 'relations' ] ) && is_array( $related_table[ 'relations' ] )
132+ ? $related_table[ 'relations' ]
133+ : false;
134+
135+ $related_table_name = ! empty( $related_table[ 'table' ] ) ? $related_table[ 'table' ] : $related_field;
136+
137+ $relation_request = $this->airtable->getContent( "$related_table_name", [
138+ 'filterByFormula' => $relation_formula
139+ ], $related_table_relations );
140+ }
141+
142+
143+ $related_records = [];
144+
145+ do
146+ {
147+ $relation_response = $relation_request->getResponse();
148+
149+ if( ! is_array( $relation_response->records ) || count( $relation_response->records ) < 0 )
150+ {
151+ break;
152+ }
153+
154+ foreach ( $relation_response->records as $related_record )
155+ {
156+ $formatted_record = $related_record->fields;
157+ $formatted_record->id = $related_record->id;
158+
159+ $related_records[] = $formatted_record;
160+ }
161+ }
162+ while( $relation_request = $relation_response->next() );
163+
164+ if( is_array( $record->fields->$related_field ) )
165+ {
166+ $record->fields->$related_field = $related_records;
167+ }
168+ else
169+ {
170+ $record->fields->$related_field = count( $related_records ) > 0
171+ ? $related_records[ 0 ]
172+ : null;
173+ }
174+
175+ return $record;
176+
177+ }
178+
179+ public function next()
180+ {
181+
182+ if( ! $this->parsedContent )
183+ {
184+ return false;
185+ }
186+
187+
188+ if( ! isset( $this[ 'offset' ] ) )
189+ {
190+ return false;
191+ }
192+
193+ $this->request->offset = $this[ 'offset' ];
194+
195+ return $this->request;
196+
197+ }
198+
199+ public function __get( $key )
200+ {
201+ if( ! $this->parsedContent || ! isset( $this->parsedContent->$key ) )
202+ {
203+ return null;
204+ }
205+
206+ return $this->parsedContent->$key;
207+ }
208+
209+ public function __toString()
210+ {
211+ return $this->content;
212+ }
213+
214+ public function __isset( $key )
215+ {
216+ return $this->parsedContent && isset( $this->parsedContent->$key );
217+ }
218+
219+ public function offsetExists($offset)
220+ {
221+ return $this->parsedContent && isset( $this->parsedContent->$offset );
222+ }
223+
224+ public function offsetGet($offset)
225+ {
226+ return $this->parsedContent && isset( $this->parsedContent->$offset )
227+ ? $this->parsedContent->$offset
228+ : null;
229+ }
230+
231+ public function offsetSet($offset, $value)
232+ {
233+ if( $this->parsedContent )
234+ {
235+ $this->parsedContent->$offset = $value;
236+ }
237+ }
238+
239+ public function offsetUnset($offset)
240+ {
241+ if( $this->parsedContent && isset( $this->parsedContent->$offset ) )
242+ {
243+ unset( $this->parsedContent->$offset );
244+ }
245+ }
246+}
templates/.gitignorenew file+2
@@ -0,0 +1,2 @@
1+# OS
2+.DS_Store
templates/index.htmlmode 100644 → 100755+1
@@ -1,6 +1,7 @@
11{% extends "base.html" %}
22{% block title %} {{ section.title }} {% endblock title %}
33{% block content %}
4+{% include "partials/top-alert.html" %}
45<div class="wrapper">
56 <div class="container">
67 <div class="no-side-nav-column">
templates/page.html−24
@@ -8,30 +8,6 @@
88 <div class="wrapper">
99 <div class="container container--fixed content-nav-container">
1010 <div id="js-content-nav-toggle" class="content-nav-icon"></div>
11− <nav class="content-nav">
12− <ul class="list">
13− <li class="list__item content-nav__item">
14− <a href="{{ config.base_url }}/posts/">
15− All
16− </a>
17− </li>
18− <li class="list__item content-nav__item">
19− <a href="{{ config.base_url }}/posts/essays/">
20− Essays
21− </a>
22− </li>
23− <li class="list__item content-nav__item">
24− <a href="{{ config.base_url }}/posts/media/">
25− Media
26− </a>
27− </li>
28− <li class="list__item content-nav__item">
29− <a href="{{ config.base_url }}/posts/updates/">
30− Updates
31− </a>
32− </li>
33− </ul>
34− </nav>
3511 <div class="content-spacer"></div>
3612 </div>
3713 <div class="container">
templates/partials/navigation-special.htmlnew file+60
@@ -0,0 +1,60 @@
1+{% block content %}
2+<header id="main-nav" class="header">
3+ <div class="wrapper">
4+ <div id="js-main-nav-toggle" class="header__nav-icon"></div>
5+ </div>
6+ <nav class="header__navigation">
7+ <div class="wrapper">
8+ <div class="container">
9+ <div class="nav-column">
10+ <div class="nav-links mb24">
11+ <ul class="list">
12+ {% for item in config.extra.urbit_menu %}
13+ <li class="list__item">
14+ <h2 class="mt0 mb6">
15+ <a href="{{ item.url | replace(from="$BASE_URL", to=config.base_url) }}">
16+ {{ item.name }}
17+ </a>
18+ </h2>
19+ </li>
20+ {% endfor %}
21+ </ul>
22+ </div>
23+ <section class="mt9 mb6">
24+ <h4 class="mb1">Follow</h4>
25+ <ul class="list">
26+ <li class="list__item">
27+ <a class="mono" href="#">twitter.com/urbit</a>
28+ </li>
29+ <li class="list__item">
30+ <a class="mono" href="#">github.com/urbit</a>
31+ </li>
32+ </ul>
33+ </section>
34+ <section class="mb6">
35+ <h4 class="mb1">Contact</h4>
36+ <ul class="list">
37+ <li class="list__item">
38+ <a class="mono" href="mailto:support@urbit.org">support@urbit.org</a>
39+ </li>
40+ </ul>
41+ </section>
42+ <section class="mb6">
43+ <h4 class="mb1">Company</h4>
44+ <ul class="list">
45+ {% for item in config.extra.urbit_footer %}
46+ <li class="list__item">
47+ <a class="mono" href="{{ item.url | replace(from="$BASE_URL", to=config.base_url) }}">
48+ {{ item.name }}
49+ </a>
50+ </li>
51+ {% endfor %}
52+ </ul>
53+ </section>
54+ </div>
55+ </div>
56+ </div>
57+ </nav>
58+ </div>
59+</header>
60+{% endblock content %}
templates/partials/top-alert.htmlnew file+9
@@ -0,0 +1,9 @@
1+{% block content %}
2+<!-- <div class="bg-black z3 w-full mb4">
3+ <div class="top-bar-inner">
4+ <p class="white fs35 mv0">
5+ Interested in developing on Urbit? <a href="/hoonschool" class="white">Sign up for Hoon School!</a>
6+ </p>
7+ </div>
8+</div> -->
9+{% endblock content %}
templates/sections/index.htmlnew file+46
@@ -0,0 +1,46 @@
1+{% extends "base.html" %}
2+
3+{% block content %}
4+<div class="wrapper">
5+ <div class="container">
6+ <div class="no-side-nav-column">
7+ <h1 class="mt10">Urbit</h1>
8+ <p class="mb6">A personal server built from scratch.</p>
9+ <ul class="list">
10+ <li class="list__item">
11+ <h2>
12+ <a href="{{config.base_url ~ "/primer/"}}">
13+ Primer
14+ </a>
15+ </h2>
16+ <p class="mb6">
17+ A 10-minute read on what Urbit is and why it exists.
18+ </p>
19+ </li>
20+ <li class="list__item">
21+ <h2>
22+ <a href="{{config.base_url ~ "/posts/"}}">
23+ Posts
24+ </a>
25+ </h2>
26+ <p class="mb6">
27+ Regular updates, occasional essays and media.
28+ </p>
29+ </li>
30+ <li class="list__item">
31+ <h2>
32+ <a href="{{config.base_url ~ "/docs/"}}">
33+ Docs
34+ </a>
35+ </h2>
36+ <p class="mb6">
37+ Installation instructions and complete system documentation.
38+ </p>
39+ </li>
40+ </ul>
41+ <!-- footer -->
42+ {% include "partials/footer.html" %}
43+ </div>
44+ </div>
45+</div>
46+{% endblock content %}