Commit 3193dfa15b5d606169a465bf9d61717c1595670f

Authored by Francisco Marcelo de Araújo Lima Júnior
2 parents 5d6ae4ae 181fc268

Merge branch 'master' into rails3-custom_fields

Showing 78 changed files with 4129 additions and 663 deletions   Show diff stats
@@ -1,251 +0,0 @@ @@ -1,251 +0,0 @@
1 -If you are not listed here, but should be, please write to the noosfero mailing  
2 -list: http://listas.softwarelivre.org/cgi-bin/mailman/listinfo/noosfero-dev  
3 -(this list requires subscription to post, but since you are an author of  
4 -noosfero, that's not a problem).  
5 -  
6 -Developers  
7 -==========  
8 -  
9 -Ábner Silva de Oliveira <abner.oliveira@serpro.gov.br>  
10 -Alan Freihof Tygel <alantygel@gmail.com>  
11 -alcampelo <alcampelo@alcampelo.(none)>  
12 -Alessandro Palmeira <alessandro.palmeira@gmail.com>  
13 -Alessandro Palmeira + Caio C. Salgado <alessandro.palmeira@gmail.com>  
14 -Alessandro Palmeira + Caio Salgado <alessandro.palmeira@gmail.com>  
15 -Alessandro Palmeira + Caio Salgado <caio.csalgado@gmail.com>  
16 -Alessandro Palmeira + Caio Salgado + Diego Araújo + João M. M. da Silva <diegoamc90@gmail.com>  
17 -Alessandro Palmeira + Carlos Morais <alessandro.palmeira@gmail.com>  
18 -Alessandro Palmeira + Daniel Alves <alessandro.palmeira@gmail.com>  
19 -Alessandro Palmeira + Daniel Alves + Diego Araújo <diegoamc90@gmail.com>  
20 -Alessandro Palmeira + Daniel Alves + Diego Araújo + Guilherme Rojas <danpaulalves@gmail.com>  
21 -Alessandro Palmeira + Diego Araujo <alessandro.palmeira@gmail.com>  
22 -Alessandro Palmeira + Diego Araújo <alessandro.palmeira@gmail.com>  
23 -Alessandro Palmeira + Diego Araujo + Daniela Feitosa <alessandro.palmeira@gmail.com>  
24 -Alessandro Palmeira + Diego Araujo <diegoamc90@gmail.com>  
25 -Alessandro Palmeira + Diego Araújo <diegoamc90@gmail.com>  
26 -Alessandro Palmeira + Diego Araujo + Eduardo Morais <alessandro.palmeira@gmail.com>  
27 -Alessandro Palmeira + Diego Araújo + João M. M. da Silva <alessandro.palmeira@gmail.com>  
28 -Alessandro Palmeira + Diego Araújo + João M. M. da Silva <diegoamc90@gmail.com>  
29 -Alessandro Palmeira + Diego Araujo + João M. M. da Silva + Paulo Meirelles <alessandro.palmeira@gmail.com>  
30 -Alessandro Palmeira + Diego Araújo + Pedro Leal <diegoamc90@gmail.com>  
31 -Alessandro Palmeira + Diego Araújo + Pedro Leal + João M. M. da Silva <diegoamc90@gmail.com>  
32 -Alessandro Palmeira + Diego Araujo + Rafael Manzo <alessandro.palmeira@gmail.com>  
33 -Alessandro Palmeira + Eduardo Morais <alessandro.palmeira@gmail.com>  
34 -Alessandro Palmeira + Guilherme Rojas <alessandro.palmeira@gmail.com>  
35 -Alessandro Palmeira + Jefferson Fernandes <alessandro.palmeira@gmail.com>  
36 -Alessandro Palmeira + João M. M. da Silva <alessandro.palmeira@gmail.com>  
37 -Alessandro Palmeira + Joao M. M. da Silva + Diego Araujo <alessandro.palmeira@gmail.com>  
38 -Alessandro Palmeira + João M. M. da Silva + Renan Teruo <alessandro.palmeira@gmail.com>  
39 -Alessandro Palmeira + João M. M. Silva <alessandro.palmeira@gmail.com>  
40 -Alessandro Palmeira + Paulo Meirelles <alessandro.palmeira@gmail.com>  
41 -Alessandro Palmeira + Paulo Meirelles + João M. M. da Silva <alessandro.palmeira@gmail.com>  
42 -Alessandro Palmeira + Rafael Manzo <alessandro.palmeira@gmail.com>  
43 -Ana Losnak <analosnak@gmail.com>  
44 -Antonio Terceiro + Carlos Morais <terceiro@colivre.coop.br>  
45 -Antonio Terceiro + Paulo Meirelles <terceiro@colivre.coop.br>  
46 -Antonio Terceiro <terceiro@colivre.coop.br>  
47 -Arthur Del Esposte <arthurmde@gmail.com>  
48 -Arthur Del Esposte <arthurmde@yahoo.com.br>  
49 -Aurelio A. Heckert <aurelio@colivre.coop.br>  
50 -Braulio Bhavamitra <brauliobo@gmail.com>  
51 -Bráulio Bhavamitra <brauliobo@gmail.com>  
52 -Braulio Bhavamitra <braulio@eita.org.br>  
53 -Caio <caio.csalgado@gmail.com>  
54 -Caio + Diego + Pedro + João <caio.csalgado@gmail.com>  
55 -Caio Formiga <caio.formiga@gmail.com>  
56 -Caio, Pedro <caio.csalgado@gmail.com>  
57 -Caio Salgado + Alessandro Palmeira <caio.csalgado@gmail.com>  
58 -Caio Salgado <caio.csalgado@gmail.com>  
59 -Caio Salgado + Carlos Morais + Diego Araújo + Pedro Leal <diegoamc90@gmail.com>  
60 -Caio Salgado + Diego Araujo <caio.csalgado@gmail.com>  
61 -Caio Salgado + Diego Araújo <caio.csalgado@gmail.com>  
62 -Caio Salgado + Diego Araújo <diegoamc90@gmail.com>  
63 -Caio Salgado + Diego Araújo + Jefferson Fernandes <caio.csalgado@gmail.com>  
64 -Caio Salgado + Diego Araújo + João M. M. da Silva <caio.csalgado@gmail.com>  
65 -Caio Salgado + Diego Araújo + Pedro Leal <caio.csalgado@gmail.com>  
66 -Caio Salgado + Diego Araújo + Pedro Leal <diegoamc90@gmail.com>  
67 -Caio Salgado + Diego Araújo + Rafael Manzo <diegoamc90@gmail.com>  
68 -Caio Salgado + Jefferson Fernandes <caio.csalgado@gmail.com>  
69 -Caio Salgado + Jefferson Fernandes <jeffs.fernandes@gmail.com>  
70 -Caio Salgado + Rafael Manzo <caio.csalgado@gmail.com>  
71 -Caio Salgado + Renan Teruo <caio.csalgado@gmail.com>  
72 -Caio Salgado + Renan Teruo <caio.salgado@gmail.com>  
73 -Caio Salgado + Renan Teruo + Jefferson Fernandes <jeffs.fernandes@gmail.com>  
74 -Caio Salgado + Renan Teruo <renanteruoc@gmail.com>  
75 -Caio SBA <caio@colivre.coop.br>  
76 -Caio Tiago Oliveira <caiotiago@colivre.coop.br>  
77 -Carlos Andre de Souza <carlos.andre.souza@msn.com>  
78 -Carlos Morais <carlos88morais@gmail.com>  
79 -Carlos Morais + Diego Araújo <diegoamc90@gmail.com>  
80 -Carlos Morais + Eduardo Morais <carlos88morais@gmail.com>  
81 -Carlos Morais + Paulo Meirelles <carlos88morais@gmail.com>  
82 -Carlos Morais + Pedro Leal <carlos88morais@gmail.com>  
83 -Daniela Feitosa <dani@dohko.(none)>  
84 -Daniel Alves + Diego Araújo <danpaulalves@gmail.com>  
85 -Daniel Alves + Diego Araújo <diegoamc90@gmail.com>  
86 -Daniel Alves + Diego Araújo + Guilherme Rojas <danpaulalves@gmail.com>  
87 -Daniel Alves + Diego Araújo + Guilherme Rojas <diegoamc90@gmail.com>  
88 -Daniel Alves + Diego Araújo + Guilherme Rojas <guilhermehrojas@gmail.com>  
89 -Daniel Alves + Guilherme Rojas <danpaulalves@gmail.com>  
90 -Daniel Alves + Rafael Manzo <rr.manzo@gmail.com>  
91 -Daniela Soares Feitosa <danielafeitosa@colivre.coop.br>  
92 -Daniel Bucher <daniel.bucher88@gmail.com>  
93 -Daniel Cunha <daniel@colivre.coop.br>  
94 -David Carlos <ddavidcarlos1392@gmail.com>  
95 -diegoamc <diegoamc90@gmail.com>  
96 -Diego Araújo + Alessandro Palmeira <diegoamc90@gmail.com>  
97 -Diego Araújo + Alessandro Palmeira + João M. M. da Silva <diegoamc90@gmail.com>  
98 -Diego Araújo + Alessandro Palmeira + Rafael Manzo <rr.manzo@gmail.com>  
99 -Diego Araujo + Caio Salgado <diegoamc90@gmail.com>  
100 -Diego Araújo + Daniel Alves + Rafael Manzo <rr.manzo@gmail.com>  
101 -Diego Araújo <diegoamc90@gmail.com>  
102 -Diego Araújo + Eduardo Morais + Paulo Meirelles <diegoamc90@gmail.com>  
103 -Diego Araújo + Guilherme Rojas <diegoamc90@gmail.com>  
104 -Diego Araújo + Jefferson Fernandes <diegoamc90@gmail.com>  
105 -Diego Araujo + Jefferson Fernandes <jeffs.fernandes@gmail.com>  
106 -Diego Araújo + João Machini <diegoamc90@gmail.com>  
107 -Diego Araújo + João Machini <digoamc90@gmail.com>  
108 -Diego Araújo + João M. M. da Silva + Alessandro Palmeira <jaodsilv@linux.ime.usp.br>  
109 -Diego Araújo + João M. M. da Silva <diegoamc90@gmail.com>  
110 -Diego Araújo + João M. M. da Silva + João Machini <diegoamc90@gmail.com>  
111 -Diego Araújo + João M. M. da Silva + Pedro Leal <diegoamc90@gmail.com>  
112 -Diego Araújo + Paulo Meirelles <diegoamc90@gmail.com>  
113 -Diego Araújo + Pedro Leal <diegoamc90@gmail.com>  
114 -Diego Araujo + Rafael Manzo <diegoamc90@gmail.com>  
115 -Diego Araújo + Rafael Manzo <diegoamc90@gmail.com>  
116 -Diego Araújo + Renan Teruo + Alessandro Palmeira <diegoamc90@gmail.com>  
117 -Diego Araújo + Renan Teruo <diegoamc90@gmail.com>  
118 -Diego Araujo + Rodrigo Souto + Rafael Manzo <rr.manzo@gmail.com>  
119 -Diego + Jefferson <diegoamc90@gmail.com>  
120 -Diego Martinez <diegoamc90@gmail.com>  
121 -Diego Martinez <diego@diego-K55A.(none)>  
122 -Diego + Renan <renanteruoc@gmail.com>  
123 -Eduardo Tourinho Edington <eduardo.edington@serpro.gov.br>  
124 -Evandro Jr <evandrojr@gmail.com>  
125 -Evandro Junior <evandrojr@gmail.com>  
126 -Fabio Teixeira <fabio1079@gmail.com>  
127 -Fernanda Lopes <nanda.listas+psl@gmail.com>  
128 -Francisco Marcelo A. Lima Júnior <francisco.lima-junior@serpro.gov.br>  
129 -Francisco Marcelo de Araujo Lima Junior <79350259591@serpro-1457614.(none)>  
130 -Francisco Marcelo de Araújo Lima Júnior <francisco.lima-junior@serpro.gov.br>  
131 -Francisco Marcelo de Araújo Lima Júnior <maljunior@gmail.com>  
132 -Gabriela Navarro <navarro1703@gmail.com>  
133 -Grazieno Pellegrino <grazieno@gmail.com>  
134 -Gust <darksshades@hotmail.com>  
135 -Hugo Melo <hugo@riseup.net>  
136 -Isaac Canan <isaac@intelletto.com.br>  
137 -Italo Valcy <italo@dcc.ufba.br>  
138 -Jefferson Fernandes + Diego Araujo + Rafael Manzo <jeffs.fernandes@gmail.com>  
139 -Jefferson Fernandes + Joao M. M. da Silva <jeffs.fernandes@gmail.com>  
140 -Jefferson Fernandes + Joao M. M. Silva <jeffs.fernandes@gmail.com>  
141 -João da Silva + Eduardo Morais + Rafael Manzo <rr.manzo@gmail.com>  
142 -João da Silva <jaodsilv@linux.ime.usp.br>  
143 -João Marco Maciel da Silva + Rafael Manzo + Renan Teruo <jaodsilv@linux.ime.usp.br>  
144 -João M. M. da Silva + Alessandro Palmeira + Diego Araújo + Caio Salgado <jaodsilv@linux.ime.usp.br>  
145 -João M. M. da Silva + Alessandro Palmeira + Diego Araújo <jaodsilv@linux.ime.usp.br>  
146 -Joao M. M. da Silva + Alessandro Palmeira <jaodsilv@linux.ime.usp.br>  
147 -João M. M. da Silva + Alessandro Palmeira <jaodsilv@linux.ime.usp.br>  
148 -João M. M. da Silva + Alessandro Palmeira + João Machini <jaodsilv@linux.ime.usp.br>  
149 -João M. M. da Silva + Caio Salgado + Alessandro Palmeira <jaodsilv@linux.ime.usp.br>  
150 -João M. M. da Silva + Caio Salgado <jaodsilv@linux.ime.usp.br>  
151 -João M. M. da Silva + Carlos Morais <jaodsilv@linux.ime.usp.br>  
152 -João M. M. da Silva + Diego Araújo <diegoamc90@gmail.com>  
153 -João M. M. da Silva + Diego Araújo <jaodsilv@linux.ime.usp.br>  
154 -João M. M. da Silva + Diego Araújo + Pedro Leal <jaodsilv@linux.ime.usp.br>  
155 -João M. M. da Silva <jaodsilv@linux.ime.usp.br>  
156 -Joao M. M. da Silva + Jefferson Fernandes <jaodsilv@linux.ime.usp.br>  
157 -João M. M. da Silva + Jefferson Fernandes <jaodsilv@linux.ime.usp.br>  
158 -João M. M. da Silva + João M. Miranda <jaodsilv@linux.ime.usp.br>  
159 -João M. M. da Silva + Paulo Meirelles <jaodsilv@linux.ime.usp.br>  
160 -João M. M. da Silva + Pedro Leal <jaodsilv@linux.ime.usp.br>  
161 -João M. M. da Silva + Rafael Manzo + Diego Araújo <jaodsilv@linux.ime.usp.br>  
162 -João M. M. da Silva + Rafael Manzo <jaodsilv@linux.ime.usp.br>  
163 -João M. M. da Silva + Renan Teruo <jaodsilv@linux.ime.usp.br>  
164 -João M. M. Silva + Caio Salgado <jaodsilv@linux.ime.usp.br>  
165 -João M. M. Silva + Diego Araújo <jaodsilv@linux.ime.usp.br>  
166 -Joao M. M. Silva + Jefferson Fernandes <jaodsilv@linux.ime.usp.br>  
167 -João M. M. Silva + Paulo Meirelles <jaodsilv@linux.ime.usp.br>  
168 -João M. M. Silva + Rafael Manzo <jaodsilv@linux.ime.usp.br>  
169 -João M. M. Silva + Renan Teruo <jaodsilv@linux.ime.usp.br>  
170 -Joenio Costa <joenio@colivre.coop.br>  
171 -Josef Spillner <josef.spillner@tu-dresden.de>  
172 -Junior Silva <junior@bajor.localhost.localdomain>  
173 -Junior Silva <junior@sedeantigo.colivre.coop.br>  
174 -Junior Silva <juniorsilva1001@gmail.com>  
175 -Junior Silva <juniorsilva7@juniorsilva-Aspire-5750Z.(none)>  
176 -Junior Silva <juniorsilva@colivre.coop.br>  
177 -juniorsilva <juniorsilva@QonoS.localhost.localdomain>  
178 -Keilla Menezes <keilla@colivre.coop.br>  
179 -Larissa Reis <larissa@colivre.coop.br>  
180 -Larissa Reis <reiss.larissa@gmail.com>  
181 -Leandro Nunes dos Santos <81665687568@serpro-1541727.Home>  
182 -Leandro Nunes dos Santos <81665687568@serpro-1541727.(none)>  
183 -Leandro Nunes dos Santos <leandronunes@gmail.com>  
184 -Leandro Nunes dos Santos <leandro.santos@serpro.gov.br>  
185 -LinguÁgil 2010 <linguagil.bahia@gmail.com>  
186 -Lucas Melo <lucas@colivre.coop.br>  
187 -Lucas Melo <lucaspradomelo@gmail.com>  
188 -Luciano <lucianopcbr@gmail.com>  
189 -Luis David Aguilar Carlos <ludwig9003@gmail.com>  
190 -Luiz Fernando de Freitas Matos <luiz@luizff.matos@gmail.com>  
191 -Marcos Ramos <ms.ramos@outlook.com>  
192 -Martín Olivera <molivera@solar.org.ar>  
193 -Moises Machado <moises@colivre.coop.br>  
194 -Naíla Alves <naila@colivre.coop.br>  
195 -Nanda Lopes <nanda.listas+psl@gmail.com>  
196 -Paulo Meirelles + Alessandro Palmeira + João M. M. da Silva <paulo@softwarelivre.org>  
197 -Paulo Meirelles + Alessandro Palmeira <paulo@softwarelivre.org>  
198 -Paulo Meirelles + Carlos Morais <paulo@softwarelivre.org>  
199 -Paulo Meirelles + Diego Araújo <paulo@softwarelivre.org>  
200 -Paulo Meirelles + João M. M. da Silva <paulo@softwarelivre.org>  
201 -Paulo Meirelles <paulo@softwarelivre.org>  
202 -Paulo Meirelles + Rafael Manzo <paulo@softwarelivre.org>  
203 -Rafael Gomes <rafaelgomes@techfree.com.br>  
204 -Rafael Manzo + Alessandro Palmeira <rr.manzo@gmail.com>  
205 -Rafael Manzo + Daniel Alves <danpaulalves@gmail.com>  
206 -Rafael Manzo + Diego Araújo <rr.manzo@gmail.com>  
207 -Rafael Manzo + João M. M. Silva <rr.manzo@gmail.com>  
208 -Rafael Manzo + Paulo Meirelles <rr.manzo@gmail.com>  
209 -Rafael Martins <rmmartins@gmail.com>  
210 -Rafael Reggiani Manzo + Caio Salgado + Jefferson Fernandes <rr.manzo@gmail.com>  
211 -Rafael Reggiani Manzo + Diego Araujo <diegoamc90@gmail.com>  
212 -Rafael Reggiani Manzo + Diego Araujo <rr.manzo@gmail.com>  
213 -Rafael Reggiani Manzo + Diego Araújo <rr.manzo@gmail.com>  
214 -Rafael Reggiani Manzo + João M. M. da Silva <rr.manzo@gmail.com>  
215 -Rafael Reggiani Manzo <rr.manzo@gmail.com>  
216 -Raphaël Rousseau <raph@r4f.org>  
217 -Raquel Lira <raquel.lira@gmail.com>  
218 -Renan Teruo + Caio Salgado <renanteruoc@gmail.com>  
219 -Renan Teruoc + Diego Araujo <renanteruoc@gmail.com>  
220 -Renan Teruo + Diego Araujo <renanteruoc@gmail.com>  
221 -Renan Teruo + Diego Araújo <renanteruoc@gmail.com>  
222 -Renan Teruo + Paulo Meirelles <renanteruoc@gmail.com>  
223 -Renan Teruo + Rafael Manzo <renanteruoc@gmail.com>  
224 -Rodrigo Souto + Ana Losnak + Daniel Bucher + Caio Almeida + Leandro Nunes + Daniela Feitosa + Mariel Zasso <noosfero-br@listas.softwarelivre.org>  
225 -Rodrigo Souto <diguliu@gmail.com>  
226 -Rodrigo Souto <rodrigo@colivre.coop.br>  
227 -Ronny Kursawe <kursawe.ronny@googlemail.com>  
228 -root <root@debian.sdr.serpro>  
229 -Samuel R. C. Vale <srcvale@holoscopio.com>  
230 -Valessio Brito <contato@valessiobrito.com.br>  
231 -Valessio Brito <contato@valessiobrito.info>  
232 -Valessio Brito <valessio@gmail.com>  
233 -vfcosta <vfcosta@gmail.com>  
234 -Victor Carvalho <victorhugodf.ac@gmail.com>  
235 -Victor Costa <vfcosta@gmail.com>  
236 -Victor Hugo Alves de Carvalho <victorhugodf.ac@gmail.com>  
237 -Vinicius Cubas Brand <viniciuscb@gmail.com>  
238 -Visita <visita@debian.(none)>  
239 -Yann Lugrin <yann.lugrin@liquid-concept.ch>  
240 -  
241 -Ideas, specifications and incentive  
242 -===================================  
243 -Daniel Tygel <dtygel@fbes.org.br>  
244 -Guilherme Rocha <guilherme@gf7.com.br>  
245 -Raphael Rousseau <raph@r4f.org>  
246 -Théo Bondolfi <move@cooperation.net>  
247 -Vicente Aguiar <vicenteaguiar@colivre.coop.br>  
248 -  
249 -Arts  
250 -===================================  
251 -Nara Oliveira <narananet@gmail.com>  
1 -If you are not listed here, but should be, please write to the noosfero mailing list: http://listas.softwarelivre.org/cgi-bin/mailman/listinfo/noosfero-dev (this list requires subscription to post, but since you are an author of noosfero, that's not a problem). 1 +If you are not listed here, but should be, please write to the noosfero mailing
  2 +list: http://listas.softwarelivre.org/cgi-bin/mailman/listinfo/noosfero-dev
  3 +(this list requires subscription to post, but since you are an author of
  4 +noosfero, that's not a problem).
2 5
3 Developers 6 Developers
4 ========== 7 ==========
5 8
  9 +Ábner Silva de Oliveira <abner.oliveira@serpro.gov.br>
6 Alan Freihof Tygel <alantygel@gmail.com> 10 Alan Freihof Tygel <alantygel@gmail.com>
  11 +alcampelo <alcampelo@alcampelo.(none)>
7 Alessandro Palmeira <alessandro.palmeira@gmail.com> 12 Alessandro Palmeira <alessandro.palmeira@gmail.com>
8 Alessandro Palmeira + Caio C. Salgado <alessandro.palmeira@gmail.com> 13 Alessandro Palmeira + Caio C. Salgado <alessandro.palmeira@gmail.com>
9 Alessandro Palmeira + Caio Salgado <alessandro.palmeira@gmail.com> 14 Alessandro Palmeira + Caio Salgado <alessandro.palmeira@gmail.com>
@@ -35,9 +40,12 @@ Alessandro Palmeira + João M. M. Silva &lt;alessandro.palmeira@gmail.com&gt; @@ -35,9 +40,12 @@ Alessandro Palmeira + João M. M. Silva &lt;alessandro.palmeira@gmail.com&gt;
35 Alessandro Palmeira + Paulo Meirelles <alessandro.palmeira@gmail.com> 40 Alessandro Palmeira + Paulo Meirelles <alessandro.palmeira@gmail.com>
36 Alessandro Palmeira + Paulo Meirelles + João M. M. da Silva <alessandro.palmeira@gmail.com> 41 Alessandro Palmeira + Paulo Meirelles + João M. M. da Silva <alessandro.palmeira@gmail.com>
37 Alessandro Palmeira + Rafael Manzo <alessandro.palmeira@gmail.com> 42 Alessandro Palmeira + Rafael Manzo <alessandro.palmeira@gmail.com>
  43 +Ana Losnak <analosnak@gmail.com>
38 Antonio Terceiro + Carlos Morais <terceiro@colivre.coop.br> 44 Antonio Terceiro + Carlos Morais <terceiro@colivre.coop.br>
39 Antonio Terceiro + Paulo Meirelles <terceiro@colivre.coop.br> 45 Antonio Terceiro + Paulo Meirelles <terceiro@colivre.coop.br>
40 Antonio Terceiro <terceiro@colivre.coop.br> 46 Antonio Terceiro <terceiro@colivre.coop.br>
  47 +Arthur Del Esposte <arthurmde@gmail.com>
  48 +Arthur Del Esposte <arthurmde@yahoo.com.br>
41 Aurelio A. Heckert <aurelio@colivre.coop.br> 49 Aurelio A. Heckert <aurelio@colivre.coop.br>
42 Braulio Bhavamitra <brauliobo@gmail.com> 50 Braulio Bhavamitra <brauliobo@gmail.com>
43 Bráulio Bhavamitra <brauliobo@gmail.com> 51 Bráulio Bhavamitra <brauliobo@gmail.com>
@@ -65,11 +73,14 @@ Caio Salgado + Renan Teruo &lt;caio.salgado@gmail.com&gt; @@ -65,11 +73,14 @@ Caio Salgado + Renan Teruo &lt;caio.salgado@gmail.com&gt;
65 Caio Salgado + Renan Teruo + Jefferson Fernandes <jeffs.fernandes@gmail.com> 73 Caio Salgado + Renan Teruo + Jefferson Fernandes <jeffs.fernandes@gmail.com>
66 Caio Salgado + Renan Teruo <renanteruoc@gmail.com> 74 Caio Salgado + Renan Teruo <renanteruoc@gmail.com>
67 Caio SBA <caio@colivre.coop.br> 75 Caio SBA <caio@colivre.coop.br>
  76 +Caio Tiago Oliveira <caiotiago@colivre.coop.br>
  77 +Carlos Andre de Souza <carlos.andre.souza@msn.com>
68 Carlos Morais <carlos88morais@gmail.com> 78 Carlos Morais <carlos88morais@gmail.com>
69 Carlos Morais + Diego Araújo <diegoamc90@gmail.com> 79 Carlos Morais + Diego Araújo <diegoamc90@gmail.com>
70 Carlos Morais + Eduardo Morais <carlos88morais@gmail.com> 80 Carlos Morais + Eduardo Morais <carlos88morais@gmail.com>
71 Carlos Morais + Paulo Meirelles <carlos88morais@gmail.com> 81 Carlos Morais + Paulo Meirelles <carlos88morais@gmail.com>
72 Carlos Morais + Pedro Leal <carlos88morais@gmail.com> 82 Carlos Morais + Pedro Leal <carlos88morais@gmail.com>
  83 +Daniela Feitosa <dani@dohko.(none)>
73 Daniel Alves + Diego Araújo <danpaulalves@gmail.com> 84 Daniel Alves + Diego Araújo <danpaulalves@gmail.com>
74 Daniel Alves + Diego Araújo <diegoamc90@gmail.com> 85 Daniel Alves + Diego Araújo <diegoamc90@gmail.com>
75 Daniel Alves + Diego Araújo + Guilherme Rojas <danpaulalves@gmail.com> 86 Daniel Alves + Diego Araújo + Guilherme Rojas <danpaulalves@gmail.com>
@@ -78,7 +89,9 @@ Daniel Alves + Diego Araújo + Guilherme Rojas &lt;guilhermehrojas@gmail.com&gt; @@ -78,7 +89,9 @@ Daniel Alves + Diego Araújo + Guilherme Rojas &lt;guilhermehrojas@gmail.com&gt;
78 Daniel Alves + Guilherme Rojas <danpaulalves@gmail.com> 89 Daniel Alves + Guilherme Rojas <danpaulalves@gmail.com>
79 Daniel Alves + Rafael Manzo <rr.manzo@gmail.com> 90 Daniel Alves + Rafael Manzo <rr.manzo@gmail.com>
80 Daniela Soares Feitosa <danielafeitosa@colivre.coop.br> 91 Daniela Soares Feitosa <danielafeitosa@colivre.coop.br>
  92 +Daniel Bucher <daniel.bucher88@gmail.com>
81 Daniel Cunha <daniel@colivre.coop.br> 93 Daniel Cunha <daniel@colivre.coop.br>
  94 +David Carlos <ddavidcarlos1392@gmail.com>
82 diegoamc <diegoamc90@gmail.com> 95 diegoamc <diegoamc90@gmail.com>
83 Diego Araújo + Alessandro Palmeira <diegoamc90@gmail.com> 96 Diego Araújo + Alessandro Palmeira <diegoamc90@gmail.com>
84 Diego Araújo + Alessandro Palmeira + João M. M. da Silva <diegoamc90@gmail.com> 97 Diego Araújo + Alessandro Palmeira + João M. M. da Silva <diegoamc90@gmail.com>
@@ -107,15 +120,25 @@ Diego + Jefferson &lt;diegoamc90@gmail.com&gt; @@ -107,15 +120,25 @@ Diego + Jefferson &lt;diegoamc90@gmail.com&gt;
107 Diego Martinez <diegoamc90@gmail.com> 120 Diego Martinez <diegoamc90@gmail.com>
108 Diego Martinez <diego@diego-K55A.(none)> 121 Diego Martinez <diego@diego-K55A.(none)>
109 Diego + Renan <renanteruoc@gmail.com> 122 Diego + Renan <renanteruoc@gmail.com>
  123 +Eduardo Tourinho Edington <eduardo.edington@serpro.gov.br>
  124 +Evandro Jr <evandrojr@gmail.com>
  125 +Evandro Junior <evandrojr@gmail.com>
  126 +Fabio Teixeira <fabio1079@gmail.com>
110 Fernanda Lopes <nanda.listas+psl@gmail.com> 127 Fernanda Lopes <nanda.listas+psl@gmail.com>
111 Francisco Marcelo A. Lima Júnior <francisco.lima-junior@serpro.gov.br> 128 Francisco Marcelo A. Lima Júnior <francisco.lima-junior@serpro.gov.br>
112 Francisco Marcelo de Araujo Lima Junior <79350259591@serpro-1457614.(none)> 129 Francisco Marcelo de Araujo Lima Junior <79350259591@serpro-1457614.(none)>
  130 +Francisco Marcelo de Araújo Lima Júnior <francisco.lima-junior@serpro.gov.br>
  131 +Francisco Marcelo de Araújo Lima Júnior <maljunior@gmail.com>
  132 +Gabriela Navarro <navarro1703@gmail.com>
113 Grazieno Pellegrino <grazieno@gmail.com> 133 Grazieno Pellegrino <grazieno@gmail.com>
  134 +Gust <darksshades@hotmail.com>
  135 +Hugo Melo <hugo@riseup.net>
114 Isaac Canan <isaac@intelletto.com.br> 136 Isaac Canan <isaac@intelletto.com.br>
115 Italo Valcy <italo@dcc.ufba.br> 137 Italo Valcy <italo@dcc.ufba.br>
116 Jefferson Fernandes + Diego Araujo + Rafael Manzo <jeffs.fernandes@gmail.com> 138 Jefferson Fernandes + Diego Araujo + Rafael Manzo <jeffs.fernandes@gmail.com>
117 Jefferson Fernandes + Joao M. M. da Silva <jeffs.fernandes@gmail.com> 139 Jefferson Fernandes + Joao M. M. da Silva <jeffs.fernandes@gmail.com>
118 Jefferson Fernandes + Joao M. M. Silva <jeffs.fernandes@gmail.com> 140 Jefferson Fernandes + Joao M. M. Silva <jeffs.fernandes@gmail.com>
  141 +João da Silva + Eduardo Morais + Rafael Manzo <rr.manzo@gmail.com>
119 João da Silva <jaodsilv@linux.ime.usp.br> 142 João da Silva <jaodsilv@linux.ime.usp.br>
120 João Marco Maciel da Silva + Rafael Manzo + Renan Teruo <jaodsilv@linux.ime.usp.br> 143 João Marco Maciel da Silva + Rafael Manzo + Renan Teruo <jaodsilv@linux.ime.usp.br>
121 João M. M. da Silva + Alessandro Palmeira + Diego Araújo + Caio Salgado <jaodsilv@linux.ime.usp.br> 144 João M. M. da Silva + Alessandro Palmeira + Diego Araújo + Caio Salgado <jaodsilv@linux.ime.usp.br>
@@ -146,17 +169,29 @@ João M. M. Silva + Rafael Manzo &lt;jaodsilv@linux.ime.usp.br&gt; @@ -146,17 +169,29 @@ João M. M. Silva + Rafael Manzo &lt;jaodsilv@linux.ime.usp.br&gt;
146 João M. M. Silva + Renan Teruo <jaodsilv@linux.ime.usp.br> 169 João M. M. Silva + Renan Teruo <jaodsilv@linux.ime.usp.br>
147 Joenio Costa <joenio@colivre.coop.br> 170 Joenio Costa <joenio@colivre.coop.br>
148 Josef Spillner <josef.spillner@tu-dresden.de> 171 Josef Spillner <josef.spillner@tu-dresden.de>
  172 +Jose Pedro <1jpsneto@gmail.com>
  173 +Junior Silva <junior@bajor.localhost.localdomain>
  174 +Junior Silva <junior@sedeantigo.colivre.coop.br>
149 Junior Silva <juniorsilva1001@gmail.com> 175 Junior Silva <juniorsilva1001@gmail.com>
150 Junior Silva <juniorsilva7@juniorsilva-Aspire-5750Z.(none)> 176 Junior Silva <juniorsilva7@juniorsilva-Aspire-5750Z.(none)>
  177 +Junior Silva <juniorsilva@colivre.coop.br>
  178 +juniorsilva <juniorsilva@QonoS.localhost.localdomain>
151 Keilla Menezes <keilla@colivre.coop.br> 179 Keilla Menezes <keilla@colivre.coop.br>
152 Larissa Reis <larissa@colivre.coop.br> 180 Larissa Reis <larissa@colivre.coop.br>
153 Larissa Reis <reiss.larissa@gmail.com> 181 Larissa Reis <reiss.larissa@gmail.com>
  182 +Leandro Alves <leandrosustenido@gmail.com>
  183 +Leandro Nunes dos Santos <81665687568@serpro-1541727.Home>
  184 +Leandro Nunes dos Santos <81665687568@serpro-1541727.(none)>
154 Leandro Nunes dos Santos <leandronunes@gmail.com> 185 Leandro Nunes dos Santos <leandronunes@gmail.com>
155 Leandro Nunes dos Santos <leandro.santos@serpro.gov.br> 186 Leandro Nunes dos Santos <leandro.santos@serpro.gov.br>
156 LinguÁgil 2010 <linguagil.bahia@gmail.com> 187 LinguÁgil 2010 <linguagil.bahia@gmail.com>
157 Lucas Melo <lucas@colivre.coop.br> 188 Lucas Melo <lucas@colivre.coop.br>
158 Lucas Melo <lucaspradomelo@gmail.com> 189 Lucas Melo <lucaspradomelo@gmail.com>
  190 +Luciano <lucianopcbr@gmail.com>
  191 +Luciano Prestes Cavalcanti <lucianopcbr@gmail.com>
159 Luis David Aguilar Carlos <ludwig9003@gmail.com> 192 Luis David Aguilar Carlos <ludwig9003@gmail.com>
  193 +Luiz Fernando de Freitas Matos <luiz@luizff.matos@gmail.com>
  194 +Marcos Ramos <ms.ramos@outlook.com>
160 Martín Olivera <molivera@solar.org.ar> 195 Martín Olivera <molivera@solar.org.ar>
161 Moises Machado <moises@colivre.coop.br> 196 Moises Machado <moises@colivre.coop.br>
162 Naíla Alves <naila@colivre.coop.br> 197 Naíla Alves <naila@colivre.coop.br>
@@ -189,14 +224,19 @@ Renan Teruo + Diego Araujo &lt;renanteruoc@gmail.com&gt; @@ -189,14 +224,19 @@ Renan Teruo + Diego Araujo &lt;renanteruoc@gmail.com&gt;
189 Renan Teruo + Diego Araújo <renanteruoc@gmail.com> 224 Renan Teruo + Diego Araújo <renanteruoc@gmail.com>
190 Renan Teruo + Paulo Meirelles <renanteruoc@gmail.com> 225 Renan Teruo + Paulo Meirelles <renanteruoc@gmail.com>
191 Renan Teruo + Rafael Manzo <renanteruoc@gmail.com> 226 Renan Teruo + Rafael Manzo <renanteruoc@gmail.com>
  227 +Rodrigo Souto + Ana Losnak + Daniel Bucher + Caio Almeida + Leandro Nunes + Daniela Feitosa + Mariel Zasso <noosfero-br@listas.softwarelivre.org>
192 Rodrigo Souto <diguliu@gmail.com> 228 Rodrigo Souto <diguliu@gmail.com>
193 Rodrigo Souto <rodrigo@colivre.coop.br> 229 Rodrigo Souto <rodrigo@colivre.coop.br>
194 Ronny Kursawe <kursawe.ronny@googlemail.com> 230 Ronny Kursawe <kursawe.ronny@googlemail.com>
195 root <root@debian.sdr.serpro> 231 root <root@debian.sdr.serpro>
196 Samuel R. C. Vale <srcvale@holoscopio.com> 232 Samuel R. C. Vale <srcvale@holoscopio.com>
  233 +Valessio Brito <contato@valessiobrito.com.br>
  234 +Valessio Brito <contato@valessiobrito.info>
197 Valessio Brito <valessio@gmail.com> 235 Valessio Brito <valessio@gmail.com>
198 vfcosta <vfcosta@gmail.com> 236 vfcosta <vfcosta@gmail.com>
  237 +Victor Carvalho <victorhugodf.ac@gmail.com>
199 Victor Costa <vfcosta@gmail.com> 238 Victor Costa <vfcosta@gmail.com>
  239 +Victor Hugo Alves de Carvalho <victorhugodf.ac@gmail.com>
200 Vinicius Cubas Brand <viniciuscb@gmail.com> 240 Vinicius Cubas Brand <viniciuscb@gmail.com>
201 Visita <visita@debian.(none)> 241 Visita <visita@debian.(none)>
202 Yann Lugrin <yann.lugrin@liquid-concept.ch> 242 Yann Lugrin <yann.lugrin@liquid-concept.ch>
@@ -16,6 +16,7 @@ gem &#39;hpricot&#39; @@ -16,6 +16,7 @@ gem &#39;hpricot&#39;
16 gem 'nokogiri' 16 gem 'nokogiri'
17 gem 'rake', :require => false 17 gem 'rake', :require => false
18 gem 'rest-client' 18 gem 'rest-client'
  19 +gem 'exception_notification'
19 20
20 # FIXME list here all actual dependencies (i.e. the ones in debian/control), 21 # FIXME list here all actual dependencies (i.e. the ones in debian/control),
21 # with their GEM names (not the Debian package names) 22 # with their GEM names (not the Debian package names)
@@ -31,7 +32,6 @@ group :test do @@ -31,7 +32,6 @@ group :test do
31 end 32 end
32 33
33 group :cucumber do 34 group :cucumber do
34 - gem 'rake'  
35 gem 'cucumber-rails', :require => false 35 gem 'cucumber-rails', :require => false
36 gem 'capybara' 36 gem 'capybara'
37 gem 'cucumber' 37 gem 'cucumber'
@@ -62,6 +62,9 @@ GEM @@ -62,6 +62,9 @@ GEM
62 diff-lcs (1.1.3) 62 diff-lcs (1.1.3)
63 erubis (2.7.0) 63 erubis (2.7.0)
64 eventmachine (0.12.10) 64 eventmachine (0.12.10)
  65 + exception_notification (4.0.1)
  66 + actionmailer (>= 3.0.4)
  67 + activesupport (>= 3.0.4)
65 fast_gettext (0.6.8) 68 fast_gettext (0.6.8)
66 ffi (1.0.11) 69 ffi (1.0.11)
67 gherkin (2.4.21) 70 gherkin (2.4.21)
@@ -167,6 +170,7 @@ DEPENDENCIES @@ -167,6 +170,7 @@ DEPENDENCIES
167 daemons 170 daemons
168 dalli 171 dalli
169 database_cleaner 172 database_cleaner
  173 + exception_notification
170 fast_gettext 174 fast_gettext
171 hpricot 175 hpricot
172 mocha 176 mocha
app/controllers/admin/categories_controller.rb
@@ -45,9 +45,11 @@ class CategoriesController &lt; AdminController @@ -45,9 +45,11 @@ class CategoriesController &lt; AdminController
45 if request.post? 45 if request.post?
46 @category.update_attributes!(params[:category]) 46 @category.update_attributes!(params[:category])
47 @saved = true 47 @saved = true
  48 + session[:notice] = _("Category %s saved." % @category.name)
48 redirect_to :action => 'index' 49 redirect_to :action => 'index'
49 end 50 end
50 rescue Exception => e 51 rescue Exception => e
  52 + session[:notice] = _('Could not save category.')
51 render :action => 'edit' 53 render :action => 'edit'
52 end 54 end
53 end 55 end
app/controllers/admin/environment_design_controller.rb
@@ -3,9 +3,7 @@ class EnvironmentDesignController &lt; BoxOrganizerController @@ -3,9 +3,7 @@ class EnvironmentDesignController &lt; BoxOrganizerController
3 protect 'edit_environment_design', :environment 3 protect 'edit_environment_design', :environment
4 4
5 def available_blocks 5 def available_blocks
6 - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from  
7 - # the Noosfero core soon, see ActionItem3045  
8 - @available_blocks ||= [ ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock, TagsBlock ] 6 + @available_blocks ||= [ ArticleBlock, LoginBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock, TagsBlock ]
9 @available_blocks += plugins.dispatch(:extra_blocks, :type => Environment) 7 @available_blocks += plugins.dispatch(:extra_blocks, :type => Environment)
10 end 8 end
11 9
app/controllers/admin/features_controller.rb
@@ -51,4 +51,10 @@ class FeaturesController &lt; AdminController @@ -51,4 +51,10 @@ class FeaturesController &lt; AdminController
51 redirect_to :action => 'manage_fields' 51 redirect_to :action => 'manage_fields'
52 end 52 end
53 53
  54 + def search_members
  55 + arg = params[:q].downcase
  56 + result = environment.people.find(:all, :conditions => ['LOWER(name) LIKE ? OR identifier LIKE ?', "%#{arg}%", "%#{arg}%"])
  57 + render :text => prepare_to_token_input(result).to_json
  58 + end
  59 +
54 end 60 end
app/controllers/application_controller.rb
@@ -7,6 +7,12 @@ class ApplicationController &lt; ActionController::Base @@ -7,6 +7,12 @@ class ApplicationController &lt; ActionController::Base
7 before_filter :detect_stuff_by_domain 7 before_filter :detect_stuff_by_domain
8 before_filter :init_noosfero_plugins 8 before_filter :init_noosfero_plugins
9 before_filter :allow_cross_domain_access 9 before_filter :allow_cross_domain_access
  10 + before_filter :login_required, :if => :private_environment?
  11 + before_filter :verify_members_whitelist, :if => [:private_environment?, :user]
  12 +
  13 + def verify_members_whitelist
  14 + render_access_denied unless user.is_admin? || environment.in_whitelist?(user)
  15 + end
10 16
11 after_filter :set_csrf_cookie 17 after_filter :set_csrf_cookie
12 18
@@ -187,4 +193,8 @@ class ApplicationController &lt; ActionController::Base @@ -187,4 +193,8 @@ class ApplicationController &lt; ActionController::Base
187 {:results => scope.paginate(paginate_options)} 193 {:results => scope.paginate(paginate_options)}
188 end 194 end
189 195
  196 + def private_environment?
  197 + @environment.enabled?(:restrict_to_members)
  198 + end
  199 +
190 end 200 end
app/controllers/my_profile/cms_controller.rb
@@ -4,6 +4,12 @@ class CmsController &lt; MyProfileController @@ -4,6 +4,12 @@ class CmsController &lt; MyProfileController
4 4
5 include ArticleHelper 5 include ArticleHelper
6 6
  7 + def search_tags
  8 + arg = params[:term].downcase
  9 + result = ActsAsTaggableOn::Tag.find(:all, :conditions => ['LOWER(name) LIKE ?', "%#{arg}%"])
  10 + render :text => prepare_to_token_input_by_label(result).to_json, :content_type => 'application/json'
  11 + end
  12 +
7 def self.protect_if(*args) 13 def self.protect_if(*args)
8 before_filter(*args) do |c| 14 before_filter(*args) do |c|
9 user, profile = c.send(:user), c.send(:profile) 15 user, profile = c.send(:user), c.send(:profile)
app/controllers/my_profile/memberships_controller.rb
@@ -21,6 +21,9 @@ class MembershipsController &lt; MyProfileController @@ -21,6 +21,9 @@ class MembershipsController &lt; MyProfileController
21 @back_to = params[:back_to] || url_for(:action => 'index') 21 @back_to = params[:back_to] || url_for(:action => 'index')
22 if request.post? && @community.valid? 22 if request.post? && @community.valid?
23 @community = Community.create_after_moderation(user, params[:community].merge({:environment => environment})) 23 @community = Community.create_after_moderation(user, params[:community].merge({:environment => environment}))
  24 + if @community.new_record?
  25 + session[:notice] = _('Your new community creation request will be evaluated by an administrator. You will be notified.')
  26 + end
24 redirect_to @back_to 27 redirect_to @back_to
25 return 28 return
26 end 29 end
app/controllers/my_profile/tasks_controller.rb
@@ -4,6 +4,7 @@ class TasksController &lt; MyProfileController @@ -4,6 +4,7 @@ class TasksController &lt; MyProfileController
4 4
5 def index 5 def index
6 @filter = params[:filter_type].blank? ? nil : params[:filter_type] 6 @filter = params[:filter_type].blank? ? nil : params[:filter_type]
  7 + @task_types = Task.pending_types_for(profile)
7 @tasks = Task.to(profile).without_spam.pending.of(@filter).order_by('created_at', 'asc').paginate(:per_page => Task.per_page, :page => params[:page]) 8 @tasks = Task.to(profile).without_spam.pending.of(@filter).order_by('created_at', 'asc').paginate(:per_page => Task.per_page, :page => params[:page])
8 @failed = params ? params[:failed] : {} 9 @failed = params ? params[:failed] : {}
9 end 10 end
app/controllers/public/account_controller.rb
@@ -15,11 +15,23 @@ class AccountController &lt; ApplicationController @@ -15,11 +15,23 @@ class AccountController &lt; ApplicationController
15 15
16 def activate 16 def activate
17 @user = User.find_by_activation_code(params[:activation_code]) if params[:activation_code] 17 @user = User.find_by_activation_code(params[:activation_code]) if params[:activation_code]
18 - if @user and @user.activate  
19 - @message = _("Your account has been activated, now you can log in!")  
20 - check_redirection  
21 - session[:join] = params[:join] unless params[:join].blank?  
22 - render :action => 'login', :userlogin => @user.login 18 + if @user
  19 + unless @user.environment.enabled?('admin_must_approve_new_users')
  20 + if @user.activate
  21 + @message = _("Your account has been activated, now you can log in!")
  22 + check_redirection
  23 + session[:join] = params[:join] unless params[:join].blank?
  24 + render :action => 'login', :userlogin => @user.login
  25 + end
  26 + else
  27 + if @user.create_moderate_task
  28 + session[:notice] = _('Thanks for registering. The administrators were notified.')
  29 + @register_pending = true
  30 + @user.activation_code = nil
  31 + @user.save!
  32 + redirect_to :controller => :home
  33 + end
  34 + end
23 else 35 else
24 session[:notice] = _("It looks like you're trying to activate an account. Perhaps have already activated this account?") 36 session[:notice] = _("It looks like you're trying to activate an account. Perhaps have already activated this account?")
25 redirect_to :controller => :home 37 redirect_to :controller => :home
@@ -115,6 +127,7 @@ class AccountController &lt; ApplicationController @@ -115,6 +127,7 @@ class AccountController &lt; ApplicationController
115 check_join_in_community(@user) 127 check_join_in_community(@user)
116 go_to_signup_initial_page 128 go_to_signup_initial_page
117 else 129 else
  130 + session[:notice] = _('Thanks for registering!')
118 @register_pending = true 131 @register_pending = true
119 end 132 end
120 end 133 end
app/helpers/application_helper.rb
@@ -1391,4 +1391,14 @@ module ApplicationHelper @@ -1391,4 +1391,14 @@ module ApplicationHelper
1391 content_tag('ul', article.versions.map {|v| link_to("r#{v.version}", @page.url.merge(:version => v.version))}) 1391 content_tag('ul', article.versions.map {|v| link_to("r#{v.version}", @page.url.merge(:version => v.version))})
1392 end 1392 end
1393 1393
  1394 + def labelled_colorpicker_field(human_name, object_name, method, options = {})
  1395 + options[:id] ||= 'text-field-' + FormsHelper.next_id_number
  1396 + content_tag('label', human_name, :for => options[:id], :class => 'formlabel') +
  1397 + colorpicker_field(object_name, method, options.merge(:class => 'colorpicker_field'))
  1398 + end
  1399 +
  1400 + def colorpicker_field(object_name, method, options = {})
  1401 + text_field(object_name, method, options.merge(:class => 'colorpicker_field'))
  1402 + end
  1403 +
1394 end 1404 end
app/helpers/article_helper.rb
@@ -83,6 +83,10 @@ module ArticleHelper @@ -83,6 +83,10 @@ module ArticleHelper
83 array.map { |object| {:id => object.id, :name => object.name} } 83 array.map { |object| {:id => object.id, :name => object.name} }
84 end 84 end
85 85
  86 + def prepare_to_token_input_by_label(array)
  87 + array.map { |object| {:label => object.name, :value => object.name} }
  88 + end
  89 +
86 def cms_label_for_new_children 90 def cms_label_for_new_children
87 _('New article') 91 _('New article')
88 end 92 end
app/helpers/blog_helper.rb
@@ -17,13 +17,13 @@ module BlogHelper @@ -17,13 +17,13 @@ module BlogHelper
17 _('Configure blog') 17 _('Configure blog')
18 end 18 end
19 19
20 - def list_posts(articles, format = 'full') 20 + def list_posts(articles, format = 'full', paginate = true)
21 pagination = will_paginate(articles, { 21 pagination = will_paginate(articles, {
22 :param_name => 'npage', 22 :param_name => 'npage',
23 :previous_label => _('&laquo; Newer posts'), 23 :previous_label => _('&laquo; Newer posts'),
24 :next_label => _('Older posts &raquo;'), 24 :next_label => _('Older posts &raquo;'),
25 :params => {:action=>"view_page", :page=>articles.first.parent.path.split('/'), :controller=>"content_viewer"} 25 :params => {:action=>"view_page", :page=>articles.first.parent.path.split('/'), :controller=>"content_viewer"}
26 - }) if articles.present? 26 + }) if articles.present? && paginate
27 content = [] 27 content = []
28 artic_len = articles.length 28 artic_len = articles.length
29 articles.each_with_index{ |art,i| 29 articles.each_with_index{ |art,i|
app/helpers/categories_helper.rb
1 module CategoriesHelper 1 module CategoriesHelper
2 2
3 -  
4 - COLORS = [  
5 - [ N_('Do not display at the menu'), nil ],  
6 - [ N_('Orange'), 1],  
7 - [ N_('Green'), 2],  
8 - [ N_('Purple'), 3],  
9 - [ N_('Red'), 4],  
10 - [ N_('Dark Green'), 5],  
11 - [ N_('Blue Oil'), 6],  
12 - [ N_('Blue'), 7],  
13 - [ N_('Brown'), 8],  
14 - [ N_('Light Green'), 9],  
15 - [ N_('Light Blue'), 10],  
16 - [ N_('Dark Blue'), 11],  
17 - [ N_('Blue Pool'), 12],  
18 - [ N_('Beige'), 13],  
19 - [ N_('Yellow'), 14],  
20 - [ N_('Light Brown'), 15]  
21 - ]  
22 -  
23 TYPES = [ 3 TYPES = [
24 [ _('General Category'), Category.to_s ], 4 [ _('General Category'), Category.to_s ],
25 [ _('Product Category'), ProductCategory.to_s ], 5 [ _('Product Category'), ProductCategory.to_s ],
26 [ _('Region'), Region.to_s ], 6 [ _('Region'), Region.to_s ],
27 ] 7 ]
28 8
29 - def select_color_for_category  
30 - if @category.top_level?  
31 - labelled_form_field(_('Display at the menu?'), select('category', 'display_color', CategoriesHelper::COLORS.map {|item| [gettext(item[0]), item[1]] }))  
32 - else  
33 - ""  
34 - end  
35 - end  
36 -  
37 - def display_color_for_category(category)  
38 - color = category.display_color  
39 - if color.nil?  
40 - ""  
41 - else  
42 - "[" + gettext(CategoriesHelper::COLORS.find {|item| item[1] == color}.first) + "]"  
43 - end  
44 - end  
45 -  
46 def select_category_type(field) 9 def select_category_type(field)
47 value = params[field] 10 value = params[field]
48 labelled_form_field(_('Type of category'), select_tag('type', options_for_select(TYPES, value))) 11 labelled_form_field(_('Type of category'), select_tag('type', options_for_select(TYPES, value)))
49 end 12 end
50 13
  14 + def category_color_style(category)
  15 + return '' if category.nil? or category.display_color.blank?
  16 + 'background-color: #'+category.display_color+';'
  17 + end
  18 +
51 #FIXME make this test 19 #FIXME make this test
52 def selected_category_link(cat) 20 def selected_category_link(cat)
53 js_remove = "jQuery('#selected-category-#{cat.id}').remove();" 21 js_remove = "jQuery('#selected-category-#{cat.id}').remove();"
app/helpers/layout_helper.rb
@@ -27,6 +27,7 @@ module LayoutHelper @@ -27,6 +27,7 @@ module LayoutHelper
27 'thickbox', 27 'thickbox',
28 'lightbox', 28 'lightbox',
29 'colorbox', 29 'colorbox',
  30 + 'inputosaurus',
30 pngfix_stylesheet_path, 31 pngfix_stylesheet_path,
31 ] + tokeninput_stylesheets 32 ] + tokeninput_stylesheets
32 plugins_stylesheets = @plugins.select(&:stylesheet?).map { |plugin| plugin.class.public_path('style.css') } 33 plugins_stylesheets = @plugins.select(&:stylesheet?).map { |plugin| plugin.class.public_path('style.css') }
app/helpers/token_helper.rb
@@ -18,6 +18,7 @@ module TokenHelper @@ -18,6 +18,7 @@ module TokenHelper
18 options[:on_add] ||= 'null' 18 options[:on_add] ||= 'null'
19 options[:on_delete] ||= 'null' 19 options[:on_delete] ||= 'null'
20 options[:on_ready] ||= 'null' 20 options[:on_ready] ||= 'null'
  21 + options[:query_param] ||= 'q'
21 22
22 result = text_field_tag(name, nil, text_field_options.merge(html_options.merge({:id => element_id}))) 23 result = text_field_tag(name, nil, text_field_options.merge(html_options.merge({:id => element_id})))
23 result += javascript_tag("jQuery('##{element_id}') 24 result += javascript_tag("jQuery('##{element_id}')
@@ -30,7 +31,7 @@ module TokenHelper @@ -30,7 +31,7 @@ module TokenHelper
30 searchDelay: #{options[:search_delay].to_json}, 31 searchDelay: #{options[:search_delay].to_json},
31 preventDuplicates: #{options[:prevent_duplicates].to_json}, 32 preventDuplicates: #{options[:prevent_duplicates].to_json},
32 backspaceDeleteItem: #{options[:backspace_delete_item].to_json}, 33 backspaceDeleteItem: #{options[:backspace_delete_item].to_json},
33 - queryParam: #{name.to_json}, 34 + queryParam: #{options[:query_param].to_json},
34 tokenLimit: #{options[:token_limit].to_json}, 35 tokenLimit: #{options[:token_limit].to_json},
35 onResult: #{options[:on_result]}, 36 onResult: #{options[:on_result]},
36 onAdd: #{options[:on_add]}, 37 onAdd: #{options[:on_add]},
@@ -48,4 +49,4 @@ module TokenHelper @@ -48,4 +49,4 @@ module TokenHelper
48 result 49 result
49 end 50 end
50 51
51 -end  
52 \ No newline at end of file 52 \ No newline at end of file
  53 +end
app/models/box.rb
@@ -28,9 +28,6 @@ class Box &lt; ActiveRecord::Base @@ -28,9 +28,6 @@ class Box &lt; ActiveRecord::Base
28 CategoriesBlock, 28 CategoriesBlock,
29 CommunitiesBlock, 29 CommunitiesBlock,
30 EnterprisesBlock, 30 EnterprisesBlock,
31 - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from  
32 - # the Noosfero core soon, see ActionItem3045  
33 - EnvironmentStatisticsBlock,  
34 FansBlock, 31 FansBlock,
35 FavoriteEnterprisesBlock, 32 FavoriteEnterprisesBlock,
36 FeedReaderBlock, 33 FeedReaderBlock,
@@ -53,9 +50,6 @@ class Box &lt; ActiveRecord::Base @@ -53,9 +50,6 @@ class Box &lt; ActiveRecord::Base
53 CommunitiesBlock, 50 CommunitiesBlock,
54 DisabledEnterpriseMessageBlock, 51 DisabledEnterpriseMessageBlock,
55 EnterprisesBlock, 52 EnterprisesBlock,
56 - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from  
57 - # the Noosfero core soon, see ActionItem3045  
58 - EnvironmentStatisticsBlock,  
59 FansBlock, 53 FansBlock,
60 FavoriteEnterprisesBlock, 54 FavoriteEnterprisesBlock,
61 FeaturedProductsBlock, 55 FeaturedProductsBlock,
app/models/category.rb
@@ -14,9 +14,6 @@ class Category &lt; ActiveRecord::Base @@ -14,9 +14,6 @@ class Category &lt; ActiveRecord::Base
14 validates_uniqueness_of :slug,:scope => [ :environment_id, :parent_id ], :message => N_('{fn} is already being used by another category.').fix_i18n 14 validates_uniqueness_of :slug,:scope => [ :environment_id, :parent_id ], :message => N_('{fn} is already being used by another category.').fix_i18n
15 belongs_to :environment 15 belongs_to :environment
16 16
17 - validates_inclusion_of :display_color, :in => 1..15, :allow_nil => true  
18 - validates_uniqueness_of :display_color, :scope => :environment_id, :if => (lambda { |cat| ! cat.display_color.nil? }), :message => N_('{fn} was already assigned to another category.').fix_i18n  
19 -  
20 # Finds all top level categories for a given environment. 17 # Finds all top level categories for a given environment.
21 scope :top_level_for, lambda { |environment| 18 scope :top_level_for, lambda { |environment|
22 {:conditions => ['parent_id is null and environment_id = ?', environment.id ]} 19 {:conditions => ['parent_id is null and environment_id = ?', environment.id ]}
@@ -42,6 +39,13 @@ class Category &lt; ActiveRecord::Base @@ -42,6 +39,13 @@ class Category &lt; ActiveRecord::Base
42 39
43 acts_as_having_image 40 acts_as_having_image
44 41
  42 + before_save :normalize_display_color
  43 +
  44 + def normalize_display_color
  45 + display_color.gsub!('#', '') if display_color
  46 + display_color = nil if display_color.blank?
  47 + end
  48 +
45 scope :from_types, lambda { |types| 49 scope :from_types, lambda { |types|
46 types.select{ |t| t.blank? }.empty? ? 50 types.select{ |t| t.blank? }.empty? ?
47 { :conditions => { :type => types } } : 51 { :conditions => { :type => types } } :
@@ -101,4 +105,12 @@ class Category &lt; ActiveRecord::Base @@ -101,4 +105,12 @@ class Category &lt; ActiveRecord::Base
101 self.children.find(:all, :conditions => {:display_in_menu => true}).empty? 105 self.children.find(:all, :conditions => {:display_in_menu => true}).empty?
102 end 106 end
103 107
  108 + def with_color
  109 + if display_color.blank?
  110 + parent.nil? ? nil : parent.with_color
  111 + else
  112 + self
  113 + end
  114 + end
  115 +
104 end 116 end
app/models/environment.rb
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 # domains. 3 # domains.
4 class Environment < ActiveRecord::Base 4 class Environment < ActiveRecord::Base
5 5
6 - attr_accessible :name, :is_default, :signup_welcome_text_subject, :signup_welcome_text_body, :terms_of_use, :message_for_disabled_enterprise, :news_amount_by_folder, :default_language, :languages, :description, :organization_approval_method, :enabled_plugins, :enabled_features, :redirection_after_login, :redirection_after_signup, :contact_email, :theme, :reports_lower_bound, :noreply_email, :signup_welcome_screen_body 6 + attr_accessible :name, :is_default, :signup_welcome_text_subject, :signup_welcome_text_body, :terms_of_use, :message_for_disabled_enterprise, :news_amount_by_folder, :default_language, :languages, :description, :organization_approval_method, :enabled_plugins, :enabled_features, :redirection_after_login, :redirection_after_signup, :contact_email, :theme, :reports_lower_bound, :noreply_email, :signup_welcome_screen_body, :members_whitelist_enabled, :members_whitelist
7 7
8 has_many :users 8 has_many :users
9 9
@@ -124,6 +124,7 @@ class Environment &lt; ActiveRecord::Base @@ -124,6 +124,7 @@ class Environment &lt; ActiveRecord::Base
124 'organizations_are_moderated_by_default' => _("Organizations have moderated publication by default"), 124 'organizations_are_moderated_by_default' => _("Organizations have moderated publication by default"),
125 'enable_organization_url_change' => _("Allow organizations to change their URL"), 125 'enable_organization_url_change' => _("Allow organizations to change their URL"),
126 'admin_must_approve_new_communities' => _("Admin must approve creation of communities"), 126 'admin_must_approve_new_communities' => _("Admin must approve creation of communities"),
  127 + 'admin_must_approve_new_users' => _("Admin must approve registration of new users"),
127 'show_balloon_with_profile_links_when_clicked' => _('Show a balloon with profile links when a profile image is clicked'), 128 'show_balloon_with_profile_links_when_clicked' => _('Show a balloon with profile links when a profile image is clicked'),
128 'xmpp_chat' => _('XMPP/Jabber based chat'), 129 'xmpp_chat' => _('XMPP/Jabber based chat'),
129 'show_zoom_button_on_article_images' => _('Show a zoom link on all article images'), 130 'show_zoom_button_on_article_images' => _('Show a zoom link on all article images'),
@@ -132,7 +133,8 @@ class Environment &lt; ActiveRecord::Base @@ -132,7 +133,8 @@ class Environment &lt; ActiveRecord::Base
132 'send_welcome_email_to_new_users' => _('Send welcome e-mail to new users'), 133 'send_welcome_email_to_new_users' => _('Send welcome e-mail to new users'),
133 'allow_change_of_redirection_after_login' => _('Allow users to set the page to redirect after login'), 134 'allow_change_of_redirection_after_login' => _('Allow users to set the page to redirect after login'),
134 'display_my_communities_on_user_menu' => _('Display on menu the list of communities the user can manage'), 135 'display_my_communities_on_user_menu' => _('Display on menu the list of communities the user can manage'),
135 - 'display_my_enterprises_on_user_menu' => _('Display on menu the list of enterprises the user can manage') 136 + 'display_my_enterprises_on_user_menu' => _('Display on menu the list of enterprises the user can manage'),
  137 + 'restrict_to_members' => _('Show content only to members')
136 } 138 }
137 end 139 end
138 140
@@ -175,9 +177,6 @@ class Environment &lt; ActiveRecord::Base @@ -175,9 +177,6 @@ class Environment &lt; ActiveRecord::Base
175 177
176 # "left" area 178 # "left" area
177 env.boxes[1].blocks << LoginBlock.new 179 env.boxes[1].blocks << LoginBlock.new
178 - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from  
179 - # the Noosfero core soon, see ActionItem3045  
180 - env.boxes[1].blocks << EnvironmentStatisticsBlock.new  
181 env.boxes[1].blocks << RecentDocumentsBlock.new 180 env.boxes[1].blocks << RecentDocumentsBlock.new
182 181
183 # "right" area 182 # "right" area
@@ -303,6 +302,17 @@ class Environment &lt; ActiveRecord::Base @@ -303,6 +302,17 @@ class Environment &lt; ActiveRecord::Base
303 settings[:signup_welcome_screen_body].present? 302 settings[:signup_welcome_screen_body].present?
304 end 303 end
305 304
  305 + settings_items :members_whitelist_enabled, :type => :boolean, :default => false
  306 + settings_items :members_whitelist, :type => Array, :default => []
  307 +
  308 + def in_whitelist?(person)
  309 + !members_whitelist_enabled || members_whitelist.include?(person.id)
  310 + end
  311 +
  312 + def members_whitelist=(members)
  313 + settings[:members_whitelist] = members.split(',').map(&:to_i)
  314 + end
  315 +
306 def news_amount_by_folder=(amount) 316 def news_amount_by_folder=(amount)
307 settings[:news_amount_by_folder] = amount.to_i 317 settings[:news_amount_by_folder] = amount.to_i
308 end 318 end
app/models/environment_statistics_block.rb
@@ -1,33 +0,0 @@ @@ -1,33 +0,0 @@
1 -# TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from  
2 -# the Noosfero core soon, see ActionItem3045  
3 -  
4 -class EnvironmentStatisticsBlock < Block  
5 -  
6 - def self.description  
7 - _('Environment stastistics (DEPRECATED)')  
8 - end  
9 -  
10 - def default_title  
11 - _('Statistics for %s') % owner.name  
12 - end  
13 -  
14 - def help  
15 - _('This block presents some statistics about your environment.')  
16 - end  
17 -  
18 - def content(args={})  
19 - users = owner.people.visible.count  
20 - enterprises = owner.enterprises.visible.count  
21 - communities = owner.communities.visible.count  
22 -  
23 - info = []  
24 - info << (n_('One user', '%{num} users', users) % { :num => users })  
25 - unless owner.enabled?('disable_asset_enterprises')  
26 - info << (n_('One enterprise', '%{num} enterprises', enterprises) % { :num => enterprises })  
27 - end  
28 - info << (n_('One community', '%{num} communities', communities) % { :num => communities })  
29 -  
30 - block_title(title) + content_tag('ul', info.map {|item| content_tag('li', item) }.join("\n"))  
31 - end  
32 -  
33 -end  
app/models/moderate_user_registration.rb 0 → 100644
@@ -0,0 +1,59 @@ @@ -0,0 +1,59 @@
  1 +class ModerateUserRegistration < Task
  2 +
  3 + settings_items :user_id, :type => String
  4 + settings_items :name, :type => String
  5 + settings_items :author_name, :type => String
  6 + settings_items :email, :type => String
  7 +
  8 + after_create :schedule_spam_checking
  9 +
  10 + alias :environment :target
  11 + alias :environment= :target=
  12 +
  13 + def schedule_spam_checking
  14 + self.delay.check_for_spam
  15 + end
  16 +
  17 + include Noosfero::Plugin::HotSpot
  18 +
  19 + def sender
  20 + "#{name} (#{email})"
  21 + end
  22 +
  23 + def perform
  24 + user=environment.users.find_by_id(user_id)
  25 + user.activate
  26 + end
  27 +
  28 + def title
  29 + _("New user")
  30 + end
  31 +
  32 + def subject
  33 + name
  34 + end
  35 +
  36 + def information
  37 + { :message => _('%{sender} wants to register.'),
  38 + :variables => {:sender => sender} }
  39 + end
  40 +
  41 + def icon
  42 + result = {:type => :defined_image, :src => '/images/icons-app/person-minor.png', :name => name}
  43 + end
  44 +
  45 + def target_notification_description
  46 + _('%{sender} tried to register.') %
  47 + {:sender => sender}
  48 + end
  49 +
  50 + def target_notification_message
  51 + target_notification_description + "\n\n" +
  52 + _('You need to login on %{system} in order to approve or reject this user.') % { :environment => self.environment }
  53 + end
  54 +
  55 + def target_notification_message
  56 + _("User \"%{user}\" just requested to register. You have to approve or reject it through the \"Pending Validations\" section in your control panel.\n") % { :user => self.name }
  57 + end
  58 +
  59 +end
0 \ No newline at end of file 60 \ No newline at end of file
app/models/task.rb
@@ -73,10 +73,6 @@ class Task &lt; ActiveRecord::Base @@ -73,10 +73,6 @@ class Task &lt; ActiveRecord::Base
73 end 73 end
74 end 74 end
75 75
76 - def self.all_types  
77 - %w[Invitation EnterpriseActivation AddMember Ticket SuggestArticle AddFriend CreateCommunity AbuseComplaint ApproveComment ApproveArticle CreateEnterprise ChangePassword EmailActivation InviteFriend InviteMember]  
78 - end  
79 -  
80 # this method finished the task. It calls #perform, which must be overriden 76 # this method finished the task. It calls #perform, which must be overriden
81 # by subclasses. At the end a message (as returned by #finish_message) is 77 # by subclasses. At the end a message (as returned by #finish_message) is
82 # sent to the requestor with #notify_requestor. 78 # sent to the requestor with #notify_requestor.
@@ -254,6 +250,10 @@ class Task &lt; ActiveRecord::Base @@ -254,6 +250,10 @@ class Task &lt; ActiveRecord::Base
254 { :conditions => [environment_condition, profile_condition].compact.join(' OR ') } 250 { :conditions => [environment_condition, profile_condition].compact.join(' OR ') }
255 } 251 }
256 252
  253 + def self.pending_types_for(profile)
  254 + Task.to(profile).pending.select('distinct type').map { |t| [t.class.name, t.title] }
  255 + end
  256 +
257 def opened? 257 def opened?
258 status == Task::Status::ACTIVE || status == Task::Status::HIDDEN 258 status == Task::Status::ACTIVE || status == Task::Status::HIDDEN
259 end 259 end
app/models/user.rb
@@ -47,8 +47,12 @@ class User &lt; ActiveRecord::Base @@ -47,8 +47,12 @@ class User &lt; ActiveRecord::Base
47 47
48 user.person = p 48 user.person = p
49 end 49 end
50 - if user.environment.enabled?('skip_new_user_email_confirmation')  
51 - user.activate 50 + if user.environment.enabled?('skip_new_user_email_confirmation')
  51 + if user.environment.enabled?('admin_must_approve_new_users')
  52 + create_moderate_task
  53 + else
  54 + user.activate
  55 + end
52 end 56 end
53 end 57 end
54 after_create :deliver_activation_code 58 after_create :deliver_activation_code
@@ -137,6 +141,15 @@ class User &lt; ActiveRecord::Base @@ -137,6 +141,15 @@ class User &lt; ActiveRecord::Base
137 end 141 end
138 end 142 end
139 143
  144 + def create_moderate_task
  145 + @task = ModerateUserRegistration.new
  146 + @task.user_id = self.id
  147 + @task.name = self.name
  148 + @task.email = self.email
  149 + @task.target = self.environment
  150 + @task.save
  151 + end
  152 +
140 def activated? 153 def activated?
141 self.activation_code.nil? && !self.activated_at.nil? 154 self.activation_code.nil? && !self.activated_at.nil?
142 end 155 end
app/sweepers/profile_sweeper.rb
@@ -8,9 +8,6 @@ class ProfileSweeper # &lt; ActiveRecord::Observer @@ -8,9 +8,6 @@ class ProfileSweeper # &lt; ActiveRecord::Observer
8 end 8 end
9 9
10 def after_create(profile) 10 def after_create(profile)
11 - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from  
12 - # the Noosfero core soon, see ActionItem3045  
13 - expire_statistics_block_cache(profile)  
14 end 11 end
15 12
16 protected 13 protected
@@ -31,13 +28,6 @@ protected @@ -31,13 +28,6 @@ protected
31 expire_blogs(profile) if profile.organization? 28 expire_blogs(profile) if profile.organization?
32 end 29 end
33 30
34 - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from  
35 - # the Noosfero core soon, see ActionItem3045  
36 - def expire_statistics_block_cache(profile)  
37 - blocks = profile.environment.blocks.select { |b| b.kind_of?(EnvironmentStatisticsBlock) }  
38 - BlockSweeper.expire_blocks(blocks)  
39 - end  
40 -  
41 def expire_blogs(profile) 31 def expire_blogs(profile)
42 profile.blogs.select{|b| !b.empty?}.each do |blog| 32 profile.blogs.select{|b| !b.empty?}.each do |blog|
43 pages = blog.posts.count / blog.posts_per_page + 1 33 pages = blog.posts.count / blog.posts_per_page + 1
app/views/account/signup.html.erb
@@ -2,18 +2,36 @@ @@ -2,18 +2,36 @@
2 <div id='thanks-for-signing'> 2 <div id='thanks-for-signing'>
3 <% if environment.has_custom_welcome_screen? %> 3 <% if environment.has_custom_welcome_screen? %>
4 <%= environment.settings[:signup_welcome_screen_body].html_safe %> 4 <%= environment.settings[:signup_welcome_screen_body].html_safe %>
5 - <% else %>  
6 - <h1><%= _("Welcome to %s!") % environment.name %></h1>  
7 - <h3><%= _("Thanks for signing up, we're thrilled to have you on our social network!") %></h3>  
8 - <p><%= _("Firstly, some tips for getting started:") %></p>  
9 - <h4><%= _("Confirm your account!") %></h4> 5 + <% elsif environment.enabled?('admin_must_approve_new_users')%>
  6 + <h1><%= _("Welcome to %s!") % environment.name %></h1>
  7 + <h3><%= _("Thanks for signing up, we're thrilled to have you on our social network!") %></h3>
  8 + <p><%= _("Firstly, some tips for getting started:") %></p>
  9 + <% unless environment.enabled?('skip_new_user_email_confirmation') %>
  10 + <h4><%= _("Confirm your account and wait for admin approvement!") %></h4>
10 <p><%= _("You should receive a welcome email from us shortly. Please take a second to follow the link within to confirm your account.") %></p> 11 <p><%= _("You should receive a welcome email from us shortly. Please take a second to follow the link within to confirm your account.") %></p>
11 - <p><%= _("You won't appear as %s until your account is confirmed.") % link_to(_('user'), {:controller => :search, :action => :people, :filter => 'more_recent'}, :target => '_blank') %></p>  
12 - <h4><%= _("What to do next?") %></h4>  
13 - <p><%= _("%s. Upload an avatar and let your friends find you easily :)") % link_to(_('Customize your profile'), {:controller => 'doc', :section => 'user', :topic => 'editing-person-info'}, :target => '_blank') %></p>  
14 - <p><%= _("Learn the guidelines. Read the %s for more details on how to use this social network!") % link_to(_('Documentation'), {:controller => 'doc'}, :target => '_blank') %></p>  
15 - <p><%= _("%s your Gmail, Yahoo and Hotmail contacts!") % link_to(_('Invite and find'), {:controller => 'doc', :section => 'user', :topic => 'invite-contacts'}, :target => '_blank') %></p>  
16 - <p><%= _("Start exploring and have fun!") %></p> 12 + <p><%= _("You won't appear as %s until your account is confirmed and approved.") % link_to(_('user'), {:controller => :search, :action => :people, :filter => 'more_recent'}, :target => '_blank') %></p>
  13 + <% else %>
  14 + <h4><%= _("Wait for admin approvement!") %></h4>
  15 + <p><%= _("The administrators will evaluate your signup request for approvement.") %></p>
  16 + <p><%= _("You won't appear as %s until your account is approved.") % link_to(_('user'), {:controller => :search, :action => :people, :filter => 'more_recent'}, :target => '_blank') %></p>
  17 + <% end %>
  18 + <h4><%= _("What to do next?") %></h4>
  19 + <p><%= _("%s. Upload an avatar and let your friends find you easily :)") % link_to(_('Customize your profile'), {:controller => 'doc', :section => 'user', :topic => 'editing-person-info'}, :target => '_blank') %></p>
  20 + <p><%= _("Learn the guidelines. Read the %s for more details on how to use this social network!") % link_to(_('Documentation'), {:controller => 'doc'}, :target => '_blank') %></p>
  21 + <p><%= _("%s your Gmail, Yahoo and Hotmail contacts!") % link_to(_('Invite and find'), {:controller => 'doc', :section => 'user', :topic => 'invite-contacts'}, :target => '_blank') %></p>
  22 + <p><%= _("Start exploring and have fun!") %></p>
  23 + <% else %>
  24 + <h1><%= _("Welcome to %s!") % environment.name %></h1>
  25 + <h3><%= _("Thanks for signing up, we're thrilled to have you on our social network!") %></h3>
  26 + <p><%= _("Firstly, some tips for getting started:") %></p>
  27 + <h4><%= _("Confirm your account!") %></h4>
  28 + <p><%= _("You should receive a welcome email from us shortly. Please take a second to follow the link within to confirm your account.") %></p>
  29 + <p><%= _("You won't appear as %s until your account is confirmed.") % link_to(_('user'), {:controller => :search, :action => :people, :filter => 'more_recent'}, :target => '_blank') %></p>
  30 + <h4><%= _("What to do next?") %></h4>
  31 + <p><%= _("%s. Upload an avatar and let your friends find you easily :)") % link_to(_('Customize your profile'), {:controller => 'doc', :section => 'user', :topic => 'editing-person-info'}, :target => '_blank') %></p>
  32 + <p><%= _("Learn the guidelines. Read the %s for more details on how to use this social network!") % link_to(_('Documentation'), {:controller => 'doc'}, :target => '_blank') %></p>
  33 + <p><%= _("%s your Gmail, Yahoo and Hotmail contacts!") % link_to(_('Invite and find'), {:controller => 'doc', :section => 'user', :topic => 'invite-contacts'}, :target => '_blank') %></p>
  34 + <p><%= _("Start exploring and have fun!") %></p>
17 <% end %> 35 <% end %>
18 </div> 36 </div>
19 <% else %> 37 <% else %>
app/views/categories/_category.html.erb
1 <li> 1 <li>
2 <div class='treeitem'> 2 <div class='treeitem'>
3 - <%= display_color_for_category(category) %>  
4 - <%= category.name %> 3 + <% unless category_color_style(category).empty? %>
  4 + <span class="color_marker" style="<%= category_color_style(category) %>" ></span>
  5 + <% end %>
  6 + <span><%= category.name %></span>
  7 +
5 <% if category.children.count > 0 %> 8 <% if category.children.count > 0 %>
6 <div class='button' id="category-loading-<%= category.id %>" style="position: relative;"> 9 <div class='button' id="category-loading-<%= category.id %>" style="position: relative;">
7 <a href="#" id="show-button-<%= category.id %>" class="show-button" onclick="return false;" data-category="<%= category.id %>"><%= _('Show') %></a> 10 <a href="#" id="show-button-<%= category.id %>" class="show-button" onclick="return false;" data-category="<%= category.id %>"><%= _('Show') %></a>
app/views/categories/_form.html.erb
  1 +<%= stylesheet_link_tag 'spectrum.css' %>
  2 +<%= javascript_include_tag "spectrum.js" %>
  3 +<%= javascript_include_tag "colorpicker-noosfero.js" %>
  4 +
1 <%= error_messages_for 'category' %> 5 <%= error_messages_for 'category' %>
2 6
3 <%= labelled_form_for 'category', :html => { :multipart => true} do |f| %> 7 <%= labelled_form_for 'category', :html => { :multipart => true} do |f| %>
@@ -13,12 +17,13 @@ @@ -13,12 +17,13 @@
13 <% end %> 17 <% end %>
14 <% end %> 18 <% end %>
15 19
16 - <%= select_color_for_category if !environment.enabled?('disable_categories_menu') %>  
17 -  
18 <%= required f.text_field('name') %> 20 <%= required f.text_field('name') %>
19 21
20 <%= labelled_check_box(_('Display in the menu'), 'category[display_in_menu]', '1', @category.display_in_menu) %> 22 <%= labelled_check_box(_('Display in the menu'), 'category[display_in_menu]', '1', @category.display_in_menu) %>
21 23
  24 + <%= labelled_colorpicker_field(_('Pick a color'), :category, 'display_color' ) unless environment.enabled?('disable_categories_menu')%>
  25 + <span id="color_preview" class = "color_marker" style="<%= category_color_style(@category) %>" ></span>
  26 +
22 <%= f.fields_for :image_builder, @category.image do |i| %> 27 <%= f.fields_for :image_builder, @category.image do |i| %>
23 <%= file_field_or_thumbnail(_('Image:'), @category.image, i) %> 28 <%= file_field_or_thumbnail(_('Image:'), @category.image, i) %>
24 <% end %> 29 <% end %>
app/views/cms/edit.html.erb
@@ -31,9 +31,18 @@ @@ -31,9 +31,18 @@
31 31
32 <%= select_categories(:article, _('Categorize your article')) %> 32 <%= select_categories(:article, _('Categorize your article')) %>
33 33
  34 + <br />
  35 +
34 <%= f.text_field('tag_list', :size => 64) %> 36 <%= f.text_field('tag_list', :size => 64) %>
35 <%= content_tag( 'small', _('Separate tags with commas') ) %> 37 <%= content_tag( 'small', _('Separate tags with commas') ) %>
36 38
  39 + <script>
  40 + jQuery('#article_tag_list').inputosaurus({
  41 + autoCompleteSource: <%= "'/myprofile/#{profile.identifier}/cms/search_tags'," %>
  42 + activateFinalResult : true
  43 + })
  44 + </script>
  45 +
37 <div id='edit-article-options'> 46 <div id='edit-article-options'>
38 <%= options_for_article(@article, @tokenized_children) %> 47 <%= options_for_article(@article, @tokenized_children) %>
39 </div> 48 </div>
app/views/content_viewer/blog_page.html.erb
@@ -9,13 +9,15 @@ @@ -9,13 +9,15 @@
9 </div> 9 </div>
10 <hr class="pre-posts"/> 10 <hr class="pre-posts"/>
11 <div class="blog-posts"> 11 <div class="blog-posts">
  12 + <% paginate = true %>
12 <%= 13 <%=
13 posts = @posts 14 posts = @posts
14 format = blog.visualization_format 15 format = blog.visualization_format
15 if inside_block 16 if inside_block
16 posts = blog.posts.paginate(:page=>1, :per_page=>inside_block.posts_per_page) 17 posts = blog.posts.paginate(:page=>1, :per_page=>inside_block.posts_per_page)
17 format = inside_block.visualization_format 18 format = inside_block.visualization_format
  19 + paginate = false
18 end 20 end
19 - (blog.empty? ? content_tag('em', _('(no posts)')) : list_posts(posts, format)) 21 + (blog.empty? ? content_tag('em', _('(no posts)')) : list_posts(posts, format, paginate))
20 %> 22 %>
21 </div> 23 </div>
app/views/content_viewer/versioned_article.html.erb
@@ -23,7 +23,6 @@ @@ -23,7 +23,6 @@
23 <p id="no-current-version"> 23 <p id="no-current-version">
24 <%= _('This is not the latest version of this content.') %> 24 <%= _('This is not the latest version of this content.') %>
25 </p> 25 </p>
26 -</div>  
27 26
28 <% version_license = @page.version_license(@version) %> 27 <% version_license = @page.version_license(@version) %>
29 <%# This seemingly doubled verification exists because the article-sub-header 28 <%# This seemingly doubled verification exists because the article-sub-header
app/views/features/index.html.erb
@@ -37,6 +37,18 @@ Check all the features you want to enable for your environment, uncheck all the @@ -37,6 +37,18 @@ Check all the features you want to enable for your environment, uncheck all the
37 <%= select_organization_approval_method('environment', 'organization_approval_method') %> 37 <%= select_organization_approval_method('environment', 'organization_approval_method') %>
38 <hr/> 38 <hr/>
39 39
  40 +<h3><%= _('Members Whitelist') %></h3>
  41 + <div class="option">
  42 + <%= check_box :environment, :members_whitelist_enabled %>
  43 + <label><%= _('Enable whitelist') %></label>
  44 + </div>
  45 + <div class="input">
  46 + <div class="info"><%= _('Allow these people to access this environment:') %></div>
  47 + <% tokenized_members = prepare_to_token_input(environment.people.find(:all, :conditions => {:id => environment.members_whitelist})) %>
  48 + <%= token_input_field_tag('environment[members_whitelist]', 'search-members', {:action => 'search_members'}, {:focus => false, :hint_text => _('Type in a search term for a user'), :pre_populate => tokenized_members}) %>
  49 + </div>
  50 +<hr/>
  51 +
40 <div> 52 <div>
41 <% button_bar do %> 53 <% button_bar do %>
42 <%= submit_button('save', _('Save changes')) %> 54 <%= submit_button('save', _('Save changes')) %>
app/views/layouts/_javascript.html.erb
@@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
4 'jquery-ui-1.10.4/js/jquery-ui-1.10.4.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery-validation/jquery.validate', 4 'jquery-ui-1.10.4/js/jquery-ui-1.10.4.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery-validation/jquery.validate',
5 'jquery.cookie', 'jquery.ba-bbq.min.js', 'reflection', 'jquery.tokeninput', 5 'jquery.cookie', 'jquery.ba-bbq.min.js', 'reflection', 'jquery.tokeninput',
6 'add-and-join', 'report-abuse', 'catalog', 'manage-products', 'autogrow', 6 'add-and-join', 'report-abuse', 'catalog', 'manage-products', 'autogrow',
7 -'jquery-timepicker-addon/dist/jquery-ui-timepicker-addon', 'application.js', 'rails.js', :cache => 'cache/application' %> 7 +'jquery-timepicker-addon/dist/jquery-ui-timepicker-addon', 'application.js', 'rails.js', 'inputosaurus.js', :cache => 'cache/application' %>
8 8
9 <% language = FastGettext.locale %> 9 <% language = FastGettext.locale %>
10 <% %w{messages methods}.each do |type| %> 10 <% %w{messages methods}.each do |type| %>
app/views/tasks/index.html.erb
@@ -3,10 +3,9 @@ @@ -3,10 +3,9 @@
3 <h1><%= _("%s's pending tasks") % profile.name %></h1> 3 <h1><%= _("%s's pending tasks") % profile.name %></h1>
4 <p> 4 <p>
5 5
6 -<% type_collection = [[nil, _('All')]] %>  
7 -<% type_collection += Task.all_types.sort_by {|klass| klass.constantize.new.title}.map{|s| [s, s.constantize.new.title] } %>  
8 -  
9 - 6 +<%
  7 + type_collection = [[nil, _('All')]] + @task_types
  8 +%>
10 9
11 <% if !@failed.blank? %> 10 <% if !@failed.blank? %>
12 <div id="errorExplanation"> 11 <div id="errorExplanation">
@@ -39,7 +38,7 @@ @@ -39,7 +38,7 @@
39 38
40 <ul class='task-list'> 39 <ul class='task-list'>
41 <p> 40 <p>
42 - <%= labelled_select(_('Filter')+': ', :filter_type, :first, :last, @filter, type_collection, :onchange => 'document.location.href = "?filter_type="+this.value')%> 41 + <%= labelled_select(_('Filter')+': ', :filter_type, :first, :last, @filter, type_collection, :onchange => "document.location.href = '?filter_type='+this.value") %>
43 </p> 42 </p>
44 <p> 43 <p>
45 <%= labelled_select(_("Set all to: "), 'set-decisions', 'first', 'last', nil, [['',""],['accept',_("Accept")],['reject',_("Reject")],['skip',_("Skip")]], :id => "up-set-all-tasks-to") %> 44 <%= labelled_select(_("Set all to: "), 'set-decisions', 'first', 'last', nil, [['',""],['accept',_("Accept")],['reject',_("Reject")],['skip',_("Skip")]], :id => "up-set-all-tasks-to") %>
baseplugins/statistics 0 → 120000
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +../plugins/statistics
0 \ No newline at end of file 2 \ No newline at end of file
config/initializers/exception_notification.rb
1 unless NOOSFERO_CONF['exception_recipients'].blank? 1 unless NOOSFERO_CONF['exception_recipients'].blank?
2 - require 'noosfero.rb'  
3 - require 'exception_notification.rb'  
4 - ExceptionNotifier.sender_address = "noreply@#{Noosfero.default_hostname}"  
5 - ExceptionNotifier.email_prefix = "[Noosfero ERROR] "  
6 - ExceptionNotifier.exception_recipients = NOOSFERO_CONF['exception_recipients']  
7 - ActionController::Base.send :include, ExceptionNotifiable 2 + Noosfero::Application.config.middleware.use ExceptionNotification::Rack,
  3 + :email => {
  4 + :sender_address => "noreply@#{Noosfero.default_hostname}",
  5 + :email_prefix => "[Noosfero ERROR] ",
  6 + :exception_recipients => NOOSFERO_CONF['exception_recipients']
  7 + }
8 end 8 end
config/initializers/passenger.rb 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +if defined? PhusionPassenger
  2 +
  3 + # from http://russbrooks.com/2010/10/20/rails-cache-memcache-on-passenger-with-smart-spawning
  4 + PhusionPassenger.on_event :starting_worker_process do |forked|
  5 + if forked
  6 + Rails.cache.instance_variable_get(:@data).reset if Rails.cache.class == ActiveSupport::Cache::MemCacheStore
  7 + end
  8 + end
  9 +end
db/migrate/20140724134601_fix_yaml_encoding.rb
@@ -6,6 +6,7 @@ class FixYamlEncoding &lt; ActiveRecord::Migration @@ -6,6 +6,7 @@ class FixYamlEncoding &lt; ActiveRecord::Migration
6 fix_encoding(Profile, 'data') 6 fix_encoding(Profile, 'data')
7 fix_encoding(ActionTracker::Record, 'params') 7 fix_encoding(ActionTracker::Record, 'params')
8 fix_encoding(Article, 'setting') 8 fix_encoding(Article, 'setting')
  9 + fix_encoding(Task, 'data')
9 end 10 end
10 11
11 def self.down 12 def self.down
db/migrate/20140807134625_change_category_display_color_to_string.rb 0 → 100644
@@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
  1 +class ChangeCategoryDisplayColorToString < ActiveRecord::Migration
  2 +
  3 + COLORS = ['ffa500', '00FF00', 'a020f0', 'ff0000', '006400', '191970', '0000ff', 'a52a2a', '32cd32', 'add8e6', '483d8b', 'b8e9ee', 'f5f5dc', 'ffff00', 'f4a460']
  4 +
  5 + def self.up
  6 + change_table :categories do |t|
  7 + t.string :display_color_tmp, :limit => 6
  8 + end
  9 +
  10 + COLORS.each_with_index do |color, i|
  11 + Category.update_all({:display_color_tmp => color}, {:display_color => i+1})
  12 + end
  13 +
  14 + change_table :categories do |t|
  15 + t.remove :display_color
  16 + t.rename :display_color_tmp, :display_color
  17 + end
  18 + end
  19 +
  20 + def self.down
  21 + puts "WARNING: only old defined colors will be reverted"
  22 +
  23 + change_table :categories do |t|
  24 + t.integer :display_color_tmp
  25 + end
  26 +
  27 + COLORS.each_with_index do |color, i|
  28 + Category.update_all({:display_color_tmp => i+1}, {:display_color => color})
  29 + end
  30 +
  31 + change_table :categories do |t|
  32 + t.remove :display_color
  33 + t.rename :display_color_tmp, :display_color
  34 + end
  35 + end
  36 +end
db/migrate/20140827191326_remove_environment_statistics_block.rb 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +class RemoveEnvironmentStatisticsBlock < ActiveRecord::Migration
  2 + def self.up
  3 + update("UPDATE blocks SET type = 'StatisticsBlock' WHERE type = 'EnvironmentStatisticsBlock'")
  4 + end
  5 +
  6 + def self.down
  7 + say("Nothing to undo (cannot recover the data)")
  8 + end
  9 +end
@@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
11 # 11 #
12 # It's strongly recommended to check this file into your version control system. 12 # It's strongly recommended to check this file into your version control system.
13 13
14 -ActiveRecord::Schema.define(:version => 20140808185510) do 14 +ActiveRecord::Schema.define(:version => 20140827191326) do
15 15
16 create_table "abuse_reports", :force => true do |t| 16 create_table "abuse_reports", :force => true do |t|
17 t.integer "reporter_id" 17 t.integer "reporter_id"
@@ -194,19 +194,19 @@ ActiveRecord::Schema.define(:version =&gt; 20140808185510) do @@ -194,19 +194,19 @@ ActiveRecord::Schema.define(:version =&gt; 20140808185510) do
194 create_table "categories", :force => true do |t| 194 create_table "categories", :force => true do |t|
195 t.string "name" 195 t.string "name"
196 t.string "slug" 196 t.string "slug"
197 - t.text "path", :default => ""  
198 - t.integer "display_color" 197 + t.text "path", :default => ""
199 t.integer "environment_id" 198 t.integer "environment_id"
200 t.integer "parent_id" 199 t.integer "parent_id"
201 t.string "type" 200 t.string "type"
202 t.float "lat" 201 t.float "lat"
203 t.float "lng" 202 t.float "lng"
204 - t.boolean "display_in_menu", :default => false  
205 - t.integer "children_count", :default => 0  
206 - t.boolean "accept_products", :default => true 203 + t.boolean "display_in_menu", :default => false
  204 + t.integer "children_count", :default => 0
  205 + t.boolean "accept_products", :default => true
207 t.integer "image_id" 206 t.integer "image_id"
208 t.string "acronym" 207 t.string "acronym"
209 t.string "abbreviation" 208 t.string "abbreviation"
  209 + t.string "display_color", :limit => 6
210 end 210 end
211 211
212 create_table "categories_profiles", :id => false, :force => true do |t| 212 create_table "categories_profiles", :id => false, :force => true do |t|
debian/changelog
  1 +noosfero (1.0~rc1) wheezy-test; urgency=low
  2 +
  3 + * First 1.0 release candidate
  4 +
  5 + -- Rodrigo Souto <vagrant@wheezy-base> Fri, 15 Aug 2014 16:35:35 -0300
  6 +
1 noosfero (0.99.0~rc20140618202455) wheezy-test; urgency=low 7 noosfero (0.99.0~rc20140618202455) wheezy-test; urgency=low
2 8
3 * Another rc with rails3 9 * Another rc with rails3
debian/control
@@ -32,10 +32,10 @@ Package: noosfero @@ -32,10 +32,10 @@ Package: noosfero
32 Architecture: all 32 Architecture: all
33 Depends: 33 Depends:
34 rails3 (>= 3.2.6-1~), 34 rails3 (>= 3.2.6-1~),
35 - ruby,  
36 - ruby1.9.3, 35 + ruby (>= 1:1.9.3),
37 rake, 36 rake,
38 ruby-dalli, 37 ruby-dalli,
  38 + ruby-exception-notification,
39 ruby-fast-gettext, 39 ruby-fast-gettext,
40 ruby-pg, 40 ruby-pg,
41 ruby-rmagick, 41 ruby-rmagick,
1 -AUTHORS 1 +AUTHORS.md
  2 +INSTALL.awstats.md
  3 +INSTALL.chat.md
  4 +INSTALL.email.md
  5 +INSTALL.https.md
  6 +INSTALL.multitenancy.md
  7 +INSTALL.varnish.md
@@ -29,4 +29,4 @@ override_dh_clean: @@ -29,4 +29,4 @@ override_dh_clean:
29 29
30 override_dh_auto_build: 30 override_dh_auto_build:
31 dh_auto_build 31 dh_auto_build
32 - rake noosfero:translations:compile 32 + rake noosfero:translations:compile > /dev/null
features/signup.feature
@@ -298,3 +298,55 @@ Feature: signup @@ -298,3 +298,55 @@ Feature: signup
298 And wait for the captcha signup time 298 And wait for the captcha signup time
299 And I press "Create my account" 299 And I press "Create my account"
300 Then "José da Silva" should be a member of "Free Software" 300 Then "José da Silva" should be a member of "Free Software"
  301 +
  302 + @selenium
  303 + Scenario: user registration is moderated by admin
  304 + Given feature "admin_must_approve_new_users" is enabled on environment
  305 + And feature "skip_new_user_email_confirmation" is disabled on environment
  306 + And I go to /account/signup
  307 + And I fill in "Username" with "teste"
  308 + And I fill in "Password" with "123456"
  309 + And I fill in "Password confirmation" with "123456"
  310 + And I fill in "e-Mail" with "teste@teste.com"
  311 + And I fill in "Full name" with "Teste da Silva"
  312 + And wait for the captcha signup time
  313 + And I press "Create my account"
  314 + And I go to teste's confirmation URL
  315 + And I am logged in as admin
  316 + And I follow "Control panel"
  317 + And I follow "Tasks"
  318 + And I choose "Accept"
  319 + And I press "Apply!"
  320 + And I follow "Logout"
  321 + And Teste da Silva's account is activated
  322 + And I follow "Login"
  323 + And I fill in "Username / Email" with "teste"
  324 + And I fill in "Password" with "123456"
  325 + And I press "Log in"
  326 + Then I should see "teste"
  327 +
  328 +
  329 + @selenium
  330 + Scenario: user registration is not accepted by the admin
  331 + Given feature "admin_must_approve_new_users" is enabled on environment
  332 + And feature "skip_new_user_email_confirmation" is disabled on environment
  333 + And I go to /account/signup
  334 + And I fill in "Username" with "teste"
  335 + And I fill in "Password" with "123456"
  336 + And I fill in "Password confirmation" with "123456"
  337 + And I fill in "e-Mail" with "teste@teste.com"
  338 + And I fill in "Full name" with "Teste da Silva"
  339 + And wait for the captcha signup time
  340 + And I press "Create my account"
  341 + And I go to teste's confirmation URL
  342 + And I am logged in as admin
  343 + And I follow "Control panel"
  344 + And I follow "Tasks"
  345 + And I choose "Reject"
  346 + And I press "Apply!"
  347 + And I follow "Logout"
  348 + And I follow "Login"
  349 + And I fill in "Username / Email" with "teste"
  350 + And I fill in "Password" with "123456"
  351 + And I press "Log in"
  352 + Then I should not see "teste"
301 \ No newline at end of file 353 \ No newline at end of file
features/step_definitions/web_steps.rb
@@ -11,8 +11,14 @@ require File.expand_path(File.join(File.dirname(__FILE__), &quot;..&quot;, &quot;support&quot;, &quot;pat @@ -11,8 +11,14 @@ require File.expand_path(File.join(File.dirname(__FILE__), &quot;..&quot;, &quot;support&quot;, &quot;pat
11 11
12 module WithinHelpers 12 module WithinHelpers
13 def with_scope(locator) 13 def with_scope(locator)
14 - locator = locator ? first(locator) : locator  
15 - locator ? within(locator) { yield } : yield 14 + if locator
  15 + locator = first(locator) || locator
  16 + within(locator) do
  17 + yield
  18 + end
  19 + else
  20 + yield
  21 + end
16 end 22 end
17 end 23 end
18 World(WithinHelpers) 24 World(WithinHelpers)
lib/noosfero.rb
@@ -2,8 +2,6 @@ @@ -2,8 +2,6 @@
2 2
3 require 'fast_gettext' 3 require 'fast_gettext'
4 module Noosfero 4 module Noosfero
5 - PROJECT = 'noosfero'  
6 - VERSION = '0.99.0~rc20140618202455'  
7 5
8 def self.pattern_for_controllers_in_directory(dir) 6 def self.pattern_for_controllers_in_directory(dir)
9 disjunction = controllers_in_directory(dir).join('|') 7 disjunction = controllers_in_directory(dir).join('|')
@@ -95,5 +93,6 @@ module Noosfero @@ -95,5 +93,6 @@ module Noosfero
95 93
96 end 94 end
97 95
  96 +require 'noosfero/version'
98 require 'noosfero/constants' 97 require 'noosfero/constants'
99 require 'noosfero/core_ext' 98 require 'noosfero/core_ext'
lib/noosfero/version.rb 0 → 100644
@@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
  1 +module Noosfero
  2 + PROJECT = 'noosfero'
  3 + VERSION = '1.0~rc1'
  4 +end
lib/tasks/plugins.rake
@@ -7,7 +7,11 @@ namespace :noosfero do @@ -7,7 +7,11 @@ namespace :noosfero do
7 plugin_migration_dirs = Dir.glob(Rails.root.join('{baseplugins,config/plugins}', '*', 'db', 'migrate')) 7 plugin_migration_dirs = Dir.glob(Rails.root.join('{baseplugins,config/plugins}', '*', 'db', 'migrate'))
8 8
9 task :load_config do 9 task :load_config do
10 - dirs = Dir.glob("{baseplugins,config/plugins}/*/db/migrate") 10 + dirs = Dir.glob("{baseplugins,config/plugins}/*").uniq do |dir|
  11 + File.basename(dir)
  12 + end.map do |dir|
  13 + File.join(dir, 'db/migrate')
  14 + end
11 dirs.each do |dir| 15 dirs.each do |dir|
12 ActiveRecord::Migrator.migrations_paths << dir 16 ActiveRecord::Migrator.migrations_paths << dir
13 end 17 end
lib/tasks/release.rake
1 # encoding: UTF-8 1 # encoding: UTF-8
2 2
  3 +require 'noosfero/version'
  4 +$version = Noosfero::VERSION
  5 +
3 namespace :noosfero do 6 namespace :noosfero do
4 7
5 def pendencies_on_authors 8 def pendencies_on_authors
6 - sh "git status | grep 'AUTHORS' > /dev/null" do |ok, res| 9 + sh "git status | grep 'AUTHORS.md' > /dev/null" do |ok, res|
7 return {:ok => !ok, :res => res} 10 return {:ok => !ok, :res => res}
8 end 11 end
9 end 12 end
10 13
11 def pendencies_on_repo 14 def pendencies_on_repo
12 - sh "git status | grep 'nothing.*commit' > /dev/null" do |ok, res| 15 + sh "git status | grep 'nothing.*commit' > /dev/null" do |ok, res|
13 return {:ok => ok, :res => res} 16 return {:ok => ok, :res => res}
14 end 17 end
15 end 18 end
16 19
17 def pendencies_on_public_errors 20 def pendencies_on_public_errors
18 - sh "git status | grep -e '500.html' -e '503.html' > /dev/null" do |ok, res| 21 + sh "git status | grep -e '500.html' -e '503.html' > /dev/null" do |ok, res|
19 return {:ok => !ok, :res => res} 22 return {:ok => !ok, :res => res}
20 end 23 end
21 end 24 end
@@ -40,32 +43,16 @@ namespace :noosfero do @@ -40,32 +43,16 @@ namespace :noosfero do
40 end 43 end
41 end 44 end
42 45
43 - def version  
44 - require 'noosfero'  
45 - Noosfero::VERSION  
46 - end  
47 -  
48 desc 'checks if there is already a tag for the current version' 46 desc 'checks if there is already a tag for the current version'
49 task :check_tag do 47 task :check_tag do
50 - sh "git tag | grep '^#{version}$' >/dev/null" do |ok, res| 48 + sh "git tag | grep '^#{$version}$' >/dev/null" do |ok, res|
51 if ok 49 if ok
52 - raise "******** There is already a tag for version #{version}, cannot continue" 50 + raise "******** There is already a tag for version #{$version}, cannot continue"
53 end 51 end
54 end 52 end
55 - puts "Not found tag for version #{version}, we can go on." 53 + puts "Not found tag for version #{$version}, we can go on."
56 end 54 end
57 55
58 - desc 'checks the version of the Debian package'  
59 - task :check_debian_package do  
60 - debian_version = `dpkg-parsechangelog | grep Version: | cut -d ' ' -f 2`.strip  
61 - unless debian_version =~ /^#{version}/  
62 - puts "Version mismatch: Debian version = #{debian_version}, Noosfero upstream version = #{version}"  
63 - puts "Run `dch -v #{version}` to add a new changelog entry that upgrades the Debian version"  
64 - raise "Version mismatch between noosfero version and debian package version"  
65 - end  
66 - end  
67 -  
68 -  
69 AUTHORS_HEADER = <<EOF 56 AUTHORS_HEADER = <<EOF
70 If you are not listed here, but should be, please write to the noosfero mailing 57 If you are not listed here, but should be, please write to the noosfero mailing
71 list: http://listas.softwarelivre.org/cgi-bin/mailman/listinfo/noosfero-dev 58 list: http://listas.softwarelivre.org/cgi-bin/mailman/listinfo/noosfero-dev
@@ -91,15 +78,15 @@ Arts @@ -91,15 +78,15 @@ Arts
91 Nara Oliveira <narananet@gmail.com> 78 Nara Oliveira <narananet@gmail.com>
92 EOF 79 EOF
93 80
94 - desc 'updates the AUTHORS file' 81 + desc 'updates the authors file'
95 task :authors do 82 task :authors do
96 begin 83 begin
97 - File.open("AUTHORS", 'w') do |output| 84 + File.open("AUTHORS.md", 'w') do |output|
98 output.puts AUTHORS_HEADER 85 output.puts AUTHORS_HEADER
99 output.puts `git log --pretty=format:'%aN <%aE>' | sort | uniq` 86 output.puts `git log --pretty=format:'%aN <%aE>' | sort | uniq`
100 output.puts AUTHORS_FOOTER 87 output.puts AUTHORS_FOOTER
101 end 88 end
102 - commit_changes(['AUTHORS'], 'Updating AUTHORS file') if !pendencies_on_authors[:ok] 89 + commit_changes(['AUTHORS.md'], 'Updating authors file') if !pendencies_on_authors[:ok]
103 rescue Exception => e 90 rescue Exception => e
104 rm_f 'AUTHORS' 91 rm_f 'AUTHORS'
105 raise e 92 raise e
@@ -131,100 +118,113 @@ EOF @@ -131,100 +118,113 @@ EOF
131 end 118 end
132 119
133 desc "uploads the packages to the repository" 120 desc "uploads the packages to the repository"
134 - task :upload_packages, :release_kind do |t, args|  
135 - release_kind = args[:release_kind] || 'stable'  
136 - sh "dput --unchecked #{release_kind} #{Dir['pkg/*.changes'].first}" 121 + task :upload_packages, :target do |t, args|
  122 + target = args[:target] || 'stable'
  123 + sh "dput --unchecked noosfero-#{target} #{Dir['pkg/*.changes'].first}"
137 end 124 end
138 125
139 desc 'sets the new version on apropriate files' 126 desc 'sets the new version on apropriate files'
140 - task :set_version, :release_kind do |t, args| 127 + task :set_version, :target do |t, args|
141 next if File.exist?("tmp/pending-release") 128 next if File.exist?("tmp/pending-release")
142 - release_kind = args[:release_kind] || 'stable'  
143 -  
144 - if release_kind =~ /test/  
145 - version_question = "Release candidate of which version: "  
146 - if release_kind == 'squeeze-test'  
147 - distribution = 'squeeze-test'  
148 - elsif release_kind == 'wheezy-test'  
149 - distribution = 'wheezy-test' 129 + target = args[:target]
  130 +
  131 + new_version = $version.dup
  132 +
  133 + if target =~ /-test$/
  134 + if new_version =~ /~rc\d\+/
  135 + new_version.sub!(/\~rc([0-9]+)/) { "~rc#{$1.to_i + 1}" }
  136 + else
  137 + new_version += '~rc1'
150 end 138 end
151 else 139 else
152 - version_question = "Version that is being released: "  
153 - distribution = 'unstable' 140 + new_version.sub!(/~rc[0-9]+/, '')
154 end 141 end
155 142
156 - version_name = new_version = ask(version_question)  
157 -  
158 - if release_kind =~ /test/  
159 - timestamp = Time.now.strftime('%Y%m%d%H%M%S')  
160 - version_name += "~rc#{timestamp}"  
161 - end 143 + puts "Current version: #{$version}"
  144 + ask("Version to release" % new_version, new_version)
162 release_message = ask("Release message") 145 release_message = ask("Release message")
163 146
164 - sh 'git checkout debian/changelog lib/noosfero.rb'  
165 - sh "sed -i \"s/VERSION = '[^']*'/VERSION = '#{version_name}'/\" lib/noosfero.rb"  
166 - sh "dch --newversion #{version_name} --distribution #{distribution} --force-distribution '#{release_message}'" 147 + sh 'git checkout debian/changelog lib/noosfero/version.rb'
  148 + sh "sed -i \"s/VERSION = '[^']*'/VERSION = '#{new_version}'/\" lib/noosfero/version.rb"
  149 + sh "dch --newversion #{new_version} --distribution #{target} --force-distribution '#{release_message}'"
167 150
168 - sh 'git diff debian/changelog lib/noosfero.rb'  
169 - if confirm("Commit version bump to #{version_name} on #{distribution} distribution")  
170 - sh 'git add debian/changelog lib/noosfero.rb'  
171 - sh "git commit -m 'Bumping version #{version_name}'" 151 + sh 'git diff debian/changelog lib/noosfero/version.rb'
  152 + if confirm("Commit version bump to #{new_version} on #{target} distribution")
  153 + sh 'git add debian/changelog lib/noosfero/version.rb'
  154 + sh "git commit -m 'Bumping version #{new_version}'"
172 sh "touch tmp/pending-release" 155 sh "touch tmp/pending-release"
173 else 156 else
174 - sh 'git checkout debian/changelog lib/noosfero.rb' 157 + sh 'git checkout debian/changelog lib/noosfero/version.rb'
175 abort 'Version update not confirmed. Reverting changes and exiting...' 158 abort 'Version update not confirmed. Reverting changes and exiting...'
176 end 159 end
  160 +
  161 + $version = new_version
  162 + end
  163 +
  164 + task :check_release_deps do
  165 + missing = false
  166 + {
  167 + dput: :dput,
  168 + dch: :devscripts,
  169 + }.each do |program, package|
  170 + if ! system("which #{program} >/dev/null 2>&1")
  171 + puts "Program #{program} missing, install the package #{package}"
  172 + missing = true
  173 + end
  174 + end
  175 + abort if missing
177 end 176 end
178 177
179 desc 'prepares a release tarball' 178 desc 'prepares a release tarball'
180 - task :release, :release_kind do |t, args|  
181 - release_kind = args[:release_kind] || 'stable' 179 + task :release, :target do |t, args|
  180 + target = args[:target]
  181 + if ! target
  182 + abort "Usage: rake noosfero:release[TARGET]"
  183 + end
  184 +
  185 + puts "==> Checking required packages"
  186 + Rake::Task['noosfero:check_release_deps'].invoke
182 187
183 puts "==> Updating authors..." 188 puts "==> Updating authors..."
184 Rake::Task['noosfero:authors'].invoke 189 Rake::Task['noosfero:authors'].invoke
185 190
186 - Rake::Task['noosfero:set_version'].invoke(release_kind)  
187 -  
188 - puts "==> Checking tags..."  
189 - Rake::Task['noosfero:check_tag'].invoke  
190 -  
191 - puts "==> Checking debian package version..."  
192 - Rake::Task['noosfero:check_debian_package'].invoke  
193 -  
194 puts "==> Checking translations..." 191 puts "==> Checking translations..."
195 Rake::Task['noosfero:error-pages:translate'].invoke 192 Rake::Task['noosfero:error-pages:translate'].invoke
196 if !pendencies_on_public_errors[:ok] 193 if !pendencies_on_public_errors[:ok]
197 commit_changes(['public/500.html', 'public/503.html'], 'Updating public error pages') 194 commit_changes(['public/500.html', 'public/503.html'], 'Updating public error pages')
198 end 195 end
199 196
  197 + Rake::Task['noosfero:set_version'].invoke(target)
  198 +
  199 + puts "==> Checking tags..."
  200 + Rake::Task['noosfero:check_tag'].invoke
  201 +
200 puts "==> Checking repository..." 202 puts "==> Checking repository..."
201 Rake::Task['noosfero:check_repo'].invoke 203 Rake::Task['noosfero:check_repo'].invoke
202 204
203 puts "==> Preparing debian packages..." 205 puts "==> Preparing debian packages..."
204 Rake::Task['noosfero:debian_packages'].invoke 206 Rake::Task['noosfero:debian_packages'].invoke
205 - if confirm('Do you want to upload the packages')  
206 - puts "==> Uploading debian packages..."  
207 - Rake::Task['noosfero:upload_packages'].invoke(release_kind)  
208 - end  
209 207
210 - sh "git tag #{version.gsub('~','-')}"  
211 - push_tags = confirm('Push new version tag')  
212 - if push_tags 208 + sh "git tag #{$version.gsub('~','-')}"
  209 + if confirm('Push new version tag')
213 repository = ask('Repository name', 'origin') 210 repository = ask('Repository name', 'origin')
214 puts "==> Uploading tags..." 211 puts "==> Uploading tags..."
215 - sh "git push #{repository} #{version.gsub('~','-')}" 212 + sh "git push #{repository} #{$version.gsub('~','-')}"
216 end 213 end
217 214
218 - sh "rm tmp/pending-release" if Dir["tmp/pending-release"].first.present? 215 + if confirm('Do you want to upload the packages')
  216 + puts "==> Uploading debian packages..."
  217 + Rake::Task['noosfero:upload_packages'].invoke(target)
  218 + else
  219 + puts "I: please upload the package manually!"
  220 + end
219 221
220 - puts "I: please upload the tarball and Debian packages to the website!"  
221 - puts "I: please push the tag for version #{version} that was just created!" if !push_tags  
222 - puts "I: notify the community about this sparkling new version!" 222 + rm_f "rm tmp/pending-release"
223 end 223 end
224 224
225 desc 'Build Debian packages' 225 desc 'Build Debian packages'
226 task :debian_packages => :package do 226 task :debian_packages => :package do
227 - target = "pkg/noosfero-#{Noosfero::VERSION}" 227 + target = "pkg/noosfero-#{$version}"
228 228
229 # base pre-config 229 # base pre-config
230 mkdir "#{target}/tmp" 230 mkdir "#{target}/tmp"
@@ -240,7 +240,7 @@ EOF @@ -240,7 +240,7 @@ EOF
240 desc 'Test Debian package' 240 desc 'Test Debian package'
241 task 'debian:test' => :debian_packages do 241 task 'debian:test' => :debian_packages do
242 Dir.chdir 'pkg' do 242 Dir.chdir 'pkg' do
243 - rm_rf "noosfero-#{Noosfero::VERSION}" 243 + rm_rf "noosfero-#{$version}"
244 sh 'apt-ftparchive packages . > Packages' 244 sh 'apt-ftparchive packages . > Packages'
245 sh 'apt-ftparchive release . > Release' 245 sh 'apt-ftparchive release . > Release'
246 end 246 end
plugins/community_track/lib/community_track_plugin/track_helper.rb
1 module CommunityTrackPlugin::TrackHelper 1 module CommunityTrackPlugin::TrackHelper
2 2
  3 + include CategoriesHelper
  4 +
3 def category_class(track) 5 def category_class(track)
4 'category_' + (track.categories.empty? ? 'not_defined' : track.categories.first.name.to_slug) 6 'category_' + (track.categories.empty? ? 'not_defined' : track.categories.first.name.to_slug)
5 end 7 end
@@ -9,4 +11,13 @@ module CommunityTrackPlugin::TrackHelper @@ -9,4 +11,13 @@ module CommunityTrackPlugin::TrackHelper
9 excerpt(lead_stripped, lead_stripped.first(3), track.image ? 180 : 300) 11 excerpt(lead_stripped, lead_stripped.first(3), track.image ? 180 : 300)
10 end 12 end
11 13
  14 + def track_color_style(track)
  15 + category_color_style(track.categories.first.with_color) if !track.categories.empty?
  16 + end
  17 +
  18 + def track_name_color_style(track)
  19 + category = track.categories.empty? ? nil : track.categories.first.with_color
  20 + category ? "color: ##{category.display_color};" : ''
  21 + end
  22 +
12 end 23 end
plugins/community_track/test/unit/community_track_plugin/track_helper_test.rb
@@ -52,4 +52,16 @@ class TrackHelperTest &lt; ActiveSupport::TestCase @@ -52,4 +52,16 @@ class TrackHelperTest &lt; ActiveSupport::TestCase
52 assert_equal 186, track_card_lead(@track).length 52 assert_equal 186, track_card_lead(@track).length
53 end 53 end
54 54
  55 + should 'return category color if its defined' do
  56 + category1 = fast_create(Category, :name => 'education', :display_color => 'fbfbfb')
  57 + @track.categories << category1
  58 + assert_equal 'background-color: #fbfbfb;', track_color_style(@track)
  59 + end
  60 +
  61 + should 'return category color for track name' do
  62 + category1 = fast_create(Category, :name => 'education', :display_color => 'fbfbfb')
  63 + @track.categories << category1
  64 + assert_equal 'color: #fbfbfb;', track_name_color_style(@track)
  65 + end
  66 +
55 end 67 end
plugins/community_track/views/blocks/_track_card.html.erb
@@ -2,13 +2,13 @@ @@ -2,13 +2,13 @@
2 <div class="item_card <%= category_class(track_card) %>"> 2 <div class="item_card <%= category_class(track_card) %>">
3 <a href="<%= url_for track_card.url %>"> 3 <a href="<%= url_for track_card.url %>">
4 <div class="track_content"> 4 <div class="track_content">
5 - <div class="title"> 5 + <div class="title" style="<%= track_color_style(track_card) %>">
6 <%= track_card.category_name %> 6 <%= track_card.category_name %>
7 </div> 7 </div>
8 <div class="image"> 8 <div class="image">
9 <%= image_tag track_card.image.public_filename if track_card.image %> 9 <%= image_tag track_card.image.public_filename if track_card.image %>
10 </div> 10 </div>
11 - <div class="name"> 11 + <div class="name" style="<%= track_name_color_style(track_card) %>">
12 <%= track_card.name %> 12 <%= track_card.name %>
13 </div> 13 </div>
14 <div class="lead"> 14 <div class="lead">
po/pt/noosfero.po
@@ -13,7 +13,7 @@ msgid &quot;&quot; @@ -13,7 +13,7 @@ msgid &quot;&quot;
13 msgstr "" 13 msgstr ""
14 "Project-Id-Version: noosfero 0.47.1\n" 14 "Project-Id-Version: noosfero 0.47.1\n"
15 "POT-Creation-Date: 2014-06-05 20:27-0000\n" 15 "POT-Creation-Date: 2014-06-05 20:27-0000\n"
16 -"PO-Revision-Date: 2013-07-30 12:53-0300\n" 16 +"PO-Revision-Date: 2014-08-22 11:19-0300\n"
17 "Last-Translator: Rodrigo Souto <rodrigo@colivre.coop.br>\n" 17 "Last-Translator: Rodrigo Souto <rodrigo@colivre.coop.br>\n"
18 "Language-Team: Noosfero Develeopers <noosfero-dev@listas.softwarelivre.org>\n" 18 "Language-Team: Noosfero Develeopers <noosfero-dev@listas.softwarelivre.org>\n"
19 "Language: pt\n" 19 "Language: pt\n"
@@ -469,7 +469,7 @@ msgstr &quot;Local: &quot; @@ -469,7 +469,7 @@ msgstr &quot;Local: &quot;
469 #: app/helpers/boxes_helper.rb:37 app/helpers/boxes_helper.rb:63 469 #: app/helpers/boxes_helper.rb:37 app/helpers/boxes_helper.rb:63
470 #: app/models/main_block.rb:4 470 #: app/models/main_block.rb:4
471 msgid "Main content" 471 msgid "Main content"
472 -msgstr "Gerenciar conteúdo" 472 +msgstr "Conteúdo principal"
473 473
474 #: app/helpers/boxes_helper.rb:100 474 #: app/helpers/boxes_helper.rb:100
475 msgid "This block is invisible. Your visitors will not see it." 475 msgid "This block is invisible. Your visitors will not see it."
@@ -8925,7 +8925,7 @@ msgstr &quot;Boas vindas ao ambiente %s&quot; @@ -8925,7 +8925,7 @@ msgstr &quot;Boas vindas ao ambiente %s&quot;
8925 msgid "" 8925 msgid ""
8926 "Thanks for signing up, we're thrilled to have you on our social network!" 8926 "Thanks for signing up, we're thrilled to have you on our social network!"
8927 msgstr "" 8927 msgstr ""
8928 -"Obrigado por se registrar! Agora verifique seu e-mail para ativar sua conta!" 8928 +"Obrigado por se registrar, estamos empolgados de ter você na nossa rede social!"
8929 8929
8930 #: app/views/account/signup.rhtml:5 8930 #: app/views/account/signup.rhtml:5
8931 msgid "Firstly, some tips for getting started:" 8931 msgid "Firstly, some tips for getting started:"
public/javascripts/add-and-join.js
1 jQuery(function($) { 1 jQuery(function($) {
2 2
3 $(".add-friend").live('click', function(){ 3 $(".add-friend").live('click', function(){
4 - clicked = $(this) 4 + clicked = $(this);
5 url = clicked.attr("href"); 5 url = clicked.attr("href");
6 loading_for_button(this); 6 loading_for_button(this);
7 $.post(url, function(data){ 7 $.post(url, function(data){
@@ -12,7 +12,7 @@ jQuery(function($) { @@ -12,7 +12,7 @@ jQuery(function($) {
12 }) 12 })
13 13
14 $(".join-community").live('click', function(){ 14 $(".join-community").live('click', function(){
15 - clicked = $(".join-community"); 15 + clicked = $(this);
16 url = clicked.attr("href"); 16 url = clicked.attr("href");
17 loading_for_button(this); 17 loading_for_button(this);
18 $.post(url, function(data){ 18 $.post(url, function(data){
@@ -29,7 +29,7 @@ jQuery(function($) { @@ -29,7 +29,7 @@ jQuery(function($) {
29 }) 29 })
30 30
31 $(".leave-community").live('click', function(){ 31 $(".leave-community").live('click', function(){
32 - clicked = $(".leave-community"); 32 + clicked = $(this);
33 url = clicked.attr("href"); 33 url = clicked.attr("href");
34 loading_for_button(this); 34 loading_for_button(this);
35 $.post(url, function(data){ 35 $.post(url, function(data){
public/javascripts/colorpicker-noosfero.js 0 → 100644
@@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
  1 +jQuery(document).ready(function($) {
  2 + $(".colorpicker_field").spectrum({
  3 + showInput: true,
  4 + showInitial: false,
  5 + preferredFormat: "hex",
  6 + allowEmpty: true,
  7 + showPalette: true,
  8 + palette: [
  9 + ["rgb(0, 0, 0)", "rgb(67, 67, 67)", "rgb(102, 102, 102)",
  10 + "rgb(204, 204, 204)", "rgb(217, 217, 217)","rgb(255, 255, 255)"],
  11 + ["rgb(152, 0, 0)", "rgb(255, 0, 0)", "rgb(255, 153, 0)", "rgb(255, 255, 0)", "rgb(0, 255, 0)",
  12 + "rgb(0, 255, 255)", "rgb(74, 134, 232)", "rgb(0, 0, 255)", "rgb(153, 0, 255)", "rgb(255, 0, 255)"],
  13 + ["rgb(230, 184, 175)", "rgb(244, 204, 204)", "rgb(252, 229, 205)", "rgb(255, 242, 204)", "rgb(217, 234, 211)",
  14 + "rgb(208, 224, 227)", "rgb(201, 218, 248)", "rgb(207, 226, 243)", "rgb(217, 210, 233)", "rgb(234, 209, 220)",
  15 + "rgb(221, 126, 107)", "rgb(234, 153, 153)", "rgb(249, 203, 156)", "rgb(255, 229, 153)", "rgb(182, 215, 168)",
  16 + "rgb(162, 196, 201)", "rgb(164, 194, 244)", "rgb(159, 197, 232)", "rgb(180, 167, 214)", "rgb(213, 166, 189)",
  17 + "rgb(204, 65, 37)", "rgb(224, 102, 102)", "rgb(246, 178, 107)", "rgb(255, 217, 102)", "rgb(147, 196, 125)",
  18 + "rgb(118, 165, 175)", "rgb(109, 158, 235)", "rgb(111, 168, 220)", "rgb(142, 124, 195)", "rgb(194, 123, 160)",
  19 + "rgb(166, 28, 0)", "rgb(204, 0, 0)", "rgb(230, 145, 56)", "rgb(241, 194, 50)", "rgb(106, 168, 79)",
  20 + "rgb(69, 129, 142)", "rgb(60, 120, 216)", "rgb(61, 133, 198)", "rgb(103, 78, 167)", "rgb(166, 77, 121)",
  21 + "rgb(91, 15, 0)", "rgb(102, 0, 0)", "rgb(120, 63, 4)", "rgb(127, 96, 0)", "rgb(39, 78, 19)",
  22 + "rgb(12, 52, 61)", "rgb(28, 69, 135)", "rgb(7, 55, 99)", "rgb(32, 18, 77)", "rgb(76, 17, 48)"]
  23 + ]
  24 + });
  25 +});
public/javascripts/inputosaurus.js 0 → 100644
@@ -0,0 +1,523 @@ @@ -0,0 +1,523 @@
  1 +/**
  2 + * Inputosaurus Text
  3 + *
  4 + * Must be instantiated on an <input> element
  5 + * Allows multiple input items. Each item is represented with a removable tag that appears to be inside the input area.
  6 + *
  7 + * @requires:
  8 + *
  9 + * jQuery 1.7+
  10 + * jQueryUI 1.8+ Core
  11 + *
  12 + * @version 0.1.6
  13 + * @author Dan Kielp <dan@sproutsocial.com>
  14 + * @created October 3,2012
  15 + *
  16 + */
  17 +
  18 +
  19 +(function($) {
  20 +
  21 + var inputosaurustext = {
  22 +
  23 + version: "0.1.6",
  24 +
  25 + eventprefix: "inputosaurus",
  26 +
  27 + options: {
  28 +
  29 + // bindable events
  30 + //
  31 + // 'change' - triggered whenever a tag is added or removed (should be similar to binding the the change event of the instantiated input
  32 + // 'keyup' - keyup event on the newly created input
  33 +
  34 + // while typing, the user can separate values using these delimiters
  35 + // the value tags are created on the fly when an inputDelimiter is detected
  36 + inputDelimiters : [',', ';'],
  37 +
  38 + // this separator is used to rejoin all input items back to the value of the original <input>
  39 + outputDelimiter : ',',
  40 +
  41 + allowDuplicates : false,
  42 +
  43 + parseOnBlur : false,
  44 +
  45 + // optional wrapper for widget
  46 + wrapperElement : null,
  47 +
  48 + width : null,
  49 +
  50 + // simply passing an autoComplete source (array, string or function) will instantiate autocomplete functionality
  51 + autoCompleteSource : '',
  52 +
  53 + // When forcing users to select from the autocomplete list, allow them to press 'Enter' to select an item if it's the only option left.
  54 + activateFinalResult : false,
  55 +
  56 + // manipulate and return the input value after parseInput() parsing
  57 + // the array of tag names is passed and expected to be returned as an array after manipulation
  58 + parseHook : null,
  59 +
  60 + // define a placeholder to display when the input is empty
  61 + placeholder: null,
  62 +
  63 + // when you check for duplicates it check for the case
  64 + caseSensitiveDuplicates: false
  65 + },
  66 +
  67 + _create: function() {
  68 + var widget = this,
  69 + els = {},
  70 + o = widget.options,
  71 + placeholder = o.placeholder || this.element.attr('placeholder') || null;
  72 +
  73 + this._chosenValues = [];
  74 +
  75 + // Create the elements
  76 + els.ul = $('<ul class="inputosaurus-container">');
  77 + els.input = $('<input type="text" />');
  78 + els.inputCont = $('<li class="inputosaurus-input inputosaurus-required"></li>');
  79 + els.origInputCont = $('<li class="inputosaurus-input-hidden inputosaurus-required">');
  80 +
  81 + // define starting placeholder
  82 + if (placeholder) {
  83 + o.placeholder = placeholder;
  84 + els.input.attr('placeholder', o.placeholder);
  85 + if (o.width) {
  86 + els.input.css('min-width', o.width - 50);
  87 + }
  88 + }
  89 +
  90 + o.wrapperElement && o.wrapperElement.append(els.ul);
  91 + this.element.replaceWith(o.wrapperElement || els.ul);
  92 + els.origInputCont.append(this.element).hide();
  93 +
  94 + els.inputCont.append(els.input);
  95 + els.ul.append(els.inputCont);
  96 + els.ul.append(els.origInputCont);
  97 +
  98 + o.width && els.ul.css('width', o.width);
  99 +
  100 + this.elements = els;
  101 +
  102 + widget._attachEvents();
  103 +
  104 + // if instantiated input already contains a value, parse that junk
  105 + if($.trim(this.element.val())){
  106 + els.input.val( this.element.val() );
  107 + this.parseInput();
  108 + }
  109 +
  110 + this._instAutocomplete();
  111 + },
  112 +
  113 + _instAutocomplete : function() {
  114 + if(this.options.autoCompleteSource){
  115 + var widget = this;
  116 +
  117 + this.elements.input.autocomplete({
  118 + position : {
  119 + of : this.elements.ul
  120 + },
  121 + source : this.options.autoCompleteSource,
  122 + minLength : 1,
  123 + select : function(ev, ui){
  124 + ev.preventDefault();
  125 + widget.elements.input.val(ui.item.value);
  126 + widget.parseInput();
  127 + },
  128 + open : function() {
  129 + // Older versions of jQueryUI have a different namespace
  130 + var auto = $(this).data('ui-autocomplete') || $(this).data('autocomplete');
  131 + var menu = auto.menu,
  132 + $menuItems;
  133 +
  134 +
  135 + // zIndex will force the element on top of anything (like a dialog it's in)
  136 + menu.element.zIndex && menu.element.zIndex($(this).zIndex() + 1);
  137 + menu.element.width(widget.elements.ul.outerWidth());
  138 +
  139 + // auto-activate the result if it's the only one
  140 + if(widget.options.activateFinalResult){
  141 + $menuItems = menu.element.find('li');
  142 +
  143 + // activate single item to allow selection upon pressing 'Enter'
  144 + if($menuItems.size() === 1){
  145 + menu[menu.activate ? 'activate' : 'focus']($.Event('click'), $menuItems);
  146 + }
  147 + }
  148 + }
  149 + });
  150 + }
  151 + },
  152 +
  153 + _autoCompleteMenuPosition : function() {
  154 + var widget;
  155 + if(this.options.autoCompleteSource){
  156 + widget = this.elements.input.data('ui-autocomplete') || this.elements.input.data('autocomplete');
  157 + widget && widget.menu.element.position({
  158 + of: this.elements.ul,
  159 + my: 'left top',
  160 + at: 'left bottom',
  161 + collision: 'none'
  162 + });
  163 + }
  164 + },
  165 +
  166 + /*_closeAutoCompleteMenu : function() {
  167 + if(this.options.autoCompleteSource){
  168 + this.elements.input.autocomplete('close');
  169 + }
  170 + },*/
  171 +
  172 + parseInput : function(ev) {
  173 + var widget = (ev && ev.data.widget) || this,
  174 + val,
  175 + delimiterFound = false,
  176 + values = [];
  177 +
  178 + val = widget.elements.input.val();
  179 +
  180 + val && (delimiterFound = widget._containsDelimiter(val));
  181 +
  182 + if(delimiterFound !== false){
  183 + values = val.split(delimiterFound);
  184 + } else if(!ev || ev.which === $.ui.keyCode.ENTER && !$('.ui-menu-item.ui-state-focus').size() && !$('.ui-menu-item .ui-state-focus').size() && !$('#ui-active-menuitem').size()){
  185 + values.push(val);
  186 + ev && ev.preventDefault();
  187 +
  188 + // prevent autoComplete menu click from causing a false 'blur'
  189 + } else if(ev.type === 'blur' && !$('#ui-active-menuitem').size()){
  190 + values.push(val);
  191 + }
  192 +
  193 + $.isFunction(widget.options.parseHook) && (values = widget.options.parseHook(values));
  194 +
  195 + if(values.length){
  196 + widget._setChosen(values);
  197 + widget.elements.input.val('');
  198 + widget._resizeInput();
  199 + }
  200 +
  201 + widget._resetPlaceholder();
  202 + },
  203 +
  204 + _inputFocus : function(ev) {
  205 + var widget = ev.data.widget || this;
  206 +
  207 + widget.elements.input.value || (widget.options.autoCompleteSource.length && widget.elements.input.autocomplete('search', ''));
  208 + },
  209 +
  210 + _inputKeypress : function(ev) {
  211 + var widget = ev.data.widget || this;
  212 +
  213 + ev.type === 'keyup' && widget._trigger('keyup', ev, widget);
  214 +
  215 + switch(ev.which){
  216 + case $.ui.keyCode.BACKSPACE:
  217 + ev.type === 'keydown' && widget._inputBackspace(ev);
  218 + break;
  219 +
  220 + case $.ui.keyCode.LEFT:
  221 + ev.type === 'keydown' && widget._inputBackspace(ev);
  222 + break;
  223 +
  224 + default :
  225 + widget.parseInput(ev);
  226 + widget._resizeInput(ev);
  227 + }
  228 +
  229 + // reposition autoComplete menu as <ul> grows and shrinks vertically
  230 + if(widget.options.autoCompleteSource){
  231 + setTimeout(function(){widget._autoCompleteMenuPosition.call(widget);}, 200);
  232 + }
  233 + },
  234 +
  235 + // the input dynamically resizes based on the length of its value
  236 + _resizeInput : function(ev) {
  237 + var widget = (ev && ev.data.widget) || this,
  238 + maxWidth = widget.elements.ul.width(),
  239 + val = widget.elements.input.val(),
  240 + txtWidth = 25 + val.length * 8;
  241 +
  242 + widget.elements.input.width(txtWidth < maxWidth ? txtWidth : maxWidth);
  243 + },
  244 +
  245 + // resets placeholder on representative input
  246 + _resetPlaceholder: function () {
  247 + var placeholder = this.options.placeholder,
  248 + input = this.elements.input,
  249 + width = this.options.width || 'inherit';
  250 + if (placeholder && this.element.val().length === 0) {
  251 + input.attr('placeholder', placeholder).css('min-width', width - 50)
  252 + }else {
  253 + input.attr('placeholder', '').css('min-width', 'inherit')
  254 + }
  255 + },
  256 +
  257 + // if our input contains no value and backspace has been pressed, select the last tag
  258 + _inputBackspace : function(ev) {
  259 + var widget = (ev && ev.data.widget) || this;
  260 + lastTag = widget.elements.ul.find('li:not(.inputosaurus-required):last');
  261 +
  262 + // IE goes back in history if the event isn't stopped
  263 + ev.stopPropagation();
  264 +
  265 + if((!$(ev.currentTarget).val() || (('selectionStart' in ev.currentTarget) && ev.currentTarget.selectionStart === 0 && ev.currentTarget.selectionEnd === 0)) && lastTag.size()){
  266 + ev.preventDefault();
  267 + lastTag.find('a').focus();
  268 + }
  269 +
  270 + },
  271 +
  272 + _editTag : function(ev) {
  273 + var widget = (ev && ev.data.widget) || this,
  274 + tagName = '',
  275 + $closest = $(ev.currentTarget).closest('li'),
  276 + tagKey = $closest.data('ui-inputosaurus') || $closest.data('inputosaurus');
  277 +
  278 + if(!tagKey){
  279 + return true;
  280 + }
  281 +
  282 + ev.preventDefault();
  283 +
  284 + $.each(widget._chosenValues, function(i,v) {
  285 + v.key === tagKey && (tagName = v.value);
  286 + });
  287 +
  288 + widget.elements.input.val(tagName);
  289 +
  290 + widget._removeTag(ev);
  291 + widget._resizeInput(ev);
  292 + },
  293 +
  294 + _tagKeypress : function(ev) {
  295 + var widget = ev.data.widget;
  296 + switch(ev.which){
  297 +
  298 + case $.ui.keyCode.BACKSPACE:
  299 + ev && ev.preventDefault();
  300 + ev && ev.stopPropagation();
  301 + $(ev.currentTarget).trigger('click');
  302 + break;
  303 +
  304 + // 'e' - edit tag (removes tag and places value into visible input
  305 + case 69:
  306 + widget._editTag(ev);
  307 + break;
  308 +
  309 + case $.ui.keyCode.LEFT:
  310 + ev.type === 'keydown' && widget._prevTag(ev);
  311 + break;
  312 +
  313 + case $.ui.keyCode.RIGHT:
  314 + ev.type === 'keydown' && widget._nextTag(ev);
  315 + break;
  316 +
  317 + case $.ui.keyCode.DOWN:
  318 + ev.type === 'keydown' && widget._focus(ev);
  319 + break;
  320 + }
  321 + },
  322 +
  323 + // select the previous tag or input if no more tags exist
  324 + _prevTag : function(ev) {
  325 + var widget = (ev && ev.data.widget) || this,
  326 + tag = $(ev.currentTarget).closest('li'),
  327 + previous = tag.prev();
  328 +
  329 + if(previous.is('li')){
  330 + previous.find('a').focus();
  331 + } else {
  332 + widget._focus();
  333 + }
  334 + },
  335 +
  336 + // select the next tag or input if no more tags exist
  337 + _nextTag : function(ev) {
  338 + var widget = (ev && ev.data.widget) || this,
  339 + tag = $(ev.currentTarget).closest('li'),
  340 + next = tag.next();
  341 +
  342 + if(next.is('li:not(.inputosaurus-input)')){
  343 + next.find('a').focus();
  344 + } else {
  345 + widget._focus();
  346 + }
  347 + },
  348 +
  349 + // return the inputDelimiter that was detected or false if none were found
  350 + _containsDelimiter : function(tagStr) {
  351 +
  352 + var found = false;
  353 +
  354 + $.each(this.options.inputDelimiters, function(k,v) {
  355 + if(tagStr.indexOf(v) !== -1){
  356 + found = v;
  357 + }
  358 + });
  359 +
  360 + return found;
  361 + },
  362 +
  363 + _setChosen : function(valArr) {
  364 + var self = this;
  365 +
  366 + if(!$.isArray(valArr)){
  367 + return false;
  368 + }
  369 +
  370 + $.each(valArr, function(k,v) {
  371 + var exists = false,
  372 + obj = {
  373 + key : '',
  374 + value : ''
  375 + };
  376 +
  377 + v = $.trim(v);
  378 +
  379 + $.each(self._chosenValues, function(kk,vv) {
  380 + if(!self.options.caseSensitiveDuplicates){
  381 + vv.value.toLowerCase() === v.toLowerCase() && (exists = true);
  382 + }
  383 + else{
  384 + vv.value === v && (exists = true);
  385 + }
  386 + });
  387 +
  388 + if(v !== '' && (!exists || self.options.allowDuplicates)){
  389 +
  390 + obj.key = 'mi_' + Math.random().toString( 16 ).slice( 2, 10 );
  391 + obj.value = v;
  392 + self._chosenValues.push(obj);
  393 +
  394 + self._renderTags();
  395 + }
  396 + });
  397 + self._setValue(self._buildValue());
  398 + },
  399 +
  400 + _buildValue : function() {
  401 + var widget = this,
  402 + value = '';
  403 +
  404 + $.each(this._chosenValues, function(k,v) {
  405 + value += value.length ? widget.options.outputDelimiter + v.value : v.value;
  406 + });
  407 +
  408 + return value;
  409 + },
  410 +
  411 + _setValue : function(value) {
  412 + var val = this.element.val();
  413 +
  414 + if(val !== value){
  415 + this.element.val(value);
  416 + this._trigger('change');
  417 + }
  418 + },
  419 +
  420 + // @name text for tag
  421 + // @className optional className for <li>
  422 + _createTag : function(name, key, className) {
  423 + className = className ? ' class="' + className + '"' : '';
  424 +
  425 + if(name !== undefined){
  426 + return $('<li' + className + ' data-inputosaurus="' + key + '"><span>' + name + '</span> <a href="javascript:void(0);" class="ficon">&#x2716;</a></li>');
  427 + }
  428 + },
  429 +
  430 + _renderTags : function() {
  431 + var self = this;
  432 +
  433 + this.elements.ul.find('li:not(.inputosaurus-required)').remove();
  434 +
  435 + $.each(this._chosenValues, function(k,v) {
  436 + var el = self._createTag(v.value, v.key);
  437 + self.elements.ul.find('li.inputosaurus-input').before(el);
  438 + });
  439 + },
  440 +
  441 + _removeTag : function(ev) {
  442 + var $closest = $(ev.currentTarget).closest('li'),
  443 + key = $closest.data('ui-inputosaurus') || $closest.data('inputosaurus'),
  444 + indexFound = false,
  445 + widget = (ev && ev.data.widget) || this;
  446 +
  447 +
  448 + $.each(widget._chosenValues, function(k,v) {
  449 + if(key === v.key){
  450 + indexFound = k;
  451 + }
  452 + });
  453 +
  454 + indexFound !== false && widget._chosenValues.splice(indexFound, 1);
  455 +
  456 + widget._setValue(widget._buildValue());
  457 +
  458 + $(ev.currentTarget).closest('li').remove();
  459 + widget.elements.input.focus();
  460 + },
  461 +
  462 + _focus : function(ev) {
  463 + var widget = (ev && ev.data.widget) || this,
  464 + $closest = $(ev.target).closest('li'),
  465 + $data = $closest.data('ui-inputosaurus') || $closest.data('inputosaurus');
  466 +
  467 + if(!ev || !$data){
  468 + widget.elements.input.focus();
  469 + }
  470 + },
  471 +
  472 + _tagFocus : function(ev) {
  473 + $(ev.currentTarget).parent()[ev.type === 'focusout' ? 'removeClass' : 'addClass']('inputosaurus-selected');
  474 + },
  475 +
  476 + refresh : function() {
  477 + var delim = this.options.outputDelimiter,
  478 + val = this.element.val(),
  479 + values = [];
  480 +
  481 + values.push(val);
  482 + delim && (values = val.split(delim));
  483 +
  484 + if(values.length){
  485 + this._chosenValues = [];
  486 +
  487 + $.isFunction(this.options.parseHook) && (values = this.options.parseHook(values));
  488 +
  489 + this._setChosen(values);
  490 + this._renderTags();
  491 + this.elements.input.val('');
  492 + this._resizeInput();
  493 + }
  494 + },
  495 +
  496 + _attachEvents : function() {
  497 + var widget = this;
  498 +
  499 + this.elements.input.on('keyup.inputosaurus', {widget : widget}, this._inputKeypress);
  500 + this.elements.input.on('keydown.inputosaurus', {widget : widget}, this._inputKeypress);
  501 + this.elements.input.on('change.inputosaurus', {widget : widget}, this._inputKeypress);
  502 + this.elements.input.on('focus.inputosaurus', {widget : widget}, this._inputFocus);
  503 + this.options.parseOnBlur && this.elements.input.on('blur.inputosaurus', {widget : widget}, this.parseInput);
  504 +
  505 + this.elements.ul.on('click.inputosaurus', {widget : widget}, this._focus);
  506 + this.elements.ul.on('click.inputosaurus', 'a', {widget : widget}, this._removeTag);
  507 + this.elements.ul.on('dblclick.inputosaurus', 'li', {widget : widget}, this._editTag);
  508 + this.elements.ul.on('focus.inputosaurus', 'a', {widget : widget}, this._tagFocus);
  509 + this.elements.ul.on('blur.inputosaurus', 'a', {widget : widget}, this._tagFocus);
  510 + this.elements.ul.on('keydown.inputosaurus', 'a', {widget : widget}, this._tagKeypress);
  511 + },
  512 +
  513 + _destroy: function() {
  514 + this.elements.input.unbind('.inputosaurus');
  515 +
  516 + this.elements.ul.replaceWith(this.element);
  517 +
  518 + }
  519 + };
  520 +
  521 + $.widget("ui.inputosaurus", inputosaurustext);
  522 +})(jQuery);
  523 +
public/javascripts/spectrum.js 0 → 100644
@@ -0,0 +1,2259 @@ @@ -0,0 +1,2259 @@
  1 +// Spectrum Colorpicker v1.4.1
  2 +// https://github.com/bgrins/spectrum
  3 +// Author: Brian Grinstead
  4 +// License: MIT
  5 +
  6 +(function (window, $, undefined) {
  7 + "use strict";
  8 +
  9 + var defaultOpts = {
  10 +
  11 + // Callbacks
  12 + beforeShow: noop,
  13 + move: noop,
  14 + change: noop,
  15 + show: noop,
  16 + hide: noop,
  17 +
  18 + // Options
  19 + color: false,
  20 + flat: false,
  21 + showInput: false,
  22 + allowEmpty: false,
  23 + showButtons: true,
  24 + clickoutFiresChange: false,
  25 + showInitial: false,
  26 + showPalette: false,
  27 + showPaletteOnly: false,
  28 + hideAfterPaletteSelect: false,
  29 + togglePaletteOnly: false,
  30 + showSelectionPalette: true,
  31 + localStorageKey: false,
  32 + appendTo: "body",
  33 + maxSelectionSize: 7,
  34 + cancelText: "cancel",
  35 + chooseText: "choose",
  36 + togglePaletteMoreText: "more",
  37 + togglePaletteLessText: "less",
  38 + clearText: "Clear Color Selection",
  39 + noColorSelectedText: "No Color Selected",
  40 + preferredFormat: false,
  41 + className: "", // Deprecated - use containerClassName and replacerClassName instead.
  42 + containerClassName: "",
  43 + replacerClassName: "",
  44 + showAlpha: false,
  45 + theme: "sp-light",
  46 + palette: [["#ffffff", "#000000", "#ff0000", "#ff8000", "#ffff00", "#008000", "#0000ff", "#4b0082", "#9400d3"]],
  47 + selectionPalette: [],
  48 + disabled: false
  49 + },
  50 + spectrums = [],
  51 + IE = !!/msie/i.exec( window.navigator.userAgent ),
  52 + rgbaSupport = (function() {
  53 + function contains( str, substr ) {
  54 + return !!~('' + str).indexOf(substr);
  55 + }
  56 +
  57 + var elem = document.createElement('div');
  58 + var style = elem.style;
  59 + style.cssText = 'background-color:rgba(0,0,0,.5)';
  60 + return contains(style.backgroundColor, 'rgba') || contains(style.backgroundColor, 'hsla');
  61 + })(),
  62 + inputTypeColorSupport = (function() {
  63 + var colorInput = $("<input type='color' value='!' />")[0];
  64 + return colorInput.type === "color" && colorInput.value !== "!";
  65 + })(),
  66 + replaceInput = [
  67 + "<div class='sp-replacer'>",
  68 + "<div class='sp-preview'><div class='sp-preview-inner'></div></div>",
  69 + "<div class='sp-dd'>&#9660;</div>",
  70 + "</div>"
  71 + ].join(''),
  72 + markup = (function () {
  73 +
  74 + // IE does not support gradients with multiple stops, so we need to simulate
  75 + // that for the rainbow slider with 8 divs that each have a single gradient
  76 + var gradientFix = "";
  77 + if (IE) {
  78 + for (var i = 1; i <= 6; i++) {
  79 + gradientFix += "<div class='sp-" + i + "'></div>";
  80 + }
  81 + }
  82 +
  83 + return [
  84 + "<div class='sp-container sp-hidden'>",
  85 + "<div class='sp-palette-container'>",
  86 + "<div class='sp-palette sp-thumb sp-cf'></div>",
  87 + "<div class='sp-palette-button-container sp-cf'>",
  88 + "<button type='button' class='sp-palette-toggle'></button>",
  89 + "</div>",
  90 + "</div>",
  91 + "<div class='sp-picker-container'>",
  92 + "<div class='sp-top sp-cf'>",
  93 + "<div class='sp-fill'></div>",
  94 + "<div class='sp-top-inner'>",
  95 + "<div class='sp-color'>",
  96 + "<div class='sp-sat'>",
  97 + "<div class='sp-val'>",
  98 + "<div class='sp-dragger'></div>",
  99 + "</div>",
  100 + "</div>",
  101 + "</div>",
  102 + "<div class='sp-clear sp-clear-display'>",
  103 + "</div>",
  104 + "<div class='sp-hue'>",
  105 + "<div class='sp-slider'></div>",
  106 + gradientFix,
  107 + "</div>",
  108 + "</div>",
  109 + "<div class='sp-alpha'><div class='sp-alpha-inner'><div class='sp-alpha-handle'></div></div></div>",
  110 + "</div>",
  111 + "<div class='sp-input-container sp-cf'>",
  112 + "<input class='sp-input' type='text' spellcheck='false' />",
  113 + "</div>",
  114 + "<div class='sp-initial sp-thumb sp-cf'></div>",
  115 + "<div class='sp-button-container sp-cf'>",
  116 + "<a class='sp-cancel' href='#'></a>",
  117 + "<button type='button' class='sp-choose'></button>",
  118 + "</div>",
  119 + "</div>",
  120 + "</div>"
  121 + ].join("");
  122 + })();
  123 +
  124 + function paletteTemplate (p, color, className, opts) {
  125 + var html = [];
  126 + for (var i = 0; i < p.length; i++) {
  127 + var current = p[i];
  128 + if(current) {
  129 + var tiny = tinycolor(current);
  130 + var c = tiny.toHsl().l < 0.5 ? "sp-thumb-el sp-thumb-dark" : "sp-thumb-el sp-thumb-light";
  131 + c += (tinycolor.equals(color, current)) ? " sp-thumb-active" : "";
  132 + var formattedString = tiny.toString(opts.preferredFormat || "rgb");
  133 + var swatchStyle = rgbaSupport ? ("background-color:" + tiny.toRgbString()) : "filter:" + tiny.toFilter();
  134 + html.push('<span title="' + formattedString + '" data-color="' + tiny.toRgbString() + '" class="' + c + '"><span class="sp-thumb-inner" style="' + swatchStyle + ';" /></span>');
  135 + } else {
  136 + var cls = 'sp-clear-display';
  137 + html.push($('<div />')
  138 + .append($('<span data-color="" style="background-color:transparent;" class="' + cls + '"></span>')
  139 + .attr('title', opts.noColorSelectedText)
  140 + )
  141 + .html()
  142 + );
  143 + }
  144 + }
  145 + return "<div class='sp-cf " + className + "'>" + html.join('') + "</div>";
  146 + }
  147 +
  148 + function hideAll() {
  149 + for (var i = 0; i < spectrums.length; i++) {
  150 + if (spectrums[i]) {
  151 + spectrums[i].hide();
  152 + }
  153 + }
  154 + }
  155 +
  156 + function instanceOptions(o, callbackContext) {
  157 + var opts = $.extend({}, defaultOpts, o);
  158 + opts.callbacks = {
  159 + 'move': bind(opts.move, callbackContext),
  160 + 'change': bind(opts.change, callbackContext),
  161 + 'show': bind(opts.show, callbackContext),
  162 + 'hide': bind(opts.hide, callbackContext),
  163 + 'beforeShow': bind(opts.beforeShow, callbackContext)
  164 + };
  165 +
  166 + return opts;
  167 + }
  168 +
  169 + function spectrum(element, o) {
  170 +
  171 + var opts = instanceOptions(o, element),
  172 + flat = opts.flat,
  173 + showSelectionPalette = opts.showSelectionPalette,
  174 + localStorageKey = opts.localStorageKey,
  175 + theme = opts.theme,
  176 + callbacks = opts.callbacks,
  177 + resize = throttle(reflow, 10),
  178 + visible = false,
  179 + dragWidth = 0,
  180 + dragHeight = 0,
  181 + dragHelperHeight = 0,
  182 + slideHeight = 0,
  183 + slideWidth = 0,
  184 + alphaWidth = 0,
  185 + alphaSlideHelperWidth = 0,
  186 + slideHelperHeight = 0,
  187 + currentHue = 0,
  188 + currentSaturation = 0,
  189 + currentValue = 0,
  190 + currentAlpha = 1,
  191 + palette = [],
  192 + paletteArray = [],
  193 + paletteLookup = {},
  194 + selectionPalette = opts.selectionPalette.slice(0),
  195 + maxSelectionSize = opts.maxSelectionSize,
  196 + draggingClass = "sp-dragging",
  197 + shiftMovementDirection = null;
  198 +
  199 + var doc = element.ownerDocument,
  200 + body = doc.body,
  201 + boundElement = $(element),
  202 + disabled = false,
  203 + container = $(markup, doc).addClass(theme),
  204 + pickerContainer = container.find(".sp-picker-container"),
  205 + dragger = container.find(".sp-color"),
  206 + dragHelper = container.find(".sp-dragger"),
  207 + slider = container.find(".sp-hue"),
  208 + slideHelper = container.find(".sp-slider"),
  209 + alphaSliderInner = container.find(".sp-alpha-inner"),
  210 + alphaSlider = container.find(".sp-alpha"),
  211 + alphaSlideHelper = container.find(".sp-alpha-handle"),
  212 + textInput = container.find(".sp-input"),
  213 + paletteContainer = container.find(".sp-palette"),
  214 + initialColorContainer = container.find(".sp-initial"),
  215 + cancelButton = container.find(".sp-cancel"),
  216 + clearButton = container.find(".sp-clear"),
  217 + chooseButton = container.find(".sp-choose"),
  218 + toggleButton = container.find(".sp-palette-toggle"),
  219 + isInput = boundElement.is("input"),
  220 + isInputTypeColor = isInput && inputTypeColorSupport && boundElement.attr("type") === "color",
  221 + shouldReplace = isInput && !flat,
  222 + replacer = (shouldReplace) ? $(replaceInput).addClass(theme).addClass(opts.className).addClass(opts.replacerClassName) : $([]),
  223 + offsetElement = (shouldReplace) ? replacer : boundElement,
  224 + previewElement = replacer.find(".sp-preview-inner"),
  225 + initialColor = opts.color || (isInput && boundElement.val()),
  226 + colorOnShow = false,
  227 + preferredFormat = opts.preferredFormat,
  228 + currentPreferredFormat = preferredFormat,
  229 + clickoutFiresChange = !opts.showButtons || opts.clickoutFiresChange,
  230 + isEmpty = !initialColor,
  231 + allowEmpty = opts.allowEmpty && !isInputTypeColor;
  232 +
  233 + function applyOptions() {
  234 +
  235 + if (opts.showPaletteOnly) {
  236 + opts.showPalette = true;
  237 + }
  238 +
  239 + toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText);
  240 +
  241 + if (opts.palette) {
  242 + palette = opts.palette.slice(0);
  243 + paletteArray = $.isArray(palette[0]) ? palette : [palette];
  244 + paletteLookup = {};
  245 + for (var i = 0; i < paletteArray.length; i++) {
  246 + for (var j = 0; j < paletteArray[i].length; j++) {
  247 + var rgb = tinycolor(paletteArray[i][j]).toRgbString();
  248 + paletteLookup[rgb] = true;
  249 + }
  250 + }
  251 + }
  252 +
  253 + container.toggleClass("sp-flat", flat);
  254 + container.toggleClass("sp-input-disabled", !opts.showInput);
  255 + container.toggleClass("sp-alpha-enabled", opts.showAlpha);
  256 + container.toggleClass("sp-clear-enabled", allowEmpty);
  257 + container.toggleClass("sp-buttons-disabled", !opts.showButtons);
  258 + container.toggleClass("sp-palette-buttons-disabled", !opts.togglePaletteOnly);
  259 + container.toggleClass("sp-palette-disabled", !opts.showPalette);
  260 + container.toggleClass("sp-palette-only", opts.showPaletteOnly);
  261 + container.toggleClass("sp-initial-disabled", !opts.showInitial);
  262 + container.addClass(opts.className).addClass(opts.containerClassName);
  263 +
  264 + reflow();
  265 + }
  266 +
  267 + function initialize() {
  268 +
  269 + if (IE) {
  270 + container.find("*:not(input)").attr("unselectable", "on");
  271 + }
  272 +
  273 + applyOptions();
  274 +
  275 + if (shouldReplace) {
  276 + boundElement.after(replacer).hide();
  277 + }
  278 +
  279 + if (!allowEmpty) {
  280 + clearButton.hide();
  281 + }
  282 +
  283 + if (flat) {
  284 + boundElement.after(container).hide();
  285 + }
  286 + else {
  287 +
  288 + var appendTo = opts.appendTo === "parent" ? boundElement.parent() : $(opts.appendTo);
  289 + if (appendTo.length !== 1) {
  290 + appendTo = $("body");
  291 + }
  292 +
  293 + appendTo.append(container);
  294 + }
  295 +
  296 + updateSelectionPaletteFromStorage();
  297 +
  298 + offsetElement.bind("click.spectrum touchstart.spectrum", function (e) {
  299 + if (!disabled) {
  300 + toggle();
  301 + }
  302 +
  303 + e.stopPropagation();
  304 +
  305 + if (!$(e.target).is("input")) {
  306 + e.preventDefault();
  307 + }
  308 + });
  309 +
  310 + if(boundElement.is(":disabled") || (opts.disabled === true)) {
  311 + disable();
  312 + }
  313 +
  314 + // Prevent clicks from bubbling up to document. This would cause it to be hidden.
  315 + container.click(stopPropagation);
  316 +
  317 + // Handle user typed input
  318 + textInput.change(setFromTextInput);
  319 + textInput.bind("paste", function () {
  320 + setTimeout(setFromTextInput, 1);
  321 + });
  322 + textInput.keydown(function (e) { if (e.keyCode == 13) { setFromTextInput(); } });
  323 +
  324 + cancelButton.text(opts.cancelText);
  325 + cancelButton.bind("click.spectrum", function (e) {
  326 + e.stopPropagation();
  327 + e.preventDefault();
  328 + hide("cancel");
  329 + });
  330 +
  331 + clearButton.attr("title", opts.clearText);
  332 + clearButton.bind("click.spectrum", function (e) {
  333 + e.stopPropagation();
  334 + e.preventDefault();
  335 + isEmpty = true;
  336 + move();
  337 +
  338 + if(flat) {
  339 + //for the flat style, this is a change event
  340 + updateOriginalInput(true);
  341 + }
  342 + });
  343 +
  344 + chooseButton.text(opts.chooseText);
  345 + chooseButton.bind("click.spectrum", function (e) {
  346 + e.stopPropagation();
  347 + e.preventDefault();
  348 +
  349 + if (isValid()) {
  350 + updateOriginalInput(true);
  351 + hide();
  352 + }
  353 + });
  354 +
  355 + toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText);
  356 + toggleButton.bind("click.spectrum", function (e) {
  357 + e.stopPropagation();
  358 + e.preventDefault();
  359 +
  360 + opts.showPaletteOnly = !opts.showPaletteOnly;
  361 +
  362 + // To make sure the Picker area is drawn on the right, next to the
  363 + // Palette area (and not below the palette), first move the Palette
  364 + // to the left to make space for the picker, plus 5px extra.
  365 + // The 'applyOptions' function puts the whole container back into place
  366 + // and takes care of the button-text and the sp-palette-only CSS class.
  367 + if (!opts.showPaletteOnly && !flat) {
  368 + container.css('left', '-=' + (pickerContainer.outerWidth(true) + 5));
  369 + }
  370 + applyOptions();
  371 + });
  372 +
  373 + draggable(alphaSlider, function (dragX, dragY, e) {
  374 + currentAlpha = (dragX / alphaWidth);
  375 + isEmpty = false;
  376 + if (e.shiftKey) {
  377 + currentAlpha = Math.round(currentAlpha * 10) / 10;
  378 + }
  379 +
  380 + move();
  381 + }, dragStart, dragStop);
  382 +
  383 + draggable(slider, function (dragX, dragY) {
  384 + currentHue = parseFloat(dragY / slideHeight);
  385 + isEmpty = false;
  386 + if (!opts.showAlpha) {
  387 + currentAlpha = 1;
  388 + }
  389 + move();
  390 + }, dragStart, dragStop);
  391 +
  392 + draggable(dragger, function (dragX, dragY, e) {
  393 +
  394 + // shift+drag should snap the movement to either the x or y axis.
  395 + if (!e.shiftKey) {
  396 + shiftMovementDirection = null;
  397 + }
  398 + else if (!shiftMovementDirection) {
  399 + var oldDragX = currentSaturation * dragWidth;
  400 + var oldDragY = dragHeight - (currentValue * dragHeight);
  401 + var furtherFromX = Math.abs(dragX - oldDragX) > Math.abs(dragY - oldDragY);
  402 +
  403 + shiftMovementDirection = furtherFromX ? "x" : "y";
  404 + }
  405 +
  406 + var setSaturation = !shiftMovementDirection || shiftMovementDirection === "x";
  407 + var setValue = !shiftMovementDirection || shiftMovementDirection === "y";
  408 +
  409 + if (setSaturation) {
  410 + currentSaturation = parseFloat(dragX / dragWidth);
  411 + }
  412 + if (setValue) {
  413 + currentValue = parseFloat((dragHeight - dragY) / dragHeight);
  414 + }
  415 +
  416 + isEmpty = false;
  417 + if (!opts.showAlpha) {
  418 + currentAlpha = 1;
  419 + }
  420 +
  421 + move();
  422 +
  423 + }, dragStart, dragStop);
  424 +
  425 + if (!!initialColor) {
  426 + set(initialColor);
  427 +
  428 + // In case color was black - update the preview UI and set the format
  429 + // since the set function will not run (default color is black).
  430 + updateUI();
  431 + currentPreferredFormat = preferredFormat || tinycolor(initialColor).format;
  432 +
  433 + addColorToSelectionPalette(initialColor);
  434 + }
  435 + else {
  436 + updateUI();
  437 + }
  438 +
  439 + if (flat) {
  440 + show();
  441 + }
  442 +
  443 + function paletteElementClick(e) {
  444 + if (e.data && e.data.ignore) {
  445 + set($(e.target).closest(".sp-thumb-el").data("color"));
  446 + move();
  447 + }
  448 + else {
  449 + set($(e.target).closest(".sp-thumb-el").data("color"));
  450 + move();
  451 + updateOriginalInput(true);
  452 + if (opts.hideAfterPaletteSelect) {
  453 + hide();
  454 + }
  455 + }
  456 +
  457 + return false;
  458 + }
  459 +
  460 + var paletteEvent = IE ? "mousedown.spectrum" : "click.spectrum touchstart.spectrum";
  461 + paletteContainer.delegate(".sp-thumb-el", paletteEvent, paletteElementClick);
  462 + initialColorContainer.delegate(".sp-thumb-el:nth-child(1)", paletteEvent, { ignore: true }, paletteElementClick);
  463 + }
  464 +
  465 + function updateSelectionPaletteFromStorage() {
  466 +
  467 + if (localStorageKey && window.localStorage) {
  468 +
  469 + // Migrate old palettes over to new format. May want to remove this eventually.
  470 + try {
  471 + var oldPalette = window.localStorage[localStorageKey].split(",#");
  472 + if (oldPalette.length > 1) {
  473 + delete window.localStorage[localStorageKey];
  474 + $.each(oldPalette, function(i, c) {
  475 + addColorToSelectionPalette(c);
  476 + });
  477 + }
  478 + }
  479 + catch(e) { }
  480 +
  481 + try {
  482 + selectionPalette = window.localStorage[localStorageKey].split(";");
  483 + }
  484 + catch (e) { }
  485 + }
  486 + }
  487 +
  488 + function addColorToSelectionPalette(color) {
  489 + if (showSelectionPalette) {
  490 + var rgb = tinycolor(color).toRgbString();
  491 + if (!paletteLookup[rgb] && $.inArray(rgb, selectionPalette) === -1) {
  492 + selectionPalette.push(rgb);
  493 + while(selectionPalette.length > maxSelectionSize) {
  494 + selectionPalette.shift();
  495 + }
  496 + }
  497 +
  498 + if (localStorageKey && window.localStorage) {
  499 + try {
  500 + window.localStorage[localStorageKey] = selectionPalette.join(";");
  501 + }
  502 + catch(e) { }
  503 + }
  504 + }
  505 + }
  506 +
  507 + function getUniqueSelectionPalette() {
  508 + var unique = [];
  509 + if (opts.showPalette) {
  510 + for (var i = 0; i < selectionPalette.length; i++) {
  511 + var rgb = tinycolor(selectionPalette[i]).toRgbString();
  512 +
  513 + if (!paletteLookup[rgb]) {
  514 + unique.push(selectionPalette[i]);
  515 + }
  516 + }
  517 + }
  518 +
  519 + return unique.reverse().slice(0, opts.maxSelectionSize);
  520 + }
  521 +
  522 + function drawPalette() {
  523 +
  524 + var currentColor = get();
  525 +
  526 + var html = $.map(paletteArray, function (palette, i) {
  527 + return paletteTemplate(palette, currentColor, "sp-palette-row sp-palette-row-" + i, opts);
  528 + });
  529 +
  530 + updateSelectionPaletteFromStorage();
  531 +
  532 + if (selectionPalette) {
  533 + html.push(paletteTemplate(getUniqueSelectionPalette(), currentColor, "sp-palette-row sp-palette-row-selection", opts));
  534 + }
  535 +
  536 + paletteContainer.html(html.join(""));
  537 + }
  538 +
  539 + function drawInitial() {
  540 + if (opts.showInitial) {
  541 + var initial = colorOnShow;
  542 + var current = get();
  543 + initialColorContainer.html(paletteTemplate([initial, current], current, "sp-palette-row-initial", opts));
  544 + }
  545 + }
  546 +
  547 + function dragStart() {
  548 + if (dragHeight <= 0 || dragWidth <= 0 || slideHeight <= 0) {
  549 + reflow();
  550 + }
  551 + container.addClass(draggingClass);
  552 + shiftMovementDirection = null;
  553 + boundElement.trigger('dragstart.spectrum', [ get() ]);
  554 + }
  555 +
  556 + function dragStop() {
  557 + container.removeClass(draggingClass);
  558 + boundElement.trigger('dragstop.spectrum', [ get() ]);
  559 + }
  560 +
  561 + function setFromTextInput() {
  562 +
  563 + var value = textInput.val();
  564 +
  565 + if ((value === null || value === "") && allowEmpty) {
  566 + set(null);
  567 + updateOriginalInput(true);
  568 + }
  569 + else {
  570 + var tiny = tinycolor(value);
  571 + if (tiny.isValid()) {
  572 + set(tiny);
  573 + updateOriginalInput(true);
  574 + }
  575 + else {
  576 + textInput.addClass("sp-validation-error");
  577 + }
  578 + }
  579 + }
  580 +
  581 + function toggle() {
  582 + if (visible) {
  583 + hide();
  584 + }
  585 + else {
  586 + show();
  587 + }
  588 + }
  589 +
  590 + function show() {
  591 + var event = $.Event('beforeShow.spectrum');
  592 +
  593 + if (visible) {
  594 + reflow();
  595 + return;
  596 + }
  597 +
  598 + boundElement.trigger(event, [ get() ]);
  599 +
  600 + if (callbacks.beforeShow(get()) === false || event.isDefaultPrevented()) {
  601 + return;
  602 + }
  603 +
  604 + hideAll();
  605 + visible = true;
  606 +
  607 + $(doc).bind("click.spectrum", hide);
  608 + $(window).bind("resize.spectrum", resize);
  609 + replacer.addClass("sp-active");
  610 + container.removeClass("sp-hidden");
  611 +
  612 + reflow();
  613 + updateUI();
  614 +
  615 + colorOnShow = get();
  616 +
  617 + drawInitial();
  618 + callbacks.show(colorOnShow);
  619 + boundElement.trigger('show.spectrum', [ colorOnShow ]);
  620 + }
  621 +
  622 + function hide(e) {
  623 +
  624 + // Return on right click
  625 + if (e && e.type == "click" && e.button == 2) { return; }
  626 +
  627 + // Return if hiding is unnecessary
  628 + if (!visible || flat) { return; }
  629 + visible = false;
  630 +
  631 + $(doc).unbind("click.spectrum", hide);
  632 + $(window).unbind("resize.spectrum", resize);
  633 +
  634 + replacer.removeClass("sp-active");
  635 + container.addClass("sp-hidden");
  636 +
  637 + var colorHasChanged = !tinycolor.equals(get(), colorOnShow);
  638 +
  639 + if (colorHasChanged) {
  640 + if (clickoutFiresChange && e !== "cancel") {
  641 + updateOriginalInput(true);
  642 + }
  643 + else {
  644 + revert();
  645 + }
  646 + }
  647 +
  648 + callbacks.hide(get());
  649 + boundElement.trigger('hide.spectrum', [ get() ]);
  650 + }
  651 +
  652 + function revert() {
  653 + set(colorOnShow, true);
  654 + }
  655 +
  656 + function set(color, ignoreFormatChange) {
  657 + if (tinycolor.equals(color, get())) {
  658 + // Update UI just in case a validation error needs
  659 + // to be cleared.
  660 + updateUI();
  661 + return;
  662 + }
  663 +
  664 + var newColor, newHsv;
  665 + if (!color && allowEmpty) {
  666 + isEmpty = true;
  667 + } else {
  668 + isEmpty = false;
  669 + newColor = tinycolor(color);
  670 + newHsv = newColor.toHsv();
  671 +
  672 + currentHue = (newHsv.h % 360) / 360;
  673 + currentSaturation = newHsv.s;
  674 + currentValue = newHsv.v;
  675 + currentAlpha = newHsv.a;
  676 + }
  677 + updateUI();
  678 +
  679 + if (newColor && newColor.isValid() && !ignoreFormatChange) {
  680 + currentPreferredFormat = preferredFormat || newColor.getFormat();
  681 + }
  682 + }
  683 +
  684 + function get(opts) {
  685 + opts = opts || { };
  686 +
  687 + if (allowEmpty && isEmpty) {
  688 + return null;
  689 + }
  690 +
  691 + return tinycolor.fromRatio({
  692 + h: currentHue,
  693 + s: currentSaturation,
  694 + v: currentValue,
  695 + a: Math.round(currentAlpha * 100) / 100
  696 + }, { format: opts.format || currentPreferredFormat });
  697 + }
  698 +
  699 + function isValid() {
  700 + return !textInput.hasClass("sp-validation-error");
  701 + }
  702 +
  703 + function move() {
  704 + updateUI();
  705 +
  706 + callbacks.move(get());
  707 + boundElement.trigger('move.spectrum', [ get() ]);
  708 + }
  709 +
  710 + function updateUI() {
  711 +
  712 + textInput.removeClass("sp-validation-error");
  713 +
  714 + updateHelperLocations();
  715 +
  716 + // Update dragger background color (gradients take care of saturation and value).
  717 + var flatColor = tinycolor.fromRatio({ h: currentHue, s: 1, v: 1 });
  718 + dragger.css("background-color", flatColor.toHexString());
  719 +
  720 + // Get a format that alpha will be included in (hex and names ignore alpha)
  721 + var format = currentPreferredFormat;
  722 + if (currentAlpha < 1 && !(currentAlpha === 0 && format === "name")) {
  723 + if (format === "hex" || format === "hex3" || format === "hex6" || format === "name") {
  724 + format = "rgb";
  725 + }
  726 + }
  727 +
  728 + var realColor = get({ format: format }),
  729 + displayColor = '';
  730 +
  731 + //reset background info for preview element
  732 + previewElement.removeClass("sp-clear-display");
  733 + previewElement.css('background-color', 'transparent');
  734 +
  735 + if (!realColor && allowEmpty) {
  736 + // Update the replaced elements background with icon indicating no color selection
  737 + previewElement.addClass("sp-clear-display");
  738 + }
  739 + else {
  740 + var realHex = realColor.toHexString(),
  741 + realRgb = realColor.toRgbString();
  742 +
  743 + // Update the replaced elements background color (with actual selected color)
  744 + if (rgbaSupport || realColor.alpha === 1) {
  745 + previewElement.css("background-color", realRgb);
  746 + }
  747 + else {
  748 + previewElement.css("background-color", "transparent");
  749 + previewElement.css("filter", realColor.toFilter());
  750 + }
  751 +
  752 + if (opts.showAlpha) {
  753 + var rgb = realColor.toRgb();
  754 + rgb.a = 0;
  755 + var realAlpha = tinycolor(rgb).toRgbString();
  756 + var gradient = "linear-gradient(left, " + realAlpha + ", " + realHex + ")";
  757 +
  758 + if (IE) {
  759 + alphaSliderInner.css("filter", tinycolor(realAlpha).toFilter({ gradientType: 1 }, realHex));
  760 + }
  761 + else {
  762 + alphaSliderInner.css("background", "-webkit-" + gradient);
  763 + alphaSliderInner.css("background", "-moz-" + gradient);
  764 + alphaSliderInner.css("background", "-ms-" + gradient);
  765 + // Use current syntax gradient on unprefixed property.
  766 + alphaSliderInner.css("background",
  767 + "linear-gradient(to right, " + realAlpha + ", " + realHex + ")");
  768 + }
  769 + }
  770 +
  771 + displayColor = realColor.toString(format);
  772 + }
  773 +
  774 + // Update the text entry input as it changes happen
  775 + if (opts.showInput) {
  776 + textInput.val(displayColor);
  777 + }
  778 +
  779 + if (opts.showPalette) {
  780 + drawPalette();
  781 + }
  782 +
  783 + drawInitial();
  784 + }
  785 +
  786 + function updateHelperLocations() {
  787 + var s = currentSaturation;
  788 + var v = currentValue;
  789 +
  790 + if(allowEmpty && isEmpty) {
  791 + //if selected color is empty, hide the helpers
  792 + alphaSlideHelper.hide();
  793 + slideHelper.hide();
  794 + dragHelper.hide();
  795 + }
  796 + else {
  797 + //make sure helpers are visible
  798 + alphaSlideHelper.show();
  799 + slideHelper.show();
  800 + dragHelper.show();
  801 +
  802 + // Where to show the little circle in that displays your current selected color
  803 + var dragX = s * dragWidth;
  804 + var dragY = dragHeight - (v * dragHeight);
  805 + dragX = Math.max(
  806 + -dragHelperHeight,
  807 + Math.min(dragWidth - dragHelperHeight, dragX - dragHelperHeight)
  808 + );
  809 + dragY = Math.max(
  810 + -dragHelperHeight,
  811 + Math.min(dragHeight - dragHelperHeight, dragY - dragHelperHeight)
  812 + );
  813 + dragHelper.css({
  814 + "top": dragY + "px",
  815 + "left": dragX + "px"
  816 + });
  817 +
  818 + var alphaX = currentAlpha * alphaWidth;
  819 + alphaSlideHelper.css({
  820 + "left": (alphaX - (alphaSlideHelperWidth / 2)) + "px"
  821 + });
  822 +
  823 + // Where to show the bar that displays your current selected hue
  824 + var slideY = (currentHue) * slideHeight;
  825 + slideHelper.css({
  826 + "top": (slideY - slideHelperHeight) + "px"
  827 + });
  828 + }
  829 + }
  830 +
  831 + function updateOriginalInput(fireCallback) {
  832 + var color = get(),
  833 + displayColor = '',
  834 + hasChanged = !tinycolor.equals(color, colorOnShow);
  835 +
  836 + if (color) {
  837 + displayColor = color.toString(currentPreferredFormat);
  838 + // Update the selection palette with the current color
  839 + addColorToSelectionPalette(color);
  840 + }
  841 +
  842 + if (isInput) {
  843 + boundElement.val(displayColor);
  844 + }
  845 +
  846 + colorOnShow = color;
  847 +
  848 + if (fireCallback && hasChanged) {
  849 + callbacks.change(color);
  850 + boundElement.trigger('change', [ color ]);
  851 + }
  852 + }
  853 +
  854 + function reflow() {
  855 + dragWidth = dragger.width();
  856 + dragHeight = dragger.height();
  857 + dragHelperHeight = dragHelper.height();
  858 + slideWidth = slider.width();
  859 + slideHeight = slider.height();
  860 + slideHelperHeight = slideHelper.height();
  861 + alphaWidth = alphaSlider.width();
  862 + alphaSlideHelperWidth = alphaSlideHelper.width();
  863 +
  864 + if (!flat) {
  865 + container.css("position", "absolute");
  866 + container.offset(getOffset(container, offsetElement));
  867 + }
  868 +
  869 + updateHelperLocations();
  870 +
  871 + if (opts.showPalette) {
  872 + drawPalette();
  873 + }
  874 +
  875 + boundElement.trigger('reflow.spectrum');
  876 + }
  877 +
  878 + function destroy() {
  879 + boundElement.show();
  880 + offsetElement.unbind("click.spectrum touchstart.spectrum");
  881 + container.remove();
  882 + replacer.remove();
  883 + spectrums[spect.id] = null;
  884 + }
  885 +
  886 + function option(optionName, optionValue) {
  887 + if (optionName === undefined) {
  888 + return $.extend({}, opts);
  889 + }
  890 + if (optionValue === undefined) {
  891 + return opts[optionName];
  892 + }
  893 +
  894 + opts[optionName] = optionValue;
  895 + applyOptions();
  896 + }
  897 +
  898 + function enable() {
  899 + disabled = false;
  900 + boundElement.attr("disabled", false);
  901 + offsetElement.removeClass("sp-disabled");
  902 + }
  903 +
  904 + function disable() {
  905 + hide();
  906 + disabled = true;
  907 + boundElement.attr("disabled", true);
  908 + offsetElement.addClass("sp-disabled");
  909 + }
  910 +
  911 + initialize();
  912 +
  913 + var spect = {
  914 + show: show,
  915 + hide: hide,
  916 + toggle: toggle,
  917 + reflow: reflow,
  918 + option: option,
  919 + enable: enable,
  920 + disable: disable,
  921 + set: function (c) {
  922 + set(c);
  923 + updateOriginalInput();
  924 + },
  925 + get: get,
  926 + destroy: destroy,
  927 + container: container
  928 + };
  929 +
  930 + spect.id = spectrums.push(spect) - 1;
  931 +
  932 + return spect;
  933 + }
  934 +
  935 + /**
  936 + * checkOffset - get the offset below/above and left/right element depending on screen position
  937 + * Thanks https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js
  938 + */
  939 + function getOffset(picker, input) {
  940 + var extraY = 0;
  941 + var dpWidth = picker.outerWidth();
  942 + var dpHeight = picker.outerHeight();
  943 + var inputHeight = input.outerHeight();
  944 + var doc = picker[0].ownerDocument;
  945 + var docElem = doc.documentElement;
  946 + var viewWidth = docElem.clientWidth + $(doc).scrollLeft();
  947 + var viewHeight = docElem.clientHeight + $(doc).scrollTop();
  948 + var offset = input.offset();
  949 + offset.top += inputHeight;
  950 +
  951 + offset.left -=
  952 + Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
  953 + Math.abs(offset.left + dpWidth - viewWidth) : 0);
  954 +
  955 + offset.top -=
  956 + Math.min(offset.top, ((offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
  957 + Math.abs(dpHeight + inputHeight - extraY) : extraY));
  958 +
  959 + return offset;
  960 + }
  961 +
  962 + /**
  963 + * noop - do nothing
  964 + */
  965 + function noop() {
  966 +
  967 + }
  968 +
  969 + /**
  970 + * stopPropagation - makes the code only doing this a little easier to read in line
  971 + */
  972 + function stopPropagation(e) {
  973 + e.stopPropagation();
  974 + }
  975 +
  976 + /**
  977 + * Create a function bound to a given object
  978 + * Thanks to underscore.js
  979 + */
  980 + function bind(func, obj) {
  981 + var slice = Array.prototype.slice;
  982 + var args = slice.call(arguments, 2);
  983 + return function () {
  984 + return func.apply(obj, args.concat(slice.call(arguments)));
  985 + };
  986 + }
  987 +
  988 + /**
  989 + * Lightweight drag helper. Handles containment within the element, so that
  990 + * when dragging, the x is within [0,element.width] and y is within [0,element.height]
  991 + */
  992 + function draggable(element, onmove, onstart, onstop) {
  993 + onmove = onmove || function () { };
  994 + onstart = onstart || function () { };
  995 + onstop = onstop || function () { };
  996 + var doc = element.ownerDocument || document;
  997 + var dragging = false;
  998 + var offset = {};
  999 + var maxHeight = 0;
  1000 + var maxWidth = 0;
  1001 + var hasTouch = ('ontouchstart' in window);
  1002 +
  1003 + var duringDragEvents = {};
  1004 + duringDragEvents["selectstart"] = prevent;
  1005 + duringDragEvents["dragstart"] = prevent;
  1006 + duringDragEvents["touchmove mousemove"] = move;
  1007 + duringDragEvents["touchend mouseup"] = stop;
  1008 +
  1009 + function prevent(e) {
  1010 + if (e.stopPropagation) {
  1011 + e.stopPropagation();
  1012 + }
  1013 + if (e.preventDefault) {
  1014 + e.preventDefault();
  1015 + }
  1016 + e.returnValue = false;
  1017 + }
  1018 +
  1019 + function move(e) {
  1020 + if (dragging) {
  1021 + // Mouseup happened outside of window
  1022 + if (IE && document.documentMode < 9 && !e.button) {
  1023 + return stop();
  1024 + }
  1025 +
  1026 + var touches = e.originalEvent.touches;
  1027 + var pageX = touches ? touches[0].pageX : e.pageX;
  1028 + var pageY = touches ? touches[0].pageY : e.pageY;
  1029 +
  1030 + var dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth));
  1031 + var dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight));
  1032 +
  1033 + if (hasTouch) {
  1034 + // Stop scrolling in iOS
  1035 + prevent(e);
  1036 + }
  1037 +
  1038 + onmove.apply(element, [dragX, dragY, e]);
  1039 + }
  1040 + }
  1041 +
  1042 + function start(e) {
  1043 + var rightclick = (e.which) ? (e.which == 3) : (e.button == 2);
  1044 + var touches = e.originalEvent.touches;
  1045 +
  1046 + if (!rightclick && !dragging) {
  1047 + if (onstart.apply(element, arguments) !== false) {
  1048 + dragging = true;
  1049 + maxHeight = $(element).height();
  1050 + maxWidth = $(element).width();
  1051 + offset = $(element).offset();
  1052 +
  1053 + $(doc).bind(duringDragEvents);
  1054 + $(doc.body).addClass("sp-dragging");
  1055 +
  1056 + if (!hasTouch) {
  1057 + move(e);
  1058 + }
  1059 +
  1060 + prevent(e);
  1061 + }
  1062 + }
  1063 + }
  1064 +
  1065 + function stop() {
  1066 + if (dragging) {
  1067 + $(doc).unbind(duringDragEvents);
  1068 + $(doc.body).removeClass("sp-dragging");
  1069 + onstop.apply(element, arguments);
  1070 + }
  1071 + dragging = false;
  1072 + }
  1073 +
  1074 + $(element).bind("touchstart mousedown", start);
  1075 + }
  1076 +
  1077 + function throttle(func, wait, debounce) {
  1078 + var timeout;
  1079 + return function () {
  1080 + var context = this, args = arguments;
  1081 + var throttler = function () {
  1082 + timeout = null;
  1083 + func.apply(context, args);
  1084 + };
  1085 + if (debounce) clearTimeout(timeout);
  1086 + if (debounce || !timeout) timeout = setTimeout(throttler, wait);
  1087 + };
  1088 + }
  1089 +
  1090 + /**
  1091 + * Define a jQuery plugin
  1092 + */
  1093 + var dataID = "spectrum.id";
  1094 + $.fn.spectrum = function (opts, extra) {
  1095 +
  1096 + if (typeof opts == "string") {
  1097 +
  1098 + var returnValue = this;
  1099 + var args = Array.prototype.slice.call( arguments, 1 );
  1100 +
  1101 + this.each(function () {
  1102 + var spect = spectrums[$(this).data(dataID)];
  1103 + if (spect) {
  1104 + var method = spect[opts];
  1105 + if (!method) {
  1106 + throw new Error( "Spectrum: no such method: '" + opts + "'" );
  1107 + }
  1108 +
  1109 + if (opts == "get") {
  1110 + returnValue = spect.get();
  1111 + }
  1112 + else if (opts == "container") {
  1113 + returnValue = spect.container;
  1114 + }
  1115 + else if (opts == "option") {
  1116 + returnValue = spect.option.apply(spect, args);
  1117 + }
  1118 + else if (opts == "destroy") {
  1119 + spect.destroy();
  1120 + $(this).removeData(dataID);
  1121 + }
  1122 + else {
  1123 + method.apply(spect, args);
  1124 + }
  1125 + }
  1126 + });
  1127 +
  1128 + return returnValue;
  1129 + }
  1130 +
  1131 + // Initializing a new instance of spectrum
  1132 + return this.spectrum("destroy").each(function () {
  1133 + var options = $.extend({}, opts, $(this).data());
  1134 + var spect = spectrum(this, options);
  1135 + $(this).data(dataID, spect.id);
  1136 + });
  1137 + };
  1138 +
  1139 + $.fn.spectrum.load = true;
  1140 + $.fn.spectrum.loadOpts = {};
  1141 + $.fn.spectrum.draggable = draggable;
  1142 + $.fn.spectrum.defaults = defaultOpts;
  1143 +
  1144 + $.spectrum = { };
  1145 + $.spectrum.localization = { };
  1146 + $.spectrum.palettes = { };
  1147 +
  1148 + $.fn.spectrum.processNativeColorInputs = function () {
  1149 + if (!inputTypeColorSupport) {
  1150 + $("input[type=color]").spectrum({
  1151 + preferredFormat: "hex6"
  1152 + });
  1153 + }
  1154 + };
  1155 +
  1156 + // TinyColor v1.0.0
  1157 + // https://github.com/bgrins/TinyColor
  1158 + // Brian Grinstead, MIT License
  1159 +
  1160 + (function() {
  1161 +
  1162 + var trimLeft = /^[\s,#]+/,
  1163 + trimRight = /\s+$/,
  1164 + tinyCounter = 0,
  1165 + math = Math,
  1166 + mathRound = math.round,
  1167 + mathMin = math.min,
  1168 + mathMax = math.max,
  1169 + mathRandom = math.random;
  1170 +
  1171 + var tinycolor = function tinycolor (color, opts) {
  1172 +
  1173 + color = (color) ? color : '';
  1174 + opts = opts || { };
  1175 +
  1176 + // If input is already a tinycolor, return itself
  1177 + if (color instanceof tinycolor) {
  1178 + return color;
  1179 + }
  1180 + // If we are called as a function, call using new instead
  1181 + if (!(this instanceof tinycolor)) {
  1182 + return new tinycolor(color, opts);
  1183 + }
  1184 +
  1185 + var rgb = inputToRGB(color);
  1186 + this._r = rgb.r,
  1187 + this._g = rgb.g,
  1188 + this._b = rgb.b,
  1189 + this._a = rgb.a,
  1190 + this._roundA = mathRound(100*this._a) / 100,
  1191 + this._format = opts.format || rgb.format;
  1192 + this._gradientType = opts.gradientType;
  1193 +
  1194 + // Don't let the range of [0,255] come back in [0,1].
  1195 + // Potentially lose a little bit of precision here, but will fix issues where
  1196 + // .5 gets interpreted as half of the total, instead of half of 1
  1197 + // If it was supposed to be 128, this was already taken care of by `inputToRgb`
  1198 + if (this._r < 1) { this._r = mathRound(this._r); }
  1199 + if (this._g < 1) { this._g = mathRound(this._g); }
  1200 + if (this._b < 1) { this._b = mathRound(this._b); }
  1201 +
  1202 + this._ok = rgb.ok;
  1203 + this._tc_id = tinyCounter++;
  1204 + };
  1205 +
  1206 + tinycolor.prototype = {
  1207 + isDark: function() {
  1208 + return this.getBrightness() < 128;
  1209 + },
  1210 + isLight: function() {
  1211 + return !this.isDark();
  1212 + },
  1213 + isValid: function() {
  1214 + return this._ok;
  1215 + },
  1216 + getFormat: function() {
  1217 + return this._format;
  1218 + },
  1219 + getAlpha: function() {
  1220 + return this._a;
  1221 + },
  1222 + getBrightness: function() {
  1223 + var rgb = this.toRgb();
  1224 + return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
  1225 + },
  1226 + setAlpha: function(value) {
  1227 + this._a = boundAlpha(value);
  1228 + this._roundA = mathRound(100*this._a) / 100;
  1229 + return this;
  1230 + },
  1231 + toHsv: function() {
  1232 + var hsv = rgbToHsv(this._r, this._g, this._b);
  1233 + return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a };
  1234 + },
  1235 + toHsvString: function() {
  1236 + var hsv = rgbToHsv(this._r, this._g, this._b);
  1237 + var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100);
  1238 + return (this._a == 1) ?
  1239 + "hsv(" + h + ", " + s + "%, " + v + "%)" :
  1240 + "hsva(" + h + ", " + s + "%, " + v + "%, "+ this._roundA + ")";
  1241 + },
  1242 + toHsl: function() {
  1243 + var hsl = rgbToHsl(this._r, this._g, this._b);
  1244 + return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this._a };
  1245 + },
  1246 + toHslString: function() {
  1247 + var hsl = rgbToHsl(this._r, this._g, this._b);
  1248 + var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100);
  1249 + return (this._a == 1) ?
  1250 + "hsl(" + h + ", " + s + "%, " + l + "%)" :
  1251 + "hsla(" + h + ", " + s + "%, " + l + "%, "+ this._roundA + ")";
  1252 + },
  1253 + toHex: function(allow3Char) {
  1254 + return rgbToHex(this._r, this._g, this._b, allow3Char);
  1255 + },
  1256 + toHexString: function(allow3Char) {
  1257 + return '#' + this.toHex(allow3Char);
  1258 + },
  1259 + toHex8: function() {
  1260 + return rgbaToHex(this._r, this._g, this._b, this._a);
  1261 + },
  1262 + toHex8String: function() {
  1263 + return '#' + this.toHex8();
  1264 + },
  1265 + toRgb: function() {
  1266 + return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a };
  1267 + },
  1268 + toRgbString: function() {
  1269 + return (this._a == 1) ?
  1270 + "rgb(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" :
  1271 + "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")";
  1272 + },
  1273 + toPercentageRgb: function() {
  1274 + return { r: mathRound(bound01(this._r, 255) * 100) + "%", g: mathRound(bound01(this._g, 255) * 100) + "%", b: mathRound(bound01(this._b, 255) * 100) + "%", a: this._a };
  1275 + },
  1276 + toPercentageRgbString: function() {
  1277 + return (this._a == 1) ?
  1278 + "rgb(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" :
  1279 + "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")";
  1280 + },
  1281 + toName: function() {
  1282 + if (this._a === 0) {
  1283 + return "transparent";
  1284 + }
  1285 +
  1286 + if (this._a < 1) {
  1287 + return false;
  1288 + }
  1289 +
  1290 + return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false;
  1291 + },
  1292 + toFilter: function(secondColor) {
  1293 + var hex8String = '#' + rgbaToHex(this._r, this._g, this._b, this._a);
  1294 + var secondHex8String = hex8String;
  1295 + var gradientType = this._gradientType ? "GradientType = 1, " : "";
  1296 +
  1297 + if (secondColor) {
  1298 + var s = tinycolor(secondColor);
  1299 + secondHex8String = s.toHex8String();
  1300 + }
  1301 +
  1302 + return "progid:DXImageTransform.Microsoft.gradient("+gradientType+"startColorstr="+hex8String+",endColorstr="+secondHex8String+")";
  1303 + },
  1304 + toString: function(format) {
  1305 + var formatSet = !!format;
  1306 + format = format || this._format;
  1307 +
  1308 + var formattedString = false;
  1309 + var hasAlpha = this._a < 1 && this._a >= 0;
  1310 + var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "name");
  1311 +
  1312 + if (needsAlphaFormat) {
  1313 + // Special case for "transparent", all other non-alpha formats
  1314 + // will return rgba when there is transparency.
  1315 + if (format === "name" && this._a === 0) {
  1316 + return this.toName();
  1317 + }
  1318 + return this.toRgbString();
  1319 + }
  1320 + if (format === "rgb") {
  1321 + formattedString = this.toRgbString();
  1322 + }
  1323 + if (format === "prgb") {
  1324 + formattedString = this.toPercentageRgbString();
  1325 + }
  1326 + if (format === "hex" || format === "hex6") {
  1327 + formattedString = this.toHexString();
  1328 + }
  1329 + if (format === "hex3") {
  1330 + formattedString = this.toHexString(true);
  1331 + }
  1332 + if (format === "hex8") {
  1333 + formattedString = this.toHex8String();
  1334 + }
  1335 + if (format === "name") {
  1336 + formattedString = this.toName();
  1337 + }
  1338 + if (format === "hsl") {
  1339 + formattedString = this.toHslString();
  1340 + }
  1341 + if (format === "hsv") {
  1342 + formattedString = this.toHsvString();
  1343 + }
  1344 +
  1345 + return formattedString || this.toHexString();
  1346 + },
  1347 +
  1348 + _applyModification: function(fn, args) {
  1349 + var color = fn.apply(null, [this].concat([].slice.call(args)));
  1350 + this._r = color._r;
  1351 + this._g = color._g;
  1352 + this._b = color._b;
  1353 + this.setAlpha(color._a);
  1354 + return this;
  1355 + },
  1356 + lighten: function() {
  1357 + return this._applyModification(lighten, arguments);
  1358 + },
  1359 + brighten: function() {
  1360 + return this._applyModification(brighten, arguments);
  1361 + },
  1362 + darken: function() {
  1363 + return this._applyModification(darken, arguments);
  1364 + },
  1365 + desaturate: function() {
  1366 + return this._applyModification(desaturate, arguments);
  1367 + },
  1368 + saturate: function() {
  1369 + return this._applyModification(saturate, arguments);
  1370 + },
  1371 + greyscale: function() {
  1372 + return this._applyModification(greyscale, arguments);
  1373 + },
  1374 + spin: function() {
  1375 + return this._applyModification(spin, arguments);
  1376 + },
  1377 +
  1378 + _applyCombination: function(fn, args) {
  1379 + return fn.apply(null, [this].concat([].slice.call(args)));
  1380 + },
  1381 + analogous: function() {
  1382 + return this._applyCombination(analogous, arguments);
  1383 + },
  1384 + complement: function() {
  1385 + return this._applyCombination(complement, arguments);
  1386 + },
  1387 + monochromatic: function() {
  1388 + return this._applyCombination(monochromatic, arguments);
  1389 + },
  1390 + splitcomplement: function() {
  1391 + return this._applyCombination(splitcomplement, arguments);
  1392 + },
  1393 + triad: function() {
  1394 + return this._applyCombination(triad, arguments);
  1395 + },
  1396 + tetrad: function() {
  1397 + return this._applyCombination(tetrad, arguments);
  1398 + }
  1399 + };
  1400 +
  1401 + // If input is an object, force 1 into "1.0" to handle ratios properly
  1402 + // String input requires "1.0" as input, so 1 will be treated as 1
  1403 + tinycolor.fromRatio = function(color, opts) {
  1404 + if (typeof color == "object") {
  1405 + var newColor = {};
  1406 + for (var i in color) {
  1407 + if (color.hasOwnProperty(i)) {
  1408 + if (i === "a") {
  1409 + newColor[i] = color[i];
  1410 + }
  1411 + else {
  1412 + newColor[i] = convertToPercentage(color[i]);
  1413 + }
  1414 + }
  1415 + }
  1416 + color = newColor;
  1417 + }
  1418 +
  1419 + return tinycolor(color, opts);
  1420 + };
  1421 +
  1422 + // Given a string or object, convert that input to RGB
  1423 + // Possible string inputs:
  1424 + //
  1425 + // "red"
  1426 + // "#f00" or "f00"
  1427 + // "#ff0000" or "ff0000"
  1428 + // "#ff000000" or "ff000000"
  1429 + // "rgb 255 0 0" or "rgb (255, 0, 0)"
  1430 + // "rgb 1.0 0 0" or "rgb (1, 0, 0)"
  1431 + // "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
  1432 + // "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
  1433 + // "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
  1434 + // "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
  1435 + // "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
  1436 + //
  1437 + function inputToRGB(color) {
  1438 +
  1439 + var rgb = { r: 0, g: 0, b: 0 };
  1440 + var a = 1;
  1441 + var ok = false;
  1442 + var format = false;
  1443 +
  1444 + if (typeof color == "string") {
  1445 + color = stringInputToObject(color);
  1446 + }
  1447 +
  1448 + if (typeof color == "object") {
  1449 + if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) {
  1450 + rgb = rgbToRgb(color.r, color.g, color.b);
  1451 + ok = true;
  1452 + format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb";
  1453 + }
  1454 + else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) {
  1455 + color.s = convertToPercentage(color.s);
  1456 + color.v = convertToPercentage(color.v);
  1457 + rgb = hsvToRgb(color.h, color.s, color.v);
  1458 + ok = true;
  1459 + format = "hsv";
  1460 + }
  1461 + else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) {
  1462 + color.s = convertToPercentage(color.s);
  1463 + color.l = convertToPercentage(color.l);
  1464 + rgb = hslToRgb(color.h, color.s, color.l);
  1465 + ok = true;
  1466 + format = "hsl";
  1467 + }
  1468 +
  1469 + if (color.hasOwnProperty("a")) {
  1470 + a = color.a;
  1471 + }
  1472 + }
  1473 +
  1474 + a = boundAlpha(a);
  1475 +
  1476 + return {
  1477 + ok: ok,
  1478 + format: color.format || format,
  1479 + r: mathMin(255, mathMax(rgb.r, 0)),
  1480 + g: mathMin(255, mathMax(rgb.g, 0)),
  1481 + b: mathMin(255, mathMax(rgb.b, 0)),
  1482 + a: a
  1483 + };
  1484 + }
  1485 +
  1486 +
  1487 + // Conversion Functions
  1488 + // --------------------
  1489 +
  1490 + // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:
  1491 + // <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>
  1492 +
  1493 + // `rgbToRgb`
  1494 + // Handle bounds / percentage checking to conform to CSS color spec
  1495 + // <http://www.w3.org/TR/css3-color/>
  1496 + // *Assumes:* r, g, b in [0, 255] or [0, 1]
  1497 + // *Returns:* { r, g, b } in [0, 255]
  1498 + function rgbToRgb(r, g, b){
  1499 + return {
  1500 + r: bound01(r, 255) * 255,
  1501 + g: bound01(g, 255) * 255,
  1502 + b: bound01(b, 255) * 255
  1503 + };
  1504 + }
  1505 +
  1506 + // `rgbToHsl`
  1507 + // Converts an RGB color value to HSL.
  1508 + // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
  1509 + // *Returns:* { h, s, l } in [0,1]
  1510 + function rgbToHsl(r, g, b) {
  1511 +
  1512 + r = bound01(r, 255);
  1513 + g = bound01(g, 255);
  1514 + b = bound01(b, 255);
  1515 +
  1516 + var max = mathMax(r, g, b), min = mathMin(r, g, b);
  1517 + var h, s, l = (max + min) / 2;
  1518 +
  1519 + if(max == min) {
  1520 + h = s = 0; // achromatic
  1521 + }
  1522 + else {
  1523 + var d = max - min;
  1524 + s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
  1525 + switch(max) {
  1526 + case r: h = (g - b) / d + (g < b ? 6 : 0); break;
  1527 + case g: h = (b - r) / d + 2; break;
  1528 + case b: h = (r - g) / d + 4; break;
  1529 + }
  1530 +
  1531 + h /= 6;
  1532 + }
  1533 +
  1534 + return { h: h, s: s, l: l };
  1535 + }
  1536 +
  1537 + // `hslToRgb`
  1538 + // Converts an HSL color value to RGB.
  1539 + // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
  1540 + // *Returns:* { r, g, b } in the set [0, 255]
  1541 + function hslToRgb(h, s, l) {
  1542 + var r, g, b;
  1543 +
  1544 + h = bound01(h, 360);
  1545 + s = bound01(s, 100);
  1546 + l = bound01(l, 100);
  1547 +
  1548 + function hue2rgb(p, q, t) {
  1549 + if(t < 0) t += 1;
  1550 + if(t > 1) t -= 1;
  1551 + if(t < 1/6) return p + (q - p) * 6 * t;
  1552 + if(t < 1/2) return q;
  1553 + if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
  1554 + return p;
  1555 + }
  1556 +
  1557 + if(s === 0) {
  1558 + r = g = b = l; // achromatic
  1559 + }
  1560 + else {
  1561 + var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  1562 + var p = 2 * l - q;
  1563 + r = hue2rgb(p, q, h + 1/3);
  1564 + g = hue2rgb(p, q, h);
  1565 + b = hue2rgb(p, q, h - 1/3);
  1566 + }
  1567 +
  1568 + return { r: r * 255, g: g * 255, b: b * 255 };
  1569 + }
  1570 +
  1571 + // `rgbToHsv`
  1572 + // Converts an RGB color value to HSV
  1573 + // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
  1574 + // *Returns:* { h, s, v } in [0,1]
  1575 + function rgbToHsv(r, g, b) {
  1576 +
  1577 + r = bound01(r, 255);
  1578 + g = bound01(g, 255);
  1579 + b = bound01(b, 255);
  1580 +
  1581 + var max = mathMax(r, g, b), min = mathMin(r, g, b);
  1582 + var h, s, v = max;
  1583 +
  1584 + var d = max - min;
  1585 + s = max === 0 ? 0 : d / max;
  1586 +
  1587 + if(max == min) {
  1588 + h = 0; // achromatic
  1589 + }
  1590 + else {
  1591 + switch(max) {
  1592 + case r: h = (g - b) / d + (g < b ? 6 : 0); break;
  1593 + case g: h = (b - r) / d + 2; break;
  1594 + case b: h = (r - g) / d + 4; break;
  1595 + }
  1596 + h /= 6;
  1597 + }
  1598 + return { h: h, s: s, v: v };
  1599 + }
  1600 +
  1601 + // `hsvToRgb`
  1602 + // Converts an HSV color value to RGB.
  1603 + // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
  1604 + // *Returns:* { r, g, b } in the set [0, 255]
  1605 + function hsvToRgb(h, s, v) {
  1606 +
  1607 + h = bound01(h, 360) * 6;
  1608 + s = bound01(s, 100);
  1609 + v = bound01(v, 100);
  1610 +
  1611 + var i = math.floor(h),
  1612 + f = h - i,
  1613 + p = v * (1 - s),
  1614 + q = v * (1 - f * s),
  1615 + t = v * (1 - (1 - f) * s),
  1616 + mod = i % 6,
  1617 + r = [v, q, p, p, t, v][mod],
  1618 + g = [t, v, v, q, p, p][mod],
  1619 + b = [p, p, t, v, v, q][mod];
  1620 +
  1621 + return { r: r * 255, g: g * 255, b: b * 255 };
  1622 + }
  1623 +
  1624 + // `rgbToHex`
  1625 + // Converts an RGB color to hex
  1626 + // Assumes r, g, and b are contained in the set [0, 255]
  1627 + // Returns a 3 or 6 character hex
  1628 + function rgbToHex(r, g, b, allow3Char) {
  1629 +
  1630 + var hex = [
  1631 + pad2(mathRound(r).toString(16)),
  1632 + pad2(mathRound(g).toString(16)),
  1633 + pad2(mathRound(b).toString(16))
  1634 + ];
  1635 +
  1636 + // Return a 3 character hex if possible
  1637 + if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) {
  1638 + return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
  1639 + }
  1640 +
  1641 + return hex.join("");
  1642 + }
  1643 + // `rgbaToHex`
  1644 + // Converts an RGBA color plus alpha transparency to hex
  1645 + // Assumes r, g, b and a are contained in the set [0, 255]
  1646 + // Returns an 8 character hex
  1647 + function rgbaToHex(r, g, b, a) {
  1648 +
  1649 + var hex = [
  1650 + pad2(convertDecimalToHex(a)),
  1651 + pad2(mathRound(r).toString(16)),
  1652 + pad2(mathRound(g).toString(16)),
  1653 + pad2(mathRound(b).toString(16))
  1654 + ];
  1655 +
  1656 + return hex.join("");
  1657 + }
  1658 +
  1659 + // `equals`
  1660 + // Can be called with any tinycolor input
  1661 + tinycolor.equals = function (color1, color2) {
  1662 + if (!color1 || !color2) { return false; }
  1663 + return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString();
  1664 + };
  1665 + tinycolor.random = function() {
  1666 + return tinycolor.fromRatio({
  1667 + r: mathRandom(),
  1668 + g: mathRandom(),
  1669 + b: mathRandom()
  1670 + });
  1671 + };
  1672 +
  1673 +
  1674 + // Modification Functions
  1675 + // ----------------------
  1676 + // Thanks to less.js for some of the basics here
  1677 + // <https://github.com/cloudhead/less.js/blob/master/lib/less/functions.js>
  1678 +
  1679 + function desaturate(color, amount) {
  1680 + amount = (amount === 0) ? 0 : (amount || 10);
  1681 + var hsl = tinycolor(color).toHsl();
  1682 + hsl.s -= amount / 100;
  1683 + hsl.s = clamp01(hsl.s);
  1684 + return tinycolor(hsl);
  1685 + }
  1686 +
  1687 + function saturate(color, amount) {
  1688 + amount = (amount === 0) ? 0 : (amount || 10);
  1689 + var hsl = tinycolor(color).toHsl();
  1690 + hsl.s += amount / 100;
  1691 + hsl.s = clamp01(hsl.s);
  1692 + return tinycolor(hsl);
  1693 + }
  1694 +
  1695 + function greyscale(color) {
  1696 + return tinycolor(color).desaturate(100);
  1697 + }
  1698 +
  1699 + function lighten (color, amount) {
  1700 + amount = (amount === 0) ? 0 : (amount || 10);
  1701 + var hsl = tinycolor(color).toHsl();
  1702 + hsl.l += amount / 100;
  1703 + hsl.l = clamp01(hsl.l);
  1704 + return tinycolor(hsl);
  1705 + }
  1706 +
  1707 + function brighten(color, amount) {
  1708 + amount = (amount === 0) ? 0 : (amount || 10);
  1709 + var rgb = tinycolor(color).toRgb();
  1710 + rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * - (amount / 100))));
  1711 + rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * - (amount / 100))));
  1712 + rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * - (amount / 100))));
  1713 + return tinycolor(rgb);
  1714 + }
  1715 +
  1716 + function darken (color, amount) {
  1717 + amount = (amount === 0) ? 0 : (amount || 10);
  1718 + var hsl = tinycolor(color).toHsl();
  1719 + hsl.l -= amount / 100;
  1720 + hsl.l = clamp01(hsl.l);
  1721 + return tinycolor(hsl);
  1722 + }
  1723 +
  1724 + // Spin takes a positive or negative amount within [-360, 360] indicating the change of hue.
  1725 + // Values outside of this range will be wrapped into this range.
  1726 + function spin(color, amount) {
  1727 + var hsl = tinycolor(color).toHsl();
  1728 + var hue = (mathRound(hsl.h) + amount) % 360;
  1729 + hsl.h = hue < 0 ? 360 + hue : hue;
  1730 + return tinycolor(hsl);
  1731 + }
  1732 +
  1733 + // Combination Functions
  1734 + // ---------------------
  1735 + // Thanks to jQuery xColor for some of the ideas behind these
  1736 + // <https://github.com/infusion/jQuery-xcolor/blob/master/jquery.xcolor.js>
  1737 +
  1738 + function complement(color) {
  1739 + var hsl = tinycolor(color).toHsl();
  1740 + hsl.h = (hsl.h + 180) % 360;
  1741 + return tinycolor(hsl);
  1742 + }
  1743 +
  1744 + function triad(color) {
  1745 + var hsl = tinycolor(color).toHsl();
  1746 + var h = hsl.h;
  1747 + return [
  1748 + tinycolor(color),
  1749 + tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }),
  1750 + tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l })
  1751 + ];
  1752 + }
  1753 +
  1754 + function tetrad(color) {
  1755 + var hsl = tinycolor(color).toHsl();
  1756 + var h = hsl.h;
  1757 + return [
  1758 + tinycolor(color),
  1759 + tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }),
  1760 + tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }),
  1761 + tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l })
  1762 + ];
  1763 + }
  1764 +
  1765 + function splitcomplement(color) {
  1766 + var hsl = tinycolor(color).toHsl();
  1767 + var h = hsl.h;
  1768 + return [
  1769 + tinycolor(color),
  1770 + tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l}),
  1771 + tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l})
  1772 + ];
  1773 + }
  1774 +
  1775 + function analogous(color, results, slices) {
  1776 + results = results || 6;
  1777 + slices = slices || 30;
  1778 +
  1779 + var hsl = tinycolor(color).toHsl();
  1780 + var part = 360 / slices;
  1781 + var ret = [tinycolor(color)];
  1782 +
  1783 + for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results; ) {
  1784 + hsl.h = (hsl.h + part) % 360;
  1785 + ret.push(tinycolor(hsl));
  1786 + }
  1787 + return ret;
  1788 + }
  1789 +
  1790 + function monochromatic(color, results) {
  1791 + results = results || 6;
  1792 + var hsv = tinycolor(color).toHsv();
  1793 + var h = hsv.h, s = hsv.s, v = hsv.v;
  1794 + var ret = [];
  1795 + var modification = 1 / results;
  1796 +
  1797 + while (results--) {
  1798 + ret.push(tinycolor({ h: h, s: s, v: v}));
  1799 + v = (v + modification) % 1;
  1800 + }
  1801 +
  1802 + return ret;
  1803 + }
  1804 +
  1805 + // Utility Functions
  1806 + // ---------------------
  1807 +
  1808 + tinycolor.mix = function(color1, color2, amount) {
  1809 + amount = (amount === 0) ? 0 : (amount || 50);
  1810 +
  1811 + var rgb1 = tinycolor(color1).toRgb();
  1812 + var rgb2 = tinycolor(color2).toRgb();
  1813 +
  1814 + var p = amount / 100;
  1815 + var w = p * 2 - 1;
  1816 + var a = rgb2.a - rgb1.a;
  1817 +
  1818 + var w1;
  1819 +
  1820 + if (w * a == -1) {
  1821 + w1 = w;
  1822 + } else {
  1823 + w1 = (w + a) / (1 + w * a);
  1824 + }
  1825 +
  1826 + w1 = (w1 + 1) / 2;
  1827 +
  1828 + var w2 = 1 - w1;
  1829 +
  1830 + var rgba = {
  1831 + r: rgb2.r * w1 + rgb1.r * w2,
  1832 + g: rgb2.g * w1 + rgb1.g * w2,
  1833 + b: rgb2.b * w1 + rgb1.b * w2,
  1834 + a: rgb2.a * p + rgb1.a * (1 - p)
  1835 + };
  1836 +
  1837 + return tinycolor(rgba);
  1838 + };
  1839 +
  1840 +
  1841 + // Readability Functions
  1842 + // ---------------------
  1843 + // <http://www.w3.org/TR/AERT#color-contrast>
  1844 +
  1845 + // `readability`
  1846 + // Analyze the 2 colors and returns an object with the following properties:
  1847 + // `brightness`: difference in brightness between the two colors
  1848 + // `color`: difference in color/hue between the two colors
  1849 + tinycolor.readability = function(color1, color2) {
  1850 + var c1 = tinycolor(color1);
  1851 + var c2 = tinycolor(color2);
  1852 + var rgb1 = c1.toRgb();
  1853 + var rgb2 = c2.toRgb();
  1854 + var brightnessA = c1.getBrightness();
  1855 + var brightnessB = c2.getBrightness();
  1856 + var colorDiff = (
  1857 + Math.max(rgb1.r, rgb2.r) - Math.min(rgb1.r, rgb2.r) +
  1858 + Math.max(rgb1.g, rgb2.g) - Math.min(rgb1.g, rgb2.g) +
  1859 + Math.max(rgb1.b, rgb2.b) - Math.min(rgb1.b, rgb2.b)
  1860 + );
  1861 +
  1862 + return {
  1863 + brightness: Math.abs(brightnessA - brightnessB),
  1864 + color: colorDiff
  1865 + };
  1866 + };
  1867 +
  1868 + // `readable`
  1869 + // http://www.w3.org/TR/AERT#color-contrast
  1870 + // Ensure that foreground and background color combinations provide sufficient contrast.
  1871 + // *Example*
  1872 + // tinycolor.isReadable("#000", "#111") => false
  1873 + tinycolor.isReadable = function(color1, color2) {
  1874 + var readability = tinycolor.readability(color1, color2);
  1875 + return readability.brightness > 125 && readability.color > 500;
  1876 + };
  1877 +
  1878 + // `mostReadable`
  1879 + // Given a base color and a list of possible foreground or background
  1880 + // colors for that base, returns the most readable color.
  1881 + // *Example*
  1882 + // tinycolor.mostReadable("#123", ["#fff", "#000"]) => "#000"
  1883 + tinycolor.mostReadable = function(baseColor, colorList) {
  1884 + var bestColor = null;
  1885 + var bestScore = 0;
  1886 + var bestIsReadable = false;
  1887 + for (var i=0; i < colorList.length; i++) {
  1888 +
  1889 + // We normalize both around the "acceptable" breaking point,
  1890 + // but rank brightness constrast higher than hue.
  1891 +
  1892 + var readability = tinycolor.readability(baseColor, colorList[i]);
  1893 + var readable = readability.brightness > 125 && readability.color > 500;
  1894 + var score = 3 * (readability.brightness / 125) + (readability.color / 500);
  1895 +
  1896 + if ((readable && ! bestIsReadable) ||
  1897 + (readable && bestIsReadable && score > bestScore) ||
  1898 + ((! readable) && (! bestIsReadable) && score > bestScore)) {
  1899 + bestIsReadable = readable;
  1900 + bestScore = score;
  1901 + bestColor = tinycolor(colorList[i]);
  1902 + }
  1903 + }
  1904 + return bestColor;
  1905 + };
  1906 +
  1907 +
  1908 + // Big List of Colors
  1909 + // ------------------
  1910 + // <http://www.w3.org/TR/css3-color/#svg-color>
  1911 + var names = tinycolor.names = {
  1912 + aliceblue: "f0f8ff",
  1913 + antiquewhite: "faebd7",
  1914 + aqua: "0ff",
  1915 + aquamarine: "7fffd4",
  1916 + azure: "f0ffff",
  1917 + beige: "f5f5dc",
  1918 + bisque: "ffe4c4",
  1919 + black: "000",
  1920 + blanchedalmond: "ffebcd",
  1921 + blue: "00f",
  1922 + blueviolet: "8a2be2",
  1923 + brown: "a52a2a",
  1924 + burlywood: "deb887",
  1925 + burntsienna: "ea7e5d",
  1926 + cadetblue: "5f9ea0",
  1927 + chartreuse: "7fff00",
  1928 + chocolate: "d2691e",
  1929 + coral: "ff7f50",
  1930 + cornflowerblue: "6495ed",
  1931 + cornsilk: "fff8dc",
  1932 + crimson: "dc143c",
  1933 + cyan: "0ff",
  1934 + darkblue: "00008b",
  1935 + darkcyan: "008b8b",
  1936 + darkgoldenrod: "b8860b",
  1937 + darkgray: "a9a9a9",
  1938 + darkgreen: "006400",
  1939 + darkgrey: "a9a9a9",
  1940 + darkkhaki: "bdb76b",
  1941 + darkmagenta: "8b008b",
  1942 + darkolivegreen: "556b2f",
  1943 + darkorange: "ff8c00",
  1944 + darkorchid: "9932cc",
  1945 + darkred: "8b0000",
  1946 + darksalmon: "e9967a",
  1947 + darkseagreen: "8fbc8f",
  1948 + darkslateblue: "483d8b",
  1949 + darkslategray: "2f4f4f",
  1950 + darkslategrey: "2f4f4f",
  1951 + darkturquoise: "00ced1",
  1952 + darkviolet: "9400d3",
  1953 + deeppink: "ff1493",
  1954 + deepskyblue: "00bfff",
  1955 + dimgray: "696969",
  1956 + dimgrey: "696969",
  1957 + dodgerblue: "1e90ff",
  1958 + firebrick: "b22222",
  1959 + floralwhite: "fffaf0",
  1960 + forestgreen: "228b22",
  1961 + fuchsia: "f0f",
  1962 + gainsboro: "dcdcdc",
  1963 + ghostwhite: "f8f8ff",
  1964 + gold: "ffd700",
  1965 + goldenrod: "daa520",
  1966 + gray: "808080",
  1967 + green: "008000",
  1968 + greenyellow: "adff2f",
  1969 + grey: "808080",
  1970 + honeydew: "f0fff0",
  1971 + hotpink: "ff69b4",
  1972 + indianred: "cd5c5c",
  1973 + indigo: "4b0082",
  1974 + ivory: "fffff0",
  1975 + khaki: "f0e68c",
  1976 + lavender: "e6e6fa",
  1977 + lavenderblush: "fff0f5",
  1978 + lawngreen: "7cfc00",
  1979 + lemonchiffon: "fffacd",
  1980 + lightblue: "add8e6",
  1981 + lightcoral: "f08080",
  1982 + lightcyan: "e0ffff",
  1983 + lightgoldenrodyellow: "fafad2",
  1984 + lightgray: "d3d3d3",
  1985 + lightgreen: "90ee90",
  1986 + lightgrey: "d3d3d3",
  1987 + lightpink: "ffb6c1",
  1988 + lightsalmon: "ffa07a",
  1989 + lightseagreen: "20b2aa",
  1990 + lightskyblue: "87cefa",
  1991 + lightslategray: "789",
  1992 + lightslategrey: "789",
  1993 + lightsteelblue: "b0c4de",
  1994 + lightyellow: "ffffe0",
  1995 + lime: "0f0",
  1996 + limegreen: "32cd32",
  1997 + linen: "faf0e6",
  1998 + magenta: "f0f",
  1999 + maroon: "800000",
  2000 + mediumaquamarine: "66cdaa",
  2001 + mediumblue: "0000cd",
  2002 + mediumorchid: "ba55d3",
  2003 + mediumpurple: "9370db",
  2004 + mediumseagreen: "3cb371",
  2005 + mediumslateblue: "7b68ee",
  2006 + mediumspringgreen: "00fa9a",
  2007 + mediumturquoise: "48d1cc",
  2008 + mediumvioletred: "c71585",
  2009 + midnightblue: "191970",
  2010 + mintcream: "f5fffa",
  2011 + mistyrose: "ffe4e1",
  2012 + moccasin: "ffe4b5",
  2013 + navajowhite: "ffdead",
  2014 + navy: "000080",
  2015 + oldlace: "fdf5e6",
  2016 + olive: "808000",
  2017 + olivedrab: "6b8e23",
  2018 + orange: "ffa500",
  2019 + orangered: "ff4500",
  2020 + orchid: "da70d6",
  2021 + palegoldenrod: "eee8aa",
  2022 + palegreen: "98fb98",
  2023 + paleturquoise: "afeeee",
  2024 + palevioletred: "db7093",
  2025 + papayawhip: "ffefd5",
  2026 + peachpuff: "ffdab9",
  2027 + peru: "cd853f",
  2028 + pink: "ffc0cb",
  2029 + plum: "dda0dd",
  2030 + powderblue: "b0e0e6",
  2031 + purple: "800080",
  2032 + red: "f00",
  2033 + rosybrown: "bc8f8f",
  2034 + royalblue: "4169e1",
  2035 + saddlebrown: "8b4513",
  2036 + salmon: "fa8072",
  2037 + sandybrown: "f4a460",
  2038 + seagreen: "2e8b57",
  2039 + seashell: "fff5ee",
  2040 + sienna: "a0522d",
  2041 + silver: "c0c0c0",
  2042 + skyblue: "87ceeb",
  2043 + slateblue: "6a5acd",
  2044 + slategray: "708090",
  2045 + slategrey: "708090",
  2046 + snow: "fffafa",
  2047 + springgreen: "00ff7f",
  2048 + steelblue: "4682b4",
  2049 + tan: "d2b48c",
  2050 + teal: "008080",
  2051 + thistle: "d8bfd8",
  2052 + tomato: "ff6347",
  2053 + turquoise: "40e0d0",
  2054 + violet: "ee82ee",
  2055 + wheat: "f5deb3",
  2056 + white: "fff",
  2057 + whitesmoke: "f5f5f5",
  2058 + yellow: "ff0",
  2059 + yellowgreen: "9acd32"
  2060 + };
  2061 +
  2062 + // Make it easy to access colors via `hexNames[hex]`
  2063 + var hexNames = tinycolor.hexNames = flip(names);
  2064 +
  2065 +
  2066 + // Utilities
  2067 + // ---------
  2068 +
  2069 + // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }`
  2070 + function flip(o) {
  2071 + var flipped = { };
  2072 + for (var i in o) {
  2073 + if (o.hasOwnProperty(i)) {
  2074 + flipped[o[i]] = i;
  2075 + }
  2076 + }
  2077 + return flipped;
  2078 + }
  2079 +
  2080 + // Return a valid alpha value [0,1] with all invalid values being set to 1
  2081 + function boundAlpha(a) {
  2082 + a = parseFloat(a);
  2083 +
  2084 + if (isNaN(a) || a < 0 || a > 1) {
  2085 + a = 1;
  2086 + }
  2087 +
  2088 + return a;
  2089 + }
  2090 +
  2091 + // Take input from [0, n] and return it as [0, 1]
  2092 + function bound01(n, max) {
  2093 + if (isOnePointZero(n)) { n = "100%"; }
  2094 +
  2095 + var processPercent = isPercentage(n);
  2096 + n = mathMin(max, mathMax(0, parseFloat(n)));
  2097 +
  2098 + // Automatically convert percentage into number
  2099 + if (processPercent) {
  2100 + n = parseInt(n * max, 10) / 100;
  2101 + }
  2102 +
  2103 + // Handle floating point rounding errors
  2104 + if ((math.abs(n - max) < 0.000001)) {
  2105 + return 1;
  2106 + }
  2107 +
  2108 + // Convert into [0, 1] range if it isn't already
  2109 + return (n % max) / parseFloat(max);
  2110 + }
  2111 +
  2112 + // Force a number between 0 and 1
  2113 + function clamp01(val) {
  2114 + return mathMin(1, mathMax(0, val));
  2115 + }
  2116 +
  2117 + // Parse a base-16 hex value into a base-10 integer
  2118 + function parseIntFromHex(val) {
  2119 + return parseInt(val, 16);
  2120 + }
  2121 +
  2122 + // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
  2123 + // <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
  2124 + function isOnePointZero(n) {
  2125 + return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1;
  2126 + }
  2127 +
  2128 + // Check to see if string passed in is a percentage
  2129 + function isPercentage(n) {
  2130 + return typeof n === "string" && n.indexOf('%') != -1;
  2131 + }
  2132 +
  2133 + // Force a hex value to have 2 characters
  2134 + function pad2(c) {
  2135 + return c.length == 1 ? '0' + c : '' + c;
  2136 + }
  2137 +
  2138 + // Replace a decimal with it's percentage value
  2139 + function convertToPercentage(n) {
  2140 + if (n <= 1) {
  2141 + n = (n * 100) + "%";
  2142 + }
  2143 +
  2144 + return n;
  2145 + }
  2146 +
  2147 + // Converts a decimal to a hex value
  2148 + function convertDecimalToHex(d) {
  2149 + return Math.round(parseFloat(d) * 255).toString(16);
  2150 + }
  2151 + // Converts a hex value to a decimal
  2152 + function convertHexToDecimal(h) {
  2153 + return (parseIntFromHex(h) / 255);
  2154 + }
  2155 +
  2156 + var matchers = (function() {
  2157 +
  2158 + // <http://www.w3.org/TR/css3-values/#integers>
  2159 + var CSS_INTEGER = "[-\\+]?\\d+%?";
  2160 +
  2161 + // <http://www.w3.org/TR/css3-values/#number-value>
  2162 + var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?";
  2163 +
  2164 + // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
  2165 + var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")";
  2166 +
  2167 + // Actual matching.
  2168 + // Parentheses and commas are optional, but not required.
  2169 + // Whitespace can take the place of commas or opening paren
  2170 + var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
  2171 + var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
  2172 +
  2173 + return {
  2174 + rgb: new RegExp("rgb" + PERMISSIVE_MATCH3),
  2175 + rgba: new RegExp("rgba" + PERMISSIVE_MATCH4),
  2176 + hsl: new RegExp("hsl" + PERMISSIVE_MATCH3),
  2177 + hsla: new RegExp("hsla" + PERMISSIVE_MATCH4),
  2178 + hsv: new RegExp("hsv" + PERMISSIVE_MATCH3),
  2179 + hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
  2180 + hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
  2181 + hex8: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/
  2182 + };
  2183 + })();
  2184 +
  2185 + // `stringInputToObject`
  2186 + // Permissive string parsing. Take in a number of formats, and output an object
  2187 + // based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
  2188 + function stringInputToObject(color) {
  2189 +
  2190 + color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase();
  2191 + var named = false;
  2192 + if (names[color]) {
  2193 + color = names[color];
  2194 + named = true;
  2195 + }
  2196 + else if (color == 'transparent') {
  2197 + return { r: 0, g: 0, b: 0, a: 0, format: "name" };
  2198 + }
  2199 +
  2200 + // Try to match string input using regular expressions.
  2201 + // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360]
  2202 + // Just return an object and let the conversion functions handle that.
  2203 + // This way the result will be the same whether the tinycolor is initialized with string or object.
  2204 + var match;
  2205 + if ((match = matchers.rgb.exec(color))) {
  2206 + return { r: match[1], g: match[2], b: match[3] };
  2207 + }
  2208 + if ((match = matchers.rgba.exec(color))) {
  2209 + return { r: match[1], g: match[2], b: match[3], a: match[4] };
  2210 + }
  2211 + if ((match = matchers.hsl.exec(color))) {
  2212 + return { h: match[1], s: match[2], l: match[3] };
  2213 + }
  2214 + if ((match = matchers.hsla.exec(color))) {
  2215 + return { h: match[1], s: match[2], l: match[3], a: match[4] };
  2216 + }
  2217 + if ((match = matchers.hsv.exec(color))) {
  2218 + return { h: match[1], s: match[2], v: match[3] };
  2219 + }
  2220 + if ((match = matchers.hex8.exec(color))) {
  2221 + return {
  2222 + a: convertHexToDecimal(match[1]),
  2223 + r: parseIntFromHex(match[2]),
  2224 + g: parseIntFromHex(match[3]),
  2225 + b: parseIntFromHex(match[4]),
  2226 + format: named ? "name" : "hex8"
  2227 + };
  2228 + }
  2229 + if ((match = matchers.hex6.exec(color))) {
  2230 + return {
  2231 + r: parseIntFromHex(match[1]),
  2232 + g: parseIntFromHex(match[2]),
  2233 + b: parseIntFromHex(match[3]),
  2234 + format: named ? "name" : "hex"
  2235 + };
  2236 + }
  2237 + if ((match = matchers.hex3.exec(color))) {
  2238 + return {
  2239 + r: parseIntFromHex(match[1] + '' + match[1]),
  2240 + g: parseIntFromHex(match[2] + '' + match[2]),
  2241 + b: parseIntFromHex(match[3] + '' + match[3]),
  2242 + format: named ? "name" : "hex"
  2243 + };
  2244 + }
  2245 +
  2246 + return false;
  2247 + }
  2248 +
  2249 + window.tinycolor = tinycolor;
  2250 + })();
  2251 +
  2252 +
  2253 + $(function () {
  2254 + if ($.fn.spectrum.load) {
  2255 + $.fn.spectrum.processNativeColorInputs();
  2256 + }
  2257 + });
  2258 +
  2259 +})(window, jQuery);
