├── .gitignore ├── README.md ├── bsconfig.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── s3_website.yml ├── src ├── Member.re ├── MembersData.re ├── MembersList.re ├── app.css ├── app.re ├── event.re ├── footer.re ├── index.css ├── index.re ├── meetup.re ├── noUpcomingEvents.re └── registerServiceWorker.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # bucklescript 13 | /lib 14 | /types 15 | .merlin 16 | 17 | # misc 18 | .DS_Store 19 | .vscode 20 | .env.local 21 | .env.development.local 22 | .env.test.local 23 | .env.production.local 24 | 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Getting started 2 | 3 | 1. Clone the repo 4 | 2. `yarn install` 5 | 3. `yarn start` 6 | 4. Open browser and navigate to: http://localhost:3000/ 7 | 8 | ### Intro 9 | 10 | The project uses create-react-app with [reason-scripts](https://github.com/reasonml-community/reason-scripts) 11 | 12 | ### Ideas for future TODOs 13 | 14 | * Decode Json using https://github.com/BuckleTypes/bs-json 15 | * Boilerplate for all ReasonML meetups (other countries) 16 | * Profile pages for speakers/participants (way to share Code or Talks) - people can PR their profiles into the project 17 | * Fetch YouTube and Meetup data into the page 18 | * Links to all world-wide ReasonML meetups 19 | -------------------------------------------------------------------------------- /bsconfig.json: -------------------------------------------------------------------------------- 1 | /* This is the BuckleScript configuration file. Note that this is a comment; 2 | BuckleScript comes with a JSON parser that supports comments and trailing 3 | comma. If this screws with your editor highlighting, please tell us by filing 4 | an issue! */ 5 | { 6 | "name": "reasonvienna-website", 7 | "reason" : { "react-jsx" : 2}, 8 | "refmt": 3, 9 | "bs-dependencies": [ 10 | "reason-react", 11 | "bs-fetch", 12 | "bs-json", 13 | "bs-moment" 14 | ], 15 | "sources": [ 16 | { 17 | "dir": "src" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "bs-fetch": "github:reasonml-community/bs-fetch", 7 | "bs-json": "^0.2.3", 8 | "bs-moment": "^0.1.4", 9 | "moment": "^2.18.1", 10 | "react": "^15.6.1", 11 | "react-dom": "^15.6.1", 12 | "reason-scripts": "0.6.9" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test --env=jsdom", 18 | "eject": "react-scripts eject", 19 | "prepare": "npm link bs-platform" 20 | }, 21 | "devDependencies": { 22 | "bs-jest": "^0.1.0", 23 | "reason-react": "^0.2.4" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReasonVienna/website/0a216ca101bb005188db03a32f172f550b3eabc7/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | Reason Vienna 24 | 25 | 26 | 27 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "ReasonVienna", 3 | "name": "Reason Vienna Starter Website", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /s3_website.yml: -------------------------------------------------------------------------------- 1 | s3_id: <%= ENV['S3_ID'] %> 2 | s3_secret: <%= ENV['S3_SECRET'] %> 3 | s3_bucket: reasonml.re 4 | 5 | # Below are examples of all the available configurations. 6 | # See README for more detailed info on each of them. 7 | 8 | site: public 9 | 10 | index_document: index.html 11 | error_document: error.html 12 | 13 | # max_age: 14 | # "assets/*": 6000 15 | # "*": 300 16 | 17 | # gzip: 18 | # - .html 19 | # - .css 20 | # - .md 21 | # gzip_zopfli: true 22 | 23 | # See http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region for valid endpoints 24 | s3_endpoint: eu-west-1 25 | 26 | # ignore_on_server: that_folder_of_stuff_i_dont_keep_locally 27 | 28 | # exclude_from_upload: 29 | # - those_folders_of_stuff 30 | # - i_wouldnt_want_to_upload 31 | 32 | # s3_reduced_redundancy: true 33 | 34 | # cloudfront_distribution_id: your-dist-id 35 | 36 | # cloudfront_distribution_config: 37 | # default_cache_behavior: 38 | # min_TTL: <%= 60 * 60 * 24 %> 39 | # aliases: 40 | # quantity: 1 41 | # items: 42 | # CNAME: your.website.com 43 | 44 | # cloudfront_invalidate_root: true 45 | 46 | # cloudfront_wildcard_invalidation: true 47 | 48 | # concurrency_level: 5 49 | 50 | # redirects: 51 | # index.php: / 52 | # about.php: about.html 53 | # music-files/promo.mp4: http://www.youtube.com/watch?v=dQw4w9WgXcQ 54 | 55 | # routing_rules: 56 | # - condition: 57 | # key_prefix_equals: blog/some_path 58 | # redirect: 59 | # host_name: blog.example.com 60 | # replace_key_prefix_with: some_new_path/ 61 | # http_redirect_code: 301 62 | -------------------------------------------------------------------------------- /src/Member.re: -------------------------------------------------------------------------------- 1 | type member = { 2 | id: int, 3 | login: string, 4 | url: string, 5 | avatar_url: string 6 | }; 7 | 8 | let component = ReasonReact.statelessComponent("Member"); 9 | 10 | type props = {member}; 11 | 12 | let skin = 13 | ReactDOMRe.Style.make(~color="#f6f4f4", ~margin="0 auto", ~height="50px", ~width="50px", ()); 14 | 15 | let linkSkin = 16 | ReactDOMRe.Style.make( 17 | ~color="gray", 18 | ~fontFamily="Helvetica Neue, Open Sans, sans-serif", 19 | ~fontWeight="bold", 20 | ~lineHeight="1.6", 21 | ~fontSize="16px", 22 | ~textDecoration="none", 23 | ~margin="2px", 24 | ~textAlign="center", 25 | () 26 | ); 27 | 28 | let make = (~member, _children) => { 29 | ...component, 30 | render: (_self) => 31 |
32 |
33 | 34 | member.login 35 | 36 |
37 |
38 | }; 39 | -------------------------------------------------------------------------------- /src/MembersData.re: -------------------------------------------------------------------------------- 1 | let clientId = "9547d8b8def885c8e5e3"; 2 | 3 | let secret = "529880bee32f6d92515053f31eab2dc77fe68930"; 4 | 5 | let param = "?client_id=" ++ (clientId ++ ("&client_secret=" ++ secret)); 6 | 7 | let apiUrl = "https://api.github.com/orgs/ReasonVienna/members" ++ param; 8 | 9 | type member = Member.member; 10 | 11 | type members = array(member); 12 | 13 | let parseMember = (json) : member => 14 | Json.Decode.{ 15 | id: json |> field("id", int), 16 | login: json |> field("login", string), 17 | url: json |> field("url", string), 18 | avatar_url: json |> field("avatar_url", string) 19 | }; 20 | 21 | let parseMembers = (json) : array(member) => Json.Decode.(json |> array(parseMember)); 22 | 23 | let fetchMembers = (callback) => 24 | Js.Promise.( 25 | Bs_fetch.fetch(apiUrl) 26 | |> then_(Bs_fetch.Response.text) 27 | |> then_( 28 | (text) => 29 | Js.Json.parseExn(text) 30 | |> parseMembers 31 | |> ( 32 | (members) => { 33 | callback(members); 34 | resolve(None) 35 | } 36 | ) 37 | ) 38 | ); 39 | -------------------------------------------------------------------------------- /src/MembersList.re: -------------------------------------------------------------------------------- 1 | type action = 2 | | Loaded(array(Member.member)) 3 | | Loading; 4 | 5 | type state = { 6 | members: array(Member.member), 7 | loading: bool 8 | }; 9 | 10 | let component = ReasonReact.reducerComponent("MembersList"); 11 | 12 | let se = ReasonReact.stringToElement; 13 | 14 | let loadData = ({ReasonReact.state, reduce}) => { 15 | MembersData.fetchMembers(reduce((payload) => Loaded(payload))) |> ignore; 16 | reduce(() => Loading, ()) 17 | }; 18 | 19 | let make = (_children) => { 20 | ...component, 21 | initialState: fun () => ({loading: false, members: [||]}: state), 22 | didMount: (self) => { 23 | loadData(self); 24 | ReasonReact.NoUpdate 25 | }, 26 | reducer: (action, state) => 27 | switch action { 28 | | Loading => ReasonReact.Update({...state, loading: true}) 29 | | Loaded(data) => 30 | let updatedMembers = Array.concat([state.members, data]); 31 | ReasonReact.Update({members: updatedMembers, loading: false}) 32 | }, 33 | render: (self) => 34 |
35 |
36 |

(se("Members"))

37 | ( 38 | Array.mapi( 39 | (index, member) => , 40 | self.state.members 41 | ) 42 | |> ReasonReact.arrayToElement 43 | ) 44 |
45 | }; 46 | -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Open Sans, sans-serif; 3 | background-color: #f6f4f4; 4 | } 5 | 6 | code { 7 | font-family: Courier New; 8 | color: #8f8f8f; 9 | } 10 | 11 | ul { 12 | margin: 0; 13 | padding: 0; 14 | } 15 | 16 | .App { 17 | margin: 50px; 18 | } 19 | 20 | .App-logo { 21 | animation: App-logo-spin infinite 20s linear; 22 | height: 200px; 23 | } 24 | 25 | .App-header { 26 | display: flex; 27 | align-items: center; 28 | margin: 50px 0; 29 | color: #333333; 30 | } 31 | 32 | .App-intro { 33 | font-size: large; 34 | color: #444; 35 | } 36 | 37 | .block { 38 | display: inline-block; 39 | fontSize: '1rem'; 40 | fontWeight: '300'; 41 | width: '200px'; 42 | textAlign: 'center'; 43 | margin: 5px; 44 | border: 2px gray; 45 | } 46 | 47 | @keyframes App-logo-spin { 48 | from { 49 | transform: rotate(0deg); 50 | } 51 | to { 52 | transform: rotate(360deg); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/app.re: -------------------------------------------------------------------------------- 1 | type actions = 2 | | UpdateEvents(array(Event.event)); 3 | 4 | type state = { 5 | description: string, 6 | events: array(Event.event), 7 | meetups: array(Meetup.reasonMeetup) 8 | }; 9 | 10 | let knownMeetups: array(Meetup.reasonMeetup) = [| 11 | { 12 | city: "Chicago", 13 | name: "Chicago ReasonML", 14 | shortName: "CHG", 15 | page: "https://www.meetup.com/Chicago-ReasonML/", 16 | logo: "https://secure.meetupstatic.com/photos/event/3/4/c/b/global_459553515.jpeg" 17 | }, 18 | { 19 | city: "New York City", 20 | name: "Reason NYC", 21 | shortName: "NYC", 22 | page: "https://www.meetup.com/ReasonML-NYC/", 23 | logo: "https://secure.meetupstatic.com/photos/event/9/1/2/f/global_461257167.jpeg" 24 | }, 25 | { 26 | city: "Sydney", 27 | name: "Reason Sydney", 28 | shortName: "SDY", 29 | page: "https://www.meetup.com/reason-sydney/", 30 | logo: "https://secure.meetupstatic.com/photos/event/c/e/4/c/global_460672812.jpeg" 31 | }, 32 | { 33 | city: "Paris", 34 | name: "ReasonML Paris", 35 | shortName: "PAR", 36 | page: "https://www.meetup.com/ReasonML-Paris/", 37 | logo: "https://secure.meetupstatic.com/photos/event/6/4/b/1/global_457585777.jpeg" 38 | }, 39 | { 40 | city: "Oslo", 41 | name: "Reason Oslo Meetup", 42 | shortName: "OSL", 43 | page: "https://reasonoslo.xyz/", 44 | logo: "https://reasonoslo.xyz/static/logo.png" 45 | }, 46 | { 47 | city: "Montreal", 48 | name: "ReasonMTL Montreal", 49 | shortName: "MTL", 50 | page: "https://www.meetup.com/ReasonMTL/", 51 | logo: "https://secure.meetupstatic.com/photos/event/2/9/3/6/global_461710550.jpeg" 52 | }, 53 | { 54 | city: "San Francisco", 55 | name: "Silicon Valley OCaml/Reason", 56 | shortName: "SF", 57 | page: "https://www.meetup.com/sv-ocaml/", 58 | logo: "https://secure.meetupstatic.com/photos/event/2/8/c/a/global_332890442.jpeg" 59 | }, 60 | { 61 | city: "London", 62 | name: "ReasonLDN", 63 | shortName: "LDN", 64 | page: "https://www.meetup.com/ReasonLDN/", 65 | logo: "https://secure.meetupstatic.com/photos/event/5/1/7/8/global_461900856.jpeg" 66 | }, 67 | { 68 | city: "Singapore", 69 | name: "Singapore OCaml/Reason", 70 | shortName: "SIP", 71 | page: "https://www.meetup.com/SG-OCaml/", 72 | logo: "https://secure.meetupstatic.com/photos/event/2/8/c/a/global_332890442.jpeg" 73 | } 74 | |]; 75 | 76 | let upcomingEventsOrWelcomeMessage = (scheduledEvents) => 77 | switch (Array.length(scheduledEvents)) { 78 | | 0 => 79 | | _ => 80 | ReasonReact.arrayToElement( 81 | scheduledEvents 82 | |> Array.mapi((index, event: Event.event) => ) 83 | ) 84 | }; 85 | 86 | let component = ReasonReact.reducerComponent("App"); 87 | 88 | let make = (_children) => { 89 | ...component, 90 | initialState: () => {description: "loading...", events: [||], meetups: knownMeetups}, 91 | didMount: (self) => { 92 | let changeState = (events) => self.reduce((_) => UpdateEvents(events), ()); 93 | let _ = 94 | Js.Promise.( 95 | Bs_fetch.fetch( 96 | "https://crossorigin.me/https://api.meetup.com/Reason-Vienna/events?photo-host=secure&page=20&sig_id=12607916&sig=197d614dc57e10c6ee4c20dbfe9a191caf88a740" 97 | ) 98 | |> then_(Bs_fetch.Response.json) 99 | |> then_((result) => Event.parse(result) |> changeState |> resolve) 100 | ); 101 | ReasonReact.NoUpdate 102 | }, 103 | reducer: (action, state) => 104 | switch action { 105 | | UpdateEvents(events) => ReasonReact.Update({...state, events}) 106 | }, 107 | render: ({state}) => 108 |
109 |
117 |
118 |
120 | 121 | 122 | 125 | 126 | 127 | 128 | 129 | 133 | 137 | 138 | 139 | 140 |
141 |

