├── .gitignore ├── Gemfile ├── Gemfile.lock ├── Procfile ├── README.md ├── app.rb ├── helpers └── methods.rb ├── models └── classes.rb ├── public ├── css │ ├── rrssb.css │ └── style.css └── js │ ├── functions.js │ ├── rrssb.min.js │ └── script.js └── views ├── index.erb └── layout.erb /.gitignore: -------------------------------------------------------------------------------- 1 | *.cmd 2 | *.env -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gem 'sinatra' 3 | gem 'json' -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | json (1.5.5) 5 | rack (1.6.4) 6 | rack-protection (1.5.3) 7 | rack 8 | sinatra (1.4.6) 9 | rack (~> 1.4) 10 | rack-protection (~> 1.4) 11 | tilt (>= 1.3, < 3) 12 | tilt (2.0.1) 13 | 14 | PLATFORMS 15 | x86-mingw32 16 | 17 | DEPENDENCIES 18 | json 19 | sinatra 20 | 21 | BUNDLED WITH 22 | 1.10.6 23 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec ruby app.rb -p $PORT -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DataJournalismJobs.com 2 | [DataJournalismJobs.com](http://datajournalismjobs.com) is an open-source data journalism jobs board. 3 | 4 | A user who want to post a new job to the listing must submit it through a Google Form. If approved, the job will appear on the site. 5 | 6 | # Contributing 7 | Fork a branch to contribute to this project. Send a pull request to the `develop` branch. 8 | 9 | If your changes are added to the site, you'll get credit on its front page and in this README. 10 | 11 | # Jobs listing spreadsheet 12 | Click [here](https://docs.google.com/spreadsheets/d/1A_SHZ8TGIZ3AMvurohiPLSB5biySZUFOqo9dOdWPofk) for a copy of the Google Sheet doc from which DataJournalismJobs.com get its job ads. 13 | 14 | # Programming languages and libraries used 15 | - Ruby 16 | - Sinatra 17 | - json 18 | - Javascript 19 | - jQuery 20 | - Bootstrap 21 | - Ridiculously Responsive Social Sharing Buttons 22 | - CSS 23 | - Bootstrap 24 | - Ridiculously Responsive Social Sharing Buttons 25 | 26 | # Credits 27 | - Created by [Chris Persaud](http://ChrisPersaud.com). -------------------------------------------------------------------------------- /app.rb: -------------------------------------------------------------------------------- 1 | [ 2 | 'sinatra', 3 | 'open-uri', 4 | 'openssl', 5 | 'json' 6 | ].each{|g| 7 | require g 8 | } 9 | 10 | [ 11 | 'models/classes', 12 | 'helpers/methods' 13 | ].each{|rb| 14 | require_relative rb+'.rb' 15 | } 16 | 17 | get '/' do 18 | erb :index 19 | end 20 | 21 | get '/jobs' do 22 | returnArray = [] 23 | newGSheet = GSheet.new 24 | newGSheet.sheetId = ENV['JOBS_GSHEET_ID'] 25 | jobListingDataArray = newGSheet.showData 26 | 27 | jobListingDataArray.each_with_index{|jobData,idx| 28 | g = 'gsx$' 29 | t = '$t' 30 | 31 | deadlineString = jobData[g+'applicationdeadline'][t] 32 | if(deadlineString!='') 33 | deadlineDateTime = DateTime.strptime(deadlineString,'%m/%d/%Y %H:%M:%S') 34 | now = DateTime.now 35 | 36 | if(deadlineDateTime>now===false) 37 | next 38 | end 39 | end 40 | 41 | if(jobData[g+'closed'][t].to_i!=0 || jobData[g+'approved'][t].to_i!=1) 42 | next 43 | end 44 | 45 | returnHash = { 46 | :jobTitle => jobData[g+'jobtitle'][t], 47 | :moreInfoURL => addHTTP(jobData[g+'wherecanifindoutmoreaboutthisjob'][t]), 48 | :company => jobData[g+'company'][t], 49 | :jobDescription => jobData[g+'jobdescription'][t], 50 | :skills => jobData[g+'skillsrequired'][t], 51 | :jobLocation => jobData[g+'location'][t], 52 | :partTime => jobData[g+'isthisfulltimeorparttimework'][t], 53 | :companyURL => addHTTP(jobData[g+'companywebsiteurl'][t]), 54 | :apply => jobData[g+'howtoapply'][t], 55 | :pay => jobData[g+'whatsthepay'][t], 56 | :payPeriod => jobData[g+'isthatthepayrateperhourweekmonthoryear'][t], 57 | :deadline => jobData[g+'applicationdeadline'][t], 58 | :internship => jobData[g+'isthispositionaninternship'][t], 59 | :closed => jobData[g+'closed'][t], 60 | :approved => jobData[g+'approved'][t], 61 | :submitted => jobData[g+'timestamp'][t].match(/.*(?=\s)/).to_s 62 | } 63 | returnArray << returnHash 64 | } 65 | 66 | returnJSON = returnArray.reverse.to_json # Reverse array so jobs are ordered by submitted date, most recent first, 67 | return returnJSON 68 | end 69 | 70 | get '/submit' do 71 | redirect 'https://docs.google.com/forms/d/127JNB-_U7PvMSXs5R0PhiVffFNdqhvlHixNgmbsMWmY/viewform' 72 | end -------------------------------------------------------------------------------- /helpers/methods.rb: -------------------------------------------------------------------------------- 1 | def addHTTP(str) 2 | if(str.length>0) 3 | return (str.downcase[0..3]==='http') ? str : 'http://'+str 4 | else 5 | return '' 6 | end 7 | end -------------------------------------------------------------------------------- /models/classes.rb: -------------------------------------------------------------------------------- 1 | # [ 2 | # 'open-uri', 3 | # 'openssl', 4 | # 'json' 5 | # ].each{|g| 6 | # require g 7 | # } 8 | 9 | class GSheet 10 | def sheetId= sheetId 11 | @sheetId = sheetId 12 | end 13 | 14 | def showData 15 | url1 = 'https://spreadsheets.google.com/feeds/list/' 16 | url2 = '/1/public/values?alt=json' 17 | fullURL = url1+@sheetId+url2 18 | responseJSON = open( 19 | fullURL, 20 | :ssl_verify_mode=>OpenSSL::SSL::VERIFY_NONE 21 | ).read 22 | 23 | responseHash = JSON.parse(responseJSON) 24 | rowsArray = responseHash['feed']['entry'] 25 | return rowsArray 26 | end 27 | end -------------------------------------------------------------------------------- /public/css/rrssb.css: -------------------------------------------------------------------------------- 1 | .rrssb-buttons{box-sizing:border-box;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;height:36px;margin:0;padding:0;width:100%}.rrssb-buttons:after{clear:both}.rrssb-buttons:after,.rrssb-buttons:before{content:' ';display:table}.rrssb-buttons li{box-sizing:border-box;float:left;height:100%;line-height:13px;list-style:none;margin:0;padding:0 2px}.rrssb-buttons li.rrssb-email a{background-color:#0a88ff}.rrssb-buttons li.rrssb-email a:hover{background-color:#006ed6}.rrssb-buttons li.rrssb-facebook a{background-color:#306199}.rrssb-buttons li.rrssb-facebook a:hover{background-color:#244872}.rrssb-buttons li.rrssb-tumblr a{background-color:#32506d}.rrssb-buttons li.rrssb-tumblr a:hover{background-color:#22364a}.rrssb-buttons li.rrssb-linkedin a{background-color:#007bb6}.rrssb-buttons li.rrssb-linkedin a:hover{background-color:#005983}.rrssb-buttons li.rrssb-twitter a{background-color:#26c4f1}.rrssb-buttons li.rrssb-twitter a:hover{background-color:#0eaad6}.rrssb-buttons li.rrssb-googleplus a{background-color:#e93f2e}.rrssb-buttons li.rrssb-googleplus a:hover{background-color:#ce2616}.rrssb-buttons li.rrssb-youtube a{background-color:#df1c31}.rrssb-buttons li.rrssb-youtube a:hover{background-color:#b21627}.rrssb-buttons li.rrssb-reddit a{background-color:#8bbbe3}.rrssb-buttons li.rrssb-reddit a:hover{background-color:#62a3d9}.rrssb-buttons li.rrssb-pinterest a{background-color:#b81621}.rrssb-buttons li.rrssb-pinterest a:hover{background-color:#8a1119}.rrssb-buttons li.rrssb-pocket a{background-color:#ed4054}.rrssb-buttons li.rrssb-pocket a:hover{background-color:#e4162d}.rrssb-buttons li.rrssb-github a{background-color:#444}.rrssb-buttons li.rrssb-github a:hover{background-color:#2b2b2b}.rrssb-buttons li.rrssb-instagram a{background-color:#517fa4}.rrssb-buttons li.rrssb-instagram a:hover{background-color:#406582}.rrssb-buttons li.rrssb-delicious a{background-color:#0B79E5}.rrssb-buttons li.rrssb-delicious a:hover{background-color:#095fb4}.rrssb-buttons li.rrssb-vk a{background-color:#4d71a9}.rrssb-buttons li.rrssb-vk a:hover{background-color:#3d5a86}.rrssb-buttons li.rrssb-hackernews a{background-color:#f60}.rrssb-buttons li.rrssb-hackernews a:hover{background-color:#cc5200}.rrssb-buttons li a{background-color:#ccc;border-radius:2px;box-sizing:border-box;display:block;font-size:11px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-weight:700;height:100%;padding:11px 7px 12px 27px;position:relative;text-align:center;text-decoration:none;text-transform:uppercase;-webkit-transition:background-color .2s ease-in-out;transition:background-color .2s ease-in-out;width:100%}.rrssb-buttons li a .rrssb-icon{display:block;left:10px;padding-top:9px;position:absolute;top:0;width:10%}.rrssb-buttons li a .rrssb-icon svg{height:17px;width:17px}.rrssb-buttons li a .rrssb-icon svg path,.rrssb-buttons li a .rrssb-icon svg polygon{fill:#fff}.rrssb-buttons li a .rrssb-text{color:#fff}.rrssb-buttons li a:active{box-shadow:inset 1px 3px 15px 0 rgba(22,0,0,.25)}.rrssb-buttons li.small a{padding:0}.rrssb-buttons li.small a .rrssb-icon{left:auto;margin:0 auto;overflow:hidden;position:relative;top:auto;width:100%}.rrssb-buttons li.small a .rrssb-text{visibility:hidden}.rrssb-buttons.large-format,.rrssb-buttons.large-format li{height:auto}.rrssb-buttons.large-format li:first-child:nth-last-child(1) a{font-size:20px;font-size:4vw}.rrssb-buttons.large-format li:first-child:nth-last-child(2) a,.rrssb-buttons.large-format li:first-child:nth-last-child(2)~li a{font-size:16px;font-size:2vw}.rrssb-buttons.large-format li:first-child:nth-last-child(3) a,.rrssb-buttons.large-format li:first-child:nth-last-child(3)~li a{font-size:14px;font-size:1.7vw}.rrssb-buttons.large-format li:first-child:nth-last-child(4) a,.rrssb-buttons.large-format li:first-child:nth-last-child(4)~li a{font-size:13px;font-size:1.4vw}.rrssb-buttons.large-format li:first-child:nth-last-child(5) a,.rrssb-buttons.large-format li:first-child:nth-last-child(5)~li a{font-size:13px;font-size:1.2vw}.rrssb-buttons.large-format li:first-child:nth-last-child(6) a,.rrssb-buttons.large-format li:first-child:nth-last-child(6)~li a{font-size:12px;font-size:1.05vw}.rrssb-buttons.large-format li:first-child:nth-last-child(7) a,.rrssb-buttons.large-format li:first-child:nth-last-child(7)~li a{font-size:11px;font-size:.9vw}.rrssb-buttons.large-format li:first-child:nth-last-child(8) a,.rrssb-buttons.large-format li:first-child:nth-last-child(8)~li a{font-size:11px;font-size:.8vw}.rrssb-buttons.large-format li:first-child:nth-last-child(9) a,.rrssb-buttons.large-format li:first-child:nth-last-child(9)~li a{font-size:11px;font-size:.7vw}.rrssb-buttons.large-format li:first-child:nth-last-child(10) a,.rrssb-buttons.large-format li:first-child:nth-last-child(10)~li a{font-size:11px;font-size:.6vw}.rrssb-buttons.large-format li:first-child:nth-last-child(11) a,.rrssb-buttons.large-format li:first-child:nth-last-child(11)~li a{font-size:11px;font-size:.5vw}.rrssb-buttons.large-format li a{-webkit-backface-visibility:hidden;backface-visibility:hidden;border-radius:.2em;padding:8.5% 0 8.5% 12%}.rrssb-buttons.large-format li a .rrssb-icon{height:100%;left:7%;padding-top:0;width:12%}.rrssb-buttons.large-format li a .rrssb-icon svg{height:100%;position:absolute;top:0;width:100%}.rrssb-buttons.large-format li a .rrssb-text{-webkit-backface-visibility:hidden;backface-visibility:hidden}.rrssb-buttons.small-format{padding-top:5px}.rrssb-buttons.small-format li{height:80%;padding:0 1px}.rrssb-buttons.small-format li a .rrssb-icon{height:100%;padding-top:0}.rrssb-buttons.small-format li a .rrssb-icon svg{height:48%;position:relative;top:6px;width:80%}.rrssb-buttons.tiny-format{height:22px;position:relative}.rrssb-buttons.tiny-format li{padding-right:7px}.rrssb-buttons.tiny-format li a{background-color:transparent;padding:0}.rrssb-buttons.tiny-format li a .rrssb-icon svg{height:70%;width:100%}.rrssb-buttons.tiny-format li a:active,.rrssb-buttons.tiny-format li a:hover{background-color:transparent}.rrssb-buttons.tiny-format li.rrssb-email a .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-email a .rrssb-icon svg polygon{fill:#0a88ff}.rrssb-buttons.tiny-format li.rrssb-email a .rrssb-icon:hover .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-email a .rrssb-icon:hover .rrssb-icon svg polygon{fill:#0054a3}.rrssb-buttons.tiny-format li.rrssb-facebook a .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-facebook a .rrssb-icon svg polygon{fill:#306199}.rrssb-buttons.tiny-format li.rrssb-facebook a .rrssb-icon:hover .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-facebook a .rrssb-icon:hover .rrssb-icon svg polygon{fill:#18304b}.rrssb-buttons.tiny-format li.rrssb-tumblr a .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-tumblr a .rrssb-icon svg polygon{fill:#32506d}.rrssb-buttons.tiny-format li.rrssb-tumblr a .rrssb-icon:hover .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-tumblr a .rrssb-icon:hover .rrssb-icon svg polygon{fill:#121d27}.rrssb-buttons.tiny-format li.rrssb-linkedin a .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-linkedin a .rrssb-icon svg polygon{fill:#007bb6}.rrssb-buttons.tiny-format li.rrssb-linkedin a .rrssb-icon:hover .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-linkedin a .rrssb-icon:hover .rrssb-icon svg polygon{fill:#003650}.rrssb-buttons.tiny-format li.rrssb-twitter a .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-twitter a .rrssb-icon svg polygon{fill:#26c4f1}.rrssb-buttons.tiny-format li.rrssb-twitter a .rrssb-icon:hover .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-twitter a .rrssb-icon:hover .rrssb-icon svg polygon{fill:#0b84a6}.rrssb-buttons.tiny-format li.rrssb-googleplus a .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-googleplus a .rrssb-icon svg polygon{fill:#e93f2e}.rrssb-buttons.tiny-format li.rrssb-googleplus a .rrssb-icon:hover .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-googleplus a .rrssb-icon:hover .rrssb-icon svg polygon{fill:#a01e11}.rrssb-buttons.tiny-format li.rrssb-youtube a .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-youtube a .rrssb-icon svg polygon{fill:#df1c31}.rrssb-buttons.tiny-format li.rrssb-youtube a .rrssb-icon:hover .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-youtube a .rrssb-icon:hover .rrssb-icon svg polygon{fill:#84111d}.rrssb-buttons.tiny-format li.rrssb-reddit a .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-reddit a .rrssb-icon svg polygon{fill:#8bbbe3}.rrssb-buttons.tiny-format li.rrssb-reddit a .rrssb-icon:hover .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-reddit a .rrssb-icon:hover .rrssb-icon svg polygon{fill:#398bcf}.rrssb-buttons.tiny-format li.rrssb-pinterest a .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-pinterest a .rrssb-icon svg polygon{fill:#b81621}.rrssb-buttons.tiny-format li.rrssb-pinterest a .rrssb-icon:hover .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-pinterest a .rrssb-icon:hover .rrssb-icon svg polygon{fill:#5d0b11}.rrssb-buttons.tiny-format li.rrssb-pocket a .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-pocket a .rrssb-icon svg polygon{fill:#ed4054}.rrssb-buttons.tiny-format li.rrssb-pocket a .rrssb-icon:hover .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-pocket a .rrssb-icon:hover .rrssb-icon svg polygon{fill:#b61124}.rrssb-buttons.tiny-format li.rrssb-github a .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-github a .rrssb-icon svg polygon{fill:#444}.rrssb-buttons.tiny-format li.rrssb-github a .rrssb-icon:hover .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-github a .rrssb-icon:hover .rrssb-icon svg polygon{fill:#111}.rrssb-buttons.tiny-format li.rrssb-instagram a .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-instagram a .rrssb-icon svg polygon{fill:#517fa4}.rrssb-buttons.tiny-format li.rrssb-instagram a .rrssb-icon:hover .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-instagram a .rrssb-icon:hover .rrssb-icon svg polygon{fill:#2f4a60}.rrssb-buttons.tiny-format li.rrssb-delicious a .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-delicious a .rrssb-icon svg polygon{fill:#0B79E5}.rrssb-buttons.tiny-format li.rrssb-delicious a .rrssb-icon:hover .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-delicious a .rrssb-icon:hover .rrssb-icon svg polygon{fill:#064684}.rrssb-buttons.tiny-format li.rrssb-vk a .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-vk a .rrssb-icon svg polygon{fill:#4d71a9}.rrssb-buttons.tiny-format li.rrssb-vk a .rrssb-icon:hover .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-vk a .rrssb-icon:hover .rrssb-icon svg polygon{fill:#2d4263}.rrssb-buttons.tiny-format li.rrssb-hackernews a .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-hackernews a .rrssb-icon svg polygon{fill:#f60}.rrssb-buttons.tiny-format li.rrssb-hackernews a .rrssb-icon:hover .rrssb-icon svg path,.rrssb-buttons.tiny-format li.rrssb-hackernews a .rrssb-icon:hover .rrssb-icon svg polygon{fill:#993d00} -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | position: relative; 3 | min-height: 100%; 4 | } 5 | 6 | body { 7 | margin: 0 0 100px; 8 | } 9 | 10 | h1, 11 | footer { 12 | text-align: center; 13 | } 14 | 15 | footer { 16 | position: fixed; 17 | left: 0; 18 | bottom: 0; 19 | width: 100%; 20 | background: #fff; 21 | border-top: 1px solid #333; 22 | } 23 | 24 | #credits { 25 | font-size: 14px; 26 | font-weight: bold; 27 | } 28 | 29 | @media screen and (min-width: 860px){ 30 | .rrssb-buttons li a { 31 | height: unset; /* Without this, social buttons' heights will be equal to viewport's height */ 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /public/js/functions.js: -------------------------------------------------------------------------------- 1 | function validateEmail(email) { 2 | var re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i; 3 | return re.test(email); 4 | } 5 | 6 | // function moreLessText(i, jobDescriptionOpenerClicked, jobDescriptionLite, jobDescription){ 7 | // return function(){ 8 | // if(jobDescriptionOpenerClicked===false){ 9 | // this.parentNode.innerHTML = jobDescription+' Less'; 10 | // // jobDescriptionOpenerClicked = true; 11 | // // this.onclick = function(){moreLessText(jobDescriptionOpenerClicked, jobDescriptionLite, jobDescription)}; 12 | // } else { 13 | // this.parentNode.innerHTML = jobDescriptionLite+' More'; 14 | // jobDescriptionOpenerClicked = false; 15 | // } 16 | 17 | // } 18 | // } 19 | 20 | -------------------------------------------------------------------------------- /public/js/rrssb.min.js: -------------------------------------------------------------------------------- 1 | +function(t,e,r){"use strict";var i={calc:!1};e.fn.rrssb=function(t){var i=e.extend({description:r,emailAddress:r,emailBody:r,emailSubject:r,image:r,title:r,url:r},t);for(var s in i)i.hasOwnProperty(s)&&i[s]!==r&&(i[s]=n(i[s]));i.url!==r&&(e(this).find(".rrssb-facebook a").attr("href","https://www.facebook.com/sharer/sharer.php?u="+i.url),e(this).find(".rrssb-tumblr a").attr("href","http://tumblr.com/share/link?url="+i.url+(i.title!==r?"&name="+i.title:"")+(i.description!==r?"&description="+i.description:"")),e(this).find(".rrssb-linkedin a").attr("href","http://www.linkedin.com/shareArticle?mini=true&url="+i.url+(i.title!==r?"&title="+i.title:"")+(i.description!==r?"&summary="+i.description:"")),e(this).find(".rrssb-twitter a").attr("href","https://twitter.com/intent/tweet?text="+(i.description!==r?i.description:"")+"%20"+i.url),e(this).find(".rrssb-hackernews a").attr("href","https://news.ycombinator.com/submitlink?u="+i.url+(i.title!==r?"&text="+i.title:"")),e(this).find(".rrssb-reddit a").attr("href","http://www.reddit.com/submit?url="+i.url+(i.description!==r?"&text="+i.description:"")+(i.title!==r?"&title="+i.title:"")),e(this).find(".rrssb-googleplus a").attr("href","https://plus.google.com/share?url="+(i.description!==r?i.description:"")+"%20"+i.url),e(this).find(".rrssb-pinterest a").attr("href","http://pinterest.com/pin/create/button/?url="+i.url+(i.image!==r?"&media="+i.image:"")+(i.description!==r?"&description="+i.description:"")),e(this).find(".rrssb-pocket a").attr("href","https://getpocket.com/save?url="+i.url),e(this).find(".rrssb-github a").attr("href",i.url)),i.emailAddress!==r&&e(this).find(".rrssb-email a").attr("href","mailto:"+i.emailAddress+"?"+(i.emailSubject!==r?"subject="+i.emailSubject:"")+(i.emailBody!==r?"&body="+i.emailBody:""))};var s=function(){var t=e("
"),r=["calc","-webkit-calc","-moz-calc"];e("body").append(t);for(var s=0;s170&&e("li.small",r).length<1?r.addClass("large-format"):r.removeClass("large-format"),200>i?r.removeClass("small-format").addClass("tiny-format"):r.removeClass("tiny-format")})},o=function(){e(".rrssb-buttons").each(function(t){var r=e(this),i=e("li",r),s=i.filter(".small"),n=0,a=0,l=s.first(),o=parseFloat(l.attr("data-size"))+55,c=s.length;if(c===i.length){var d=42*c,m=r.width();m>d+o&&(r.removeClass("small-format"),s.first().removeClass("small"),h())}else{i.not(".small").each(function(t){var r=e(this),i=parseFloat(r.attr("data-size"))+55,s=parseFloat(r.width());n+=s,a+=i});var u=n-a;u>o&&(l.removeClass("small"),h())}})},c=function(t){e(".rrssb-buttons").each(function(t){var r=e(this),i=e("li",r);e(i.get().reverse()).each(function(t,r){var s=e(this);if(s.hasClass("small")===!1){var n=parseFloat(s.attr("data-size"))+55,a=parseFloat(s.width());if(n>a){var l=i.not(".small").last();e(l).addClass("small"),h()}}--r||o()})}),t===!0&&m(h)},h=function(){e(".rrssb-buttons").each(function(t){var r,s,n,l,o,c=e(this),h=e("li",c),d=h.filter(".small"),m=d.length;m>0&&m!==h.length?(c.removeClass("small-format"),d.css("width","42px"),n=42*m,r=h.not(".small").length,s=100/r,o=n/r,i.calc===!1?(l=(c.innerWidth()-1)/r-o,l=Math.floor(1e3*l)/1e3,l+="px"):l=i.calc+"("+s+"% - "+o+"px)",h.not(".small").css("width",l)):m===h.length?(c.addClass("small-format"),a()):(c.removeClass("small-format"),a())}),l()},d=function(){e(".rrssb-buttons").each(function(t){e(this).addClass("rrssb-"+(t+1))}),s(),a(),e(".rrssb-buttons li .rrssb-text").each(function(t){var r=e(this),i=r.width();r.closest("li").attr("data-size",i)}),c(!0)},m=function(t){e(".rrssb-buttons li.small").removeClass("small"),c(),t()},u=function(e,i,s,n){var a=t.screenLeft!==r?t.screenLeft:screen.left,l=t.screenTop!==r?t.screenTop:screen.top,o=t.innerWidth?t.innerWidth:document.documentElement.clientWidth?document.documentElement.clientWidth:screen.width,c=t.innerHeight?t.innerHeight:document.documentElement.clientHeight?document.documentElement.clientHeight:screen.height,h=o/2-s/2+a,d=c/3-n/3+l,m=t.open(e,i,"scrollbars=yes, width="+s+", height="+n+", top="+d+", left="+h);t.focus&&m.focus()},f=function(){var t={};return function(e,r,i){i||(i="Don't call this twice without a uniqueId"),t[i]&&clearTimeout(t[i]),t[i]=setTimeout(e,r)}}();e(document).ready(function(){e(document).on("click",".rrssb-buttons a.popup",{},function(t){var r=e(this);u(r.attr("href"),r.find(".rrssb-text").html(),580,470),t.preventDefault()}),e(t).resize(function(){m(h),f(function(){m(h)},200,"finished resizing")}),d()}),t.rrssbInit=d}(window,jQuery); -------------------------------------------------------------------------------- /public/js/script.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var $listings = $('#listings'); 3 | var headersArray = [ 4 | 'Job', 5 | 'Company', 6 | 'Location', 7 | 'Salary', 8 | 'Skills', 9 | 'Details', 10 | 'Apply', 11 | 'Date submitted' 12 | ]; 13 | 14 | $listings.append(''); 15 | var $headerRow = $('#header-row'); 16 | 17 | for (var i=0; i'+header+''); 20 | } 21 | 22 | $.get( 23 | '/jobs', 24 | function(data){ 25 | var jobsDataArray = JSON.parse(data); 26 | for (var i=0; i', 36 | '' 37 | ]; 38 | } 39 | 40 | if(job.internship==='Yes, paid'){ 41 | jobTitle+=' (Paid internship)'; 42 | } else if(job.internship==='Yes, unpaid'){ 43 | jobTitle+=' (Unpaid internship)'; 44 | } 45 | 46 | if(job.partTime==='Part time'){ 47 | jobTitle+=' (Part time)'; 48 | } 49 | 50 | var salary = job.pay==='' ? '' : numeral(job.pay).format('$0,0')+'/'+job.payPeriod 51 | var applyString = job.apply; 52 | var applyHref = validateEmail(applyString)===true ? '"mailto:'+applyString+'"' : '"'+applyString+'" target="_blank"'; 53 | applyString = validateEmail(applyString)===true ? applyString : "Apply here"; 54 | var jobDescription = job.jobDescription; 55 | var jobDescriptionLite = jobDescription.match(/.*?\./); 56 | var singleJobDataArray = [ 57 | moreInfoURLHrefArray[0]+job.jobTitle+moreInfoURLHrefArray[1], 58 | ''+job.company+'', 59 | job.jobLocation, 60 | salary, 61 | job.skills.replace(/\,/g, ', '), 62 | ''+ 63 | // jobDescriptionLite+' More'+ 64 | jobDescription+ 65 | '', 66 | ''+applyString+'', 67 | job.submitted 68 | ]; 69 | var singleJobTDArray = singleJobDataArray.map(function(tdContent){ 70 | return ''+tdContent+''; 71 | }); 72 | 73 | $listings.append(''+singleJobTDArray+''); 74 | 75 | var jobDescriptionOpenerClicked = false; 76 | // $('#'+i).click(moreLessText(i, jobDescriptionOpenerClicked, jobDescriptionLite, jobDescription)); 77 | } 78 | // $('.'+jobDescriptionOpener).each( 79 | // function(i,obj){ 80 | // var 81 | // } 82 | // ); 83 | } 84 | ); 85 | }) -------------------------------------------------------------------------------- /views/index.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 |
6 | -------------------------------------------------------------------------------- /views/layout.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | Data Journalism Jobs 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 30 | 31 |

32 | Data Journalism Jobs 33 | Submit new job 34 |

35 | <%= yield %> 36 | 114 | 115 | --------------------------------------------------------------------------------