public/stylesheets/application.css
@@ -2565,6 +2565,15 @@ div#activation_enterprise label, div#activation_enterprise input, div#activation @@ -2565,6 +2565,15 @@ div#activation_enterprise label, div#activation_enterprise input, div#activation
2565 color: #585858; 2565 color: #585858;
2566 font-size: 11px; 2566 font-size: 11px;
2567 } 2567 }
  2568 +.colorpicker_field {
  2569 + display: none;
  2570 +}
  2571 +.color_marker {
  2572 + display: inline-block;
  2573 + width: 10px;
  2574 + height: 10px;
  2575 + border: 1px solid;
  2576 +}
2568 .formfield input { 2577 .formfield input {
2569 text-indent: 5px; 2578 text-indent: 5px;
2570 padding: 2px 0px; 2579 padding: 2px 0px;
public/stylesheets/inputosaurus.css 0 → 100644
@@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
  1 +.inputosaurus-container .ui-autocomplete-input {
  2 + background-color: #ffffff;
  3 +}
  4 +
  5 +.inputosaurus-container {
  6 + /*background-color: #fff;*/
  7 + /*border: 1px solid #bcbec0;*/
  8 + margin: 0;
  9 + padding: 0;
  10 + display: inline-block;
  11 + cursor: text;
  12 + /**font-size: 14px;**/
  13 + /**font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;**/
  14 + max-height: 300px;
  15 + overflow: hidden;
  16 + overflow-y: auto;
  17 +}
  18 +.inputosaurus-container li {
  19 + display: block;
  20 + float: left;
  21 + overflow: hidden;
  22 + margin: 2px 2px 0;
  23 + padding: 2px 3px;
  24 + white-space: nowrap;
  25 + overflow: hidden;
  26 + -o-text-overflow: ellipsis;
  27 + -ms-text-overflow: ellipsis;
  28 + text-overflow: ellipsis;
  29 + background-color: #e5eff7;
  30 + border: #a9cae4 solid 1px;
  31 + -webkit-border-radius: 2px;
  32 + -moz-border-radius: 2px;
  33 + border-radius: 2px;
  34 + color: #5b9bcd;
  35 + -webkit-box-shadow: 0 1px 0 rgba(255,255,255,0.75) inset;
  36 + -moz-box-shadow: 0 1px 0 rgba(255,255,255,0.75) inset;
  37 + box-shadow: 0 1px 0 rgba(255,255,255,0.75) inset;
  38 + line-height: 20px;
  39 + cursor: default;
  40 +}
  41 +.inputosaurus-container li.inputosaurus-selected { background-color: #bdd6eb; }
  42 +.inputosaurus-container li a {
  43 + font-size: 16px;
  44 + color: #5b9bcd;
  45 + padding: 1px;
  46 + text-decoration: none;
  47 + outline: none;
  48 +}
  49 +.inputosaurus-container .inputosaurus-input {
  50 + border: none;
  51 + box-shadow: none;
  52 +}
  53 +.inputosaurus-container .inputosaurus-input input {
  54 + border: none;
  55 + height: 23px;
  56 + font-size: 14px;
  57 + line-height: 20px;
  58 + color: #555;
  59 + margin: 0;
  60 + outline: none;
  61 + padding: 0 0 1px 1px;
  62 + width: 25px;
  63 + -webkit-box-shadow: none;
  64 + -moz-box-shadow: none;
  65 + box-shadow: none;
  66 + background-image: none;
  67 + text-indent: 0px;
  68 +}
  69 +
  70 +.inputosaurus-container .inputosaurus-input input:hover {
  71 + -webkit-box-shadow: none;
  72 + -moz-box-shadow: none;
  73 + box-shadow: none;
  74 + background-color: #f0f0f0;
  75 +}
  76 +.inputosaurus-input-hidden { display: none; }
public/stylesheets/spectrum.css 0 → 100644
@@ -0,0 +1,506 @@ @@ -0,0 +1,506 @@
  1 +/***
  2 +Spectrum Colorpicker v1.4.1
  3 +https://github.com/bgrins/spectrum
  4 +Author: Brian Grinstead
  5 +License: MIT
  6 +***/
  7 +
  8 +.sp-container {
  9 + position:absolute;
  10 + top:0;
  11 + left:0;
  12 + display:inline-block;
  13 + *display: inline;
  14 + *zoom: 1;
  15 + /* https://github.com/bgrins/spectrum/issues/40 */
  16 + z-index: 9999994;
  17 + overflow: hidden;
  18 +}
  19 +.sp-container.sp-flat {
  20 + position: relative;
  21 +}
  22 +
  23 +/* Fix for * { box-sizing: border-box; } */
  24 +.sp-container,
  25 +.sp-container * {
  26 + -webkit-box-sizing: content-box;
  27 + -moz-box-sizing: content-box;
  28 + box-sizing: content-box;
  29 +}
  30 +
  31 +/* http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */
  32 +.sp-top {
  33 + position:relative;
  34 + width: 100%;
  35 + display:inline-block;
  36 +}
  37 +.sp-top-inner {
  38 + position:absolute;
  39 + top:0;
  40 + left:0;
  41 + bottom:0;
  42 + right:0;
  43 +}
  44 +.sp-color {
  45 + position: absolute;
  46 + top:0;
  47 + left:0;
  48 + bottom:0;
  49 + right:20%;
  50 +}
  51 +.sp-hue {
  52 + position: absolute;
  53 + top:0;
  54 + right:0;
  55 + bottom:0;
  56 + left:84%;
  57 + height: 100%;
  58 +}
  59 +
  60 +.sp-clear-enabled .sp-hue {
  61 + top:33px;
  62 + height: 77.5%;
  63 +}
  64 +
  65 +.sp-fill {
  66 + padding-top: 80%;
  67 +}
  68 +.sp-sat, .sp-val {
  69 + position: absolute;
  70 + top:0;
  71 + left:0;
  72 + right:0;
  73 + bottom:0;
  74 +}
  75 +
  76 +.sp-alpha-enabled .sp-top {
  77 + margin-bottom: 18px;
  78 +}
  79 +.sp-alpha-enabled .sp-alpha {
  80 + display: block;
  81 +}
  82 +.sp-alpha-handle {
  83 + position:absolute;
  84 + top:-4px;
  85 + bottom: -4px;
  86 + width: 6px;
  87 + left: 50%;
  88 + cursor: pointer;
  89 + border: 1px solid black;
  90 + background: white;
  91 + opacity: .8;
  92 +}
  93 +.sp-alpha {
  94 + display: none;
  95 + position: absolute;
  96 + bottom: -14px;
  97 + right: 0;
  98 + left: 0;
  99 + height: 8px;
  100 +}
  101 +.sp-alpha-inner {
  102 + border: solid 1px #333;
  103 +}
  104 +
  105 +.sp-clear {
  106 + display: none;
  107 +}
  108 +
  109 +.sp-clear.sp-clear-display {
  110 + background-position: center;
  111 +}
  112 +
  113 +.sp-clear-enabled .sp-clear {
  114 + display: block;
  115 + position:absolute;
  116 + top:0px;
  117 + right:0;
  118 + bottom:0;
  119 + left:84%;
  120 + height: 28px;
  121 +}
  122 +
  123 +/* Don't allow text selection */
  124 +.sp-container, .sp-replacer, .sp-preview, .sp-dragger, .sp-slider, .sp-alpha, .sp-clear, .sp-alpha-handle, .sp-container.sp-dragging .sp-input, .sp-container button {
  125 + -webkit-user-select:none;
  126 + -moz-user-select: -moz-none;
  127 + -o-user-select:none;
  128 + user-select: none;
  129 +}
  130 +
  131 +.sp-container.sp-input-disabled .sp-input-container {
  132 + display: none;
  133 +}
  134 +.sp-container.sp-buttons-disabled .sp-button-container {
  135 + display: none;
  136 +}
  137 +.sp-container.sp-palette-buttons-disabled .sp-palette-button-container {
  138 + display: none;
  139 +}
  140 +.sp-palette-only .sp-picker-container {
  141 + display: none;
  142 +}
  143 +.sp-palette-disabled .sp-palette-container {
  144 + display: none;
  145 +}
  146 +
  147 +.sp-initial-disabled .sp-initial {
  148 + display: none;
  149 +}
  150 +
  151 +
  152 +/* Gradients for hue, saturation and value instead of images. Not pretty... but it works */
  153 +.sp-sat {
  154 + background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#FFF), to(rgba(204, 154, 129, 0)));
  155 + background-image: -webkit-linear-gradient(left, #FFF, rgba(204, 154, 129, 0));
  156 + background-image: -moz-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
  157 + background-image: -o-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
  158 + background-image: -ms-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
  159 + background-image: linear-gradient(to right, #fff, rgba(204, 154, 129, 0));
  160 + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr=#FFFFFFFF, endColorstr=#00CC9A81)";
  161 + filter : progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr='#FFFFFFFF', endColorstr='#00CC9A81');
  162 +}
  163 +.sp-val {
  164 + background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#000000), to(rgba(204, 154, 129, 0)));
  165 + background-image: -webkit-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0));
  166 + background-image: -moz-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
  167 + background-image: -o-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
  168 + background-image: -ms-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
  169 + background-image: linear-gradient(to top, #000, rgba(204, 154, 129, 0));
  170 + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#00CC9A81, endColorstr=#FF000000)";
  171 + filter : progid:DXImageTransform.Microsoft.gradient(startColorstr='#00CC9A81', endColorstr='#FF000000');
  172 +}
  173 +
  174 +.sp-hue {
  175 + background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
  176 + background: -ms-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
  177 + background: -o-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
  178 + background: -webkit-gradient(linear, left top, left bottom, from(#ff0000), color-stop(0.17, #ffff00), color-stop(0.33, #00ff00), color-stop(0.5, #00ffff), color-stop(0.67, #0000ff), color-stop(0.83, #ff00ff), to(#ff0000));
  179 + background: -webkit-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
  180 +}
  181 +
  182 +/* IE filters do not support multiple color stops.
  183 + Generate 6 divs, line them up, and do two color gradients for each.
  184 + Yes, really.
  185 + */
  186 +.sp-1 {
  187 + height:17%;
  188 + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0000', endColorstr='#ffff00');
  189 +}
  190 +.sp-2 {
  191 + height:16%;
  192 + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff00', endColorstr='#00ff00');
  193 +}
  194 +.sp-3 {
  195 + height:17%;
  196 + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ff00', endColorstr='#00ffff');
  197 +}
  198 +.sp-4 {
  199 + height:17%;
  200 + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffff', endColorstr='#0000ff');
  201 +}
  202 +.sp-5 {
  203 + height:16%;
  204 + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0000ff', endColorstr='#ff00ff');
  205 +}
  206 +.sp-6 {
  207 + height:17%;
  208 + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff00ff', endColorstr='#ff0000');
  209 +}
  210 +
  211 +.sp-hidden {
  212 + display: none !important;
  213 +}
  214 +
  215 +/* Clearfix hack */
  216 +.sp-cf:before, .sp-cf:after { content: ""; display: table; }
  217 +.sp-cf:after { clear: both; }
  218 +.sp-cf { *zoom: 1; }
  219 +
  220 +/* Mobile devices, make hue slider bigger so it is easier to slide */
  221 +@media (max-device-width: 480px) {
  222 + .sp-color { right: 40%; }
  223 + .sp-hue { left: 63%; }
  224 + .sp-fill { padding-top: 60%; }
  225 +}
  226 +.sp-dragger {
  227 + border-radius: 5px;
  228 + height: 5px;
  229 + width: 5px;
  230 + border: 1px solid #fff;
  231 + background: #000;
  232 + cursor: pointer;
  233 + position:absolute;
  234 + top:0;
  235 + left: 0;
  236 +}
  237 +.sp-slider {
  238 + position: absolute;
  239 + top:0;
  240 + cursor:pointer;
  241 + height: 3px;
  242 + left: -1px;
  243 + right: -1px;
  244 + border: 1px solid #000;
  245 + background: white;
  246 + opacity: .8;
  247 +}
  248 +
  249 +/*
  250 +Theme authors:
  251 +Here are the basic themeable display options (colors, fonts, global widths).
  252 +See http://bgrins.github.io/spectrum/themes/ for instructions.
  253 +*/
  254 +
  255 +.sp-container {
  256 + border-radius: 0;
  257 + background-color: #ECECEC;
  258 + border: solid 1px #f0c49B;
  259 + padding: 0;
  260 +}
  261 +.sp-container, .sp-container button, .sp-container input, .sp-color, .sp-hue, .sp-clear {
  262 + font: normal 12px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif;
  263 + -webkit-box-sizing: border-box;
  264 + -moz-box-sizing: border-box;
  265 + -ms-box-sizing: border-box;
  266 + box-sizing: border-box;
  267 +}
  268 +.sp-top {
  269 + margin-bottom: 3px;
  270 +}
  271 +.sp-color, .sp-hue, .sp-clear {
  272 + border: solid 1px #666;
  273 +}
  274 +
  275 +/* Input */
  276 +.sp-input-container {
  277 + float:right;
  278 + width: 100px;
  279 + margin-bottom: 4px;
  280 +}
  281 +.sp-initial-disabled .sp-input-container {
  282 + width: 100%;
  283 +}
  284 +.sp-input {
  285 + font-size: 12px !important;
  286 + border: 1px inset;
  287 + padding: 4px 5px;
  288 + margin: 0;
  289 + width: 100%;
  290 + background:transparent;
  291 + border-radius: 3px;
  292 + color: #222;
  293 +}
  294 +.sp-input:focus {
  295 + border: 1px solid orange;
  296 +}
  297 +.sp-input.sp-validation-error {
  298 + border: 1px solid red;
  299 + background: #fdd;
  300 +}
  301 +.sp-picker-container , .sp-palette-container {
  302 + float:left;
  303 + position: relative;
  304 + padding: 10px;
  305 + padding-bottom: 300px;
  306 + margin-bottom: -290px;
  307 +}
  308 +.sp-picker-container {
  309 + width: 172px;
  310 + border-left: solid 1px #fff;
  311 +}
  312 +
  313 +/* Palettes */
  314 +.sp-palette-container {
  315 + border-right: solid 1px #ccc;
  316 +}
  317 +
  318 +.sp-palette-only .sp-palette-container {
  319 + border: 0;
  320 +}
  321 +
  322 +.sp-palette .sp-thumb-el {
  323 + display: block;
  324 + position:relative;
  325 + float:left;
  326 + width: 24px;
  327 + height: 15px;
  328 + margin: 3px;
  329 + cursor: pointer;
  330 + border:solid 2px transparent;
  331 +}
  332 +.sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active {
  333 + border-color: orange;
  334 +}
  335 +.sp-thumb-el {
  336 + position:relative;
  337 +}
  338 +
  339 +/* Initial */
  340 +.sp-initial {
  341 + float: left;
  342 + border: solid 1px #333;
  343 +}
  344 +.sp-initial span {
  345 + width: 30px;
  346 + height: 25px;
  347 + border:none;
  348 + display:block;
  349 + float:left;
  350 + margin:0;
  351 +}
  352 +
  353 +.sp-initial .sp-clear-display {
  354 + background-position: center;
  355 +}
  356 +
  357 +/* Buttons */
  358 +.sp-palette-button-container,
  359 +.sp-button-container {
  360 + float: right;
  361 +}
  362 +
  363 +/* Replacer (the little preview div that shows up instead of the <input>) */
  364 +.sp-replacer {
  365 + margin:0;
  366 + overflow:hidden;
  367 + cursor:pointer;
  368 + padding: 4px;
  369 + display:inline-block;
  370 + *zoom: 1;
  371 + *display: inline;
  372 + border: solid 1px #91765d;
  373 + background: #eee;
  374 + color: #333;
  375 + vertical-align: middle;
  376 +}
  377 +.sp-replacer:hover, .sp-replacer.sp-active {
  378 + border-color: #F0C49B;
  379 + color: #111;
  380 +}
  381 +.sp-replacer.sp-disabled {
  382 + cursor:default;
  383 + border-color: silver;
  384 + color: silver;
  385 +}
  386 +.sp-dd {
  387 + padding: 2px 0;
  388 + height: 16px;
  389 + line-height: 16px;
  390 + float:left;
  391 + font-size:10px;
  392 +}
  393 +.sp-preview {
  394 + position:relative;
  395 + width:25px;
  396 + height: 20px;
  397 + border: solid 1px #222;
  398 + margin-right: 5px;
  399 + float:left;
  400 + z-index: 0;
  401 +}
  402 +
  403 +.sp-palette {
  404 + *width: 220px;
  405 + max-width: 220px;
  406 +}
  407 +.sp-palette .sp-thumb-el {
  408 + width:16px;
  409 + height: 16px;
  410 + margin:2px 1px;
  411 + border: solid 1px #d0d0d0;
  412 +}
  413 +
  414 +.sp-container {
  415 + padding-bottom:0;
  416 +}
  417 +
  418 +
  419 +/* Buttons: http://hellohappy.org/css3-buttons/ */
  420 +.sp-container button {
  421 + background-color: #eeeeee;
  422 + background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc);
  423 + background-image: -moz-linear-gradient(top, #eeeeee, #cccccc);
  424 + background-image: -ms-linear-gradient(top, #eeeeee, #cccccc);
  425 + background-image: -o-linear-gradient(top, #eeeeee, #cccccc);
  426 + background-image: linear-gradient(to bottom, #eeeeee, #cccccc);
  427 + border: 1px solid #ccc;
  428 + border-bottom: 1px solid #bbb;
  429 + border-radius: 3px;
  430 + color: #333;
  431 + font-size: 14px;
  432 + line-height: 1;
  433 + padding: 5px 4px;
  434 + text-align: center;
  435 + text-shadow: 0 1px 0 #eee;
  436 + vertical-align: middle;
  437 +}
  438 +.sp-container button:hover {
  439 + background-color: #dddddd;
  440 + background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb);
  441 + background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb);
  442 + background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb);
  443 + background-image: -o-linear-gradient(top, #dddddd, #bbbbbb);
  444 + background-image: linear-gradient(to bottom, #dddddd, #bbbbbb);
  445 + border: 1px solid #bbb;
  446 + border-bottom: 1px solid #999;
  447 + cursor: pointer;
  448 + text-shadow: 0 1px 0 #ddd;
  449 +}
  450 +.sp-container button:active {
  451 + border: 1px solid #aaa;
  452 + border-bottom: 1px solid #888;
  453 + -webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
  454 + -moz-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
  455 + -ms-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
  456 + -o-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
  457 + box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
  458 +}
  459 +.sp-cancel {
  460 + font-size: 11px;
  461 + color: #d93f3f !important;
  462 + margin:0;
  463 + padding:2px;
  464 + margin-right: 5px;
  465 + vertical-align: middle;
  466 + text-decoration:none;
  467 +
  468 +}
  469 +.sp-cancel:hover {
  470 + color: #d93f3f !important;
  471 + text-decoration: underline;
  472 +}
  473 +
  474 +
  475 +.sp-palette span:hover, .sp-palette span.sp-thumb-active {
  476 + border-color: #000;
  477 +}
  478 +
  479 +.sp-preview, .sp-alpha, .sp-thumb-el {
  480 + position:relative;
  481 + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
  482 +}
  483 +.sp-preview-inner, .sp-alpha-inner, .sp-thumb-inner {
  484 + display:block;
  485 + position:absolute;
  486 + top:0;left:0;bottom:0;right:0;
  487 +}
  488 +
  489 +.sp-palette .sp-thumb-inner {
  490 + background-position: 50% 50%;
  491 + background-repeat: no-repeat;
  492 +}
  493 +
  494 +.sp-palette .sp-thumb-light.sp-thumb-active .sp-thumb-inner {
  495 + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUeNpiYBhsgJFMffxAXABlN5JruT4Q3wfi/0DsT64h8UD8HmpIPCWG/KemIfOJCUB+Aoacx6EGBZyHBqI+WsDCwuQ9mhxeg2A210Ntfo8klk9sOMijaURm7yc1UP2RNCMbKE9ODK1HM6iegYLkfx8pligC9lCD7KmRof0ZhjQACDAAceovrtpVBRkAAAAASUVORK5CYII=);
  496 +}
  497 +
  498 +.sp-palette .sp-thumb-dark.sp-thumb-active .sp-thumb-inner {
  499 + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMdJREFUOE+tkgsNwzAMRMugEAahEAahEAZhEAqlEAZhEAohEAYh81X2dIm8fKpEspLGvudPOsUYpxE2BIJCroJmEW9qJ+MKaBFhEMNabSy9oIcIPwrB+afvAUFoK4H0tMaQ3XtlrggDhOVVMuT4E5MMG0FBbCEYzjYT7OxLEvIHQLY2zWwQ3D+9luyOQTfKDiFD3iUIfPk8VqrKjgAiSfGFPecrg6HN6m/iBcwiDAo7WiBeawa+Kwh7tZoSCGLMqwlSAzVDhoK+6vH4G0P5wdkAAAAASUVORK5CYII=);
  500 +}
  501 +
  502 +.sp-clear-display {
  503 + background-repeat:no-repeat;
  504 + background-position: center;
  505 + background-image: url(data:image/gif;base64,R0lGODlhFAAUAPcAAAAAAJmZmZ2dnZ6enqKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq/Hx8fLy8vT09PX19ff39/j4+Pn5+fr6+vv7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAP8ALAAAAAAUABQAAAihAP9FoPCvoMGDBy08+EdhQAIJCCMybCDAAYUEARBAlFiQQoMABQhKUJBxY0SPICEYHBnggEmDKAuoPMjS5cGYMxHW3IiT478JJA8M/CjTZ0GgLRekNGpwAsYABHIypcAgQMsITDtWJYBR6NSqMico9cqR6tKfY7GeBCuVwlipDNmefAtTrkSzB1RaIAoXodsABiZAEFB06gIBWC1mLVgBa0AAOw==);
  506 +}
script/install-dependencies/debian-wheezy.sh
@@ -48,9 +48,10 @@ FPQAoNmiMgP6zGF9rgOEWMEiFEryayrz @@ -48,9 +48,10 @@ FPQAoNmiMgP6zGF9rgOEWMEiFEryayrz
48 =70DR 48 =70DR
49 -----END PGP PUBLIC KEY BLOCK----- 49 -----END PGP PUBLIC KEY BLOCK-----
50 EOF 50 EOF
51 - run sudo apt-get update  
52 fi 51 fi
53 52
  53 +run sudo apt-get update
  54 +
54 run sudo apt-get -y install dctrl-tools 55 run sudo apt-get -y install dctrl-tools
55 56
56 # needed to run noosfero 57 # needed to run noosfero
script/noosfero-plugins
@@ -77,8 +77,7 @@ run(){ @@ -77,8 +77,7 @@ run(){
77 77
78 _enable(){ 78 _enable(){
79 plugin="$1" 79 plugin="$1"
80 - cd $enabled_plugins_dir  
81 - source="../../plugins/$plugin" 80 + source="$available_plugins_dir/$plugin"
82 target="$enabled_plugins_dir/$plugin" 81 target="$enabled_plugins_dir/$plugin"
83 base="$base_plugins_dir/$plugin" 82 base="$base_plugins_dir/$plugin"
84 run "$source/before_enable.rb" 83 run "$source/before_enable.rb"
@@ -101,15 +100,11 @@ _enable(){ @@ -101,15 +100,11 @@ _enable(){
101 fi 100 fi
102 fi 101 fi
103 if [ "$installation_ok" = true ] && [ "$dependencies_ok" = true ]; then 102 if [ "$installation_ok" = true ] && [ "$dependencies_ok" = true ]; then
104 - ln -s "$source" "$plugin" 103 + ln -s "$source" "$target"
105 plugins_public_dir="$NOOSFERO_DIR/public/plugins" 104 plugins_public_dir="$NOOSFERO_DIR/public/plugins"
106 plugins_features_dir="$NOOSFERO_DIR/features/plugins" 105 plugins_features_dir="$NOOSFERO_DIR/features/plugins"
107 - cd $plugins_public_dir  
108 - test -d "$source/public" && ln -s "$source/public" "$plugin"  
109 - if [ -d "$NOOSFERO_DIR/features" ]; then  
110 - cd $plugins_features_dir  
111 - test -d "$source/features" && ln -s "$source/features" "$plugin"  
112 - fi 106 + test -d "$target/public" && ln -s "$target/public" "$plugins_public_dir/$plugin"
  107 + test -d "$NOOSFERO_DIR/features" && test -d "$target/features" && ln -s "$target/features" "$plugins_features_dir/$plugin"
113 _say "$plugin enabled" 108 _say "$plugin enabled"
114 run "$source/after_enable.rb" 109 run "$source/after_enable.rb"
115 needs_migrate=true 110 needs_migrate=true
script/quick-start
@@ -85,6 +85,7 @@ fi @@ -85,6 +85,7 @@ fi
85 run rake db:schema:load 85 run rake db:schema:load
86 run rake db:data:minimal 86 run rake db:data:minimal
87 run rake db:test:prepare 87 run rake db:test:prepare
  88 +rails runner 'Environment.default.enable("skip_new_user_email_confirmation")'
88 89
89 # FIXME compile translations depends on ruby-gettext-rails, please see debian/control 90 # FIXME compile translations depends on ruby-gettext-rails, please see debian/control
90 # run rake noosfero:translations:compile 91 # run rake noosfero:translations:compile
test/functional/application_controller_test.rb
@@ -166,7 +166,7 @@ class ApplicationControllerTest &lt; ActionController::TestCase @@ -166,7 +166,7 @@ class ApplicationControllerTest &lt; ActionController::TestCase
166 166
167 should 'display only some categories in menu' do 167 should 'display only some categories in menu' do
168 @controller.stubs(:get_layout).returns('application') 168 @controller.stubs(:get_layout).returns('application')
169 - c1 = Environment.default.categories.create!(:name => 'Category 1', :display_color => 1, :parent_id => nil, :display_in_menu => true ) 169 + c1 = Environment.default.categories.create!(:name => 'Category 1', :display_color => 'ffa500', :parent_id => nil, :display_in_menu => true )
170 c2 = Environment.default.categories.create!(:name => 'Category 2', :display_color => nil, :parent_id => c1.id, :display_in_menu => true ) 170 c2 = Environment.default.categories.create!(:name => 'Category 2', :display_color => nil, :parent_id => c1.id, :display_in_menu => true )
171 get :index 171 get :index
172 assert_tag :tag => 'a', :content => /Category 2/ 172 assert_tag :tag => 'a', :content => /Category 2/
@@ -174,7 +174,7 @@ class ApplicationControllerTest &lt; ActionController::TestCase @@ -174,7 +174,7 @@ class ApplicationControllerTest &lt; ActionController::TestCase
174 174
175 should 'not display some categories in menu' do 175 should 'not display some categories in menu' do
176 @controller.stubs(:get_layout).returns('application') 176 @controller.stubs(:get_layout).returns('application')
177 - c1 = Environment.default.categories.create!(:name => 'Category 1', :display_color => 1, :parent_id => nil, :display_in_menu => true) 177 + c1 = Environment.default.categories.create!(:name => 'Category 1', :display_color => 'ffa500', :parent_id => nil, :display_in_menu => true)
178 c2 = Environment.default.categories.create!(:name => 'Category 2', :display_color => nil, :parent_id => c1) 178 c2 = Environment.default.categories.create!(:name => 'Category 2', :display_color => nil, :parent_id => c1)
179 get :index 179 get :index
180 assert_no_tag :tag => 'a', :content => /Category 2/ 180 assert_no_tag :tag => 'a', :content => /Category 2/
@@ -239,7 +239,7 @@ class ApplicationControllerTest &lt; ActionController::TestCase @@ -239,7 +239,7 @@ class ApplicationControllerTest &lt; ActionController::TestCase
239 239
240 should 'not display categories menu if categories feature disabled' do 240 should 'not display categories menu if categories feature disabled' do
241 Environment.any_instance.stubs(:enabled?).with(anything).returns(true) 241 Environment.any_instance.stubs(:enabled?).with(anything).returns(true)
242 - c1 = Environment.default.categories.create!(:name => 'Category 1', :display_color => 1, :parent_id => nil, :display_in_menu => true ) 242 + c1 = Environment.default.categories.create!(:name => 'Category 1', :display_color => 'ffa500', :parent_id => nil, :display_in_menu => true )
243 c2 = Environment.default.categories.create!(:name => 'Category 2', :display_color => nil, :parent_id => c1.id, :display_in_menu => true ) 243 c2 = Environment.default.categories.create!(:name => 'Category 2', :display_color => nil, :parent_id => c1.id, :display_in_menu => true )
244 get :index 244 get :index
245 assert_no_tag :tag => 'a', :content => /Category 2/ 245 assert_no_tag :tag => 'a', :content => /Category 2/
@@ -557,4 +557,63 @@ class ApplicationControllerTest &lt; ActionController::TestCase @@ -557,4 +557,63 @@ class ApplicationControllerTest &lt; ActionController::TestCase
557 assert_no_tag :tag => 'meta', :attributes => { :property => 'article:published_time' } 557 assert_no_tag :tag => 'meta', :attributes => { :property => 'article:published_time' }
558 assert_no_tag :tag => 'meta', :attributes => { :property => 'og:image' } 558 assert_no_tag :tag => 'meta', :attributes => { :property => 'og:image' }
559 end 559 end
  560 +
  561 + should 'redirect to login if environment is restrict to members' do
  562 + Environment.default.enable(:restrict_to_members)
  563 + get :index
  564 + assert_redirected_to :controller => 'account', :action => 'login'
  565 + end
  566 +
  567 + should 'do not allow member not included in whitelist to access an restricted environment' do
  568 + user = create_user
  569 + e = Environment.default
  570 + e.enable(:restrict_to_members)
  571 + e.members_whitelist_enabled = true
  572 + e.save!
  573 + login_as(user.login)
  574 + get :index
  575 + assert_response :forbidden
  576 + end
  577 +
  578 + should 'allow member in whitelist to access an environment' do
  579 + user = create_user
  580 + e = Environment.default
  581 + e.members_whitelist_enabled = true
  582 + e.members_whitelist = "#{user.person.id}"
  583 + e.save!
  584 + login_as(user.login)
  585 + get :index
  586 + assert_response :success
  587 + end
  588 +
  589 + should 'allow members to access an environment if whitelist is disabled' do
  590 + user = create_user
  591 + e = Environment.default
  592 + e.members_whitelist_enabled = false
  593 + e.save!
  594 + login_as(user.login)
  595 + get :index
  596 + assert_response :success
  597 + end
  598 +
  599 + should 'allow admin to access an environment if whitelist is enabled' do
  600 + e = Environment.default
  601 + e.members_whitelist_enabled = true
  602 + e.save!
  603 + login_as(create_admin_user(e))
  604 + get :index
  605 + assert_response :success
  606 + end
  607 +
  608 + should 'not check whitelist members if the environment is not restrict to members' do
  609 + e = Environment.default
  610 + e.disable(:restrict_to_members)
  611 + e.members_whitelist_enabled = true
  612 + e.save!
  613 + @controller.expects(:verify_members_whitelist).never
  614 + login_as create_user.login
  615 + get :index
  616 + assert_response :success
  617 + end
  618 +
560 end 619 end
test/functional/categories_controller_test.rb
@@ -41,7 +41,7 @@ class CategoriesControllerTest &lt; ActionController::TestCase @@ -41,7 +41,7 @@ class CategoriesControllerTest &lt; ActionController::TestCase
41 end 41 end
42 42
43 def test_edit_save 43 def test_edit_save
44 - post :edit, :id => cat1.id, :category => { :name => 'new name for category' } 44 + post :edit, :id => cat1.id, :category => { :name => 'new name for category', :display_color => nil }
45 assert_redirected_to :action => 'index' 45 assert_redirected_to :action => 'index'
46 assert_equal 'new name for category', Category.find(cat1.id).name 46 assert_equal 'new name for category', Category.find(cat1.id).name
47 end 47 end
@@ -134,7 +134,7 @@ class CategoriesControllerTest &lt; ActionController::TestCase @@ -134,7 +134,7 @@ class CategoriesControllerTest &lt; ActionController::TestCase
134 env.save! 134 env.save!
135 get :new 135 get :new
136 136
137 - assert_no_tag :tag => 'select', :attributes => { :name => "category[display_color]" } 137 + assert_no_tag :tag => 'input', :attributes => { :name => "category[display_color]" }
138 end 138 end
139 139
140 should 'display color selection if environment.categories_menu is true' do 140 should 'display color selection if environment.categories_menu is true' do
@@ -142,7 +142,7 @@ class CategoriesControllerTest &lt; ActionController::TestCase @@ -142,7 +142,7 @@ class CategoriesControllerTest &lt; ActionController::TestCase
142 env.save! 142 env.save!
143 get :new 143 get :new
144 144
145 - assert_tag :tag => 'select', :attributes => { :name => "category[display_color]" } 145 + assert_tag :tag => 'input', :attributes => { :name => "category[display_color]" }
146 end 146 end
147 147
148 should 'not list regions and product categories' do 148 should 'not list regions and product categories' do
test/functional/cms_controller_test.rb
@@ -1807,6 +1807,23 @@ class CmsControllerTest &lt; ActionController::TestCase @@ -1807,6 +1807,23 @@ class CmsControllerTest &lt; ActionController::TestCase
1807 assert_template 'cms/publish' 1807 assert_template 'cms/publish'
1808 end 1808 end
1809 1809
  1810 + should 'response of search_tags be json' do
  1811 + get :search_tags, :profile => profile.identifier, :term => 'linux'
  1812 + assert_equal 'application/json', @response.content_type
  1813 + end
  1814 +
  1815 + should 'return empty json if does not find tag' do
  1816 + get :search_tags, :profile => profile.identifier, :term => 'linux'
  1817 + assert_equal "[]", @response.body
  1818 + end
  1819 +
  1820 + should 'return tags found' do
  1821 + tag = mock; tag.stubs(:name).returns('linux')
  1822 + ActsAsTaggableOn::Tag.stubs(:find).returns([tag])
  1823 + get :search_tags, :profile => profile.identifier, :term => 'linux'
  1824 + assert_equal '[{"label":"linux","value":"linux"}]', @response.body
  1825 + end
  1826 +
1810 protected 1827 protected
1811 1828
1812 # FIXME this is to avoid adding an extra dependency for a proper JSON parser. 1829 # FIXME this is to avoid adding an extra dependency for a proper JSON parser.
test/functional/environment_design_controller_test.rb
@@ -6,9 +6,7 @@ class EnvironmentDesignController; def rescue_action(e) raise e end; end @@ -6,9 +6,7 @@ class EnvironmentDesignController; def rescue_action(e) raise e end; end
6 6
7 class EnvironmentDesignControllerTest < ActionController::TestCase 7 class EnvironmentDesignControllerTest < ActionController::TestCase
8 8
9 - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from  
10 - # the Noosfero core soon, see ActionItem3045  
11 - ALL_BLOCKS = [ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock, TagsBlock ] 9 + ALL_BLOCKS = [ArticleBlock, LoginBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock, TagsBlock ]
12 10
13 def setup 11 def setup
14 @controller = EnvironmentDesignController.new 12 @controller = EnvironmentDesignController.new
@@ -77,18 +75,6 @@ class EnvironmentDesignControllerTest &lt; ActionController::TestCase @@ -77,18 +75,6 @@ class EnvironmentDesignControllerTest &lt; ActionController::TestCase
77 assert_tag :tag => 'p', :attributes => { :id => 'no_portal_community' } 75 assert_tag :tag => 'p', :attributes => { :id => 'no_portal_community' }
78 end 76 end
79 77
80 - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from  
81 - # the Noosfero core soon, see ActionItem3045  
82 - should 'be able to edit EnvironmentStatisticsBlock' do  
83 - login_as(create_admin_user(Environment.default))  
84 - b = EnvironmentStatisticsBlock.create!  
85 - e = Environment.default  
86 - e.boxes.create!  
87 - e.boxes.first.blocks << b  
88 - get :edit, :id => b.id  
89 - assert_tag :tag => 'input', :attributes => { :id => 'block_title' }  
90 - end  
91 -  
92 should 'be able to edit EnterprisesBlock' do 78 should 'be able to edit EnterprisesBlock' do
93 login_as(create_admin_user(Environment.default)) 79 login_as(create_admin_user(Environment.default))
94 b = EnterprisesBlock.create! 80 b = EnterprisesBlock.create!
test/functional/features_controller_test.rb
@@ -146,4 +146,20 @@ class FeaturesControllerTest &lt; ActionController::TestCase @@ -146,4 +146,20 @@ class FeaturesControllerTest &lt; ActionController::TestCase
146 assert_equal true, e.custom_community_fields['contact_person']['required'] 146 assert_equal true, e.custom_community_fields['contact_person']['required']
147 end 147 end
148 148
  149 + should 'search members by name' do
  150 + uses_host 'anhetegua.net'
  151 + person = fast_create(Person, :environment_id => Environment.find(2).id)
  152 + xhr :get, :search_members, :q => person.name[0..2]
  153 + json_response = ActiveSupport::JSON.decode(@response.body)
  154 + assert_includes json_response, {"id"=>person.id, "name"=>person.name}
  155 + end
  156 +
  157 + should 'search members by identifier' do
  158 + uses_host 'anhetegua.net'
  159 + person = fast_create(Person, :name => 'Some Name', :identifier => 'person-identifier', :environment_id => Environment.find(2).id)
  160 + xhr :get, :search_members, :q => person.identifier
  161 + json_response = ActiveSupport::JSON.decode(@response.body)
  162 + assert_includes json_response, {"id"=>person.id, "name"=>person.name}
  163 + end
  164 +
149 end 165 end
test/unit/box_test.rb
@@ -33,9 +33,6 @@ class BoxTest &lt; ActiveSupport::TestCase @@ -33,9 +33,6 @@ class BoxTest &lt; ActiveSupport::TestCase
33 assert blocks.include?('categories-block') 33 assert blocks.include?('categories-block')
34 assert blocks.include?('communities-block') 34 assert blocks.include?('communities-block')
35 assert blocks.include?('enterprises-block') 35 assert blocks.include?('enterprises-block')
36 - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from  
37 - # the Noosfero core soon, see ActionItem3045  
38 - assert blocks.include?('environment-statistics-block')  
39 assert blocks.include?('fans-block') 36 assert blocks.include?('fans-block')
40 assert blocks.include?('favorite-enterprises-block') 37 assert blocks.include?('favorite-enterprises-block')
41 assert blocks.include?('feed-reader-block') 38 assert blocks.include?('feed-reader-block')
@@ -64,9 +61,6 @@ class BoxTest &lt; ActiveSupport::TestCase @@ -64,9 +61,6 @@ class BoxTest &lt; ActiveSupport::TestCase
64 assert blocks.include?('communities-block') 61 assert blocks.include?('communities-block')
65 assert blocks.include?('disabled-enterprise-message-block') 62 assert blocks.include?('disabled-enterprise-message-block')
66 assert blocks.include?('enterprises-block') 63 assert blocks.include?('enterprises-block')
67 - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from  
68 - # the Noosfero core soon, see ActionItem3045  
69 - assert blocks.include?('environment-statistics-block')  
70 assert blocks.include?('fans-block') 64 assert blocks.include?('fans-block')
71 assert blocks.include?('favorite-enterprises-block') 65 assert blocks.include?('favorite-enterprises-block')
72 assert blocks.include?('featured-products-block') 66 assert blocks.include?('featured-products-block')
test/unit/categories_helper_test.rb
@@ -15,8 +15,26 @@ class CategoriesHelperTest &lt; ActiveSupport::TestCase @@ -15,8 +15,26 @@ class CategoriesHelperTest &lt; ActiveSupport::TestCase
15 expects(:options_for_select).with([['General Category', 'Category'],[ 'Product Category', 'ProductCategory'],[ 'Region', 'Region' ]], 'fieldvalue').returns('OPTIONS') 15 expects(:options_for_select).with([['General Category', 'Category'],[ 'Product Category', 'ProductCategory'],[ 'Region', 'Region' ]], 'fieldvalue').returns('OPTIONS')
16 expects(:select_tag).with('type', 'OPTIONS').returns('TAG') 16 expects(:select_tag).with('type', 'OPTIONS').returns('TAG')
17 expects(:labelled_form_field).with(anything, 'TAG').returns('RESULT') 17 expects(:labelled_form_field).with(anything, 'TAG').returns('RESULT')
18 - 18 +
19 assert_equal 'RESULT', select_category_type('fieldname') 19 assert_equal 'RESULT', select_category_type('fieldname')
20 end 20 end
21 21
  22 + should 'return category color if its defined' do
  23 + category1 = fast_create(Category, :name => 'education', :display_color => 'fbfbfb')
  24 + assert_equal 'background-color: #fbfbfb;', category_color_style(category1)
  25 + end
  26 +
  27 + should 'not return category parent color if category color is not defined' do
  28 + e = fast_create(Environment)
  29 + category1 = fast_create(Category, :name => 'education', :display_color => 'fbfbfb', :environment_id => e.id)
  30 + category2 = fast_create(Category, :name => 'education', :display_color => nil, :parent_id => category1.id, :environment_id => e.id)
  31 + assert_equal '', category_color_style(category2)
  32 + end
  33 +
  34 + should 'not return category parent color if category is nil' do
  35 + assert_nothing_raised do
  36 + assert_equal '', category_color_style(nil)
  37 + end
  38 + end
  39 +
22 end 40 end
test/unit/category_test.rb
@@ -159,36 +159,6 @@ class CategoryTest &lt; ActiveSupport::TestCase @@ -159,36 +159,6 @@ class CategoryTest &lt; ActiveSupport::TestCase
159 159
160 end 160 end
161 161
162 - should "limit the possibile display colors" do  
163 - c = build(Category, :name => 'test category', :environment_id => @env.id)  
164 -  
165 - c.display_color = 16  
166 - c.valid?  
167 - assert c.errors[:display_color.to_s].present?  
168 -  
169 - valid = (1..15).map { |item| item.to_i }  
170 - valid.each do |item|  
171 - c.display_color = item  
172 - c.valid?  
173 - assert !c.errors[:display_color.to_s].present?  
174 - end  
175 -  
176 - end  
177 -  
178 - should 'avoid duplicated display colors' do  
179 - c1 = fast_create(Category, :name => 'test category', :environment_id => @env.id, :display_color => 1)  
180 -  
181 - c = build(Category, :name => 'lalala', :environment_id => @env.id)  
182 - c.display_color = 1  
183 - assert !c.valid?  
184 - assert c.errors[:display_color.to_s].present?  
185 -  
186 - c.display_color = 2  
187 - c.valid?  
188 - assert !c.errors[:display_color.to_s].present?  
189 -  
190 - end  
191 -  
192 should 'be able to get top ancestor' do 162 should 'be able to get top ancestor' do
193 c1 = fast_create(Category, :name => 'test category', :environment_id => @env.id) 163 c1 = fast_create(Category, :name => 'test category', :environment_id => @env.id)
194 c2 = fast_create(Category, :name => 'test category', :environment_id => @env.id, :parent_id => c1.id) 164 c2 = fast_create(Category, :name => 'test category', :environment_id => @env.id, :parent_id => c1.id)
@@ -535,4 +505,28 @@ class CategoryTest &lt; ActiveSupport::TestCase @@ -535,4 +505,28 @@ class CategoryTest &lt; ActiveSupport::TestCase
535 assert_includes Category.on_level(parent.id), category 505 assert_includes Category.on_level(parent.id), category
536 end 506 end
537 507
  508 + should 'return self if the category has display_color defined' do
  509 + c1 = fast_create(Category)
  510 + c2 = fast_create(Category, :parent_id => c1)
  511 + c3 = fast_create(Category, :parent_id => c2, :display_color => 'FFFFFF')
  512 + c4 = fast_create(Category, :parent_id => c3, :display_color => '000000')
  513 + assert_equal c4, c4.with_color
  514 + end
  515 +
  516 + should 'return first category on hierarchy with display_color defined' do
  517 + c1 = fast_create(Category, :display_color => '111111')
  518 + c2 = fast_create(Category, :parent_id => c1)
  519 + c3 = fast_create(Category, :parent_id => c2)
  520 + c4 = fast_create(Category, :parent_id => c3)
  521 + assert_equal c1, c4.with_color
  522 + end
  523 +
  524 + should 'return nil if no category on hierarchy has display_color defined' do
  525 + c1 = fast_create(Category)
  526 + c2 = fast_create(Category, :parent_id => c1)
  527 + c3 = fast_create(Category, :parent_id => c2)
  528 + c4 = fast_create(Category, :parent_id => c3)
  529 + assert_equal nil, c4.with_color
  530 + end
  531 +
538 end 532 end
test/unit/environment_statistics_block_test.rb
@@ -1,99 +0,0 @@ @@ -1,99 +0,0 @@
1 -# TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from  
2 -# the Noosfero core soon, see ActionItem3045  
3 -  
4 -require File.dirname(__FILE__) + '/../test_helper'  
5 -  
6 -class EnvironmentStatisticsBlockTest < ActiveSupport::TestCase  
7 -  
8 - should 'inherit from Block' do  
9 - assert_kind_of Block, EnvironmentStatisticsBlock.new  
10 - end  
11 -  
12 - should 'describe itself' do  
13 - assert_not_equal Block.description, EnvironmentStatisticsBlock.description  
14 - end  
15 -  
16 - should 'provide a default title' do  
17 - owner = mock  
18 - owner.expects(:name).returns('my environment')  
19 -  
20 - block = EnvironmentStatisticsBlock.new  
21 - block.expects(:owner).returns(owner)  
22 - assert_equal 'Statistics for my environment', block.title  
23 - end  
24 -  
25 - should 'generate statistics' do  
26 - env = create(Environment)  
27 - user1 = create_user('testuser1', :environment_id => env.id)  
28 - user2 = create_user('testuser2', :environment_id => env.id)  
29 -  
30 - fast_create(Enterprise, :environment_id => env.id)  
31 - fast_create(Community, :environment_id => env.id)  
32 -  
33 - block = EnvironmentStatisticsBlock.new  
34 - env.boxes.first.blocks << block  
35 -  
36 - content = block.content  
37 -  
38 - assert_match(/One enterprise/, content)  
39 - assert_match(/2 users/, content)  
40 - assert_match(/One community/, content)  
41 - end  
42 -  
43 - should 'generate statistics including private profiles' do  
44 - env = create(Environment)  
45 - user1 = create_user('testuser1', :environment_id => env.id)  
46 - user2 = create_user('testuser2', :environment_id => env.id)  
47 - user3 = create_user('testuser3', :environment_id => env.id)  
48 - p = user3.person; p.public_profile = false; p.save!  
49 -  
50 - fast_create(Enterprise, :environment_id => env.id)  
51 - fast_create(Enterprise, :environment_id => env.id, :public_profile => false)  
52 -  
53 - fast_create(Community, :environment_id => env.id)  
54 - fast_create(Community, :environment_id => env.id, :public_profile => false)  
55 -  
56 - block = EnvironmentStatisticsBlock.new  
57 - env.boxes.first.blocks << block  
58 -  
59 - content = block.content  
60 -  
61 - assert_match /2 enterprises/, content  
62 - assert_match /3 users/, content  
63 - assert_match /2 communities/, content  
64 - end  
65 -  
66 - should 'generate statistics but not for not visible profiles' do  
67 - env = create(Environment)  
68 - user1 = create_user('testuser1', :environment_id => env.id)  
69 - user2 = create_user('testuser2', :environment_id => env.id)  
70 - user3 = create_user('testuser3', :environment_id => env.id)  
71 - p = user3.person; p.visible = false; p.save!  
72 -  
73 - fast_create(Enterprise, :environment_id => env.id)  
74 - fast_create(Enterprise, :environment_id => env.id, :visible => false)  
75 -  
76 - fast_create(Community, :environment_id => env.id)  
77 - fast_create(Community, :environment_id => env.id, :visible => false)  
78 -  
79 - block = EnvironmentStatisticsBlock.new  
80 - env.boxes.first.blocks << block  
81 -  
82 - content = block.content  
83 -  
84 - assert_match /One enterprise/, content  
85 - assert_match /2 users/, content  
86 - assert_match /One community/, content  
87 - end  
88 -  
89 - should 'not display enterprises if disabled' do  
90 - env = fast_create(Environment)  
91 - env.enable('disable_asset_enterprises', false)  
92 -  
93 - block = EnvironmentStatisticsBlock.new  
94 - block.stubs(:owner).returns(env)  
95 -  
96 - assert_no_match /enterprises/i, block.content  
97 - end  
98 -  
99 -end  
test/unit/environment_test.rb
@@ -156,7 +156,7 @@ class EnvironmentTest &lt; ActiveSupport::TestCase @@ -156,7 +156,7 @@ class EnvironmentTest &lt; ActiveSupport::TestCase
156 156
157 should 'list displayable categories' do 157 should 'list displayable categories' do
158 env = fast_create(Environment) 158 env = fast_create(Environment)
159 - cat1 = create(Category, :environment => env, :name => 'category one', :display_color => 1) 159 + cat1 = create(Category, :environment => env, :name => 'category one', :display_color => 'ffa500')
160 assert ! cat1.new_record? 160 assert ! cat1.new_record?
161 161
162 # subcategories should be ignored 162 # subcategories should be ignored
test/unit/moderate_user_registration_test.rb 0 → 100644
@@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
  1 +# encoding: UTF-8
  2 +require File.dirname(__FILE__) + '/../test_helper'
  3 +
  4 +class ModerateUserRegistrationTest < ActiveSupport::TestCase
  5 + fixtures :users, :environments
  6 +
  7 + def test_should_on_perform_activate_user
  8 + user = User.new(:login => 'lalala', :email => 'lalala@example.com', :password => 'test', :password_confirmation => 'test')
  9 + user.save!
  10 + environment = Environment.default
  11 + t= ModerateUserRegistration.new
  12 + t.user_id = user.id
  13 + t.name = user.name
  14 + t.author_name = user.name
  15 + t.email = user.email
  16 + t.target= environment
  17 + t.save!
  18 + assert !user.activated?
  19 + t.perform
  20 + assert environment.users.find_by_id(user.id).activated?
  21 + end
  22 +end
vendor/plugins/delayed_job/lib/delayed/command.rb
@@ -97,7 +97,7 @@ module Delayed @@ -97,7 +97,7 @@ module Delayed
97 Dir.chdir(Rails.root) 97 Dir.chdir(Rails.root)
98 98
99 Delayed::Worker.after_fork 99 Delayed::Worker.after_fork
100 - Delayed::Worker.logger ||= Logger.new(File.join(Rails.root, 'log', 'delayed_job.log')) 100 + Delayed::Worker.logger = Logger.new(File.join(Rails.root, 'log', "#{Rails.env}_delayed_job.log"))
101 101
102 worker = Delayed::Worker.new(@options) 102 worker = Delayed::Worker.new(@options)
103 worker.name_prefix = "#{worker_name} " 103 worker.name_prefix = "#{worker_name} "