142 | (ReasonReact.stringToElement("Reason Vienna")) 143 |

144 |

145 | (ReasonReact.stringToElement(state.description)) 146 |

147 |
148 |
    (upcomingEventsOrWelcomeMessage(state.events))
149 |
150 | 151 |
152 |
153 | }; 154 | -------------------------------------------------------------------------------- /src/event.re: -------------------------------------------------------------------------------- 1 | type event = { 2 | id: string, 3 | title: string, 4 | description: string, 5 | time: float 6 | }; 7 | 8 | let component = ReasonReact.statelessComponent("Event"); 9 | 10 | type props = {event}; 11 | 12 | module Decode = { 13 | let event = (j) => 14 | Json.Decode.{ 15 | id: j |> field("id", string), 16 | title: j |> field("name", string), 17 | description: j |> field("description", string), 18 | time: j |> field("time", float) 19 | }; 20 | let root = (json) => Json.Decode.(array(event, json)); 21 | }; 22 | 23 | let parse = (json: Js.Json.t) => Decode.root(json); 24 | 25 | let make = (~event, _children) => { 26 | ...component, 27 | render: (_self) => { 28 | let meetupTime = event.time |> Js.Date.fromFloat |> Js.Date.toISOString |> MomentRe.moment; 29 |
30 |

(ReasonReact.stringToElement("When? "))

31 | 48 |

(ReasonReact.stringToElement(event.title))

49 |
50 |
51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /src/footer.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("Footer"); 2 | 3 | type props = {meetups: array(Meetup.reasonMeetup)}; 4 | 5 | let footerSkin = ReactDOMRe.Style.make(~padding="10px", ~borderTop="10px", ~width="100%", ()); 6 | 7 | let make = (~meetups, _children) => { 8 | ...component, 9 | render: (_self) => { 10 | let ms = 11 | meetups 12 | |> Array.mapi((index, meetup) => ); 13 |
14 |
15 |

(ReasonReact.stringToElement("Meetups around the World"))

16 |
(ReasonReact.arrayToElement(ms))
17 |
18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in 9 | * IE on Windows Phone and in iOS. 10 | */ 11 | 12 | html { 13 | line-height: 1.15; /* 1 */ 14 | -ms-text-size-adjust: 100%; /* 2 */ 15 | -webkit-text-size-adjust: 100%; /* 2 */ 16 | } 17 | 18 | /* Sections 19 | ========================================================================== */ 20 | 21 | /** 22 | * Remove the margin in all browsers (opinionated). 23 | */ 24 | 25 | body { 26 | margin: 0; 27 | } 28 | 29 | /** 30 | * Add the correct display in IE 9-. 31 | */ 32 | 33 | article, 34 | aside, 35 | footer, 36 | header, 37 | nav, 38 | section { 39 | display: block; 40 | } 41 | 42 | /** 43 | * Correct the font size and margin on `h1` elements within `section` and 44 | * `article` contexts in Chrome, Firefox, and Safari. 45 | */ 46 | 47 | h1 { 48 | font-size: 2em; 49 | margin: 0.67em 0; 50 | } 51 | 52 | /* Grouping content 53 | ========================================================================== */ 54 | 55 | /** 56 | * Add the correct display in IE 9-. 57 | * 1. Add the correct display in IE. 58 | */ 59 | 60 | figcaption, 61 | figure, 62 | main { 63 | /* 1 */ 64 | display: block; 65 | } 66 | 67 | /** 68 | * Add the correct margin in IE 8. 69 | */ 70 | 71 | figure { 72 | margin: 1em 40px; 73 | } 74 | 75 | /** 76 | * 1. Add the correct box sizing in Firefox. 77 | * 2. Show the overflow in Edge and IE. 78 | */ 79 | 80 | hr { 81 | box-sizing: content-box; /* 1 */ 82 | height: 0; /* 1 */ 83 | overflow: visible; /* 2 */ 84 | } 85 | 86 | /** 87 | * 1. Correct the inheritance and scaling of font size in all browsers. 88 | * 2. Correct the odd `em` font sizing in all browsers. 89 | */ 90 | 91 | pre { 92 | font-family: monospace, monospace; /* 1 */ 93 | font-size: 1em; /* 2 */ 94 | } 95 | 96 | /* Text-level semantics 97 | ========================================================================== */ 98 | 99 | /** 100 | * 1. Remove the gray background on active links in IE 10. 101 | * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. 102 | */ 103 | 104 | a { 105 | background-color: transparent; /* 1 */ 106 | -webkit-text-decoration-skip: objects; /* 2 */ 107 | } 108 | 109 | /** 110 | * 1. Remove the bottom border in Chrome 57- and Firefox 39-. 111 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 112 | */ 113 | 114 | abbr[title] { 115 | border-bottom: none; /* 1 */ 116 | text-decoration: underline; /* 2 */ 117 | text-decoration: underline dotted; /* 2 */ 118 | } 119 | 120 | /** 121 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6. 122 | */ 123 | 124 | b, 125 | strong { 126 | font-weight: inherit; 127 | } 128 | 129 | /** 130 | * Add the correct font weight in Chrome, Edge, and Safari. 131 | */ 132 | 133 | b, 134 | strong { 135 | font-weight: bolder; 136 | } 137 | 138 | /** 139 | * 1. Correct the inheritance and scaling of font size in all browsers. 140 | * 2. Correct the odd `em` font sizing in all browsers. 141 | */ 142 | 143 | code, 144 | kbd, 145 | samp { 146 | font-family: monospace, monospace; /* 1 */ 147 | font-size: 1em; /* 2 */ 148 | } 149 | 150 | /** 151 | * Add the correct font style in Android 4.3-. 152 | */ 153 | 154 | dfn { 155 | font-style: italic; 156 | } 157 | 158 | /** 159 | * Add the correct background and color in IE 9-. 160 | */ 161 | 162 | mark { 163 | background-color: #ff0; 164 | color: #000; 165 | } 166 | 167 | /** 168 | * Add the correct font size in all browsers. 169 | */ 170 | 171 | small { 172 | font-size: 80%; 173 | } 174 | 175 | /** 176 | * Prevent `sub` and `sup` elements from affecting the line height in 177 | * all browsers. 178 | */ 179 | 180 | sub, 181 | sup { 182 | font-size: 75%; 183 | line-height: 0; 184 | position: relative; 185 | vertical-align: baseline; 186 | } 187 | 188 | sub { 189 | bottom: -0.25em; 190 | } 191 | 192 | sup { 193 | top: -0.5em; 194 | } 195 | 196 | /* Embedded content 197 | ========================================================================== */ 198 | 199 | /** 200 | * Add the correct display in IE 9-. 201 | */ 202 | 203 | audio, 204 | video { 205 | display: inline-block; 206 | } 207 | 208 | /** 209 | * Add the correct display in iOS 4-7. 210 | */ 211 | 212 | audio:not([controls]) { 213 | display: none; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Remove the border on images inside links in IE 10-. 219 | */ 220 | 221 | img { 222 | border-style: none; 223 | } 224 | 225 | /** 226 | * Hide the overflow in IE. 227 | */ 228 | 229 | svg:not(:root) { 230 | overflow: hidden; 231 | } 232 | 233 | /* Forms 234 | ========================================================================== */ 235 | 236 | /** 237 | * 1. Change the font styles in all browsers (opinionated). 238 | * 2. Remove the margin in Firefox and Safari. 239 | */ 240 | 241 | button, 242 | input, 243 | optgroup, 244 | select, 245 | textarea { 246 | font-family: sans-serif; /* 1 */ 247 | font-size: 100%; /* 1 */ 248 | line-height: 1.15; /* 1 */ 249 | margin: 0; /* 2 */ 250 | } 251 | 252 | /** 253 | * Show the overflow in IE. 254 | * 1. Show the overflow in Edge. 255 | */ 256 | 257 | button, 258 | input { 259 | /* 1 */ 260 | overflow: visible; 261 | } 262 | 263 | /** 264 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 265 | * 1. Remove the inheritance of text transform in Firefox. 266 | */ 267 | 268 | button, 269 | select { 270 | /* 1 */ 271 | text-transform: none; 272 | } 273 | 274 | /** 275 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` 276 | * controls in Android 4. 277 | * 2. Correct the inability to style clickable types in iOS and Safari. 278 | */ 279 | 280 | button, 281 | html [type='button'], 282 | /* 1 */ [type='reset'], 283 | [type='submit'] { 284 | -webkit-appearance: button; /* 2 */ 285 | } 286 | 287 | /** 288 | * Remove the inner border and padding in Firefox. 289 | */ 290 | 291 | button::-moz-focus-inner, 292 | [type='button']::-moz-focus-inner, 293 | [type='reset']::-moz-focus-inner, 294 | [type='submit']::-moz-focus-inner { 295 | border-style: none; 296 | padding: 0; 297 | } 298 | 299 | /** 300 | * Restore the focus styles unset by the previous rule. 301 | */ 302 | 303 | button:-moz-focusring, 304 | [type='button']:-moz-focusring, 305 | [type='reset']:-moz-focusring, 306 | [type='submit']:-moz-focusring { 307 | outline: 1px dotted ButtonText; 308 | } 309 | 310 | /** 311 | * Correct the padding in Firefox. 312 | */ 313 | 314 | fieldset { 315 | padding: 0.35em 0.75em 0.625em; 316 | } 317 | 318 | /** 319 | * 1. Correct the text wrapping in Edge and IE. 320 | * 2. Correct the color inheritance from `fieldset` elements in IE. 321 | * 3. Remove the padding so developers are not caught out when they zero out 322 | * `fieldset` elements in all browsers. 323 | */ 324 | 325 | legend { 326 | box-sizing: border-box; /* 1 */ 327 | color: inherit; /* 2 */ 328 | display: table; /* 1 */ 329 | max-width: 100%; /* 1 */ 330 | padding: 0; /* 3 */ 331 | white-space: normal; /* 1 */ 332 | } 333 | 334 | /** 335 | * 1. Add the correct display in IE 9-. 336 | * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. 337 | */ 338 | 339 | progress { 340 | display: inline-block; /* 1 */ 341 | vertical-align: baseline; /* 2 */ 342 | } 343 | 344 | /** 345 | * Remove the default vertical scrollbar in IE. 346 | */ 347 | 348 | textarea { 349 | overflow: auto; 350 | } 351 | 352 | /** 353 | * 1. Add the correct box sizing in IE 10-. 354 | * 2. Remove the padding in IE 10-. 355 | */ 356 | 357 | [type='checkbox'], 358 | [type='radio'] { 359 | box-sizing: border-box; /* 1 */ 360 | padding: 0; /* 2 */ 361 | } 362 | 363 | /** 364 | * Correct the cursor style of increment and decrement buttons in Chrome. 365 | */ 366 | 367 | [type='number']::-webkit-inner-spin-button, 368 | [type='number']::-webkit-outer-spin-button { 369 | height: auto; 370 | } 371 | 372 | /** 373 | * 1. Correct the odd appearance in Chrome and Safari. 374 | * 2. Correct the outline style in Safari. 375 | */ 376 | 377 | [type='search'] { 378 | -webkit-appearance: textfield; /* 1 */ 379 | outline-offset: -2px; /* 2 */ 380 | } 381 | 382 | /** 383 | * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. 384 | */ 385 | 386 | [type='search']::-webkit-search-cancel-button, 387 | [type='search']::-webkit-search-decoration { 388 | -webkit-appearance: none; 389 | } 390 | 391 | /** 392 | * 1. Correct the inability to style clickable types in iOS and Safari. 393 | * 2. Change font properties to `inherit` in Safari. 394 | */ 395 | 396 | ::-webkit-file-upload-button { 397 | -webkit-appearance: button; /* 1 */ 398 | font: inherit; /* 2 */ 399 | } 400 | 401 | /* Interactive 402 | ========================================================================== */ 403 | 404 | /* 405 | * Add the correct display in IE 9-. 406 | * 1. Add the correct display in Edge, IE, and Firefox. 407 | */ 408 | 409 | details, 410 | /* 1 */ menu { 411 | display: block; 412 | } 413 | 414 | /* 415 | * Add the correct display in all browsers. 416 | */ 417 | 418 | summary { 419 | display: list-item; 420 | } 421 | 422 | /* Scripting 423 | ========================================================================== */ 424 | 425 | /** 426 | * Add the correct display in IE 9-. 427 | */ 428 | 429 | canvas { 430 | display: inline-block; 431 | } 432 | 433 | /** 434 | * Add the correct display in IE. 435 | */ 436 | 437 | template { 438 | display: none; 439 | } 440 | 441 | /* Hidden 442 | ========================================================================== */ 443 | 444 | /** 445 | * Add the correct display in IE 10-. 446 | */ 447 | 448 | [hidden] { 449 | display: none; 450 | } 451 | -------------------------------------------------------------------------------- /src/index.re: -------------------------------------------------------------------------------- 1 | /* ReactDOMRe.renderToElementWithId "root"; */ 2 | [%bs.raw {|require('./index.css')|}]; 3 | 4 | [%bs.raw {|require('./app.css')|}]; 5 | 6 | [@bs.module "./registerServiceWorker"] external register_service_worker : unit => unit = "default"; 7 | 8 | ReactDOMRe.renderToElementWithId(, "root"); 9 | 10 | register_service_worker(); 11 | -------------------------------------------------------------------------------- /src/meetup.re: -------------------------------------------------------------------------------- 1 | type reasonMeetup = { 2 | city: string, 3 | name: string, 4 | shortName: string, 5 | page: string, 6 | logo: string 7 | }; 8 | 9 | let component = ReasonReact.statelessComponent("Meetup"); 10 | 11 | type props = {meetup: reasonMeetup}; 12 | 13 | let skin = 14 | ReactDOMRe.Style.make(~color="#f6f4f4", ~margin="0 auto", ~height="50px", ~width="50px", ()); 15 | 16 | let linkSkin = 17 | ReactDOMRe.Style.make( 18 | ~color="gray", 19 | ~fontFamily="Helvetica Neue, Open Sans, sans-serif", 20 | ~fontWeight="bold", 21 | ~lineHeight="1.6", 22 | ~fontSize="16px", 23 | ~textDecoration="none", 24 | ~margin="2px", 25 | ~textAlign="center", 26 | () 27 | ); 28 | 29 | let make = (~reasonMeetup, _children) => { 30 | ...component, 31 | render: (_self) => 32 | 33 | 34 | 39 | 40 | }; 41 | -------------------------------------------------------------------------------- /src/noUpcomingEvents.re: -------------------------------------------------------------------------------- 1 | let component = ReasonReact.statelessComponent("NoUpcomingEvents"); 2 | 3 | let make = (_children) => { 4 | ...component, 5 | render: (_self) => 6 |
7 |

(ReasonReact.stringToElement("Welcome to the ReasonML Vienna Meetup group"))

8 |
9 | ( 10 | ReasonReact.stringToElement( 11 | "If you are interested in ReasonML feel free to join our meetup page. At the moment there are no events scheduled. Check back soon to find out about upcoming meetups or hacksessions!" 12 | ) 13 | ) 14 |
15 |
16 | }; 17 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (!isLocalhost) { 36 | // Is not local host. Just register service worker 37 | registerValidSW(swUrl); 38 | } else { 39 | // This is running on localhost. Lets check if a service worker still exists or not. 40 | checkValidServiceWorker(swUrl); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | function registerValidSW(swUrl) { 47 | navigator.serviceWorker 48 | .register(swUrl) 49 | .then(registration => { 50 | registration.onupdatefound = () => { 51 | const installingWorker = registration.installing; 52 | installingWorker.onstatechange = () => { 53 | if (installingWorker.state === 'installed') { 54 | if (navigator.serviceWorker.controller) { 55 | // At this point, the old content will have been purged and 56 | // the fresh content will have been added to the cache. 57 | // It's the perfect time to display a "New content is 58 | // available; please refresh." message in your web app. 59 | console.log('New content is available; please refresh.'); 60 | } else { 61 | // At this point, everything has been precached. 62 | // It's the perfect time to display a 63 | // "Content is cached for offline use." message. 64 | console.log('Content is cached for offline use.'); 65 | } 66 | } 67 | }; 68 | }; 69 | }) 70 | .catch(error => { 71 | console.error('Error during service worker registration:', error); 72 | }); 73 | } 74 | 75 | function checkValidServiceWorker(swUrl) { 76 | // Check if the service worker can be found. If it can't reload the page. 77 | fetch(swUrl) 78 | .then(response => { 79 | // Ensure service worker exists, and that we really are getting a JS file. 80 | if ( 81 | response.status === 404 || 82 | response.headers.get('content-type').indexOf('javascript') === -1 83 | ) { 84 | // No service worker found. Probably a different app. Reload the page. 85 | navigator.serviceWorker.ready.then(registration => { 86 | registration.unregister().then(() => { 87 | window.location.reload(); 88 | }); 89 | }); 90 | } else { 91 | // Service worker found. Proceed as normal. 92 | registerValidSW(swUrl); 93 | } 94 | }) 95 | .catch(() => { 96 | console.log( 97 | 'No internet connection found. App is running in offline mode.' 98 | ); 99 | }); 100 | } 101 | 102 | export function unregister() { 103 | if ('serviceWorker' in navigator) { 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister(); 106 | }); 107 | } 108 | } 109 | --------------------------------------------------------------------------------