├── .babelrc ├── .gitignore ├── .js-modules.edn ├── .projectile ├── LICENSE ├── assets ├── fonts │ ├── IndieFlower.ttf │ └── Pacifico.ttf └── images │ ├── default.png │ ├── intro │ ├── s1.png │ ├── s2.png │ └── s3.png │ ├── logo.png │ └── lym.png ├── env ├── dev │ ├── env │ │ ├── dev.cljs │ │ ├── index.cljs │ │ └── main.cljs │ ├── externs.clj │ └── user.clj └── prod │ └── env │ └── main.cljs ├── exp.json ├── js ├── externs.js └── figwheel-bridge.js ├── package.json ├── project.clj ├── readme.md ├── src ├── cljsjs │ ├── react.cljs │ └── react │ │ ├── dom.cljs │ │ └── dom │ │ └── server.cljs ├── lymchat │ ├── api.cljs │ ├── assets.cljs │ ├── config.cljs.example │ ├── core.cljs │ ├── core_async_storage.cljs │ ├── db.cljs │ ├── exponent │ │ └── notification.cljs │ ├── handlers.cljs │ ├── locales.cljs │ ├── navigation │ │ ├── router.cljs │ │ └── tab.cljs │ ├── notification.cljs │ ├── photo.cljs │ ├── shared │ │ ├── colors.cljs │ │ ├── login.cljs │ │ ├── scene │ │ │ ├── chat.cljs │ │ │ ├── contact.cljs │ │ │ ├── group.cljs │ │ │ ├── guide.cljs │ │ │ ├── intro.cljs │ │ │ ├── invite.cljs │ │ │ ├── language.cljs │ │ │ ├── login.cljs │ │ │ ├── me.cljs │ │ │ ├── member.cljs │ │ │ ├── mention.cljs │ │ │ ├── profile.cljs │ │ │ ├── recommend_channels.cljs │ │ │ └── root.cljs │ │ └── ui.cljs │ ├── storage.cljs │ ├── styles.cljs │ ├── subs.cljs │ ├── util.cljs │ └── ws.cljs └── reagent │ ├── dom.cljs │ └── dom │ └── server.cljs └── todo.org /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react-native-stage-0/decorator-support"], 3 | "env": { 4 | "development": { 5 | "plugins": ["transform-react-jsx-source"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .exponent/**/* 3 | /target 4 | /out 5 | /classes 6 | /checkouts 7 | pom.xml 8 | pom.xml.asc 9 | *.jar 10 | *.class 11 | /.lein-* 12 | /.nrepl-port 13 | .hgignore 14 | .hg/ 15 | figwheel_server.log 16 | main.js 17 | src/lymchat/config.cljs 18 | -------------------------------------------------------------------------------- /.js-modules.edn: -------------------------------------------------------------------------------- 1 | {"src/lymchat/core_async_storage.cljs" ["react-native"], "src/lymchat/shared/scene/chat.cljs" ["./assets/images/default.png"], "src/lymchat/shared/scene/intro.cljs" ["./assets/images/intro/s1.png" "./assets/images/intro/s2.png" "./assets/images/intro/s3.png"], "src/lymchat/shared/scene/login.cljs" ["./assets/images/logo.png"], "src/lymchat/shared/scene/root.cljs" ["./assets/fonts/IndieFlower.ttf" "./assets/fonts/Pacifico.ttf"], "src/lymchat/shared/ui.cljs" ["react-native" "moment" "SwipeableListView" "react-native-aws3" "react-native-app-intro" "react-native-gifted-chat" "react-native-parsed-text" "exponent" "@exponent/ex-navigation" "@exponent/react-native-action-sheet" "@exponent/vector-icons/FontAwesome" "@exponent/vector-icons/MaterialIcons"]} -------------------------------------------------------------------------------- /.projectile: -------------------------------------------------------------------------------- 1 | -/node_modules 2 | -/target 3 | -/.git 4 | -/.exponent 5 | -/figwheel_server.log 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor tocontrol, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /assets/fonts/IndieFlower.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat-exp/425a0738b11632119be08b5a59f12244f5df1575/assets/fonts/IndieFlower.ttf -------------------------------------------------------------------------------- /assets/fonts/Pacifico.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat-exp/425a0738b11632119be08b5a59f12244f5df1575/assets/fonts/Pacifico.ttf -------------------------------------------------------------------------------- /assets/images/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat-exp/425a0738b11632119be08b5a59f12244f5df1575/assets/images/default.png -------------------------------------------------------------------------------- /assets/images/intro/s1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat-exp/425a0738b11632119be08b5a59f12244f5df1575/assets/images/intro/s1.png -------------------------------------------------------------------------------- /assets/images/intro/s2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat-exp/425a0738b11632119be08b5a59f12244f5df1575/assets/images/intro/s2.png -------------------------------------------------------------------------------- /assets/images/intro/s3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat-exp/425a0738b11632119be08b5a59f12244f5df1575/assets/images/intro/s3.png -------------------------------------------------------------------------------- /assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat-exp/425a0738b11632119be08b5a59f12244f5df1575/assets/images/logo.png -------------------------------------------------------------------------------- /assets/images/lym.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiensonqin/lymchat-exp/425a0738b11632119be08b5a59f12244f5df1575/assets/images/lym.png -------------------------------------------------------------------------------- /env/dev/env/dev.cljs: -------------------------------------------------------------------------------- 1 | (ns env.dev) 2 | (def hostname "tienson.lan") 3 | (def ip "192.168.1.114") -------------------------------------------------------------------------------- /env/dev/env/index.cljs: -------------------------------------------------------------------------------- 1 | (ns env.index 2 | (:require [env.dev :as dev])) 3 | 4 | ;; undo main.js goog preamble hack 5 | (set! js/window.goog js/undefined) 6 | 7 | (-> (js/require "figwheel-bridge") 8 | (.withModules #js {"exponent" (js/require "exponent"), "moment" (js/require "moment"), "./assets/images/intro/s1.png" (js/require "../../assets/images/intro/s1.png"), "@exponent/ex-navigation" (js/require "@exponent/ex-navigation"), "react" (js/require "react"), "@exponent/vector-icons/FontAwesome" (js/require "@exponent/vector-icons/FontAwesome"), "react-native-gifted-chat" (js/require "react-native-gifted-chat"), "./assets/fonts/IndieFlower.ttf" (js/require "../../assets/fonts/IndieFlower.ttf"), "./assets/fonts/Pacifico.ttf" (js/require "../../assets/fonts/Pacifico.ttf"), "react-native-parsed-text" (js/require "react-native-parsed-text"), "@exponent/react-native-action-sheet" (js/require "@exponent/react-native-action-sheet"), "SwipeableListView" (js/require "SwipeableListView"), "./assets/images/intro/s3.png" (js/require "../../assets/images/intro/s3.png"), "react-native-aws3" (js/require "react-native-aws3"), "./assets/images/logo.png" (js/require "../../assets/images/logo.png"), "react-native" (js/require "react-native"), "./assets/images/lym.png" (js/require "../../assets/images/lym.png"), "@exponent/vector-icons/MaterialIcons" (js/require "@exponent/vector-icons/MaterialIcons"), "react-native-app-intro" (js/require "react-native-app-intro"), "./assets/images/intro/s2.png" (js/require "../../assets/images/intro/s2.png"), "./assets/images/default.png" (js/require "../../assets/images/default.png")} 9 | ) 10 | (.start "main")) 11 | -------------------------------------------------------------------------------- /env/dev/env/main.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:figwheel-no-load env.main 2 | (:require [reagent.core :as r] 3 | [lymchat.core :as core] 4 | [figwheel.client :as figwheel :include-macros true] 5 | [env.dev])) 6 | 7 | (enable-console-print!) 8 | 9 | (def cnt (r/atom 0)) 10 | (defn reloader [] @cnt [core/app-root]) 11 | (def root-el (r/as-element [reloader])) 12 | 13 | (figwheel/watch-and-reload 14 | :websocket-url (str "ws://" env.dev/ip ":3449/figwheel-ws") 15 | :heads-up-display false 16 | :jsload-callback #(swap! cnt inc)) 17 | 18 | (core/init) 19 | -------------------------------------------------------------------------------- /env/dev/externs.clj: -------------------------------------------------------------------------------- 1 | (ns externs 2 | (:require [cljs.compiler.api :as compiler] 3 | [cljs.analyzer.api :as analyzer] 4 | [cljs.analyzer :as ana] 5 | [clojure.walk :refer [prewalk]] 6 | [clojure.pprint :refer [pprint]] 7 | [clojure.java.io :as io] 8 | [clojure.string :as str] 9 | [cljs.env :as env] 10 | [clojure.tools.reader :as r] 11 | [clojure.tools.reader.reader-types :refer [string-push-back-reader]] 12 | [cljs.tagged-literals :as tags]) 13 | (:import (clojure.lang LineNumberingPushbackReader))) 14 | 15 | ;; Idea from https://gist.github.com/Chouser/5796967 16 | 17 | ;; TODO (NAMESPACE/MODULE.method ...args) or NAMESPACE/MODULE.field not work 18 | ;; For example, (ui/Facebook.logInWithReadPermissionsAsync ...args) 19 | ;; or ui/Permissions.REMOTE_NOTIFICATIONS, 20 | ;; we already know logInWithReadPermissionsAsync is `invoke' op 21 | ;; and REMOTE_NOTIFICATIONS is a property, need to dig deeper to the ast 22 | 23 | ;; TODO ana/analyze is slow 24 | 25 | (defonce cenv (analyzer/empty-state)) 26 | 27 | (defn compile-project 28 | [src target] 29 | (analyzer/with-state cenv 30 | (compiler/with-core-cljs 31 | (compiler/compile-root src target)))) 32 | 33 | (defn get-namespaces 34 | [] 35 | (:cljs.analyzer/namespaces @cenv)) 36 | 37 | (defn print-ast [ast] 38 | (pprint ;; pprint indents output nicely 39 | (prewalk ;; rewrite each node of the ast 40 | (fn [x] 41 | (if (map? x) 42 | (select-keys x [:children :name :form :op]) ;; return selected entries of each map node 43 | x)) ;; non-map nodes are left unchanged 44 | ast))) 45 | 46 | (defn read-file 47 | [filename] 48 | (try 49 | (let [reader (string-push-back-reader (slurp filename)) 50 | endof (gensym)] 51 | (binding [r/*read-eval* false 52 | r/*data-readers* tags/*cljs-data-readers*] 53 | (->> #(r/read reader false endof) 54 | (repeatedly) 55 | (take-while #(not= % endof)) 56 | (doall)))) 57 | (catch Exception e 58 | (println e) 59 | '()))) 60 | 61 | (defn file-ast 62 | "Return the ClojureScript AST for the contents of filename. Tends to 63 | be large and to contain cycles -- be careful printing at the REPL." 64 | [filename] 65 | (binding [ana/*cljs-ns* 'cljs.user ;; default namespace 66 | ana/*cljs-file* filename] 67 | (mapv 68 | (fn [form] 69 | (try (ana/no-warn (ana/analyze (ana/empty-env) form {:cache-analysis true})) 70 | (catch Exception e 71 | (prn filename e)))) 72 | (read-file filename)))) 73 | 74 | (defn flatten-ast [ast] 75 | (mapcat #(tree-seq :children :children %) ast)) 76 | 77 | (defn get-interop-used 78 | "Return a set of symbols representing the method and field names 79 | used in interop forms in the given sequence of AST nodes." 80 | [flat-ast] 81 | (keep #(let [ret (and (map? %) 82 | (when-let [sym (some % [:method :field])] 83 | (when-not (str/starts-with? sym "cljs") 84 | sym)))] 85 | (if ret 86 | ret 87 | nil)) flat-ast)) 88 | 89 | (defn externs-for-interop [syms] 90 | (apply str 91 | "var DummyClass={};\n" 92 | (map #(str "DummyClass." % "=function(){};\n") 93 | syms))) 94 | 95 | (defn var-defined? 96 | "Returns true if the given fully-qualified symbol is known by the 97 | ClojureScript compiler to have been defined, based on its mutable set 98 | of namespaces." 99 | [sym] 100 | (contains? (let [ns (get (get-namespaces) (symbol (namespace sym)))] 101 | (merge (:defs ns) 102 | (:macros ns))) 103 | (symbol (name sym)))) 104 | 105 | (defn get-vars-used 106 | "Return a set of symbols representing all vars used or referenced in 107 | the given sequence of AST nodes." 108 | [requires flat-ast] 109 | (->> flat-ast 110 | (filter #(let [ns (-> % :info :ns)] 111 | (and (= (:op %) :var) 112 | ns 113 | (not= ns 'js)))) 114 | (map #(let [sym (-> % :info :name) 115 | sym-namespace (get requires (symbol (namespace sym))) 116 | sym-name (name sym)] 117 | (if sym-namespace 118 | (symbol (str sym-namespace) sym-name) 119 | sym))))) 120 | 121 | (defn extern-for-var [[str-namespace symbols]] 122 | (let [symbols-str (->> symbols 123 | (map (fn [sym] (format "%s.%s={};\n" (namespace sym) (name sym)))) 124 | (apply str))] 125 | (format "var %s={};\n%s" 126 | str-namespace symbols-str))) 127 | 128 | (defn externs-for-vars [grouped-syms] 129 | (apply str (map extern-for-var grouped-syms))) 130 | 131 | (defn get-undefined-vars [requires flat-ast] 132 | (->> (get-vars-used requires flat-ast) 133 | (remove var-defined?))) 134 | 135 | (defn get-undefined-vars-and-interop-used [file] 136 | (let [ast (file-ast file) 137 | ns-name (:name (first ast)) 138 | ns-requires (:requires (first ast)) 139 | flat-ast (flatten-ast ast)] 140 | [(get-undefined-vars ns-requires flat-ast) 141 | (get-interop-used flat-ast)])) 142 | 143 | ;; copy from https://github.com/ejlo/lein-externs/blob/master/src/leiningen/externs.clj 144 | (defn cljs-file? 145 | "Returns true if the java.io.File represents a normal Clojurescript source 146 | file." 147 | [^java.io.File file] 148 | (and (.isFile file) 149 | (.endsWith (.getName file) ".cljs"))) 150 | 151 | (defn get-source-paths [build-type builds] 152 | (or 153 | (when build-type 154 | (:source-paths 155 | (or ((keyword build-type) builds) 156 | (first (filter #(= (name (:id %)) build-type) builds))))) 157 | ["src" "cljs"])) 158 | 159 | (defn -main 160 | "Generate an externs file" 161 | [] 162 | ;; TODO configurable 163 | (println "Start to generate externs...") 164 | (compile-project (io/file "src") (io/file "target")) 165 | 166 | (prn (keys (get-namespaces))) 167 | 168 | (let [source-paths ["src" "env/prod"] 169 | 170 | files (->> source-paths 171 | (map io/file) 172 | (mapcat file-seq) 173 | (filter cljs-file?)) 174 | col (apply concat (doall (pmap get-undefined-vars-and-interop-used files))) 175 | vars (->> (take-nth 2 col) 176 | (remove empty?) 177 | (flatten) 178 | (set) 179 | (sort) 180 | (group-by namespace) 181 | ;; remove goog dependencies, need to dig deeper(TODO) 182 | (remove (fn [[ns _]] (str/starts-with? ns "goog"))) 183 | (externs-for-vars)) 184 | interop (->> (take-nth 2 (rest col)) 185 | (remove empty?) 186 | (flatten) 187 | (set) 188 | (sort) 189 | (externs-for-interop)) 190 | result (str vars interop)] 191 | (spit "js/externs.js" result) 192 | (println "Generated externs to js/externs.js"))) 193 | -------------------------------------------------------------------------------- /env/dev/user.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:require [figwheel-sidecar.repl-api :as ra] 3 | [clojure.java.io :as io] 4 | [clojure.string :as str] 5 | [hawk.core :as hawk] 6 | [clojure.tools.reader.edn :as edn] 7 | [clojure.set :as set])) 8 | ;; This namespace is loaded automatically by nREPL 9 | 10 | ;; read project.clj to get build configs 11 | (def project-config (->> "project.clj" 12 | slurp 13 | read-string 14 | (drop 1) 15 | (apply hash-map))) 16 | 17 | (def profiles (:profiles project-config)) 18 | 19 | (def cljs-builds (get-in profiles [:dev :cljsbuild :builds])) 20 | 21 | (defn enable-source-maps 22 | [] 23 | (prn "Enabled source maps.") 24 | (let [path "node_modules/react-native/packager/react-packager/src/Server/index.js"] 25 | (spit path 26 | (str/replace (slurp path) "/\\.map$/" "/main.map$/")))) 27 | 28 | (defn write-main-js 29 | [] 30 | (-> "'use strict';\n\n// cljsbuild adds a preamble mentioning goog so hack around it\nwindow.goog = {\n provide() {},\n require() {},\n};\nrequire('./target/env/index.js');\n" 31 | ((partial spit "main.js")))) 32 | 33 | (defn write-env-dev 34 | [] 35 | (let [hostname (.getHostName (java.net.InetAddress/getLocalHost)) 36 | ip (.getHostAddress (java.net.InetAddress/getLocalHost))] 37 | (-> "(ns env.dev)\n(def hostname \"%s\")\n(def ip \"%s\")" 38 | (format 39 | hostname 40 | ip) 41 | ((partial spit "env/dev/env/dev.cljs"))))) 42 | 43 | (defn keyword->str [s] 44 | (str/replace s 45 | #"::(\S)*" 46 | (fn [r] 47 | (str \" (str/replace (first r) #"::" "") \")))) 48 | 49 | (defn rebuild-env-index 50 | [js-modules] 51 | (let [modules (->> (file-seq (io/file "assets")) 52 | (filter #(and (not (re-find #"DS_Store" (str %))) 53 | (.isFile %))) 54 | (map (fn [file] (when-let [path (str file)] 55 | (str "../../" path)))) 56 | (concat js-modules) 57 | (distinct)) 58 | modules-map (zipmap 59 | (->> modules 60 | (map #(str "::" 61 | (if (str/starts-with? % "../../assets") 62 | (-> % 63 | (str/replace "../../" "./") 64 | (str/replace "@2x" "") 65 | (str/replace "@3x" "")) 66 | %)))) 67 | (->> modules 68 | (map #(format "(js/require \"%s\")" 69 | (-> % 70 | (str/replace "@2x" "") 71 | (str/replace "@3x" ""))))))] 72 | (try 73 | (-> "(ns env.index\n (:require [env.dev :as dev]))\n\n;; undo main.js goog preamble hack\n(set! js/window.goog js/undefined)\n\n(-> (js/require \"figwheel-bridge\")\n (.withModules %s)\n (.start \"main\"))\n" 74 | (format 75 | (str "#js " (with-out-str (println modules-map)))) 76 | (keyword->str) 77 | ((partial spit "env/dev/env/index.cljs"))) 78 | 79 | (catch Exception e 80 | (println "Error: " e))))) 81 | 82 | ;; Each file maybe corresponds to multiple modules. 83 | (defn watch-for-external-modules 84 | [] 85 | (let [path ".js-modules.edn"] 86 | (hawk/watch! [{:paths ["src"] 87 | :filter hawk/file? 88 | :handler (fn [ctx {:keys [kind file] :as event}] 89 | (let [m (edn/read-string (slurp path)) 90 | file-name (-> (.getPath file) 91 | (str/replace (str (System/getProperty "user.dir") "/") ""))] 92 | 93 | ;; file is deleted 94 | (when (= :delete kind) 95 | (let [new-m (dissoc m file-name)] 96 | (spit path new-m) 97 | (rebuild-env-index (flatten (vals new-m))))) 98 | 99 | (when (.exists file) 100 | (let [content (slurp file) 101 | js-modules (some->> 102 | content 103 | (re-seq #"\(js/require \"([^\"]+)\"\)") 104 | (map last) 105 | (vec)) 106 | commented-modules (some->> 107 | content 108 | (re-seq #"[;]+[\s]*\(js/require \"([^\"]+)\"\)") 109 | (map last) 110 | (set)) 111 | js-modules (if commented-modules 112 | (vec (remove commented-modules js-modules)) 113 | js-modules)] 114 | (let [old-js-modules (get m file-name)] 115 | (when (not= old-js-modules js-modules) 116 | (let [new-m (if (seq js-modules) 117 | (assoc m file-name js-modules) 118 | (dissoc m file-name))] 119 | (spit path new-m) 120 | 121 | (rebuild-env-index (flatten (vals new-m))))))))) 122 | ctx)}]))) 123 | 124 | (defn build-external-modules 125 | [] 126 | (let [path ".js-modules.edn" 127 | m (atom {})] 128 | ;; delete path 129 | (clojure.java.io/delete-file path) 130 | 131 | (doseq [file (file-seq (java.io.File. "src"))] 132 | (when (.isFile file) 133 | (let [file-name (-> (.getPath file) 134 | (str/replace (str (System/getProperty "user.dir") "/") "")) 135 | content (slurp file) 136 | js-modules (some->> 137 | content 138 | (re-seq #"\(js/require \"([^\"]+)\"\)") 139 | (map last) 140 | (vec)) 141 | commented-modules (some->> 142 | content 143 | (re-seq #"[;]+[\s]*\(js/require \"([^\"]+)\"\)") 144 | (map last) 145 | (set)) 146 | js-modules (if commented-modules 147 | (vec (remove commented-modules js-modules)) 148 | js-modules)] 149 | (if js-modules 150 | (swap! m assoc file-name (vec js-modules)))))) 151 | (spit path @m) 152 | (rebuild-env-index (flatten (conj (vals @m) "react"))))) 153 | 154 | (defn start-figwheel 155 | "Start figwheel for one or more builds" 156 | [& build-ids] 157 | (enable-source-maps) 158 | (write-main-js) 159 | (write-env-dev) 160 | (watch-for-external-modules) 161 | (ra/start-figwheel! 162 | {:figwheel-options {} 163 | :build-ids (if (seq build-ids) 164 | build-ids 165 | ["main"]) 166 | :all-builds cljs-builds}) 167 | (ra/cljs-repl)) 168 | 169 | (defn stop-figwheel 170 | "Stops figwheel" 171 | [] 172 | (ra/stop-figwheel!)) 173 | 174 | (defn -main 175 | [args] 176 | (case args 177 | "--figwheel" 178 | (start-figwheel) 179 | 180 | "--build-external-modules" 181 | (build-external-modules) 182 | 183 | (prn "You can run lein figwheel or lein build-external-modules."))) 184 | -------------------------------------------------------------------------------- /env/prod/env/main.cljs: -------------------------------------------------------------------------------- 1 | (ns env.main 2 | (:require [lymchat.core :as core])) 3 | 4 | (core/init) 5 | -------------------------------------------------------------------------------- /exp.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lymchat", 3 | "description": "Lymchat - Learn different cultures.", 4 | "slug": "lymchat", 5 | "sdkVersion": "11.0.0", 6 | "version": "1.0.1", 7 | "orientation": "portrait", 8 | "primaryColor": "#cccccc", 9 | "iconUrl": "http://lymchat.com/images/lymchat.png", 10 | "notification": { 11 | "iconUrl": "http://lymchat.com/images/lymchat.png", 12 | "color": "#000000" 13 | }, 14 | "loading": { 15 | "iconUrl": "http://lymchat.com/images/lymchat.png", 16 | "hideExponentText": true 17 | }, 18 | "packagerOpts": { 19 | "assetExts": ["ttf","otf"], 20 | "nonPersistent": "" 21 | }, 22 | "ios": { 23 | "bundleIdentifier": "com.lymchat.lymchat", 24 | }, 25 | "android": { 26 | "package": "com.lymchat.lymchat", 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /js/externs.js: -------------------------------------------------------------------------------- 1 | var Animated={}; 2 | Animated.Image={}; 3 | Animated.ScrollView={}; 4 | Animated.Text={}; 5 | Animated.Value={}; 6 | Animated.View={}; 7 | var Components={}; 8 | Components.AppLoading={}; 9 | Components.BlurView={}; 10 | Components.LinearGradient={}; 11 | var DummyClass={}; 12 | DummyClass.DataSource=function(){}; 13 | DummyClass.ListView=function(){}; 14 | DummyClass.OS=function(){}; 15 | DummyClass.addEventListener=function(){}; 16 | DummyClass.addListener=function(){}; 17 | DummyClass.alert=function(){}; 18 | DummyClass.all=function(){}; 19 | DummyClass.append=function(){}; 20 | DummyClass.askAsync=function(){}; 21 | DummyClass.cancelled=function(){}; 22 | DummyClass.catch=function(){}; 23 | DummyClass.cloneWithRows=function(){}; 24 | DummyClass.cloneWithRowsAndSections=function(){}; 25 | DummyClass.downloadAsync=function(){}; 26 | DummyClass.error=function(){}; 27 | DummyClass.event=function(){}; 28 | DummyClass.fetch=function(){}; 29 | DummyClass.format=function(){}; 30 | DummyClass.fqn=function(){}; 31 | DummyClass.fromModule=function(){}; 32 | DummyClass.fromNow=function(){}; 33 | DummyClass.get=function(){}; 34 | DummyClass.getAllKeys=function(){}; 35 | DummyClass.getCurrentRoute=function(){}; 36 | DummyClass.getExponentPushTokenAsync=function(){}; 37 | DummyClass.getNavigator=function(){}; 38 | DummyClass.getNewDataSource=function(){}; 39 | DummyClass.height=function(){}; 40 | DummyClass.interpolate=function(){}; 41 | DummyClass.isConnected=function(){}; 42 | DummyClass.isSame=function(){}; 43 | DummyClass.json=function(){}; 44 | DummyClass.keys=function(){}; 45 | DummyClass.launchCameraAsync=function(){}; 46 | DummyClass.launchImageLibraryAsync=function(){}; 47 | DummyClass.loadAsync=function(){}; 48 | DummyClass.logInWithReadPermissionsAsync=function(){}; 49 | DummyClass.multiGet=function(){}; 50 | DummyClass.ok=function(){}; 51 | DummyClass.openURL=function(){}; 52 | DummyClass.pop=function(){}; 53 | DummyClass.prefetch=function(){}; 54 | DummyClass.prompt=function(){}; 55 | DummyClass.push=function(){}; 56 | DummyClass.put=function(){}; 57 | DummyClass.registerComponent=function(){}; 58 | DummyClass.routeName=function(){}; 59 | DummyClass.runAfterInteractions=function(){}; 60 | DummyClass.setHidden=function(){}; 61 | DummyClass.setString=function(){}; 62 | DummyClass.showActionSheetWithOptions=function(){}; 63 | DummyClass.showLocalAlert=function(){}; 64 | DummyClass.slice=function(){}; 65 | DummyClass.spring=function(){}; 66 | DummyClass.start=function(){}; 67 | DummyClass.startOf=function(){}; 68 | DummyClass.subtract=function(){}; 69 | DummyClass.then=function(){}; 70 | DummyClass.timing=function(){}; 71 | DummyClass.toString=function(){}; 72 | DummyClass.uri=function(){}; 73 | DummyClass.vibrate=function(){}; 74 | -------------------------------------------------------------------------------- /js/figwheel-bridge.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Originally taken from https://github.com/decker405/figwheel-react-native 3 | * 4 | * @providesModule figwheel-bridge 5 | */ 6 | 7 | var CLOSURE_UNCOMPILED_DEFINES = null; 8 | var debugEnabled = false; 9 | 10 | var config = { 11 | basePath: "target/", 12 | googBasePath: 'goog/', 13 | serverPort: 8081 14 | }; 15 | 16 | var React = require('react'); 17 | var ReactNative = require('react-native'); 18 | var WebSocket = require('WebSocket'); 19 | var self; 20 | var scriptQueue = []; 21 | var serverHost = null; // will be set dynamically 22 | var fileBasePath = null; // will be set dynamically 23 | var evaluate = eval; // This is needed, direct calls to eval does not work (RN packager???) 24 | var externalModules = {}; 25 | var evalListeners = [ // Functions to be called after each js file is loaded and evaluated 26 | function (url) { 27 | if (url.indexOf('jsloader') > -1) { 28 | shimJsLoader(); 29 | } 30 | }, 31 | function (url) { 32 | if (url.indexOf('/figwheel/client/socket') > -1) { 33 | setCorrectWebSocketImpl(); 34 | } 35 | }]; 36 | 37 | var figwheelApp = function (platform, devHost) { 38 | return React.createClass({ 39 | getInitialState: function () { 40 | return {loaded: false} 41 | }, 42 | render: function () { 43 | if (!this.state.loaded) { 44 | var plainStyle = {flex: 1, alignItems: 'center', justifyContent: 'center'}; 45 | return ( 46 | 47 | Waiting for Figwheel to load files. 48 | 49 | ); 50 | } 51 | return this.state.root; 52 | }, 53 | componentDidMount: function () { 54 | var app = this; 55 | if (typeof goog === "undefined") { 56 | var url = this.props.url || 57 | this.props.exp.manifest.bundleUrl; 58 | var hostPort = url.split('/')[2].split(':'); 59 | devHost = hostPort[0]; 60 | config.serverPort = hostPort[1]; 61 | loadApp(platform, devHost, function (appRoot) { 62 | app.setState({root: appRoot, loaded: true}) 63 | }); 64 | } 65 | } 66 | }) 67 | }; 68 | 69 | function logDebug(msg) { 70 | if (debugEnabled) { 71 | console.log(msg); 72 | } 73 | } 74 | 75 | // evaluates js code ensuring proper ordering 76 | function customEval(url, javascript, success, error) { 77 | if (scriptQueue.length > 0) { 78 | if (scriptQueue[0] === url) { 79 | try { 80 | evaluate(javascript); 81 | logDebug('Evaluated: ' + url); 82 | scriptQueue.shift(); 83 | evalListeners.forEach(function (listener) { 84 | listener(url) 85 | }); 86 | success(); 87 | } catch (e) { 88 | console.error(e); 89 | error(); 90 | } 91 | } else { 92 | setTimeout(function () { 93 | customEval(url, javascript, success, error) 94 | }, 5); 95 | } 96 | } else { 97 | console.error('Something bad happened...'); 98 | error() 99 | } 100 | } 101 | 102 | var isChrome = function () { 103 | return typeof importScripts === "function" 104 | }; 105 | 106 | function asyncImportScripts(url, success, error) { 107 | logDebug('(asyncImportScripts) Importing: ' + url); 108 | scriptQueue.push(url); 109 | fetch(url) 110 | .then(function (response) { 111 | return response.text() 112 | }) 113 | .then(function (responseText) { 114 | return customEval(url, responseText, success, error); 115 | }) 116 | .catch(function (error) { 117 | console.error(error); 118 | return error(); 119 | }); 120 | } 121 | 122 | function syncImportScripts(url, success, error) { 123 | try { 124 | importScripts(url); 125 | logDebug('Evaluated: ' + url); 126 | evalListeners.forEach(function (listener) { 127 | listener(url) 128 | }); 129 | success(); 130 | } catch (e) { 131 | console.error(e); 132 | error() 133 | } 134 | } 135 | 136 | // Loads js file sync if possible or async. 137 | function importJs(src, success, error) { 138 | if (typeof success !== 'function') { 139 | success = function () { 140 | }; 141 | } 142 | if (typeof error !== 'function') { 143 | error = function () { 144 | }; 145 | } 146 | 147 | var file = fileBasePath + '/' + src; 148 | 149 | logDebug('(importJs) Importing: ' + file); 150 | if (isChrome()) { 151 | syncImportScripts(serverBaseUrl("localhost") + '/' + file, success, error); 152 | } else { 153 | asyncImportScripts(serverBaseUrl(serverHost) + '/' + file, success, error); 154 | } 155 | } 156 | 157 | function interceptRequire() { 158 | var oldRequire = window.require; 159 | console.info("Shimming require"); 160 | window.require = function (id) { 161 | console.info("Requiring: " + id); 162 | if (externalModules[id]) { 163 | return externalModules[id]; 164 | } 165 | return oldRequire(id); 166 | }; 167 | } 168 | 169 | function compileWarningsToYellowBox() { 170 | var log = window.console.log; 171 | var compileWarningRx = /Figwheel: Compile/; 172 | var compileExceptionRx = /Figwheel: Compile Exception/; 173 | var errorInFileRx = /Error on file/; 174 | var isBuffering = false; 175 | var compileExceptionBuffer = ""; 176 | window.console.log = function (msg) { 177 | log.apply(window.console, arguments); 178 | if (compileExceptionRx.test(msg)) { // enter buffering mode to get all the messages for exception 179 | isBuffering = true; 180 | compileExceptionBuffer = msg + "\n"; 181 | } else if (errorInFileRx.test(msg) && isBuffering) { // exit buffering mode and log buffered messages to YellowBox 182 | isBuffering = false; 183 | console.warn(compileExceptionBuffer + msg); 184 | compileExceptionBuffer = ""; 185 | } else if (isBuffering) { //log messages buffering mode 186 | compileExceptionBuffer += msg + "\n"; 187 | } else if (compileWarningRx.test(msg)) { 188 | console.warn(msg); 189 | } 190 | }; 191 | } 192 | 193 | function serverBaseUrl(host) { 194 | return "http://" + host + ":" + config.serverPort 195 | } 196 | 197 | function setCorrectWebSocketImpl() { 198 | figwheel.client.socket.get_websocket_imp = function () { 199 | return WebSocket; 200 | }; 201 | } 202 | 203 | function loadApp(platform, devHost, onLoadCb) { 204 | serverHost = devHost; 205 | fileBasePath = config.basePath; 206 | 207 | // callback when app is ready to get the reloadable component 208 | var mainJs = '/env/main.js'; 209 | evalListeners.push(function (url) { 210 | if (url.indexOf(mainJs) > -1) { 211 | onLoadCb(env.main.root_el); 212 | console.info('Done loading Clojure app'); 213 | } 214 | }); 215 | 216 | if (typeof goog === "undefined") { 217 | console.info('Loading Closure base.'); 218 | interceptRequire(); 219 | compileWarningsToYellowBox(); 220 | importJs('goog/base.js', function () { 221 | shimBaseGoog(); 222 | importJs('cljs_deps.js'); 223 | importJs('goog/deps.js', function () { 224 | // This is needed because of RN packager 225 | // seriously React packager? why. 226 | var googreq = goog.require; 227 | 228 | googreq('figwheel.connect'); 229 | }); 230 | }); 231 | } 232 | } 233 | 234 | function startApp(appName, platform, devHost) { 235 | ReactNative.AppRegistry.registerComponent( 236 | appName, () => figwheelApp(platform, devHost)); 237 | } 238 | 239 | function withModules(moduleById) { 240 | externalModules = moduleById; 241 | return self; 242 | } 243 | 244 | // Goog fixes 245 | function shimBaseGoog() { 246 | console.info('Shimming goog functions.'); 247 | goog.basePath = 'goog/'; 248 | goog.writeScriptSrcNode = importJs; 249 | goog.writeScriptTag_ = function (src, optSourceText) { 250 | importJs(src); 251 | return true; 252 | }; 253 | } 254 | 255 | // Figwheel fixes 256 | // Used by figwheel - uses importScript to load JS rather than