Singing With Sinatra The Recall App

Benvenuto in Traccia 2 di Cantare con Sinatra. Nella prima parte, abbiamo esaminato Rotte, come lavorare con i parametri URI, lavorare con i moduli e in che modo Sinatra differenzia le rotte tramite il metodo HTTP da loro richiesto. Oggi estenderemo le nostre conoscenze su Sinatra costruendo una piccola app basata su database, "Recall", per prendere appunti / creare una lista di cose da fare.

Utilizzeremo un database SQLite per archiviare le note e useremo il RubyGem DataMapper per comunicare con il database. Esegui il seguente all'interno di una shell per installare le gemme rilevanti:

gem installa sqlite3 datamapper dm-sqlite-adapter

A seconda di come è stato configurato RubyGems sul tuo sistema, potrebbe essere necessario il prefisso gem installazione con sudo.


Il riscaldamento

Iniziamo subito creando una nuova directory per il progetto e creando il file dell'applicazione, recall.rb. Iniziare richiedendo le gemme rilevanti:

richiedere 'rubygems' richiedono 'sinatra' richiedono 'datamapper'

Nota: Se stai usando Ruby 1.9 (che dovresti essere), puoi rilasciare la riga "require" rubygems "in quanto Ruby carica automaticamente RubyGems in ogni caso.

E impostare il database con il seguente:

DataMapper :: setup (: default, "sqlite3: // # Dir.pwd /recall.db") classe Nota include DataMapper :: Proprietà risorsa: id, proprietà Serial: content, Text,: required => true property: complete, Boolean,: required => true,: default => false proprietà: created_at, proprietà DateTime: updated_at, DateTime end DataMapper.finalize.auto_upgrade!

Sulla prima riga stiamo impostando un nuovo database SQLite3 nella directory corrente, chiamato recall.db. Di seguito, stiamo configurando una tabella 'Note' nel database.

Mentre chiamiamo la 'Note' della classe, DataMapper creerà la tabella come 'Note'. Questo è in linea con una convenzione che seguono Ruby on Rails e altri framework e moduli ORM.

All'interno della classe, stiamo impostando lo schema del database. La tabella 'Note' avrà 5 campi. Un id campo che sarà una chiave primaria intera e auto-incrementante (questo è ciò che significa 'Seriale'). UN soddisfare campo contenente testo, un booleano completare campo e due campi datetime, created_at e updated_at.

L'ultima riga indica a DataMapper di aggiornare automaticamente il database per contenere le tabelle e i campi che abbiamo impostato, e di farlo di nuovo se apportiamo modifiche allo schema.


La Home Page

Ora, creiamo la nostra home page:

In alto c'è un modulo per aggiungere una nuova nota, e sotto di esso ci sono tutte le note nel database. Per iniziare, aggiungi quanto segue al file dell'applicazione, recall.rb:

get '/' do @notes = Note.all: .order =>: id.desc @title = 'All Notes' erb: home end

Nota importante: Rimuovi il punto ('.') nel :.ordine. (WordPress interferisce con il codice di esempio).

