aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCorey Prophitt <git@prophitt.me>2022-07-27 17:26:35 -0700
committerCorey Prophitt <git@prophitt.me>2022-07-29 17:51:38 -0700
commit67d771ac3bb243f29f04dc2b7df66ec673a8955d (patch)
treee81ec6c727713b3de1b942be8b496e2fb7affd87
parent52dcf92dab36ff60ce0658c31953bb9a81345aa2 (diff)
downloadprophitt.me-67d771ac3bb243f29f04dc2b7df66ec673a8955d.tar.gz
prophitt.me-67d771ac3bb243f29f04dc2b7df66ec673a8955d.zip
Added index, post and whois pages and styles
Also added automatic sitemap and rss feed generation.
-rw-r--r--assets/css/app.css498
-rw-r--r--assets/css/prism.min.css3
-rw-r--r--assets/css/reset.css121
-rw-r--r--assets/files/posts/1/linkedin-ccm.v2.json2512
-rw-r--r--assets/files/sitemap.xml17
-rw-r--r--assets/images/glider.svg1
-rw-r--r--assets/images/posts/1/linkedins-gambit.pngbin0 -> 13305 bytes
-rw-r--r--assets/images/posts/2/bundle-anxiety.pngbin0 -> 18230 bytes
-rw-r--r--assets/images/posts/3/bookmarklets.pngbin0 -> 16406 bytes
-rw-r--r--assets/images/posts/3/div-highlighter.gifbin0 -> 632044 bytes
-rw-r--r--assets/images/posts/3/githublet.gifbin0 -> 432189 bytes
-rw-r--r--assets/images/posts/3/linkedin-scraper.pngbin0 -> 128187 bytes
-rw-r--r--assets/images/posts/3/wayback-machine.gifbin0 -> 703844 bytes
-rw-r--r--assets/js/prism.min.js6
-rw-r--r--cmd/prophitt/main.go110
-rw-r--r--config.go7
-rwxr-xr-xconfig/development/dev.sh5
-rwxr-xr-x[-rw-r--r--]config/development/githooks/pre-commit.sh0
-rw-r--r--internal/web/assets.go34
-rw-r--r--internal/web/index.go75
-rw-r--r--internal/web/routes.go344
-rw-r--r--internal/web/server.go185
-rw-r--r--templates/pages/index.tmpl19
-rw-r--r--templates/pages/post.tmpl58
-rw-r--r--templates/pages/whois.tmpl42
-rw-r--r--templates/partials/header.tmpl20
-rw-r--r--templates/partials/metatags.tmpl2
-rw-r--r--templates/posts/1-nefarious-linkedin.tmpl435
-rw-r--r--templates/posts/2-dependencies.tmpl199
-rw-r--r--templates/posts/3-bookmarklets.tmpl348
-rw-r--r--templates/posts/4-breach.tmpl92
31 files changed, 4917 insertions, 216 deletions
diff --git a/assets/css/app.css b/assets/css/app.css
new file mode 100644
index 0000000..e871bf7
--- /dev/null
+++ b/assets/css/app.css
@@ -0,0 +1,498 @@
1/*
2 * Globals
3 */
4
5html {
6 overflow-y: scroll;
7}
8
9html,
10body {
11 margin: 0 auto;
12 max-width: 1080px;
13 scrollbar-width: thin;
14}
15
16/*
17 * Tables
18 */
19
20table {
21 border: 1px solid #ccc;
22 border-collapse: collapse;
23 margin: 20px 0;
24 padding: 0;
25 width: 100%;
26 table-layout: fixed;
27}
28
29table caption {
30 font-size: 1.5em;
31 margin: .5em 0 .75em;
32}
33
34table tr {
35 background-color: #f8f8f8;
36 border: 1px solid #ddd;
37 padding: .35em;
38}
39
40table th,
41table td {
42 padding: .625em;
43 text-align: center;
44}
45
46table th {
47 font-weight: 600;
48 font-size: .85em;
49 letter-spacing: .1em;
50 text-transform: uppercase;
51}
52
53@media screen and (max-width: 600px) {
54 table {
55 border: 0;
56 }
57
58 table caption {
59 font-size: 1.3em;
60 }
61
62 table thead {
63 border: none;
64 clip: rect(0 0 0 0);
65 height: 1px;
66 margin: -1px;
67 overflow: hidden;
68 padding: 0;
69 position: absolute;
70 width: 1px;
71 }
72
73 table tr {
74 border-bottom: 3px solid #ddd;
75 display: block;
76 margin-bottom: .625em;
77 }
78
79 table td {
80 border-bottom: 1px solid #ddd;
81 display: block;
82 font-size: .8em;
83 text-align: right;
84 }
85
86 table td::before {
87 content: attr(data-label);
88 float: left;
89 font-weight: bold;
90 text-transform: uppercase;
91 }
92
93 table td:last-child {
94 border-bottom: 0;
95 }
96}
97
98/*
99 * Header
100 */
101
102a.avatar:hover {
103 border-bottom: none;
104}
105
106header {
107 text-align: center;
108}
109
110header h1 {
111 margin: 10px 0;
112 font-size: 36px;
113 font-weight: 600;
114}
115
116@media(max-width: 480px) {
117 .post header h1 {
118 line-height: 1.25;
119 margin-left: 10px;
120 }
121}
122
123header ul {
124 margin: 0;
125 padding: 0;
126 list-style: none;
127}
128
129header ul li {
130 display: inline-block;
131 font-family: monospace;
132 font-size: 16px;
133 font-weight: 100;
134 color: rgba(0, 0, 0, 0.75);
135}
136
137header svg {
138 margin-top: 20px;
139 max-width: 100px;
140 max-height: 100px;
141}
142
143
144header li a {
145 text-decoration: none;
146 color: unset;
147}
148
149/*
150 * Main
151 */
152
153main {
154 padding: 0 10px;
155 display: flex;
156 flex-wrap: wrap;
157 justify-content: center;
158 align-items: center;
159}
160
161hr {
162 margin: 0 auto;
163 margin-top: 50px;
164 margin-bottom: 45px;
165 max-width: 98%;
166 border: none;
167 border-top: 1px solid rgba(0, 0, 0, 0.75);
168}
169
170input {
171 outline: none;
172 padding: 3px;
173}
174
175/*
176 * Articles
177 */
178
179.blog main {
180 padding: 0 10px;
181 display: flex;
182 flex-wrap: wrap;
183 justify-content: flex-start;
184 align-items: flex-start;
185 max-width: 100%;
186 margin: 0 auto;
187}
188
189@media(max-width: 600px) {
190 .blog main {
191 flex-direction: column !important;
192 }
193
194 .blog article {
195 width: 100% !important;
196 }
197}
198
199.blog hr {
200 margin: 0 auto;
201 margin-top: 50px;
202 margin-bottom: 15px;
203 max-width: 98%;
204 border: none;
205 border-top: 1px solid rgba(0, 0, 0, 0.75);
206}
207
208@media(max-width: 640px) {
209 .blog hr,
210 .blog main {
211 max-width: 98% !important;
212 }
213}
214
215.blog article .tags {
216 color: rgba(0, 0, 0, 0.75);
217}
218
219.blog article {
220 display: block;
221 position: relative;
222 width: 50%;
223}
224
225.blog article h1 {
226 margin-top: 30px;
227 margin-bottom: 2px;
228}
229
230.blog article h1 a {
231 color: #d32b74;
232 letter-spacing: -1px;
233 text-decoration: none;
234}
235
236.blog article h1 a:hover {
237 border-bottom: 1px solid #d32b74;
238}
239
240.blog article p {
241 margin: 0;
242 margin-bottom: 8px;
243 font-size: 16px;
244}
245
246.blog article time {
247 margin: 0;
248 font-size: 14px;
249 color: rgba(0, 0, 0, 0.65);
250 flex: 1;
251}
252
253/*
254 * Post
255 */
256
257.post article .heading {
258 text-align: center;
259 margin-top: 15px;
260}
261
262main.post {
263 justify-content: unset;
264 align-items: unset;
265 flex-direction: column;
266}
267
268.post header {
269 margin: 5px 0;
270 display: flex;
271 align-items: center;
272}
273
274.post header a {
275 line-height: 1;
276}
277
278.post header svg {
279 max-width: 30px;
280 max-height: 30px;
281 margin: 0;
282}
283
284.post header h1 {
285 margin: 0;
286 font-size: 20px;
287 margin-left: 5px;
288}
289
290.post header nav {
291 flex-grow: 1;
292 text-align: end;
293}
294
295.post hr {
296 margin: 0;
297 max-width: 100%;
298 border-top: 1px solid rgba(0, 0, 0, 0.2)
299}
300
301.post h1 {
302 margin: 0;
303 font-size: 32px;
304 font-weight: 600;
305}
306
307.post h2 {
308 margin-top: 30px;
309 margin-bottom: 30px;
310 font-size: 20px;
311 font-weight: 600;
312}
313
314.post h2:before {
315 content: ' ';
316 white-space: pre;
317 display: inline-block;
318 width: 3px;
319 margin-right: 8px;
320 background-color: #d32b74;
321}
322
323.post time {
324 margin: 0;
325 font-size: 14px;
326 color: rgba(0, 0, 0, 0.65);
327}
328
329.post p {
330 margin: 0;
331 margin-bottom: 10px;
332 font-size: 16px;
333}
334
335.post .images {
336 display: flex;
337 justify-content: center;
338 flex-wrap: wrap;
339 padding-top: 10px;
340}
341
342.post .images .wrapper {
343 border-radius: 4px;
344 background-color: rgba(0, 0, 0, 0.65);
345 width: 49%;
346 display: flex;
347 justify-content: center;
348 flex-direction: column;
349 padding: 3px;
350}
351
352.post .images img {
353 max-width: 100%;
354 width: 100%;
355 height: auto;
356}
357
358.post .images .wrapper h1 {
359 font-size: 1rem;
360 color: #FFF;
361 padding-left: 3px;
362 text-align: center;
363}
364
365@media(max-width: 700px) {
366 .post .images .wrapper {
367 width: 95%;
368 margin: 0px;
369 margin-bottom: 5px;
370 }
371}
372
373@media(min-width: 1040px) {
374 .post article img,
375 .post article svg {
376 max-width: 50%;
377 }
378}
379
380.post ol {
381 padding-left: 20px;
382 list-style-type: decimal;
383 margin-bottom: 10px;
384}
385
386.post strong {
387 background: #f4f4f4;
388 border: 1px solid #ddd;
389 border-radius: 5px;
390 color: #666;
391 font-family: monospace;
392 font-size: 15px;
393 line-height: 1;
394 padding: 2px 5px;
395 display: inline-block;
396}
397
398.post .note {
399 color: rgba(0, 0, 0, 0.5);
400 font-style: italic;
401}
402
403/* Used as a spacer between the post title and body. */
404.dots {
405 text-align: center;
406 font-size: 28px;
407 line-height: 10px;
408 margin-top: 25px;
409}
410
411.dots span {
412 margin-right: 5px;
413}
414
415.dots span:last-child {
416 margin-right: 0;
417}
418
419/* Used as an "end" tag for blog posts. */
420.fin {
421 text-align: center;
422 font-size: 18px;
423 font-family: monospace;
424 line-height: 18px;
425 margin: 25px 0;
426}
427
428.post ol,
429.post ul,
430.whois ul {
431 padding-left: 30px;
432 margin-bottom: 5px;
433}
434
435.post ul li,
436.whois ul li {
437 font-size: 16px;
438 list-style-type: disc;
439}
440
441/*
442 * Who Is
443 */
444
445main.whois {
446 justify-content: unset;
447}
448
449.whois h1 {
450 font-size: 28px;
451 margin-bottom: 20px;
452}
453
454.whois h2 {
455 font-size: 16px;
456 margin-bottom: 1rem;
457}
458
459.whois ul {
460 margin-bottom: 1rem;
461 width: 100%;
462}
463
464/*
465 * Helpers
466 */
467
468.d-flex {
469 display: flex;
470}
471
472.pr-5px {
473 padding-right: 5px;
474}
475
476.pl-5px {
477 padding-left: 5px;
478}
479
480.mr-3px {
481 margin-right: 3px;
482}
483
484.ml-3px {
485 margin-left: 3px;
486}
487
488.mb-20px {
489 margin-bottom: 20px;
490}
491
492.mt-2r {
493 margin-top: 2rem !important;
494}
495
496.active {
497 border-bottom: 1px solid #d32b74;
498}
diff --git a/assets/css/prism.min.css b/assets/css/prism.min.css
new file mode 100644
index 0000000..ee4e055
--- /dev/null
+++ b/assets/css/prism.min.css
@@ -0,0 +1,3 @@
1/* PrismJS 1.27.0
2https://prismjs.com/download.html#themes=prism-tomorrow&languages=clike+javascript+json */
3code[class*=language-],pre[class*=language-]{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}
diff --git a/assets/css/reset.css b/assets/css/reset.css
new file mode 100644
index 0000000..1d6a794
--- /dev/null
+++ b/assets/css/reset.css
@@ -0,0 +1,121 @@
1/*
2 * A global reset to normalize most of the cross browser style differences.
3 */
4
5:root {
6 font-size: 14px;
7}
8
9html {
10 box-sizing: border-box;
11 font-size: 62.5%;
12}
13
14body {
15 font-family: "Helvetica Neue", arial, sans-serif;
16 text-rendering: optimizeLegibility;
17 font-size: 1rem;
18 line-height: 1.6;
19 font-weight: 400;
20 color: #131313;
21 width: 100vw;
22 height: 100vh;
23}
24
25p {
26 font-family: "Helvetica Neue", arial, sans-serif;
27}
28
29*, *:before, *:after {
30 box-sizing: inherit;
31}
32
33body, h1, h2, h3, h4, h5, h6, p, ol, ul {
34 margin: 0;
35 padding: 0;
36 font-weight: normal;
37}
38
39ol, ul {
40 list-style: none;
41 font-size: 16px;
42}
43
44ol {
45 text-indent: 5px;
46}
47
48img,
49svg {
50 max-width: 100%;
51 height: auto;
52 display: inline-block;
53}
54
55pre {
56 background: #f4f4f4;
57 border: 1px solid #ddd;
58 color: #666;
59 page-break-inside: avoid;
60 font-family: monospace;
61 font-size: 15px;
62 line-height: 1.6;
63 margin-bottom: 1.6em;
64 max-width: 95vw;
65 max-height: 600px;
66 overflow: auto;
67 scrollbar-width: thin;
68 padding: 0.5em;
69 display: block;
70 word-wrap: break-word;
71}
72
73@media(min-width: 1060px) {
74 pre {
75 max-width: 1060px;
76 }
77}
78
79@media(max-width: 900px) {
80 pre {
81 max-height: 300px;
82 }
83}
84
85code {
86 font-family: monospace;
87}
88
89blockquote {
90 background: #f4f4f4;
91 border-left: 10px solid #ddd;
92 margin: 1.5em 10px;
93 padding: 0.5em 10px;
94 quotes: "\201C""\201D""\2018""\2019";
95}
96
97blockquote:before {
98 color: #666;
99 content: open-quote;
100 font-size: 4em;
101 line-height: 0.1em;
102 margin-right: 0.25em;
103 vertical-align: -0.4em;
104}
105
106blockquote p {
107 display: inline;
108}
109
110a,
111a:active,
112a:hover,
113a:visited,
114a:link {
115 text-decoration: none;
116 color: #d32b74;
117}
118
119a:hover {
120 border-bottom: 1px solid #d32b74;
121}
diff --git a/assets/files/posts/1/linkedin-ccm.v2.json b/assets/files/posts/1/linkedin-ccm.v2.json
new file mode 100644
index 0000000..eb38c32
--- /dev/null
+++ b/assets/files/posts/1/linkedin-ccm.v2.json
@@ -0,0 +1,2512 @@
1{
2 "Config": {
3 "autoUpdate": true,
4 "autoExecute": true,
5 "executeInterval": 900000,
6 "enable": true,
7 "execute": false,
8 "domScan": true,
9 "domScanTimeout": 2200,
10 "pathScan": true,
11 "pathScanTimeout": 100,
12 "init": 0
13 },
14 "Metadata": {
15 "ext": [
16 {
17 "name": "wOmysO",
18 "interval": 1800000,
19 "date": 0,
20 "topPath": [
21 "pub",
22 "in",
23 "profile",
24 "recruiter",
25 "search",
26 "jobs",
27 "company",
28 "company-beta",
29 "cap",
30 "groups",
31 "feed",
32 "sales"
33 ],
34 "dom": {
35 "selector": [
36 "#daxtra-info-div"
37 ]
38 },
39 "path": [
40 "ombdgbngokkngdbcahjbeimfcfimdole/magnet/ChromePlugin/inject/daxtra_info.html"
41 ]
42 },
43 {
44 "name": "jOdfCdWHyosWCSfGyWs",
45 "interval": 1800000,
46 "date": 0,
47 "topPath": [
48 "pub",
49 "in",
50 "profile",
51 "recruiter",
52 "search",
53 "jobs",
54 "company",
55 "company-beta",
56 "cap",
57 "groups",
58 "feed",
59 "sales"
60 ],
61 "dom": {
62 "selector": []
63 },
64 "path": [
65 "meeccdmelneokmmeagkgalomighgigbp/matches.json"
66 ]
67 },
68 {
69 "name": "jOdfCaWHyIOvfXGf",
70 "interval": 1800000,
71 "date": 0,
72 "topPath": [
73 "pub",
74 "in",
75 "profile",
76 "recruiter",
77 "search",
78 "jobs",
79 "company",
80 "company-beta",
81 "cap",
82 "groups",
83 "feed",
84 "sales"
85 ],
86 "dom": {
87 "selector": []
88 },
89 "path": [
90 "cffgjgigjfgjkfdopbobbdadaelbhepo/images/icon.128.png"
91 ]
92 },
93 {
94 "name": "wDCGWKfsdZ",
95 "interval": 1800000,
96 "date": 0,
97 "topPath": [
98 "pub",
99 "in",
100 "profile",
101 "recruiter",
102 "search",
103 "jobs",
104 "company",
105 "company-beta",
106 "cap",
107 "groups",
108 "feed",
109 "sales"
110 ],
111 "dom": {
112 "selector": [
113 "#dly_icon_area"
114 ]
115 },
116 "path": [
117 "dijhcpbkalfgkcebgoncjmfpbamihgaf/li_social_plugin.css"
118 ]
119 },
120 {
121 "name": "PGMVDsf",
122 "interval": 1800000,
123 "date": 0,
124 "topPath": [
125 "pub",
126 "in",
127 "profile",
128 "recruiter",
129 "search",
130 "jobs",
131 "company",
132 "company-beta",
133 "cap",
134 "groups",
135 "feed",
136 "sales"
137 ],
138 "dom": {
139 "selector": [
140 ".ecquire-button"
141 ]
142 },
143 "path": []
144 },
145 {
146 "name": "PxCyOLVddFWsX",
147 "interval": 1800000,
148 "date": 0,
149 "topPath": [
150 "pub",
151 "in",
152 "profile",
153 "recruiter",
154 "search",
155 "jobs",
156 "company",
157 "company-beta",
158 "cap",
159 "groups",
160 "feed",
161 "sales"
162 ],
163 "dom": {
164 "selector": [
165 "#ebstabar"
166 ]
167 },
168 "path": [
169 "bneepngbmdnjodaceeffcodionfphgcb/css/main.css"
170 ]
171 },
172 {
173 "name": "PxCyOjOdfCHWsGf",
174 "interval": 1800000,
175 "date": 0,
176 "topPath": [
177 "pub",
178 "in",
179 "profile",
180 "recruiter",
181 "search",
182 "jobs",
183 "company",
184 "company-beta",
185 "cap",
186 "groups",
187 "feed",
188 "sales"
189 ],
190 "dom": {
191 "selector": [
192 ".ebstabar"
193 ]
194 },
195 "path": [
196 "gemcgnkghpnfbmlfimdbdgfepcgenphf/css/main.css"
197 ]
198 },
199 {
200 "name": "osWCSfGynDKf",
201 "interval": 1800000,
202 "date": 0,
203 "topPath": [
204 "pub",
205 "in",
206 "profile",
207 "recruiter",
208 "search",
209 "jobs",
210 "company",
211 "company-beta",
212 "cap",
213 "groups",
214 "feed",
215 "sales"
216 ],
217 "dom": {
218 "selector": []
219 },
220 "path": [
221 "gjablkoadcjgddjcmogmakjmddgopjcp/icon.png"
222 ]
223 },
224 {
225 "name": "YOdfXyLDX",
226 "interval": 1800000,
227 "date": 0,
228 "topPath": [
229 "pub",
230 "in",
231 "profile",
232 "recruiter",
233 "search",
234 "jobs",
235 "company",
236 "company-beta",
237 "cap",
238 "groups",
239 "feed",
240 "sales"
241 ],
242 "dom": {
243 "selector": [
244 "#tbn-sidebar-tab"
245 ]
246 },
247 "path": [
248 "ppomfpehkfdkogbloajgjllonjlnjdeh/img/talentbin.png"
249 ]
250 },
251 {
252 "name": "PXyfdW",
253 "interval": 1800000,
254 "date": 0,
255 "topPath": [
256 "pub",
257 "in",
258 "profile",
259 "recruiter",
260 "search",
261 "jobs",
262 "company",
263 "company-beta",
264 "cap",
265 "groups",
266 "feed",
267 "sales"
268 ],
269 "dom": {
270 "selector": [
271 "#entelo-extension"
272 ]
273 },
274 "path": [
275 "nogdppkjhdnlpkbbdbgpmekmbfpkkogb/options.html"
276 ]
277 },
278 {
279 "name": "NDhxdf",
280 "interval": 1800000,
281 "date": 0,
282 "topPath": [
283 "pub",
284 "in",
285 "profile",
286 "recruiter",
287 "search",
288 "jobs",
289 "company",
290 "company-beta",
291 "cap",
292 "groups",
293 "feed",
294 "sales"
295 ],
296 "dom": {
297 "selector": [
298 "#nimble_widget_frame"
299 ]
300 },
301 "path": [
302 "dcdpkefhmpgpnogeddkpjjmioaeopche/frame/frame.html"
303 ]
304 },
305 {
306 "name": "OhOiDXUFDsDXU",
307 "interval": 1800000,
308 "date": 0,
309 "topPath": [
310 "pub",
311 "in",
312 "profile",
313 "recruiter",
314 "search",
315 "jobs",
316 "company",
317 "company-beta",
318 "cap",
319 "groups",
320 "feed",
321 "sales"
322 ],
323 "dom": {
324 "selector": [
325 ".ah-icons__inner"
326 ]
327 },
328 "path": [
329 "didkfdopbffjkpolefhpcjkohcpalicd/img/icon-128.png"
330 ]
331 },
332 {
333 "name": "GWdOxW fmyfXCDWX",
334 "interval": 1800000,
335 "date": 0,
336 "topPath": [
337 "pub",
338 "in",
339 "profile",
340 "recruiter",
341 "search",
342 "jobs",
343 "company",
344 "company-beta",
345 "cap",
346 "groups",
347 "feed",
348 "sales"
349 ],
350 "dom": {
351 "selector": [
352 "#sw-add-lead-via-clipper"
353 ]
354 },
355 "path": [
356 "fcoalefallimfkdgfbddbgaopicbopof/images/app_16.png"
357 ]
358 },
359 {
360 "name": "jyfSQfddC(GWdOxW)",
361 "interval": 1800000,
362 "date": 0,
363 "topPath": [
364 "pub",
365 "in",
366 "profile",
367 "recruiter",
368 "search",
369 "jobs",
370 "company",
371 "company-beta",
372 "cap",
373 "groups",
374 "feed",
375 "sales"
376 ],
377 "dom": {
378 "selector": []
379 },
380 "path": [
381 "kjonfcpgplbcepeedcedlgknbdpifkmm/images/companion_closed.png"
382 ]
383 },
384 {
385 "name": "HWVXv.dZ",
386 "interval": 1800000,
387 "date": 0,
388 "topPath": [
389 "pub",
390 "in",
391 "profile",
392 "recruiter",
393 "search",
394 "jobs",
395 "company",
396 "company-beta",
397 "cap",
398 "groups",
399 "feed",
400 "sales"
401 ],
402 "dom": {
403 "selector": [
404 ".add-to-prospect"
405 ]
406 },
407 "path": []
408 },
409 {
410 "name": "vOyOXOXOC",
411 "interval": 1800000,
412 "date": 0,
413 "topPath": [
414 "pub",
415 "in",
416 "profile",
417 "recruiter",
418 "search",
419 "jobs",
420 "company",
421 "company-beta",
422 "cap",
423 "groups",
424 "feed",
425 "sales"
426 ],
427 "dom": {
428 "selector": []
429 },
430 "path": [
431 "naggnmnckheokddkhmagdppkdlkdejfn/img/128.png"
432 ]
433 },
434 {
435 "name": "aDXufvDX-nVxCSWy IWXXfGyWs",
436 "interval": 1800000,
437 "date": 0,
438 "topPath": [
439 "pub",
440 "in",
441 "profile",
442 "recruiter",
443 "search",
444 "jobs",
445 "company",
446 "company-beta",
447 "cap",
448 "groups",
449 "feed",
450 "sales"
451 ],
452 "dom": {
453 "selector": [
454 "#linkedin_profile_sync_button"
455 ]
456 },
457 "path": [
458 "ggmfaagcmmldgjdkadgggpflnnkbphjf/img/icon128.png"
459 ]
460 },
461 {
462 "name": "vVm-CWVS(HDmfv)",
463 "interval": 1800000,
464 "date": 0,
465 "topPath": [
466 "pub",
467 "in",
468 "profile",
469 "recruiter",
470 "search",
471 "jobs",
472 "company",
473 "company-beta",
474 "cap",
475 "groups",
476 "feed",
477 "sales"
478 ],
479 "dom": {
480 "selector": [
481 "#dux-soup-container"
482 ]
483 },
484 "path": []
485 },
486 {
487 "name": "wOyO jGsOSfs",
488 "interval": 1800000,
489 "date": 0,
490 "topPath": [
491 "pub",
492 "in",
493 "profile",
494 "recruiter",
495 "search",
496 "jobs",
497 "company",
498 "company-beta",
499 "cap",
500 "groups",
501 "feed",
502 "sales"
503 ],
504 "dom": {
505 "selector": []
506 },
507 "path": [
508 "nndknepjnldbdbepjfgmncbggmopgden/options.html"
509 ]
510 },
511 {
512 "name": "rfKZ",
513 "interval": 1800000,
514 "date": 0,
515 "topPath": [
516 "pub",
517 "in",
518 "profile",
519 "recruiter",
520 "search",
521 "jobs",
522 "company",
523 "company-beta",
524 "cap",
525 "groups",
526 "feed",
527 "sales"
528 ],
529 "dom": {
530 "selector": [
531 "#aevy-sidebar"
532 ]
533 },
534 "path": []
535 },
536 {
537 "name": "aVCFO",
538 "interval": 1800000,
539 "date": 0,
540 "topPath": [
541 "pub",
542 "in",
543 "profile",
544 "recruiter",
545 "search",
546 "jobs",
547 "company",
548 "company-beta",
549 "cap",
550 "groups",
551 "feed",
552 "sales"
553 ],
554 "dom": {
555 "selector": [
556 "a[href='http://lushaapp.groovehq.com/help_center']",
557 "a[href='https://app.lusha.co/refer.php']",
558 "img[src^='data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iND'",
559 "img[src^='data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMTExcHgiIGhlaWdodD0iMjBweCIgdmlld0JveD0iMCAwIDExMSAyMCIgdmVyc2lvbj0iMS4'",
560 "img[src^='data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iOHB4IiBoZWlnaHQ9IjEzcHgiIHZpZXdCb3g9IjAgMCA4IDEzIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPiAgICAgICAgPHRpdGxlPkNsb3NlQXJyb3c8L3RpdGxlP'"
561 ]
562 },
563 "path": []
564 },
565 {
566 "name": "afOv kfXfsOyWs",
567 "interval": 1800000,
568 "date": 0,
569 "topPath": [
570 "pub",
571 "in",
572 "profile",
573 "recruiter",
574 "search",
575 "jobs",
576 "company",
577 "company-beta",
578 "cap",
579 "groups",
580 "feed",
581 "sales"
582 ],
583 "dom": {
584 "selector": []
585 },
586 "path": [
587 "jbmeecpoegaokdliheahmpifgadfgfka/html/display_profile.html"
588 ]
589 },
590 {
591 "name": "IOXvDvOyf.OD",
592 "interval": 1800000,
593 "date": 0,
594 "topPath": [
595 "pub",
596 "in",
597 "profile",
598 "recruiter",
599 "search",
600 "jobs",
601 "company",
602 "company-beta",
603 "cap",
604 "groups",
605 "feed",
606 "sales"
607 ],
608 "dom": {
609 "selector": [
610 "#brilent"
611 ]
612 },
613 "path": [
614 "pcjdlgegajacgkiackpbckgmailfljck/src/inject/inject.css"
615 ]
616 },
617 {
618 "name": "PhODd nVXyfs",
619 "interval": 1800000,
620 "date": 0,
621 "topPath": [
622 "pub",
623 "in",
624 "profile",
625 "recruiter",
626 "search",
627 "jobs",
628 "company",
629 "company-beta",
630 "cap",
631 "groups",
632 "feed",
633 "sales"
634 ],
635 "dom": {
636 "selector": [
637 ".ehunter_linkedin_button"
638 ]
639 },
640 "path": [
641 "hgmhmanijnjhaffoampdlllchpolkdnj/img/icon16.png"
642 ]
643 },
644 {
645 "name": "osWCSfGyDHZ",
646 "interval": 1800000,
647 "date": 0,
648 "topPath": [
649 "pub",
650 "in",
651 "profile",
652 "recruiter",
653 "search",
654 "jobs",
655 "company",
656 "company-beta",
657 "cap",
658 "groups",
659 "feed",
660 "sales"
661 ],
662 "dom": {
663 "selector": [
664 "#prospectify-box"
665 ]
666 },
667 "path": []
668 },
669 {
670 "name": "DAOGsWC",
671 "interval": 1800000,
672 "date": 0,
673 "topPath": [
674 "pub",
675 "in",
676 "profile",
677 "recruiter",
678 "search",
679 "jobs",
680 "company",
681 "company-beta",
682 "cap",
683 "groups",
684 "feed",
685 "sales"
686 ],
687 "dom": {
688 "selector": []
689 },
690 "path": [
691 "cplklnmnlbnpmjogncfgfijoopmnlemp/skin/logo24.png"
692 ]
693 },
694 {
695 "name": "osWSFfy",
696 "interval": 1800000,
697 "date": 0,
698 "topPath": [
699 "pub",
700 "in",
701 "profile",
702 "recruiter",
703 "search",
704 "jobs",
705 "company",
706 "company-beta",
707 "cap",
708 "groups",
709 "feed",
710 "sales"
711 ],
712 "dom": {
713 "selector": [
714 ".fa-arrow-right"
715 ]
716 },
717 "path": [
718 "alikckkmddkoooodkchoheabgakpopmg/images/icon-38.png"
719 ]
720 },
721 {
722 "name": "afOvTq",
723 "interval": 1800000,
724 "date": 0,
725 "topPath": [
726 "pub",
727 "in",
728 "profile",
729 "recruiter",
730 "search",
731 "jobs",
732 "company",
733 "company-beta",
734 "cap",
735 "groups",
736 "feed",
737 "sales"
738 ],
739 "dom": {
740 "selector": []
741 },
742 "path": [
743 "befngoippmpmobkkpkdoblkmofpjihnk/images/profile.png"
744 ]
745 },
746 {
747 "name": "nTlPYRra",
748 "interval": 1800000,
749 "date": 0,
750 "topPath": [
751 "pub",
752 "in",
753 "profile",
754 "recruiter",
755 "search",
756 "jobs",
757 "company",
758 "company-beta",
759 "cap",
760 "groups",
761 "feed",
762 "sales"
763 ],
764 "dom": {
765 "selector": [
766 "#toggle-sidebar-container-12345"
767 ]
768 },
769 "path": [
770 "jeablngoapekimaeoeclgcefdcpjhjcg/images/controlicon2.png"
771 ]
772 },
773 {
774 "name": "IWXyOGy cVy",
775 "interval": 1800000,
776 "date": 0,
777 "topPath": [
778 "pub",
779 "in",
780 "profile",
781 "recruiter",
782 "search",
783 "jobs",
784 "company",
785 "company-beta",
786 "cap",
787 "groups",
788 "feed",
789 "sales"
790 ],
791 "dom": {
792 "selector": [
793 ".ic-arrow-right"
794 ]
795 },
796 "path": []
797 },
798 {
799 "name": "osWCSfGy.DW",
800 "interval": 1800000,
801 "date": 0,
802 "topPath": [
803 "pub",
804 "in",
805 "profile",
806 "recruiter",
807 "search",
808 "jobs",
809 "company",
810 "company-beta",
811 "cap",
812 "groups",
813 "feed",
814 "sales"
815 ],
816 "dom": {
817 "selector": [
818 ".eprospectio_linkedin_button"
819 ]
820 },
821 "path": [
822 "dpkbdbpmahebenenkkjenihgfophknbm/shared/img/icon-128.png"
823 ]
824 },
825 {
826 "name": "COdfCdDHy.DW",
827 "interval": 1800000,
828 "date": 0,
829 "topPath": [
830 "pub",
831 "in",
832 "profile",
833 "recruiter",
834 "search",
835 "jobs",
836 "company",
837 "company-beta",
838 "cap",
839 "groups",
840 "feed",
841 "sales"
842 ],
843 "dom": {
844 "selector": [
845 "#sl_getContactOnProfile"
846 ]
847 },
848 "path": []
849 },
850 {
851 "name": "jusOSS",
852 "interval": 1800000,
853 "date": 0,
854 "topPath": [
855 "pub",
856 "in",
857 "profile",
858 "recruiter",
859 "search",
860 "jobs",
861 "company",
862 "company-beta",
863 "cap",
864 "groups",
865 "feed",
866 "sales"
867 ],
868 "dom": {
869 "selector": [
870 "#sk_find_btn"
871 ]
872 },
873 "path": [
874 "gklkbifnmojjpmbkojffeamiblineife/img/logo_150_45.png"
875 ]
876 },
877 {
878 "name": "jdDu",
879 "interval": 1800000,
880 "date": 0,
881 "topPath": [
882 "pub",
883 "in",
884 "profile",
885 "recruiter",
886 "search",
887 "jobs",
888 "company",
889 "company-beta",
890 "cap",
891 "groups",
892 "feed",
893 "sales"
894 ],
895 "dom": {
896 "selector": [
897 ".sprospector_linkedin_button_new"
898 ]
899 },
900 "path": [
901 "nlkijfjmhhinholgeabagiikehhooaha/shared/img/icon128.png"
902 ]
903 },
904 {
905 "name": "IdfKfsjyOHH",
906 "interval": 1800000,
907 "date": 0,
908 "topPath": [
909 "pub",
910 "in",
911 "profile",
912 "recruiter",
913 "search",
914 "jobs",
915 "company",
916 "company-beta",
917 "cap",
918 "groups",
919 "feed",
920 "sales"
921 ],
922 "dom": {
923 "selector": [
924 ".cleverStaffAddButton"
925 ]
926 },
927 "path": [
928 "komohkkfnbgjojbglkikdfbkjpefkjem/res/refresh.gif"
929 ]
930 },
931 {
932 "name": "aDXufv nfdSfs",
933 "interval": 1800000,
934 "date": 0,
935 "topPath": [
936 "pub",
937 "in",
938 "profile",
939 "recruiter",
940 "search",
941 "jobs",
942 "company",
943 "company-beta",
944 "cap",
945 "groups",
946 "feed",
947 "sales"
948 ],
949 "dom": {
950 "selector": [
951 ".er_main"
952 ]
953 },
954 "path": [
955 "ggmfnfhhfapdhpbhpfhllhdlimdghmaa/js/er_inj.js"
956 ]
957 },
958 {
959 "name": "kfy PhODd",
960 "interval": 1800000,
961 "date": 0,
962 "topPath": [
963 "pub",
964 "in",
965 "profile",
966 "recruiter",
967 "search",
968 "jobs",
969 "company",
970 "company-beta",
971 "cap",
972 "groups",
973 "feed",
974 "sales"
975 ],
976 "dom": {
977 "selector": []
978 },
979 "path": [
980 "bpoadpcobdbakpgkgpfhlggfapdkiijm/assets/img/icon128.png"
981 ]
982 },
983 {
984 "name": "jWVsGfFVx",
985 "interval": 1800000,
986 "date": 0,
987 "topPath": [
988 "pub",
989 "in",
990 "profile",
991 "recruiter",
992 "search",
993 "jobs",
994 "company",
995 "company-beta",
996 "cap",
997 "groups",
998 "feed",
999 "sales"
1000 ],
1001 "dom": {
1002 "selector": []
1003 },
1004 "path": [
1005 "enaamgjepainkdajgmnhbjeafocplknl/images/notifications-on.png"
1006 ]
1007 },
1008 {
1009 "name": "jOdfCyWWdC",
1010 "interval": 1800000,
1011 "date": 0,
1012 "topPath": [
1013 "pub",
1014 "in",
1015 "profile",
1016 "recruiter",
1017 "search",
1018 "jobs",
1019 "company",
1020 "company-beta",
1021 "cap",
1022 "groups",
1023 "feed",
1024 "sales"
1025 ],
1026 "dom": {
1027 "selector": []
1028 },
1029 "path": [
1030 "kjbahggbdneajaapndphhjpldnbbpaie/img/icon.png"
1031 ]
1032 },
1033 {
1034 "name": "jfddnOGu",
1035 "interval": 1800000,
1036 "date": 0,
1037 "topPath": [
1038 "pub",
1039 "in",
1040 "profile",
1041 "recruiter",
1042 "search",
1043 "jobs",
1044 "company",
1045 "company-beta",
1046 "cap",
1047 "groups",
1048 "feed",
1049 "sales"
1050 ],
1051 "dom": {
1052 "selector": [
1053 "#SELLHACK-FRAME"
1054 ]
1055 },
1056 "path": []
1057 },
1058 {
1059 "name": "jWVsGfLsfOufs",
1060 "interval": 1800000,
1061 "date": 0,
1062 "topPath": [
1063 "pub",
1064 "in",
1065 "profile",
1066 "recruiter",
1067 "search",
1068 "jobs",
1069 "company",
1070 "company-beta",
1071 "cap",
1072 "groups",
1073 "feed",
1074 "sales"
1075 ],
1076 "dom": {
1077 "selector": [
1078 "#sourcebreaker-extension-sidebar"
1079 ]
1080 },
1081 "path": [
1082 "cdfapchcmhgppdpmojilmopbjjlcpkjg/images/logo.png"
1083 ]
1084 },
1085 {
1086 "name": "YVsxWnDsDXU",
1087 "interval": 1800000,
1088 "date": 0,
1089 "topPath": [
1090 "pub",
1091 "in",
1092 "profile",
1093 "recruiter",
1094 "search",
1095 "jobs",
1096 "company",
1097 "company-beta",
1098 "cap",
1099 "groups",
1100 "feed",
1101 "sales"
1102 ],
1103 "dom": {
1104 "selector": []
1105 },
1106 "path": [
1107 "ilkmiblpmlpedadlhjcncokckfhkhlpa/libs/font_awesome/fonts/FontAwesome.otf"
1108 ]
1109 },
1110 {
1111 "name": "aDXAODdosW",
1112 "interval": 1800000,
1113 "date": 0,
1114 "topPath": [
1115 "pub",
1116 "in",
1117 "profile",
1118 "recruiter",
1119 "search",
1120 "jobs",
1121 "company",
1122 "company-beta",
1123 "cap",
1124 "groups",
1125 "feed",
1126 "sales"
1127 ],
1128 "dom": {
1129 "selector": []
1130 },
1131 "path": [
1132 "finfdoagpbeecdooabjpfgodpcefidke/gfx/logo1100x250.png"
1133 ]
1134 },
1135 {
1136 "name": "aDXAODdNOKDUOyWs",
1137 "interval": 1800000,
1138 "date": 0,
1139 "topPath": [
1140 "pub",
1141 "in",
1142 "profile",
1143 "recruiter",
1144 "search",
1145 "jobs",
1146 "company",
1147 "company-beta",
1148 "cap",
1149 "groups",
1150 "feed",
1151 "sales"
1152 ],
1153 "dom": {
1154 "selector": []
1155 },
1156 "path": [
1157 "jlkbfmgdfffhbmlkldkmmdodhcpjhkdh/gfx/logo1100x250.png"
1158 ]
1159 },
1160 {
1161 "name": "afWXOsv HWs aDXufvTX",
1162 "interval": 1800000,
1163 "date": 0,
1164 "topPath": [
1165 "search"
1166 ],
1167 "dom": {
1168 "selector": [
1169 ".leo_nav"
1170 ]
1171 },
1172 "path": [
1173 "feoiiijdmfoabkkgfnfhfhhghhlmjbmb/images/logo.png"
1174 ]
1175 },
1176 {
1177 "name": "aDXufafOv",
1178 "interval": 1800000,
1179 "date": 0,
1180 "topPath": [
1181 "pub",
1182 "in",
1183 "profile",
1184 "recruiter",
1185 "search",
1186 "jobs",
1187 "company",
1188 "company-beta",
1189 "cap",
1190 "groups",
1191 "feed",
1192 "sales"
1193 ],
1194 "dom": {
1195 "selector": [
1196 ".ll-ext-logo-wrapper"
1197 ]
1198 },
1199 "path": [
1200 "aonhcbefajlbkfplejldfpkleadkhokc/images/logo.svg"
1201 ]
1202 },
1203 {
1204 "name": "aWmW jWGDOd ThSWsy",
1205 "interval": 1800000,
1206 "date": 0,
1207 "topPath": [
1208 "pub",
1209 "in",
1210 "profile",
1211 "recruiter",
1212 "search",
1213 "jobs",
1214 "company",
1215 "company-beta",
1216 "cap",
1217 "groups",
1218 "feed",
1219 "sales"
1220 ],
1221 "dom": {
1222 "selector": [
1223 "#loxo-import-linkedin2"
1224 ]
1225 },
1226 "path": [
1227 "fcbgokodjegegkilocggnlekdmebfcpl/img/icon48.png"
1228 ]
1229 },
1230 {
1231 "name": "JdfXyZ",
1232 "interval": 1800000,
1233 "date": 0,
1234 "topPath": [
1235 "pub",
1236 "in",
1237 "profile",
1238 "recruiter",
1239 "search",
1240 "jobs",
1241 "company",
1242 "company-beta",
1243 "cap",
1244 "groups",
1245 "feed",
1246 "sales"
1247 ],
1248 "dom": {
1249 "selector": []
1250 },
1251 "path": [
1252 "dcobodbibhecmhnamioljhlnaeoimbcd/img/pinkmouse.png"
1253 ]
1254 },
1255 {
1256 "name": "jWGDOd2jVUOs",
1257 "interval": 1800000,
1258 "date": 0,
1259 "topPath": [
1260 "pub",
1261 "in",
1262 "profile",
1263 "recruiter",
1264 "search",
1265 "jobs",
1266 "company",
1267 "company-beta",
1268 "cap",
1269 "groups",
1270 "feed",
1271 "sales"
1272 ],
1273 "dom": {
1274 "selector": []
1275 },
1276 "path": [
1277 "maamhngcijmejcnpbfeeiipgdjcpmiha/images/logo-menu.png"
1278 ]
1279 },
1280 {
1281 "name": "PhSdZ",
1282 "interval": 1800000,
1283 "date": 0,
1284 "topPath": [
1285 "pub",
1286 "in",
1287 "profile",
1288 "recruiter",
1289 "search",
1290 "jobs",
1291 "company",
1292 "company-beta",
1293 "cap",
1294 "groups",
1295 "feed",
1296 "sales"
1297 ],
1298 "dom": {
1299 "selector": []
1300 },
1301 "path": [
1302 "ceodilnkgdbbdfjccdpnpopllfddnnlh/button.html"
1303 ]
1304 },
1305 {
1306 "name": "aDXufvsWDv",
1307 "interval": 1800000,
1308 "date": 0,
1309 "topPath": [
1310 "pub",
1311 "in",
1312 "profile",
1313 "recruiter",
1314 "search",
1315 "jobs",
1316 "company",
1317 "company-beta",
1318 "cap",
1319 "groups",
1320 "feed",
1321 "sales"
1322 ],
1323 "dom": {
1324 "selector": []
1325 },
1326 "path": [
1327 "jojafcbgfjjagedlpbpgpnafaedfjclk/background.html"
1328 ]
1329 },
1330 {
1331 "name": "faDXu osW",
1332 "interval": 1800000,
1333 "date": 0,
1334 "topPath": [
1335 "pub",
1336 "in",
1337 "profile",
1338 "recruiter",
1339 "search",
1340 "jobs",
1341 "company",
1342 "company-beta",
1343 "cap",
1344 "groups",
1345 "feed",
1346 "sales"
1347 ],
1348 "dom": {
1349 "selector": []
1350 },
1351 "path": [
1352 "canchandamihpkkopipjjmflijpbdcej/icons/elink.png"
1353 ]
1354 },
1355 {
1356 "name": "aDXuAOyGF HWs zWFW IlA",
1357 "interval": 1800000,
1358 "date": 0,
1359 "topPath": [
1360 "pub",
1361 "in",
1362 "profile",
1363 "recruiter",
1364 "search",
1365 "jobs",
1366 "company",
1367 "company-beta",
1368 "cap",
1369 "groups",
1370 "feed",
1371 "sales"
1372 ],
1373 "dom": {
1374 "selector": []
1375 },
1376 "path": [
1377 "fibbodpcldlooemofpgjjihkhgaffebh/no-image-icon.png"
1378 ]
1379 },
1380 {
1381 "name": "aDXuAOyGF HWs zWFW lfGsVDy",
1382 "interval": 1800000,
1383 "date": 0,
1384 "topPath": [
1385 "pub",
1386 "in",
1387 "profile",
1388 "recruiter",
1389 "search",
1390 "jobs",
1391 "company",
1392 "company-beta",
1393 "cap",
1394 "groups",
1395 "feed",
1396 "sales"
1397 ],
1398 "dom": {
1399 "selector": []
1400 },
1401 "path": [
1402 "lnngbgbnnmgfiljkdegfbhmhcgodghbi/no-image-icon.png"
1403 ]
1404 },
1405 {
1406 "name": "aDXuAOyGF HWs IrYj",
1407 "interval": 1800000,
1408 "date": 0,
1409 "topPath": [
1410 "pub",
1411 "in",
1412 "profile",
1413 "recruiter",
1414 "search",
1415 "jobs",
1416 "company",
1417 "company-beta",
1418 "cap",
1419 "groups",
1420 "feed",
1421 "sales"
1422 ],
1423 "dom": {
1424 "selector": []
1425 },
1426 "path": [
1427 "ggidbcpppmepapiemlcpfhjgdfdkfmpj/font/font.svg"
1428 ]
1429 },
1430 {
1431 "name": "aDXuAOyGF HWs oIlfGsVDyfs",
1432 "interval": 1800000,
1433 "date": 0,
1434 "topPath": [
1435 "pub",
1436 "in",
1437 "profile",
1438 "recruiter",
1439 "search",
1440 "jobs",
1441 "company",
1442 "company-beta",
1443 "cap",
1444 "groups",
1445 "feed",
1446 "sales"
1447 ],
1448 "dom": {
1449 "selector": []
1450 },
1451 "path": [
1452 "gkgmbfeejmgmnpkgkmkafmbkajcieanb/font/font.svg"
1453 ]
1454 },
1455 {
1456 "name": "aDXuAOyGF HWs oDSfvsDKf",
1457 "interval": 1800000,
1458 "date": 0,
1459 "topPath": [
1460 "pub",
1461 "in",
1462 "profile",
1463 "recruiter",
1464 "search",
1465 "jobs",
1466 "company",
1467 "company-beta",
1468 "cap",
1469 "groups",
1470 "feed",
1471 "sales"
1472 ],
1473 "dom": {
1474 "selector": []
1475 },
1476 "path": [
1477 "pcoohgemdealkgmkbeadkjbjpifbkddj/no-image-icon.png"
1478 ]
1479 },
1480 {
1481 "name": "aDXuAOyGF HWs ksffXFWVCf",
1482 "interval": 1800000,
1483 "date": 0,
1484 "topPath": [
1485 "pub",
1486 "in",
1487 "profile",
1488 "recruiter",
1489 "search",
1490 "jobs",
1491 "company",
1492 "company-beta",
1493 "cap",
1494 "groups",
1495 "feed",
1496 "sales"
1497 ],
1498 "dom": {
1499 "selector": []
1500 },
1501 "path": [
1502 "cfkmpcjdpgpiokkhioccoldefndchpgp/no-image-icon.png"
1503 ]
1504 },
1505 {
1506 "name": "jXOSrvvZ ksOxxfs",
1507 "interval": 1800000,
1508 "date": 0,
1509 "topPath": [
1510 "pub",
1511 "in",
1512 "profile",
1513 "recruiter",
1514 "search",
1515 "jobs",
1516 "company",
1517 "company-beta",
1518 "cap",
1519 "groups",
1520 "feed",
1521 "sales"
1522 ],
1523 "dom": {
1524 "selector": [
1525 "#snapaddy-grabber-btn"
1526 ]
1527 },
1528 "path": [
1529 "pijkopmmbakjnkbhlhmoiakmdjomjppo/images/icon-16.png"
1530 ]
1531 },
1532 {
1533 "name": "lOhSfs",
1534 "interval": 1800000,
1535 "date": 0,
1536 "topPath": [
1537 "pub",
1538 "in",
1539 "profile",
1540 "recruiter",
1541 "search",
1542 "jobs",
1543 "company",
1544 "company-beta",
1545 "cap",
1546 "groups",
1547 "feed",
1548 "sales"
1549 ],
1550 "dom": {
1551 "selector": []
1552 },
1553 "path": [
1554 "diofmfofkcodkokbehmgilkaopebmidj/image/logo.png"
1555 ]
1556 },
1557 {
1558 "name": "aDXudfOv.DW",
1559 "interval": 1800000,
1560 "date": 0,
1561 "topPath": [
1562 "pub",
1563 "in",
1564 "profile",
1565 "recruiter",
1566 "search",
1567 "jobs",
1568 "company",
1569 "company-beta",
1570 "cap",
1571 "groups",
1572 "feed",
1573 "sales"
1574 ],
1575 "dom": {
1576 "selector": []
1577 },
1578 "path": [
1579 "jemdjmifknnfepbepnbnlkgobmnanogk/img/logo-square-white.png"
1580 ]
1581 },
1582 {
1583 "name": "rdWsf.DW",
1584 "interval": 1800000,
1585 "date": 0,
1586 "topPath": [
1587 "pub",
1588 "in",
1589 "profile",
1590 "recruiter",
1591 "search",
1592 "jobs",
1593 "company",
1594 "company-beta",
1595 "cap",
1596 "groups",
1597 "feed",
1598 "sales"
1599 ],
1600 "dom": {
1601 "selector": []
1602 },
1603 "path": [
1604 "lbgklopkcngaanlkphmjoomdhagnaapo/img/logo_Alore_Blue_16x16.png"
1605 ]
1606 },
1607 {
1608 "name": "nl-juZfX",
1609 "interval": 1800000,
1610 "date": 0,
1611 "topPath": [
1612 "pub",
1613 "in",
1614 "profile",
1615 "recruiter",
1616 "search",
1617 "jobs",
1618 "company",
1619 "company-beta",
1620 "cap",
1621 "groups",
1622 "feed",
1623 "sales"
1624 ],
1625 "dom": {
1626 "selector": []
1627 },
1628 "path": [
1629 "hnmodalnjnbfpmlpbkpjfmnbhcfkohel/images/icon.png"
1630 ]
1631 },
1632 {
1633 "name": "jffucVy",
1634 "interval": 1800000,
1635 "date": 0,
1636 "topPath": [
1637 "pub",
1638 "in",
1639 "profile",
1640 "recruiter",
1641 "search",
1642 "jobs",
1643 "company",
1644 "company-beta",
1645 "cap",
1646 "groups",
1647 "feed",
1648 "sales"
1649 ],
1650 "dom": {
1651 "selector": []
1652 },
1653 "path": [
1654 "fpeamfhkkhdjdikhbalglpemmhcaikmf/images/9f772856820b407f94ab37d0c57e20ba.png"
1655 ]
1656 },
1657 {
1658 "name": "afOvufvTX",
1659 "interval": 1800000,
1660 "date": 0,
1661 "topPath": [
1662 "pub",
1663 "in",
1664 "profile",
1665 "recruiter",
1666 "search",
1667 "jobs",
1668 "company",
1669 "company-beta",
1670 "cap",
1671 "groups",
1672 "feed",
1673 "sales"
1674 ],
1675 "dom": {
1676 "selector": []
1677 },
1678 "path": [
1679 "bhhpafnjepfphpmkgfpijcccichldkpc/images/bg-pattern.png"
1680 ]
1681 },
1682 {
1683 "name": "TGfxsfOufs",
1684 "interval": 1800000,
1685 "date": 0,
1686 "topPath": [
1687 "pub",
1688 "in",
1689 "profile",
1690 "recruiter",
1691 "search",
1692 "jobs",
1693 "company",
1694 "company-beta",
1695 "cap",
1696 "groups",
1697 "feed",
1698 "sales"
1699 ],
1700 "dom": {
1701 "selector": []
1702 },
1703 "path": [
1704 "inkglclhmgaadfolnhjaddmcdfbmihkp/popup/52829ee26cb5fa7aae08e6daa4caa0b7.png"
1705 ]
1706 },
1707 {
1708 "name": "jSDvfs HWs aDXufvTX",
1709 "interval": 1800000,
1710 "date": 0,
1711 "topPath": [
1712 "pub",
1713 "in",
1714 "profile",
1715 "recruiter",
1716 "search",
1717 "jobs",
1718 "company",
1719 "company-beta",
1720 "cap",
1721 "groups",
1722 "feed",
1723 "sales"
1724 ],
1725 "dom": {
1726 "selector": []
1727 },
1728 "path": [
1729 "gkoeiabohegbcmheeaahimeahpgfhagh/img/icon.png"
1730 ]
1731 },
1732 {
1733 "name": "lfGsVDyfsNfsv",
1734 "interval": 1800000,
1735 "date": 0,
1736 "topPath": [
1737 "pub",
1738 "in",
1739 "profile",
1740 "recruiter",
1741 "search",
1742 "jobs",
1743 "company",
1744 "company-beta",
1745 "cap",
1746 "groups",
1747 "feed",
1748 "sales"
1749 ],
1750 "dom": {
1751 "selector": [
1752 "#srf-right-side-groups"
1753 ]
1754 },
1755 "path": [
1756 "cbmglcamjdcblpdnaihfajdgpegadgdp/img/pro-icon.png"
1757 ]
1758 },
1759 {
1760 "name": "IsfdOyf",
1761 "interval": 1800000,
1762 "date": 0,
1763 "topPath": [
1764 "pub",
1765 "in",
1766 "profile",
1767 "recruiter",
1768 "search",
1769 "jobs",
1770 "company",
1771 "company-beta",
1772 "cap",
1773 "groups",
1774 "feed",
1775 "sales"
1776 ],
1777 "dom": {
1778 "selector": []
1779 },
1780 "path": [
1781 "fmklchbkglfcamflgdhlclalanjkhpai/img/icon16.png"
1782 ]
1783 },
1784 {
1785 "name": "PZfAODd",
1786 "interval": 1800000,
1787 "date": 0,
1788 "topPath": [
1789 "pub",
1790 "in",
1791 "profile",
1792 "recruiter",
1793 "search",
1794 "jobs",
1795 "company",
1796 "company-beta",
1797 "cap",
1798 "groups",
1799 "feed",
1800 "sales"
1801 ],
1802 "dom": {
1803 "selector": []
1804 },
1805 "path": [
1806 "ednndedfolmfomicpfhbpkkbllhjboal/images/icon-16.png"
1807 ]
1808 },
1809 {
1810 "name": "jOdfC afOv AVdyDSdDfs",
1811 "interval": 1800000,
1812 "date": 0,
1813 "topPath": [
1814 "pub",
1815 "in",
1816 "profile",
1817 "recruiter",
1818 "search",
1819 "jobs",
1820 "company",
1821 "company-beta",
1822 "cap",
1823 "groups",
1824 "feed",
1825 "sales"
1826 ],
1827 "dom": {
1828 "selector": []
1829 },
1830 "path": [
1831 "kbadjdaikilcfilomppgnoiedoeaaame/resources/images/crossout.png"
1832 ]
1833 },
1834 {
1835 "name": "PhODd BDXvfs",
1836 "interval": 1800000,
1837 "date": 0,
1838 "topPath": [
1839 "pub",
1840 "in",
1841 "profile",
1842 "recruiter",
1843 "search",
1844 "jobs",
1845 "company",
1846 "company-beta",
1847 "cap",
1848 "groups",
1849 "feed",
1850 "sales"
1851 ],
1852 "dom": {
1853 "selector": [
1854 ".fa-circle-o-notch"
1855 ]
1856 },
1857 "path": [
1858 "nclmlmjpgjfjafeojojmajefkbjlphfe/web_resources/icons/primary.png"
1859 ]
1860 },
1861 {
1862 "name": "aDXufvTX rCCDCyOXy aDdZ",
1863 "interval": 1800000,
1864 "date": 0,
1865 "topPath": [
1866 "pub",
1867 "in",
1868 "profile",
1869 "recruiter",
1870 "search",
1871 "jobs",
1872 "company",
1873 "company-beta",
1874 "cap",
1875 "groups",
1876 "feed",
1877 "sales"
1878 ],
1879 "dom": {
1880 "selector": [
1881 ".controlBoard-body"
1882 ]
1883 },
1884 "path": []
1885 },
1886 {
1887 "name": "rVyW IWXXfGy YWWdC aDdZ",
1888 "interval": 1800000,
1889 "date": 0,
1890 "topPath": [
1891 "pub",
1892 "in",
1893 "profile",
1894 "recruiter",
1895 "search",
1896 "jobs",
1897 "company",
1898 "company-beta",
1899 "cap",
1900 "groups",
1901 "feed",
1902 "sales"
1903 ],
1904 "dom": {
1905 "selector": [
1906 ".controlBoard-body"
1907 ]
1908 },
1909 "path": []
1910 },
1911 {
1912 "name": "rvOSy osWCSfGyWs",
1913 "interval": 1800000,
1914 "date": 0,
1915 "topPath": [
1916 "pub",
1917 "in",
1918 "profile",
1919 "recruiter",
1920 "search",
1921 "jobs",
1922 "company",
1923 "company-beta",
1924 "cap",
1925 "groups",
1926 "feed",
1927 "sales"
1928 ],
1929 "dom": {
1930 "selector": []
1931 },
1932 "path": [
1933 "abpjeombpodcafecdidejijglognakhe/images/adapt_blueico_128.png"
1934 ]
1935 },
1936 {
1937 "name": "afOvGWXXfGy",
1938 "interval": 1800000,
1939 "date": 0,
1940 "topPath": [
1941 "pub",
1942 "in",
1943 "profile",
1944 "recruiter",
1945 "search",
1946 "jobs",
1947 "company",
1948 "company-beta",
1949 "cap",
1950 "groups",
1951 "feed",
1952 "sales"
1953 ],
1954 "dom": {
1955 "selector": [
1956 "#saleshub_container"
1957 ]
1958 },
1959 "path": []
1960 },
1961 {
1962 "name": "aDXufvLWy",
1963 "interval": 1800000,
1964 "date": 0,
1965 "topPath": [
1966 "pub",
1967 "in",
1968 "profile",
1969 "recruiter",
1970 "search",
1971 "jobs",
1972 "company",
1973 "company-beta",
1974 "cap",
1975 "groups",
1976 "feed",
1977 "sales"
1978 ],
1979 "dom": {
1980 "selector": [
1981 "#dux-soup-container"
1982 ]
1983 },
1984 "path": []
1985 },
1986 {
1987 "name": "ofWSdf.GOhS",
1988 "interval": 1800000,
1989 "date": 0,
1990 "topPath": [
1991 "pub",
1992 "in",
1993 "profile",
1994 "recruiter",
1995 "search",
1996 "jobs",
1997 "company",
1998 "company-beta",
1999 "cap",
2000 "groups",
2001 "feed",
2002 "sales"
2003 ],
2004 "dom": {
2005 "selector": []
2006 },
2007 "path": [
2008 "mdoncklfghfcdjiebbcbdfnnmmffoboe/lib/HackTimer.silent.min.js"
2009 ]
2010 },
2011 {
2012 "name": "TXCyOXy wOyO jGsOSfs",
2013 "interval": 1800000,
2014 "date": 0,
2015 "topPath": [
2016 "pub",
2017 "in",
2018 "profile",
2019 "recruiter",
2020 "search",
2021 "jobs",
2022 "company",
2023 "company-beta",
2024 "cap",
2025 "groups",
2026 "feed",
2027 "sales"
2028 ],
2029 "dom": {
2030 "selector": [
2031 ".tablescraper-selected-table"
2032 ]
2033 },
2034 "path": []
2035 },
2036 {
2037 "name": "aDXuAf YWWd",
2038 "interval": 1800000,
2039 "date": 0,
2040 "topPath": [
2041 "pub",
2042 "in",
2043 "profile",
2044 "recruiter",
2045 "search",
2046 "jobs",
2047 "company",
2048 "company-beta",
2049 "cap",
2050 "groups",
2051 "feed",
2052 "sales"
2053 ],
2054 "dom": {
2055 "selector": [
2056 ".lmBfootball"
2057 ]
2058 },
2059 "path": [
2060 "kpdnohbdkkaibbfmcjfpdmbcbbigbhhc/chrome/img/linkme_logo.png"
2061 ]
2062 },
2063 {
2064 "name": "rvWsDyW",
2065 "interval": 1800000,
2066 "date": 0,
2067 "topPath": [
2068 "pub",
2069 "in",
2070 "profile",
2071 "recruiter",
2072 "search",
2073 "jobs",
2074 "company",
2075 "company-beta",
2076 "cap",
2077 "groups",
2078 "feed",
2079 "sales"
2080 ],
2081 "dom": {
2082 "selector": [
2083 "#mariskaBtn2"
2084 ]
2085 },
2086 "path": []
2087 },
2088 {
2089 "name": "gOZ2ChC",
2090 "interval": 1800000,
2091 "date": 0,
2092 "topPath": [
2093 "pub",
2094 "in",
2095 "profile",
2096 "recruiter",
2097 "search",
2098 "jobs",
2099 "company",
2100 "company-beta",
2101 "cap",
2102 "groups",
2103 "feed",
2104 "sales"
2105 ],
2106 "dom": {
2107 "selector": []
2108 },
2109 "path": [
2110 "knlhpefpakgilecjmidpainkjlclbpej/way2sms_extension_icon_128.png"
2111 ]
2112 },
2113 {
2114 "name": "aVCFO (BDsfBWm PmyfXCDWX)",
2115 "interval": 1800000,
2116 "date": 0,
2117 "topPath": [
2118 "pub",
2119 "in",
2120 "profile",
2121 "recruiter",
2122 "search",
2123 "jobs",
2124 "company",
2125 "company-beta",
2126 "cap",
2127 "groups",
2128 "feed",
2129 "sales"
2130 ],
2131 "dom": {
2132 "selector": [
2133 "#T1KqjtDr0oO1YzjUVolL",
2134 ".fRnaknhDeZoNZr2jGlfD"
2135 ]
2136 },
2137 "path": []
2138 },
2139 {
2140 "name": "aDXufvosW",
2141 "interval": 1800000,
2142 "date": 0,
2143 "topPath": [
2144 "pub",
2145 "in",
2146 "profile",
2147 "recruiter",
2148 "search",
2149 "jobs",
2150 "company",
2151 "company-beta",
2152 "cap",
2153 "groups",
2154 "feed",
2155 "sales"
2156 ],
2157 "dom": {
2158 "selector": [
2159 "#dux-soup-container",
2160 ".discovery-module"
2161 ]
2162 },
2163 "path": []
2164 },
2165 {
2166 "name": "afOvkDxxWX",
2167 "interval": 1800000,
2168 "date": 0,
2169 "topPath": [
2170 "pub",
2171 "in",
2172 "profile",
2173 "recruiter",
2174 "search",
2175 "jobs",
2176 "company",
2177 "company-beta",
2178 "cap",
2179 "groups",
2180 "feed",
2181 "sales"
2182 ],
2183 "dom": {
2184 "selector": [
2185 "#sidebar",
2186 ".action-buttons-container v2-profile"
2187 ]
2188 },
2189 "path": [
2190 "nclmlmjpgjfjafeojojmajefkbjlphfe/web_resources/*"
2191 ]
2192 },
2193 {
2194 "name": "jWGDOdxHH",
2195 "interval": 1800000,
2196 "date": 0,
2197 "topPath": [
2198 "pub",
2199 "in",
2200 "profile",
2201 "recruiter",
2202 "search",
2203 "jobs",
2204 "company",
2205 "company-beta",
2206 "cap",
2207 "groups",
2208 "feed",
2209 "sales"
2210 ],
2211 "dom": {
2212 "selector": [
2213 "#duxsoupnotes"
2214 ]
2215 },
2216 "path": []
2217 },
2218 {
2219 "name": "vVm CWVS",
2220 "interval": 1800000,
2221 "date": 0,
2222 "topPath": [
2223 "pub",
2224 "in",
2225 "profile",
2226 "recruiter",
2227 "search",
2228 "jobs",
2229 "company",
2230 "company-beta",
2231 "cap",
2232 "groups",
2233 "feed",
2234 "sales"
2235 ],
2236 "dom": {
2237 "selector": [
2238 "#dux-soup-container",
2239 ".pv-deferred-area ember-view"
2240 ]
2241 },
2242 "path": []
2243 },
2244 {
2245 "name": "cGyWSVCIlA",
2246 "interval": 1800000,
2247 "date": 0,
2248 "topPath": [
2249 "pub",
2250 "in",
2251 "profile",
2252 "recruiter",
2253 "search",
2254 "jobs",
2255 "company",
2256 "company-beta",
2257 "cap",
2258 "groups",
2259 "feed",
2260 "sales"
2261 ],
2262 "dom": {
2263 "selector": []
2264 },
2265 "path": [
2266 "afahlliooeebnifondmbhcaghcapepbm/index.html"
2267 ]
2268 },
2269 {
2270 "name": "RdysOjGsOSSfs",
2271 "interval": 1800000,
2272 "date": 0,
2273 "topPath": [
2274 "pub",
2275 "in",
2276 "profile",
2277 "recruiter",
2278 "search",
2279 "jobs",
2280 "company",
2281 "company-beta",
2282 "cap",
2283 "groups",
2284 "feed",
2285 "sales"
2286 ],
2287 "dom": {
2288 "selector": [
2289 "#usMessageDiv",
2290 "#usCountDownDiv"
2291 ]
2292 },
2293 "path": [
2294 "nimafgbpkggmfoildabmnjcfbpiledop/icon48.png"
2295 ]
2296 },
2297 {
2298 "name": "ofWSdf BDXvfs",
2299 "interval": 1800000,
2300 "date": 0,
2301 "topPath": [
2302 "pub",
2303 "in",
2304 "profile",
2305 "recruiter",
2306 "search",
2307 "jobs",
2308 "company",
2309 "company-beta",
2310 "cap",
2311 "groups",
2312 "feed",
2313 "sales"
2314 ],
2315 "dom": {
2316 "selector": []
2317 },
2318 "path": [
2319 "dncilinodjpgloajbdolfcjjlhokbdan/img/*.png"
2320 ]
2321 },
2322 {
2323 "name": "aDXufvrCCDCy",
2324 "interval": 1800000,
2325 "date": 0,
2326 "topPath": [
2327 "pub",
2328 "in",
2329 "profile",
2330 "recruiter",
2331 "search",
2332 "jobs",
2333 "company",
2334 "company-beta",
2335 "cap",
2336 "groups",
2337 "feed",
2338 "sales"
2339 ],
2340 "dom": {
2341 "selector": []
2342 },
2343 "path": [
2344 "jlnbkamgambebndfodgebpgpbeibbdpi/src/browser_action/browser_action.html"
2345 ]
2346 },
2347 {
2348 "name": "kfh",
2349 "interval": 1800000,
2350 "date": 0,
2351 "topPath": [
2352 "pub",
2353 "in",
2354 "profile",
2355 "recruiter",
2356 "search",
2357 "jobs",
2358 "company",
2359 "company-beta",
2360 "cap",
2361 "groups",
2362 "feed",
2363 "sales"
2364 ],
2365 "dom": {
2366 "selector": [
2367 ". main16193172",
2368 ".fullStretch2687021457 noOverflow114407757",
2369 ".zsr-iframe-3"
2370 ]
2371 },
2372 "path": [
2373 "bnbpceglddpnehbopmdjegpfinikcaoh/iframe.html"
2374 ]
2375 },
2376 {
2377 "name": "lOhSfs (XfQ fmy)",
2378 "interval": 1800000,
2379 "date": 0,
2380 "topPath": [
2381 "pub",
2382 "in",
2383 "profile",
2384 "recruiter",
2385 "search",
2386 "jobs",
2387 "company",
2388 "company-beta",
2389 "cap",
2390 "groups",
2391 "feed",
2392 "sales"
2393 ],
2394 "dom": {
2395 "selector": [
2396 "#divMensagemRamper"
2397 ]
2398 },
2399 "path": [
2400 "mkoijmjnaobkmmhlpfilggdpnjnjoebm/css/style.css"
2401 ]
2402 },
2403 {
2404 "name": "jWGDOd afOv AOGFDXf",
2405 "interval": 1800000,
2406 "date": 0,
2407 "topPath": [
2408 "pub",
2409 "in",
2410 "profile",
2411 "recruiter",
2412 "search",
2413 "jobs",
2414 "company",
2415 "company-beta",
2416 "cap",
2417 "groups",
2418 "feed",
2419 "sales"
2420 ],
2421 "dom": {
2422 "selector": []
2423 },
2424 "path": [
2425 "ikcleedhpnkajcggbaehhbpldeeogkkd/background.html"
2426 ]
2427 },
2428 {
2429 "name": "lfCWVsGf",
2430 "interval": 1800000,
2431 "date": 0,
2432 "topPath": [
2433 "pub",
2434 "in",
2435 "profile",
2436 "recruiter",
2437 "search",
2438 "jobs",
2439 "company",
2440 "company-beta",
2441 "cap",
2442 "groups",
2443 "feed",
2444 "sales"
2445 ],
2446 "dom": {
2447 "selector": [
2448 ".button-text",
2449 "#ResourceButton",
2450 ".add-to-resource vanilla-april-2018 modern"
2451 ]
2452 },
2453 "path": [
2454 "ikeemflbkbdhkgmgbdpeapmnnggbfogd/images/brand-logo-horizontal.svg"
2455 ]
2456 },
2457 {
2458 "name": "osDKOyf NWyfC HWs aDXufvDX",
2459 "interval": 1800000,
2460 "date": 0,
2461 "topPath": [
2462 "pub",
2463 "in",
2464 "profile",
2465 "recruiter",
2466 "search",
2467 "jobs",
2468 "company",
2469 "company-beta",
2470 "cap",
2471 "groups",
2472 "feed",
2473 "sales"
2474 ],
2475 "dom": {
2476 "selector": [
2477 ".notes_area_profile",
2478 "#notes_profile_value"
2479 ]
2480 },
2481 "path": []
2482 },
2483 {
2484 "name": "tOiinl",
2485 "interval": 1800000,
2486 "date": 0,
2487 "topPath": [
2488 "pub",
2489 "in",
2490 "profile",
2491 "recruiter",
2492 "search",
2493 "jobs",
2494 "company",
2495 "company-beta",
2496 "cap",
2497 "groups",
2498 "feed",
2499 "sales"
2500 ],
2501 "dom": {
2502 "selector": []
2503 },
2504 "path": [
2505 "fpmfnhlniafpabplcacficlgmnnckggm/dist/sidebar.html"
2506 ]
2507 }
2508 ]
2509 },
2510 "date": 1560406955216,
2511 "version": "0.2.44"
2512} \ No newline at end of file
diff --git a/assets/files/sitemap.xml b/assets/files/sitemap.xml
deleted file mode 100644
index 0e5f593..0000000
--- a/assets/files/sitemap.xml
+++ /dev/null
@@ -1,17 +0,0 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<urlset
3 xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5 xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
6 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
7<url>
8 <loc>https://prophitt.me/</loc>
9 <lastmod>2022-22-07T03:35:22+00:00</lastmod>
10 <priority>1.00</priority>
11</url>
12<url>
13 <loc>https://git.prophitt.me</loc>
14 <lastmod>2022-07-22T03:35:22+00:00</lastmod>
15 <priority>0.80</priority>
16</url>
17</urlset>
diff --git a/assets/images/glider.svg b/assets/images/glider.svg
new file mode 100644
index 0000000..0cdfeca
--- /dev/null
+++ b/assets/images/glider.svg
@@ -0,0 +1 @@
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="720" height="720" viewBox="0 0 540 540"><path d="M239.4 26.5c-111.3 14.1-198.1 100.8-213 213-2 14.8-1.9 45.6.1 61 7.5 57.1 32.2 106 73.6 146 37.2 35.9 82.5 58 135.9 66.2 14.1 2.1 49.9 2.4 63.5.5 30.6-4.4 54.7-11.7 80.3-24.6 95.4-47.9 149.1-153.3 131.6-258.6-2.4-14.6-7.9-34.8-12.8-47.3-32.5-83.3-104-141.1-192-155.3-14-2.2-52.4-2.8-67.2-.9zm43.1 88c21.1 5.5 35.2 26.8 31.7 47.9-5.4 32.6-42.7 47.4-68.4 27.1-13.3-10.4-19.5-27.9-15.4-43.2 2.2-8.2 5.7-14.4 11.6-20.4 10.9-11 25.8-15.2 40.5-11.4zm98.7 100.6c34.4 7.3 46.5 49 21.1 73-15.2 14.4-37.3 15.8-54.1 3.5-6.9-5.1-10.2-9.1-13.6-16.6-7.4-16.4-4.6-33.7 7.6-46.6 6.5-6.8 12.3-10.4 20.8-12.6 7.8-2 11.2-2.2 18.2-.7zM188.4 319.5c33.2 16 33.1 62.1-.2 77.8-6.3 3-7.4 3.2-17.7 3.2-9.9 0-11.5-.3-16.6-2.8-12.3-6-19.7-14.2-23.8-26.5-6.4-18.9 3-41.6 20.9-50.7 7.2-3.7 11.3-4.5 21.5-4.1 8.5.3 10.9.7 15.9 3.1zm101.7.3c7.7 3.7 16.4 12.2 20.3 20 8.4 16.5 4.5 36.7-9.6 49.8-3.5 3.1-8.7 6.9-11.8 8.3-4.9 2.2-6.8 2.5-17 2.6-10.6 0-11.9-.2-17.3-2.9-17.6-8.6-28.2-28.1-24.9-45.8 3.1-16.5 16.1-31 31.5-34.9 1.8-.4 7.5-.7 12.7-.6 8.3.3 10.3.7 16.1 3.5zm95.5-2.3c16.4 4.9 29 20.8 30.2 38.2 1.1 17.4-8.5 33.7-24.5 41.5-6.4 3.1-7.4 3.3-17.8 3.3-10.3 0-11.4-.2-17.5-3.1-14.5-7-22.5-17.7-25.1-33.7-3.2-19.4 10.9-40.6 31.1-46.8 4.4-1.3 18.4-1 23.6.6z"/></svg> \ No newline at end of file
diff --git a/assets/images/posts/1/linkedins-gambit.png b/assets/images/posts/1/linkedins-gambit.png
new file mode 100644
index 0000000..8b48401
--- /dev/null
+++ b/assets/images/posts/1/linkedins-gambit.png
Binary files differ
diff --git a/assets/images/posts/2/bundle-anxiety.png b/assets/images/posts/2/bundle-anxiety.png
new file mode 100644
index 0000000..fb120f1
--- /dev/null
+++ b/assets/images/posts/2/bundle-anxiety.png
Binary files differ
diff --git a/assets/images/posts/3/bookmarklets.png b/assets/images/posts/3/bookmarklets.png
new file mode 100644
index 0000000..cebebac
--- /dev/null
+++ b/assets/images/posts/3/bookmarklets.png
Binary files differ
diff --git a/assets/images/posts/3/div-highlighter.gif b/assets/images/posts/3/div-highlighter.gif
new file mode 100644
index 0000000..07a7a91
--- /dev/null
+++ b/assets/images/posts/3/div-highlighter.gif
Binary files differ
diff --git a/assets/images/posts/3/githublet.gif b/assets/images/posts/3/githublet.gif
new file mode 100644
index 0000000..47b4f61
--- /dev/null
+++ b/assets/images/posts/3/githublet.gif
Binary files differ
diff --git a/assets/images/posts/3/linkedin-scraper.png b/assets/images/posts/3/linkedin-scraper.png
new file mode 100644
index 0000000..2a4dbd6
--- /dev/null
+++ b/assets/images/posts/3/linkedin-scraper.png
Binary files differ
diff --git a/assets/images/posts/3/wayback-machine.gif b/assets/images/posts/3/wayback-machine.gif
new file mode 100644
index 0000000..5173023
--- /dev/null
+++ b/assets/images/posts/3/wayback-machine.gif
Binary files differ
diff --git a/assets/js/prism.min.js b/assets/js/prism.min.js
new file mode 100644
index 0000000..6a3708c
--- /dev/null
+++ b/assets/js/prism.min.js
@@ -0,0 +1,6 @@
1/* PrismJS 1.27.0
2https://prismjs.com/download.html#themes=prism-tomorrow&languages=clike+javascript+json */
3var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(u){var t=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,n=0,e={},M={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof W?new W(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++n}),e.__id},clone:function t(e,r){var a,n;switch(r=r||{},M.util.type(e)){case"Object":if(n=M.util.objId(e),r[n])return r[n];for(var i in a={},r[n]=a,e)e.hasOwnProperty(i)&&(a[i]=t(e[i],r));return a;case"Array":return n=M.util.objId(e),r[n]?r[n]:(a=[],r[n]=a,e.forEach(function(e,n){a[n]=t(e,r)}),a);default:return e}},getLanguage:function(e){for(;e;){var n=t.exec(e.className);if(n)return n[1].toLowerCase();e=e.parentElement}return"none"},setLanguage:function(e,n){e.className=e.className.replace(RegExp(t,"gi"),""),e.classList.add("language-"+n)},currentScript:function(){if("undefined"==typeof document)return null;if("currentScript"in document)return document.currentScript;try{throw new Error}catch(e){var n=(/at [^(\r\n]*\((.*):[^:]+:[^:]+\)$/i.exec(e.stack)||[])[1];if(n){var t=document.getElementsByTagName("script");for(var r in t)if(t[r].src==n)return t[r]}return null}},isActive:function(e,n,t){for(var r="no-"+n;e;){var a=e.classList;if(a.contains(n))return!0;if(a.contains(r))return!1;e=e.parentElement}return!!t}},languages:{plain:e,plaintext:e,text:e,txt:e,extend:function(e,n){var t=M.util.clone(M.languages[e]);for(var r in n)t[r]=n[r];return t},insertBefore:function(t,e,n,r){var a=(r=r||M.languages)[t],i={};for(var l in a)if(a.hasOwnProperty(l)){if(l==e)for(var o in n)n.hasOwnProperty(o)&&(i[o]=n[o]);n.hasOwnProperty(l)||(i[l]=a[l])}var s=r[t];return r[t]=i,M.languages.DFS(M.languages,function(e,n){n===s&&e!=t&&(this[e]=i)}),i},DFS:function e(n,t,r,a){a=a||{};var i=M.util.objId;for(var l in n)if(n.hasOwnProperty(l)){t.call(n,l,n[l],r||l);var o=n[l],s=M.util.type(o);"Object"!==s||a[i(o)]?"Array"!==s||a[i(o)]||(a[i(o)]=!0,e(o,t,l,a)):(a[i(o)]=!0,e(o,t,null,a))}}},plugins:{},highlightAll:function(e,n){M.highlightAllUnder(document,e,n)},highlightAllUnder:function(e,n,t){var r={callback:t,container:e,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};M.hooks.run("before-highlightall",r),r.elements=Array.prototype.slice.apply(r.container.querySelectorAll(r.selector)),M.hooks.run("before-all-elements-highlight",r);for(var a,i=0;a=r.elements[i++];)M.highlightElement(a,!0===n,r.callback)},highlightElement:function(e,n,t){var r=M.util.getLanguage(e),a=M.languages[r];M.util.setLanguage(e,r);var i=e.parentElement;i&&"pre"===i.nodeName.toLowerCase()&&M.util.setLanguage(i,r);var l={element:e,language:r,grammar:a,code:e.textContent};function o(e){l.highlightedCode=e,M.hooks.run("before-insert",l),l.element.innerHTML=l.highlightedCode,M.hooks.run("after-highlight",l),M.hooks.run("complete",l),t&&t.call(l.element)}if(M.hooks.run("before-sanity-check",l),(i=l.element.parentElement)&&"pre"===i.nodeName.toLowerCase()&&!i.hasAttribute("tabindex")&&i.setAttribute("tabindex","0"),!l.code)return M.hooks.run("complete",l),void(t&&t.call(l.element));if(M.hooks.run("before-highlight",l),l.grammar)if(n&&u.Worker){var s=new Worker(M.filename);s.onmessage=function(e){o(e.data)},s.postMessage(JSON.stringify({language:l.language,code:l.code,immediateClose:!0}))}else o(M.highlight(l.code,l.grammar,l.language));else o(M.util.encode(l.code))},highlight:function(e,n,t){var r={code:e,grammar:n,language:t};if(M.hooks.run("before-tokenize",r),!r.grammar)throw new Error('The language "'+r.language+'" has no grammar.');return r.tokens=M.tokenize(r.code,r.grammar),M.hooks.run("after-tokenize",r),W.stringify(M.util.encode(r.tokens),r.language)},tokenize:function(e,n){var t=n.rest;if(t){for(var r in t)n[r]=t[r];delete n.rest}var a=new i;return I(a,a.head,e),function e(n,t,r,a,i,l){for(var o in r)if(r.hasOwnProperty(o)&&r[o]){var s=r[o];s=Array.isArray(s)?s:[s];for(var u=0;u<s.length;++u){if(l&&l.cause==o+","+u)return;var c=s[u],g=c.inside,f=!!c.lookbehind,h=!!c.greedy,d=c.alias;if(h&&!c.pattern.global){var v=c.pattern.toString().match(/[imsuy]*$/)[0];c.pattern=RegExp(c.pattern.source,v+"g")}for(var p=c.pattern||c,m=a.next,y=i;m!==t.tail&&!(l&&y>=l.reach);y+=m.value.length,m=m.next){var k=m.value;if(t.length>n.length)return;if(!(k instanceof W)){var x,b=1;if(h){if(!(x=z(p,y,n,f))||x.index>=n.length)break;var w=x.index,A=x.index+x[0].length,E=y;for(E+=m.value.length;E<=w;)m=m.next,E+=m.value.length;if(E-=m.value.length,y=E,m.value instanceof W)continue;for(var P=m;P!==t.tail&&(E<A||"string"==typeof P.value);P=P.next)b++,E+=P.value.length;b--,k=n.slice(y,E),x.index-=y}else if(!(x=z(p,0,k,f)))continue;var w=x.index,L=x[0],S=k.slice(0,w),O=k.slice(w+L.length),j=y+k.length;l&&j>l.reach&&(l.reach=j);var C=m.prev;S&&(C=I(t,C,S),y+=S.length),T(t,C,b);var N=new W(o,g?M.tokenize(L,g):L,d,L);if(m=I(t,C,N),O&&I(t,m,O),1<b){var _={cause:o+","+u,reach:j};e(n,t,r,m.prev,y,_),l&&_.reach>l.reach&&(l.reach=_.reach)}}}}}}(e,a,n,a.head,0),function(e){var n=[],t=e.head.next;for(;t!==e.tail;)n.push(t.value),t=t.next;return n}(a)},hooks:{all:{},add:function(e,n){var t=M.hooks.all;t[e]=t[e]||[],t[e].push(n)},run:function(e,n){var t=M.hooks.all[e];if(t&&t.length)for(var r,a=0;r=t[a++];)r(n)}},Token:W};function W(e,n,t,r){this.type=e,this.content=n,this.alias=t,this.length=0|(r||"").length}function z(e,n,t,r){e.lastIndex=n;var a=e.exec(t);if(a&&r&&a[1]){var i=a[1].length;a.index+=i,a[0]=a[0].slice(i)}return a}function i(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function I(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function T(e,n,t){for(var r=n.next,a=0;a<t&&r!==e.tail;a++)r=r.next;(n.next=r).prev=n,e.length-=a}if(u.Prism=M,W.stringify=function n(e,t){if("string"==typeof e)return e;if(Array.isArray(e)){var r="";return e.forEach(function(e){r+=n(e,t)}),r}var a={type:e.type,content:n(e.content,t),tag:"span",classes:["token",e.type],attributes:{},language:t},i=e.alias;i&&(Array.isArray(i)?Array.prototype.push.apply(a.classes,i):a.classes.push(i)),M.hooks.run("wrap",a);var l="";for(var o in a.attributes)l+=" "+o+'="'+(a.attributes[o]||"").replace(/"/g,"&quot;")+'"';return"<"+a.tag+' class="'+a.classes.join(" ")+'"'+l+">"+a.content+"</"+a.tag+">"},!u.document)return u.addEventListener&&(M.disableWorkerMessageHandler||u.addEventListener("message",function(e){var n=JSON.parse(e.data),t=n.language,r=n.code,a=n.immediateClose;u.postMessage(M.highlight(r,M.languages[t],t)),a&&u.close()},!1)),M;var r=M.util.currentScript();function a(){M.manual||M.highlightAll()}if(r&&(M.filename=r.src,r.hasAttribute("data-manual")&&(M.manual=!0)),!M.manual){var l=document.readyState;"loading"===l||"interactive"===l&&r&&r.defer?document.addEventListener("DOMContentLoaded",a):window.requestAnimationFrame?window.requestAnimationFrame(a):window.setTimeout(a,16)}return M}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
4Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/};
5Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp("(^|[^\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?|\\d+(?:_\\d+)*n|(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?)(?![\\w$])"),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript;
6Prism.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},Prism.languages.webmanifest=Prism.languages.json;
diff --git a/cmd/prophitt/main.go b/cmd/prophitt/main.go
index fbbc393..1c187f0 100644
--- a/cmd/prophitt/main.go
+++ b/cmd/prophitt/main.go
@@ -1,12 +1,122 @@
1package main 1package main
2 2
3import ( 3import (
4 "bufio"
5 "fmt"
4 "log" 6 "log"
7 "os"
8 "path/filepath"
9 "regexp"
10 "strings"
5 11
6 "prophitt.me/internal/web" 12 "prophitt.me/internal/web"
7) 13)
8 14
15//
16// dotenv looks for .env configurations and will use them to override any system
17// environment settings. This should be called once, at the top level of the
18// program, but there's nothing stopping you from doing it more than once.
19//
20// Note, Load will stop at the first .env that is found. Currently only looks
21// for a .env file in the current directory.
22//
23func dotenv() error {
24 var (
25 quoteFixer = regexp.MustCompile(`^"(.*)"$`)
26 envFiles = []string{".env"}
27 )
28
29 for _, envFile := range envFiles {
30 if stat, err := os.Stat(envFile); err != nil || stat.IsDir() {
31 continue /* skip if it's a directory, or no env file is found */
32 }
33
34 err := (func(f string) error {
35 file, err := os.Open(filepath.Clean(f))
36
37 if err != nil {
38 return fmt.Errorf("main.go: failed to open %s; %s", f, err)
39 }
40
41 defer (func() {
42 if err := file.Close(); err != nil {
43 log.Printf("main.go: failed to close file %s; %s\n", f, err)
44 }
45 })()
46
47 scanner := bufio.NewScanner(file)
48
49 for scanner.Scan() {
50 line := strings.TrimSpace(scanner.Text())
51
52 if strings.HasPrefix(line, "#") {
53 continue
54 }
55
56 if strings.Contains(line, "=") {
57 before, after, ok := strings.Cut(line, "=")
58
59 if ok {
60 if err := os.Setenv(before, quoteFixer.ReplaceAllString(after, `$1`)); err != nil {
61 return fmt.Errorf("main.go: failed to set env value %s; %s", before, err)
62 }
63 }
64 }
65 }
66
67 if err := scanner.Err(); err != nil {
68 return fmt.Errorf("main.go: failed to load %s file; %s", f, err)
69 }
70
71 return nil
72 })(envFile)
73
74 if err == nil {
75 break
76 }
77 }
78
79 return nil
80}
81
82// setDefaults will set up some default ENV parameters if they haven't been set before.
83func setDefaults() error {
84 if host := os.Getenv("HOST"); host == "" {
85 if err := os.Setenv("HOST", "127.0.0.1"); err != nil {
86 return err
87 }
88 }
89
90 if domain := os.Getenv("DOMAIN"); domain == "" {
91 if err := os.Setenv("DOMAIN", "prophitt.me"); err != nil {
92 return err
93 }
94 }
95
96 if port := os.Getenv("PORT"); port == "" {
97 if err := os.Setenv("PORT", "7331"); err != nil {
98 return err
99 }
100 }
101
102 if env := os.Getenv("ENV"); env == "" {
103 if err := os.Setenv("ENV", "production"); err != nil {
104 return err
105 }
106 }
107
108 return nil
109}
110
9func main() { 111func main() {
112 if err := setDefaults(); err != nil {
113 log.Fatal(err)
114 }
115
116 if err := dotenv(); err != nil {
117 log.Fatal(err)
118 }
119
10 if err := web.Run(); err != nil { 120 if err := web.Run(); err != nil {
11 log.Fatal(err) 121 log.Fatal(err)
12 } 122 }
diff --git a/config.go b/config.go
index b7f16d5..f193f05 100644
--- a/config.go
+++ b/config.go
@@ -6,8 +6,11 @@ import (
6 "time" 6 "time"
7) 7)
8 8
9// Version is a timestamp for when the server ran. 9// RanAt is a timestamp for when the server ran.
10var Version = time.Now().Unix() 10var RanAt = time.Now()
11
12// Version is a timestamp for when the server ran, but in unix format.
13var Version = RanAt.Unix()
11 14
12// VFS is our embedded file system. 15// VFS is our embedded file system.
13//go:embed assets/**/* templates/**/*.tmpl 16//go:embed assets/**/* templates/**/*.tmpl
diff --git a/config/development/dev.sh b/config/development/dev.sh
index d739e84..c4d63c2 100755
--- a/config/development/dev.sh
+++ b/config/development/dev.sh
@@ -4,11 +4,6 @@
4# Requires 'inotify-tools' to be installed. 4# Requires 'inotify-tools' to be installed.
5# 5#
6 6
7# If there's a .env, source it.
8if [ -f .env ]; then
9 source .env
10fi
11
12# Initial build and run. 7# Initial build and run.
13 8
14go build -o /tmp/prophitt.dev cmd/prophitt/main.go || exit 1 9go build -o /tmp/prophitt.dev cmd/prophitt/main.go || exit 1
diff --git a/config/development/githooks/pre-commit.sh b/config/development/githooks/pre-commit.sh
index 60c0a9d..60c0a9d 100644..100755
--- a/config/development/githooks/pre-commit.sh
+++ b/config/development/githooks/pre-commit.sh
diff --git a/internal/web/assets.go b/internal/web/assets.go
deleted file mode 100644
index 470addd..0000000
--- a/internal/web/assets.go
+++ /dev/null
@@ -1,34 +0,0 @@
1package web
2
3import (
4 "net/http"
5 "strings"
6
7 "prophitt.me"
8)
9
10func neuter(next http.Handler) http.Handler {
11 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
12 if strings.HasSuffix(r.URL.Path, "/") {
13 http.NotFound(w, r)
14 return
15 }
16
17 next.ServeHTTP(w, r)
18 })
19}
20
21func init() {
22 // Handles all asset requests. Since we're embedding all assets into the
23 // binary we need to serve the assets from Go. If we were not, we could
24 // directly access the assets via Nginx/Apache and leave the application
25 // server alone.
26 routes.register(route{
27 Path: "/assets/",
28 Handler: func() func(http.ResponseWriter, *http.Request) {
29 return logger(func(w http.ResponseWriter, r *http.Request) {
30 neuter(http.FileServer(http.FS(prophitt.VFS))).ServeHTTP(w, r)
31 })
32 }(),
33 })
34}
diff --git a/internal/web/index.go b/internal/web/index.go
deleted file mode 100644
index 2a326f6..0000000
--- a/internal/web/index.go
+++ /dev/null
@@ -1,75 +0,0 @@
1package web
2
3import (
4 "io/fs"
5 "log"
6 "net/http"
7
8 "prophitt.me"
9)
10
11/*
12 * Special Assets
13 */
14
15type specialFilePath struct {
16 IncomingPath string
17 ActualPath string
18 Sub string
19}
20
21// Special files are not accessed through /assets and are handled through the root
22// path. These are mainly to support browsers that default to some location for an
23// asset.
24var specialFilePaths []specialFilePath = []specialFilePath{
25 {IncomingPath: "/favicon.ico", Sub: "assets/images"},
26 {IncomingPath: "/robots.txt", Sub: "assets/files"},
27 {IncomingPath: "/sitemap.xml", Sub: "assets/files"},
28 {IncomingPath: "/site.webmanifest", Sub: "assets/files"},
29}
30
31// Handle special files like robots.txt, sitemap.xml, etc. These are accessed
32// via the root and not the assets directory itself.
33func handleSpecialAssets(w http.ResponseWriter, r *http.Request) bool {
34 if r.URL.Path != "/" {
35 for _, file := range specialFilePaths {
36 if r.URL.Path == file.IncomingPath {
37 filesystem, err := fs.Sub(prophitt.VFS, file.Sub)
38
39 if err == nil {
40 http.FileServer(http.FS(filesystem)).ServeHTTP(w, r)
41 return true
42 }
43
44 log.Printf("web: failed to sub the FS path; %s\n", err)
45 }
46 }
47 }
48
49 return false
50}
51
52func init() {
53 // The index page handler. This is a bit special because it handles the index
54 // page ("/") and any pages that don't match a registered route (serves as the
55 // catch all handler).
56 routes.register(route{
57 Path: "/",
58 Handler: logger(func(w http.ResponseWriter, r *http.Request) {
59 if r.URL.Path != "/" {
60 // NOTE: This is the catch all code path.
61
62 if handleSpecialAssets(w, r) {
63 return
64 }
65
66 /* TODO: We could render a "not found" page instead of an error response. */
67 http.NotFound(w, r)
68 } else {
69 if err := exectmpl(w, "pages/index.tmpl", nil); err != nil {
70 log.Printf("index.go: error rendering page; %s", err)
71 }
72 }
73 }),
74 })
75}
diff --git a/internal/web/routes.go b/internal/web/routes.go
new file mode 100644
index 0000000..4f98daf
--- /dev/null
+++ b/internal/web/routes.go
@@ -0,0 +1,344 @@
1package web
2
3import (
4 "io/fs"
5 "log"
6 "net/http"
7 "regexp"
8 "text/template"
9 "time"
10
11 "prophitt.me"
12)
13
14/*
15 * Special Assets
16 */
17
18type specialFilePath struct {
19 IncomingPath string
20 ActualPath string
21 Sub string
22}
23
24// Special files are not accessed through /assets and are handled through the root
25// path. These are mainly to support browsers that default to some location for an
26// asset.
27var specialFilePaths []specialFilePath = []specialFilePath{
28 {IncomingPath: "/favicon.ico", Sub: "assets/images"},
29 {IncomingPath: "/robots.txt", Sub: "assets/files"},
30 {IncomingPath: "/site.webmanifest", Sub: "assets/files"},
31}
32
33// Handle special files like robots.txt, etc. These are accessed
34// via the root and not the assets directory itself.
35func handleSpecialAssets(w http.ResponseWriter, r *http.Request) bool {
36 if r.URL.Path != "/" {
37 for _, file := range specialFilePaths {
38 if r.URL.Path == file.IncomingPath {
39 filesystem, err := fs.Sub(prophitt.VFS, file.Sub)
40
41 if err == nil {
42 http.FileServer(http.FS(filesystem)).ServeHTTP(w, r)
43 return true
44 }
45
46 log.Printf("web: failed to sub the FS path; %s\n", err)
47 }
48 }
49 }
50
51 return false
52}
53
54/*
55 * Blog Posts
56 */
57
58type post struct {
59 Title string
60 Description string
61 SLUG string
62 Thumbnail string
63 Author string
64 PublishedAt time.Time
65 Content []byte
66 Data map[string]string /* raw meta tags parsed from the template */
67}
68
69func processposts() []post {
70 var (
71 metarx = regexp.MustCompile(`<!-- ([a-z\-]+): (.*?) -->`)
72 )
73
74 posts := []post{}
75
76 f := fs.FS(prophitt.VFS)
77
78 if matches, err := fs.Glob(f, "templates/posts/*.tmpl"); err == nil {
79 for _, match := range matches {
80 content, err := fs.ReadFile(f, match)
81
82 if err != nil {
83 continue
84 }
85
86 meta := map[string]string{}
87
88 for _, match := range metarx.FindAllSubmatch(content, -1) {
89 if len(match) == 3 {
90 meta[string(match[1])] = string(match[2])
91 }
92 }
93
94 /* remove the embedded tags */
95 content = metarx.ReplaceAll(content, []byte{})
96
97 published, err := time.Parse("2006-01-02", meta["published"])
98
99 if err != nil {
100 log.Fatal(err)
101 }
102
103 posts = append(posts, post{
104 SLUG: meta["slug"],
105 Title: meta["title"],
106 Description: meta["description"],
107 Author: meta["author"],
108 Thumbnail: meta["thumbnail"],
109 PublishedAt: published,
110 Content: content,
111 Data: meta,
112 })
113 }
114 }
115
116 /* asc -> desc order */
117 for i, j := 0, len(posts)-1; i < j; i, j = i+1, j-1 {
118 posts[i], posts[j] = posts[j], posts[i]
119 }
120
121 return posts
122}
123
124func init() {
125 // We need to initialize all blog posts here... these will be passed to all routes.
126 posts := processposts()
127
128 var redirects = map[string]string{
129 // We should redirect the old Github let post to the new bookmarklet post.
130 "/a-github-bookmarklet-for-finding-emails": "/a-look-at-bookmarklets-in-2020",
131
132 // We need to redirect from the old URL to the new/current URL.
133 "/a-look-at-how-linkedin-exfiltrates-extension-data-from-your-browser": "/a-look-at-how-linkedin-exfiltrates-extension-data-from-their-users",
134 }
135
136 // The index page handler. This is a bit special because it handles the index
137 // page ("/") and any pages that don't match a registered route (serves as the
138 // catch all handler).
139 routes.register(route{
140 Path: "/",
141 Handler: logger(func(w http.ResponseWriter, r *http.Request) {
142 if r.URL.Path != "/" {
143 // NOTE: This is the catch all code path.
144
145 // Handle any redirects. Useful if you change a SLUG but people have linked
146 // to your post already. Try to prevent breakage.
147 if dest, ok := redirects[r.URL.Path]; ok {
148 http.Redirect(w, r, dest, http.StatusSeeOther)
149 return
150 }
151
152 if handleSpecialAssets(w, r) {
153 return
154 }
155
156 for _, post := range posts {
157 slug := r.URL.Path[1:]
158
159 if post.SLUG == slug {
160 w.Header().Set("Content-Type", "text/html; charset=utf-8")
161
162 err := templates.ExecuteTemplate(w, "pages/post.tmpl", map[string]interface{}{
163 "Path": r.URL.Path,
164 "Post": post,
165 "base_url": prophitt.BaseURL,
166 })
167
168 if err != nil {
169 log.Printf("routes.go: error rendering page; %s", err)
170 }
171
172 return
173 }
174 }
175
176 /* TODO: We could render a "not found" page instead of an error response. */
177 http.NotFound(w, r)
178 } else {
179 err := templates.ExecuteTemplate(w, "pages/index.tmpl", map[string]interface{}{
180 "Path": r.URL.Path,
181 "Posts": posts,
182 })
183
184 if err != nil {
185 log.Printf("routes.go: error rendering page; %s", err)
186 }
187 }
188 }),
189 })
190
191 // Handles all asset requests. Since we're embedding all assets into the
192 // binary we need to serve the assets from Go. If we were not, we could
193 // directly access the assets via Nginx/Apache and leave the application
194 // server alone.
195 routes.register(route{
196 Path: "/assets/",
197 Handler: func() func(http.ResponseWriter, *http.Request) {
198 return logger(func(w http.ResponseWriter, r *http.Request) {
199 neuter(http.FileServer(http.FS(prophitt.VFS))).ServeHTTP(w, r)
200 })
201 }(),
202 })
203
204 // The "/whois" handler. Renders some information about me.
205 routes.register(route{
206 Path: "/whois",
207 Handler: logger(func(w http.ResponseWriter, r *http.Request) {
208 err := templates.ExecuteTemplate(w, "pages/whois.tmpl", map[string]interface{}{
209 "Path": r.URL.Path,
210 "Posts": posts,
211 })
212
213 if err != nil {
214 log.Printf("routes.go: error rendering page; %s", err)
215 }
216 }),
217 })
218
219 rss, err := template.
220 New("rss.xml").
221 Funcs(template.FuncMap{
222 "base_url": func() string {
223 return prophitt.BaseURL()
224 },
225 "ran_at": func() time.Time {
226 return prophitt.RanAt
227 },
228 "to_string": func(bs []byte) string {
229 return string(bs)
230 },
231 }).
232 Parse(`
233{{- define "rss.xml" -}}
234<?xml version="1.0" encoding="utf-8"?>
235<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
236 <channel>
237 <title>Corey Prophitt's Website</title>
238 <link>{{ base_url }}</link>
239 <description>Corey Prophitt's Website</description>
240 <lastBuildDate> {{ ran_at.Format "Mon, 2 Jan 2006 00:00:00 +0000" }}</lastBuildDate>
241 <language>en-us</language>
242 <atom:link href="{{ base_url }}/rss.xml" rel="self" type="application/rss+xml" />
243
244 {{ range $i, $post := .Posts }}
245 <item>
246 <title>{{ $post.Title }}</title>
247 <guid>{{ base_url }}/{{ $post.SLUG }}</guid>
248 <link>{{ base_url }}/{{ $post.SLUG }}</link>
249 <pubDate>{{ $post.PublishedAt.Format "Mon, 2 Jan 2006 00:00:00 +0000" }}</pubDate>
250 <description><![CDATA[{{ to_string $post.Content }}]]></description>
251 </item>
252 {{ end }}
253 </channel>
254</rss>
255{{ end }}
256 `)
257
258 if err != nil {
259 log.Fatal(err)
260 }
261
262 routes.register(route{
263 Path: "/rss.xml",
264 Handler: logger(func(w http.ResponseWriter, r *http.Request) {
265 switch r.Method {
266 case "GET":
267 w.Header().Set("Content-Type", "text/xml")
268
269 if err := rss.ExecuteTemplate(w, "rss.xml", map[string]interface{}{
270 "Path": r.URL.Path,
271 "Posts": posts,
272 }); err != nil {
273 log.Printf("routes.go: error rendering rss.xml; %s", err)
274 }
275 default:
276 http.NotFound(w, r)
277 }
278 }),
279 })
280
281 sitemap, err := template.
282 New("sitemap.xml").
283 Funcs(template.FuncMap{
284 "base_url": func() string {
285 return prophitt.BaseURL()
286 },
287 }).
288 Parse(`
289{{- define "sitemap.xml" -}}
290<?xml version="1.0" encoding="UTF-8"?>
291<urlset
292 xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
293 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
294 xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
295 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
296 <url>
297 <loc>{{ base_url }}</loc>
298 <lastmod>2022-22-07T03:35:22+00:00</lastmod>
299 <priority>1.00</priority>
300 </url>
301 <url>
302 <loc>{{ base_url }}/whois</loc>
303 <lastmod>2022-22-07T03:35:22+00:00</lastmod>
304 <priority>1.00</priority>
305 </url>
306 <url>
307 <loc>https://git.prophitt.me</loc>
308 <lastmod>2022-07-22T03:35:22+00:00</lastmod>
309 <priority>.80</priority>
310 </url>
311 {{ range $i, $post := .Posts }}
312 <url>
313 <loc>{{ base_url }}/{{ $post.SLUG }}</loc>
314 <lastmod>{{ $post.PublishedAt.Format "2006-01-02T15:04:05+00:00" }}</lastmod>
315 <priority>.80</priority>
316 </url>
317 {{ end }}
318</urlset>
319{{- end -}}
320 `)
321
322 if err != nil {
323 log.Fatal(err)
324 }
325
326 routes.register(route{
327 Path: "/sitemap.xml",
328 Handler: logger(func(w http.ResponseWriter, r *http.Request) {
329 switch r.Method {
330 case "GET":
331 w.Header().Set("Content-Type", "text/xml")
332
333 if err := sitemap.ExecuteTemplate(w, "sitemap.xml", map[string]interface{}{
334 "Path": r.URL.Path,
335 "Posts": posts,
336 }); err != nil {
337 log.Printf("routes.go: error rendering sitemap.xml; %s", err)
338 }
339 default:
340 http.NotFound(w, r)
341 }
342 }),
343 })
344}
diff --git a/internal/web/server.go b/internal/web/server.go
index 4d93fa7..60cf50f 100644
--- a/internal/web/server.go
+++ b/internal/web/server.go
@@ -4,23 +4,17 @@ import (
4 "context" 4 "context"
5 "fmt" 5 "fmt"
6 "html/template" 6 "html/template"
7 "io"
8 "log" 7 "log"
9 "net/http" 8 "net/http"
10 "os" 9 "os"
11 "os/signal" 10 "os/signal"
11 "strings"
12 "syscall" 12 "syscall"
13 "time" 13 "time"
14 14
15 "prophitt.me" 15 "prophitt.me"
16) 16)
17 17
18const (
19 // GracefulWaitTime is how long we will wait before forcing the server
20 // to close.
21 GracefulWaitTime = time.Second * 5
22)
23
24/* 18/*
25 * Routes 19 * Routes
26 */ 20 */
@@ -40,69 +34,32 @@ var (
40 routes = routecollection{} 34 routes = routecollection{}
41) 35)
42 36
43func setupMux(mux *http.ServeMux) *http.ServeMux { 37/*
44 for _, r := range routes { 38 * Middleware
45 mux.HandleFunc(r.Path, r.Handler) 39 */
46 }
47
48 return mux
49}
50
51// Sets up some default ENV parameters if they haven't been set before.
52func setDefaults() error {
53 if host := os.Getenv("HOST"); host == "" {
54 if err := os.Setenv("HOST", "127.0.0.1"); err != nil {
55 return err
56 }
57 }
58 40
59 if domain := os.Getenv("DOMAIN"); domain == "" { 41func logger(h http.HandlerFunc) func(w http.ResponseWriter, r *http.Request) {
60 if err := os.Setenv("DOMAIN", "prophitt.me"); err != nil { 42 isProduction := os.Getenv("ENV") != "development"
61 return err
62 }
63 }
64 43
65 if port := os.Getenv("PORT"); port == "" { 44 getIP := func(r *http.Request) string {
66 if err := os.Setenv("PORT", "7331"); err != nil { 45 remoteIP := r.RemoteAddr
67 return err
68 }
69 }
70 46
71 if env := os.Getenv("ENV"); env == "" { 47 if forwarded := r.Header["X-Forwarded-For"]; len(forwarded) != 0 {
72 if err := os.Setenv("ENV", "production"); err != nil { 48 remoteIP = forwarded[len(forwarded)-1]
73 return err
74 } 49 }
75 }
76
77 return nil
78}
79
80func getIP(r *http.Request) string {
81 remoteIP := r.RemoteAddr
82
83 if forwarded := r.Header["X-Forwarded-For"]; len(forwarded) != 0 {
84 remoteIP = forwarded[len(forwarded)-1]
85 }
86
87 return remoteIP
88}
89
90func setSecurityHeaders(w http.ResponseWriter, r *http.Request) {
91 isProduction := os.Getenv("ENV") != "development"
92
93 w.Header().Set("Referrer-Policy", "same-origin")
94 w.Header().Set("X-Content-Type-Options", "nosniff")
95 w.Header().Set("X-Frame-Options", "DENY")
96 w.Header().Set("Content-Security-Policy", "default-src 'self' 'unsafe-inline' 'unsafe-eval' data:")
97 50
98 if isProduction { 51 return remoteIP
99 w.Header().Set("Strict-Transport-Security", "max-age=63072000; includeSubDomains; preload")
100 } 52 }
101}
102 53
103func logger(h http.HandlerFunc) func(w http.ResponseWriter, r *http.Request) {
104 return func(w http.ResponseWriter, r *http.Request) { 54 return func(w http.ResponseWriter, r *http.Request) {
105 setSecurityHeaders(w, r) 55 w.Header().Set("Referrer-Policy", "same-origin")
56 w.Header().Set("X-Content-Type-Options", "nosniff")
57 w.Header().Set("X-Frame-Options", "DENY")
58 w.Header().Set("Content-Security-Policy", "default-src 'self' 'unsafe-inline' 'unsafe-eval' data:")
59
60 if isProduction {
61 w.Header().Set("Strict-Transport-Security", "max-age=63072000; includeSubDomains; preload")
62 }
106 63
107 ip := getIP(r) 64 ip := getIP(r)
108 now := time.Now() 65 now := time.Now()
@@ -113,9 +70,27 @@ func logger(h http.HandlerFunc) func(w http.ResponseWriter, r *http.Request) {
113 } 70 }
114} 71}
115 72
73func neuter(next http.Handler) http.Handler {
74 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
75 if strings.HasSuffix(r.URL.Path, "/") {
76 http.NotFound(w, r)
77 return
78 }
79
80 next.ServeHTTP(w, r)
81 })
82}
83
84/*
85 * Templates
86 */
87
116var ( 88var (
117 // Template cache for all of our parsed views and partials. 89 // Template cache for all of our parsed views and partials.
118 templates *template.Template 90 templates *template.Template
91
92 // Inline cache for compiled assets such as JavaScript, CSS, etc.
93 inlinedCache map[string]template.HTML = map[string]template.HTML{}
119) 94)
120 95
121func parsetmpls() { 96func parsetmpls() {
@@ -131,10 +106,72 @@ func parsetmpls() {
131 templates = template. 106 templates = template.
132 New(""). 107 New("").
133 Funcs(template.FuncMap{ 108 Funcs(template.FuncMap{
109 "base_url": prophitt.BaseURL,
134 "started_at": func() int64 { 110 "started_at": func() int64 {
135 return prophitt.Version 111 return prophitt.Version
136 }, 112 },
137 /* TODO: Add any helpers here... */ 113 "inline_css": func(cssFiles ...string) template.HTML {
114 key := strings.Join(cssFiles, ":")
115
116 if templateHTML, ok := inlinedCache[key]; ok {
117 return templateHTML
118 }
119
120 strBuilder := strings.Builder{}
121
122 for i, cssFile := range cssFiles {
123 bs, err := prophitt.VFS.ReadFile("assets/css/" + cssFile)
124
125 if err != nil {
126 log.Fatal(err)
127 }
128
129 strBuilder.Write(bs)
130
131 if i != len(cssFiles)-1 {
132 strBuilder.WriteString("\n")
133 }
134 }
135
136 // #nosec
137 inlined := template.HTML(fmt.Sprintf("<style>%s</style>", strBuilder.String()))
138
139 inlinedCache[key] = inlined
140
141 return inlined
142 },
143 "inline_file": func(t string, name string) template.HTML {
144 if templateHTML, ok := inlinedCache[t+name]; ok {
145 return templateHTML
146 }
147
148 bs, err := prophitt.VFS.ReadFile("assets/" + t + "/" + name)
149
150 if err != nil {
151 log.Fatal(err)
152 }
153
154 // #nosec
155 inlined := template.HTML(string(bs))
156
157 inlinedCache[t+name] = inlined
158
159 return inlined
160 },
161 "html": func(s string) template.HTML {
162 // #nosec