├── .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