Nella seconda riga vedi come recuperiamo tutte le note dal database. Se hai già utilizzato ActiveRecord (l'ORM utilizzato in Rails), la sintassi di DataMapper sarà molto familiare. Le note sono assegnate al @gli appunti variabile di istanza. È importante utilizzare le variabili di istanza (ovvero le variabili che iniziano con an @) in modo che siano accessibili dall'interno del file di visualizzazione.

Abbiamo impostato il @titolo variabile di istanza e carica il views / home.erb visualizza il file attraverso il parser ERB.

Crea il views / home.erb visualizza il file e avvialo con il seguente:

<% # display notes %>

Abbiamo una forma semplice quali POST alla home page ('/'), e al di sotto di questo c'è un codice ERB che funge da segnaposto per ora.


layout

Il lotto degli standard HTML tra di voi potrebbe aver subito un lieve ictus dopo aver visto che il nostro file di visualizzazione home non contiene doctype o altri tag HTML. Bene, c'è una ragione per quello. Creare un layout.erb file nel tuo visualizzazioni/ directory contenente quanto segue:

    <%= @title + ' | Recall' %>      

Richiamare

perché sei troppo occupato per ricordare

<%= yield %>

Un'app per Nettuts+.

Le due parti interessanti qui sono le linee 5 e 18. Sulla linea 5 vedi il primo uso di <%=? %> Tag ERB. <%= è diverso dall'ordinario <% come stampa ciò che è dentro Quindi qui stiamo mostrando ciò che c'è nel @titolo variabile di istanza seguita da | Richiamare per la pagina </code> etichetta.</p> <p>Sulla linea 18 c'è <code><%= yield %></code>. Sinatra mostrerà questo <code>layout.erb</code> file su tutte le rotte. E il contenuto effettivo per quella rotta verrà inserito ovunque <code>dare la precedenza</code> è. <code>dare la precedenza</code> è un termine che significa essenzialmente "fermati qui, inserisci quello che ti aspetta, poi continua".</p> <p>Avvia il server con <code>shotgun recall.rb</code> nella shell, e dare un'occhiata alla home page nel browser. Dovresti vedere il contenuto dal file di layout e il modulo dal reale <code>home.erb</code> vista.</p> <img src="//accentsconagua.com/img/images_26_3/singing-with-sinatra-the-recall-app_2.png"> <hr> <h2>CSS</h2> <p>Nel file di layout abbiamo incluso due file CSS. Sinatra può caricare file statici (ad esempio CSS, JS, immagini ecc.) Da una cartella denominata <code>pubblico/</code> nella directory principale. Quindi crea quella directory e al suo interno due file: <code>reset.css</code> e <code>style.css</code>. Il reset contiene il reset CSS di HTML5 Boilerplate:</p> <pre>/ * HTML5? Boilerplate style.css contiene un reset, la normalizzazione dei font e alcuni stili di base. il credito è lasciato dove il credito è dovuto. molta ispirazione è stata presa da questi progetti: yui.yahooapis.com/2.8.1/build/base/base.css camendesign.com/design/ praegnanz.de/weblog/htmlcssjs-kickstart * / / * html5doctor.com Reimposta il foglio di stile ( Reset Meyer di Eric Meyer + linea base HTML5 v1.6.1 2010-09-17 | Autori: Eric Meyer e Richard Clark html5doctor.com/html-5-reset-stylesheet/ * / html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre , abbr, indirizzo, citare, codice, del, dfn, em, img, ins, kbd, q, samp, piccolo, forte, sub, sup, var, b, i, dl, dt, dd, ol, ul, li , fieldset, forma, etichetta, legenda, tabella, didascalia, tbody, tfoot, thead, tr, th, td, articolo, a parte, canvas, dettagli, figcaption, figure, footer, header, hgroup, menu, nav, section, summary , tempo, contrassegno, audio, video margine: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; articolo, a parte, dettagli, figcaption, figure, footer, header, hgroup, menu, nav, section display: block; blockquote, q quotes: none; blockquote: before, blockquote: after, q: before, q: after content: "; content: none; ins background-color: # ff9; color: # 000; text-decoration: none; mark background -color: # ff9; color: # 000; font-style: italic; font-weight: bold; del text-decoration: line-through; abbr [titolo], dfn [titolo] border-bottom: 1px punteggiato; cursore: help; tabella border-collapse: collapse; border-spacing: 0; hr display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; input, selezionare vertical-align: middle; / * END RESET CSS * / / * normalizzazione dei font ispirata al font fonts.css: developer.yahoo.com/yui/ * / body della libreria YUI : 13px / 1.231 sans-serif; * font-size: small; / * hack mantenuto per preservare la specificità * / select, input, textarea, button font: 99% sans-serif; / * normalize monospace sizing * it. wikipedia.org/wiki/MediaWiki_talk:Common.css/Archive_11#Teletype_style_fix_for_Chrome * / pre, codice, kbd, samp font-family: monospace, sans-serif; / * * minimal b ase styles * / body, select, input, textarea / * # 444 sembra migliore del nero: twitter.com/H_FJ/statuses/11800719859 * / color: # 444; / * imposta qui il tuo carattere di base, per applicare uniformemente * / / * font-family: Georgia, serif; * / / * le intestazioni (h1, h2, ecc.) non hanno font o dimensioni predefinite. definisci te stesso. * / h1, h2, h3, h4, h5, h6 font-weight: bold; / * forza sempre una barra di scorrimento in non-IE: * / html overflow-y: scroll; / * trattamento focalizzato accessibile: people.opera.com/patrickl/experiments/keyboard/test * / a: hover, a: active outline: none; a, a: active, a: visited color: # 607890; a: hover color: # 036; ul, ol margin-left: 2em; ol list-style-type: decimal; / * rimuovi i margini per gli elenchi di navigazione * / nav ul, nav li margin: 0; list-style: none; list-style-image: none; small font-size: 85%; forte, th font-weight: bold; td vertical-align: top; / * imposta sub, sup senza influenzare l'altezza della riga: gist.github.com/413930 * / sub, sup font-size: 75%; altezza della linea: 0; posizione: relativa; sup top: -0.5em; sub bottom: -0.25em; pre / * www.pathf.com/blogs/2008/05/formatting-quoted-code-in-blog-posts-css21-white-space-pre-wrap/ * / white-space: pre; white-space: pre-wrap; white-space: pre-line; word-wrap: break-word; imbottitura: 15px; textarea overflow: auto; / * www.sitepoint.com/blogs/2010/08/20/ie-remove-textarea-scrollbars/ * / .ie6 legend, .ie7 legend margin-left: -7px; / * thnx ivannikolic! * / / * allinea le caselle di controllo, le radio, gli input di testo con la loro etichetta: Thierry Koblentz tjkdesign.com/ez-css/css/base.css * / input [type = "radio"] vertical-align: text-bottom; input [type = "checkbox"] vertical-align: bottom; .ie7 input [type = "checkbox"] vertical-align: baseline; .ie6 input vertical-align: text-bottom; / * cursore a forma di mano sugli elementi di input selezionabili * / label, input [type = "button"], input [type = "submit"], input [type = "image"], button cursor: pointer; / * I browser webkit aggiungono un margine di 2px al di fuori del chrome degli elementi del modulo * / button, input, select, textarea margin: 0; / * colori per la validità del modulo * / input: valido, textarea: valido input: non valido, textarea: invalid border-radius: 1px; -moz-box-shadow: 0px 0px 5px rosso; -webkit-box-shadow: 0px 0px 5px rosso; box-shadow: 0px 0px 5px rosso; .no-boxshadow input: invalid, .no-boxshadow textarea: invalid background-color: # f0dddd; / * Queste dichiarazioni di selezione devono essere separate. Nessuna ombra di testo: twitter.com/miketaylr/status/12228805301 Inoltre: hot pink. * / :: - moz-selection background: # FF5E99; color: #fff; text-shadow: none; :: selection background: # FF5E99; color: #fff; text-shadow: none; / * j.mp/webkit-tap-highlight-color * / a: link -webkit-tap-highlight-color: # FF5E99; / * rendono i pulsanti riproducibili in IE: www.viget.com/inspire/styling-the-button-element-in-internet-explorer/ * / button width: auto; overflow: visibile; / * ridimensionamento bicubico per IMG non nativa: code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ * / .ie7 img -ms-interpolation-mode: bicubic; </pre> <p>E <code>style.css</code> contiene alcuni stili di base per rendere l'app molto carina:</p> <pre>body margin: 35px auto; larghezza: 640 px; header text-align: center; margine: 0 0 20 px; header h1 display: inline; dimensione del font: 32px; intestazione h1 a: link, intestazione h1 a: visited color: # 444; decorazione del testo: nessuna; header h2 font-size: 16px; stile di carattere: corsivo; colore: n. 999; #main margin: 0 0 20px; #add margin: 0 0 20px; #add textarea height: 30px; larghezza: 510 px; imbottitura: 10px; border: 1px solid #ddd; #add input height: 50px; larghezza: 100 px; margine: -50px 0 0; border: 1px solid #ddd; sfondo: bianco; #edit textarea height: 30px; larghezza: 480 px; imbottitura: 10px; border: 1px solid #ddd; #edit input [type = submit] height: 50px; larghezza: 100 px; margine: -50px 0 0; border: 1px solid #ddd; sfondo: bianco; #edit input [type = checkbox] height: 50px; larghezza: 20px; article border: 1px solid #eee; border-top: nessuno; imbottitura: 15px 10px; articolo: first-of-type border: 1px solid #eee; articolo: nth-child (even) background: #fafafa; article.complete background: # fedae3; article span font-size: 0.8em; p margin: 0 0 5px; .meta font-size: 0.8em; stile di carattere: corsivo; colore: # 888; .links font-size: 1.8em; altezza della linea: 0.8em; float: giusto; margine: -10px 0 0; .links a display: block; decorazione del testo: nessuna; </pre> <p>Aggiorna la pagina nel tuo browser e tutto dovrebbe essere più stilizzato. Non preoccuparti troppo di questo CSS; rende solo le cose un po 'più belle!</p> <img src="//accentsconagua.com/img/images_26_3/singing-with-sinatra-the-recall-app_3.png"> <hr> <h2>Aggiunta di una nota al database</h2> <p>In questo momento, se provi a inviare il modulo sulla home page, otterrai un errore di route. Creiamo ora la rotta POST per la home page:</p> <pre>post '/' do n = Note.new n.content = params [: contenuto] n.created_at = Time.now n.updated_at = Time.now n.save reindirizzamento '/' fine</pre> <p>Quindi, quando viene effettuata una richiesta di post sulla home page, creiamo un nuovo oggetto Note in <code>n</code> (grazie a DataMapper ORM, <code>Note.new</code> rappresenta una nuova riga nel <code>gli appunti</code> tabella nel database). Il <code>soddisfare</code> campo è impostato per i dati inviati dalla textarea e il <code>created_at</code> e <code>updated_at</code> i campi datetime sono impostati sul timestamp corrente.</p> <p>La nuova nota viene quindi salvata e l'utente viene reindirizzato alla home page in cui verrà visualizzata la nuova nota.</p> <hr> <h2>Visualizzazione delle note</h2> <p>Quindi abbiamo aggiunto una nuova nota, ma non possiamo ancora vederla sulla homepage perché non abbiamo scritto il codice per essa. Dentro il <code>views / home.erb</code> visualizza il file, sostituisci il <code><%# display notes %></code> coincide:</p> <pre><% @notes.each do |note| %> <article <%= 'class="complete"' if note.complete %>> <p> <%= note.content %> <span>"> [Modifica]</span> </p> <p> / Completo ">? </p> <p>Creato: <%= note.created_at %></p> </article> <% end %></pre> <p>Sulla prima riga iniziamo un ciclo attraverso ciascuno dei <code>@gli appunti</code> (in alternativa, avremmo potuto scrivere <code>per nota in @notes</code>, ma usare un blocco, come siamo qui, è una pratica migliore). Sulla linea 2, diamo il <code><article></code> una classe di <code>completare</code> se la nota corrente è impostata su <code>completare</code>. Il resto dovrebbe essere abbastanza semplice.</p> <hr> <h2>Modifica di una nota</h2> <p>Quindi possiamo aggiungere e visualizzare le note. Ora abbiamo solo bisogno di modificarli ed eliminarli.</p> <p>Potresti averlo notato nel nostro <code>home.erb</code> vista abbiamo impostato un <code>[modificare]</code> link per ogni nota a ciò che è essenzialmente <code>/: Id</code>, quindi creiamo quella rotta ora:</p> <pre>get '/: id' do @note = Note.get params [: id] @title = "Modifica nota ## params [: id]" erb: modifica fine</pre> <p>Ritiriamo la nota richiesta dal database utilizzando l'ID fornito, impostiamo a <code>@titolo</code> variabile e carica il <code>views / edit.erb</code> visualizza il file attraverso il parser ERB.</p> <p>Immettere quanto segue per <code>views / edit.erb</code> vista:</p> <pre><% if @note %> <form action="/<%= @note.id %>"method =" post "> <input type="hidden" name="_method" value="put"> <textarea name="content"><%= @note.content %></textarea> <input type="checkbox" name="complete" <%= "checked" if @note.complete %>> <input type="submit"> </form> <p>/ Delete "> Elimina</p> <% else %> <p>Nota non trovata.</p> <% end %></pre> <p>Questa è una visione abbastanza semplice. Un modulo che rimanda alla pagina corrente, una textarea contenente il contenuto della nota e una casella di controllo che viene controllata se la nota è impostata su <code>completare</code>.</p> <p>Ma guarda la terza riga. Misterioso. Per spiegare questo, abbiamo bisogno di tenere traccia di un po '.</p> <h3>Servizi RESTful</h3> <p>Hai sentito parlare dei due termini GET e POST.</p> <ul> <li> <strong>OTTENERE: </strong>Il più comune. Generalmente è richiesto per una pagina e può essere inserito nei segnalibri.</li> <li> <strong>INVIARE: </strong> Utilizzato per l'invio di dati e non può essere inserito nei segnalibri.</li> </ul> <p>Ma GET e POST non sono gli unici "verbi HTTP": ce ne sono altri due che dovresti sapere: PUT e DELETE.</p> <p>Tecnicamente, il POST dovrebbe essere usato solo per creare qualcosa, ad esempio la creazione di una nuova nota nella tua fantastica nuova app web.</p> <p>PUT è il verbo per modificare qualcosa. E DELETE, hai indovinato, è per l'eliminazione di qualcosa.</p> <p>Avere questi quattro verbi è un ottimo modo per separare un'app. È logico. Sfortunatamente, i browser Web non supportano effettivamente richieste PUT o DELETE, motivo per cui probabilmente non ne hai mai sentito parlare prima.</p> <p>Quindi, tornando in pista qui, se vogliamo dividere logicamente la nostra app (che Sinatra incoraggia), dobbiamo fingere queste richieste PUT e DELETE. Vedrai il nostro modulo <code>azione</code> è impostato per <code>inviare</code>. Il nascosto <code>_metodo</code> campo di input che abbiamo impostato <code>mettere</code> sulla terza riga lascia Sinatra fingere questa richiesta PUT, mentre effettivamente usa un POST. Rails, tra gli altri framework, fa le cose in modo simile.</p> <hr> <h2>Lasciaci Mettere</h2> <p>Ora abbiamo simulato la nostra richiesta PUT, possiamo creare un percorso per questo:</p> <pre>metti '/: id' do n = Note.get params [: id] n.content = params [: content] n.complete = params [: complete]? 1: 0 n.updated_at = Time.now n.save reindirizza '/' fine</pre> <p>È tutto piuttosto semplice. Otteniamo la nota rilevante usando l'ID nell'URI, impostiamo i campi sui nuovi valori, salviamo e reindirizziamo verso casa. Notate come sulla quarta linea usiamo un operatore ternario per impostare <code>n.complete</code> a <code>1</code> Se <code>params [:] completi</code> esiste, o <code>0</code> altrimenti. Questo perché il valore di una casella di controllo è inviato solo con un modulo se è selezionato, quindi stiamo semplicemente verificando l'esistenza di esso.</p> <hr> <h2>Eliminazione di una nota</h2> <p>Nel nostro <code>edit.erb</code> vista, abbiamo aggiunto un link 'Elimina' a ciò che è essenzialmente il percorso <code>/: Id / delete</code>. Aggiungi questo al tuo file di applicazione:</p> <pre>get '/: id / delete' do @note = Note.get params [: id] @title = "Conferma cancellazione della nota ## params [: id]" erb: elimina fine</pre> <p>In questa pagina avremo la conferma da parte dell'utente che in realtà vogliono cancellare questa nota. Crea il file di visualizzazione su <code>views / delete.erb</code> con il seguente:</p> <pre><% if @note %> <p>Sei sicuro di voler eliminare la seguente nota: <em>"<%= @note.content %>"</em>?</p> <form action="/<%= @note.id %>"method =" post "> <input type="hidden" name="_method" value="delete"> <input type="submit" value="Yes, Delete It!"> "> Annulla </form> <% else %> <p>Nota non trovata.</p> <% end %></pre> <p>Si noti che proprio come abbiamo simulato una richiesta PUT impostando un hidden <code>_metodo</code> campo di input, ora stiamo simulando una richiesta DELETE.</p> <hr> <h2>Il percorso DELETE</h2> <p>Sono sicuro che ti stai rendendo conto di questo ormai. La rotta di cancellazione è:</p> <pre>cancella '/: id' do n = Note.get params [: id] n.destroy reindirizza '/' fine</pre> <p>Provalo! Ora dovresti essere in grado di visualizzare, aggiungere, modificare e rimuovere note. C'è solo un'altra cosa? </p> <hr> <h2>Contrassegnare una nota come "Completa"</h2> <p>In questo momento se vuoi impostare una nota come <code>completare</code> devi andare nella vista Modifica e selezionare la casella in quella pagina. Facciamo un po 'più semplice questo processo.</p> <p>Indietro quando abbiamo impostato la home page principale, abbiamo incluso un <code>/: Id / completo</code> link su ogni nota. Facciamo ora quella rotta, che semplicemente imposterà una nota come completa (o incompleta se era già stata impostata per il completamento):</p> <pre>get '/: id / complete' do n = Note.get params [: id] n.complete = n.complete? 0: 1 # capovolgi n.updated_at = Time.now n.save reindirizza '/' fine</pre> <hr> <h2>Conclusione</h2> <p>Tu e Sinatra fate un duetto! Hai scritto molto rapidamente una semplice app Web che esegue tutte le operazioni CRUD che ti aspetteresti che un'app eseguisse. È scritto in codice Ruby super sexy e pulito, ed è separato nelle sue parti logiche.</p> <p>Nella parte finale di Singing with Sinatra, l'Encore, miglioreremo la gestione degli errori, proteggere l'app da XSS e creare un feed RSS per le note.</p> <p><strong>Nota:</strong> Puoi sfogliare i file di progetto finali per questo tutorial su GitHub.</p> <div class="rek-block"> <center> <ins class="adsbygoogle" style="display:inline-block;width:580px;height:400px" data-ad-client="ca-pub-3810161443300697" data-ad-slot="9434875811"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </center> </div> <div class="h-alltags"> <a href="articles/code">Codice</a> </div> </div> </div> </div> </div> <div class="next_posts clearfix"> <div class="n_post"> <div class="next_posts-h1 left_nh1"><a href="/articles/code/singing-with-sinatra.html">Cantando con Sinatra</a></div> <div class="next_posts-img" style="background-image: url('//accentsconagua.com/img/images_26_3/singing-with-sinatra.jpg');"></div> </div> <div class="n_post"> <div class="next_posts-h1 right_nh1"><a href="/articles/code/singing-with-sinatra-the-encore.html">Cantando con Sinatra - The Encore</a></div> <div class="next_posts-img" style="background-image: url('//accentsconagua.com/img/images_26_3/singing-with-sinatra-the-encore.jpg');"></div> </div> </div> <footer> <div class="container"> <div class="footer-langs"> <ul class="site-langs-list"> <li><a href="https://www.accentsconagua.com"><i class="flag flag-DE"></i>Deutsch</a></li> <li><a href="https://fr.accentsconagua.com"><i class="flag flag-FR"></i>Français</a></li> <li><a href="https://nl.accentsconagua.com"><i class="flag flag-NL"></i>Nederlands</a></li> <li><a href="https://no.accentsconagua.com"><i class="flag flag-NO"></i>Norsk</a></li> <li><a href="https://sv.accentsconagua.com"><i class="flag flag-SE"></i>Svenska</a></li> <li><a href="https://it.accentsconagua.com"><i class="flag flag-IT"></i>Italiano</a></li> <li><a href="https://es.accentsconagua.com"><i class="flag flag-ES"></i>Español</a></li> <li><a href="https://ro.accentsconagua.com"><i class="flag flag-RO"></i>Românesc</a></li> </ul> </div> <div class="h-block"><a href="/">it.accentsconagua.com</a><div class="h-block-a"></div></div> <div class="footer-text"> Informazioni interessanti e consigli utili sulla programmazione. Sviluppo di siti web, web design e sviluppo web. Tutorial di Photoshop. Creazione di giochi per computer e applicazioni mobili. Diventa un programmatore professionista da zero. </div> </div> </footer> <div class="search"> <img class="searchico" src="//accentsconagua.com/img/search.svg" alt=""> </div> <div class="modal"> <div class="modal-content"> <span class="close-button">×</span> <input class="searchmain" type="text" id="search-input" placeholder="Ricerca..."> <ul class="searchli" id="results-container"></ul> </div> </div> <link rel="stylesheet" href="css/flags.css"> <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/cookieconsent2/3.1.0/cookieconsent.min.css" /> <script src="//cdnjs.cloudflare.com/ajax/libs/cookieconsent2/3.1.0/cookieconsent.min.js"></script> <script> window.addEventListener("load", function(){ window.cookieconsent.initialise({ "palette": { "popup": { "background": "#edeff5", "text": "#838391" }, "button": { "background": "#4b81e8" } }, "theme": "classic", "position": "bottom-right" })}); </script> <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> <script src="js/scripts.min.js"></script> <script src="js/common.js"></script> <link rel="stylesheet" href="css/fontawesome-all.min.css"> <script> var modal = document.querySelector(".modal"); var trigger = document.querySelector(".search"); var closeButton = document.querySelector(".close-button"); function toggleModal() { modal.classList.toggle("show-modal"); } function windowOnClick(event) { if (event.target === modal) { toggleModal(); } } trigger.addEventListener("click", toggleModal); closeButton.addEventListener("click", toggleModal); window.addEventListener("click", windowOnClick); </script> <script src="https://unpkg.com/simple-jekyll-search@1.5.0/dest/simple-jekyll-search.min.js"></script> <script> SimpleJekyllSearch({ searchInput: document.getElementById('search-input'), resultsContainer: document.getElementById('results-container'), json: '/search.json', searchResultTemplate: '<li><a href="{url}">{title}</a></li>' }) </script> <script src="jquery.unveil2.min.js"></script> <script> $('img').unveil(); </script> </body> </html>