@@ -35,9 +35,26 @@ defmodule Surface.Components.Link do
35
35
@ moduledoc deprecated: "Use liveview's built-in `<.link>` instead"
36
36
37
37
use Surface.Component
38
- use Surface.Components.Events
39
38
40
- import Surface.Components.Utils
39
+ @ valid_uri_schemes [
40
+ "http:" ,
41
+ "https:" ,
42
+ "ftp:" ,
43
+ "ftps:" ,
44
+ "mailto:" ,
45
+ "news:" ,
46
+ "irc:" ,
47
+ "gopher:" ,
48
+ "nntp:" ,
49
+ "feed:" ,
50
+ "telnet:" ,
51
+ "mms:" ,
52
+ "rtsp:" ,
53
+ "svn:" ,
54
+ "tel:" ,
55
+ "fax:" ,
56
+ "xmpp:"
57
+ ]
41
58
42
59
@ doc "The page to link to"
43
60
prop to , :any , required: true
@@ -61,6 +78,43 @@ defmodule Surface.Components.Link do
61
78
"""
62
79
prop opts , :keyword , default: [ ]
63
80
81
+ @ doc "Triggered when the component receives click"
82
+ prop click , :event
83
+
84
+ @ doc "Triggered when a click event happens outside of the element"
85
+ prop click_away , :event
86
+
87
+ # TODO: Remove this when LV min is >= v0.20.15
88
+ @ doc "Triggered when the component captures click"
89
+ prop capture_click , :event
90
+
91
+ @ doc "Triggered when the component loses focus"
92
+ prop blur , :event
93
+
94
+ @ doc "Triggered when the component receives focus"
95
+ prop focus , :event
96
+
97
+ @ doc "Triggered when the page loses focus"
98
+ prop window_blur , :event
99
+
100
+ @ doc "Triggered when the page receives focus"
101
+ prop window_focus , :event
102
+
103
+ @ doc "Triggered when a key on the keyboard is pressed"
104
+ prop keydown , :event
105
+
106
+ @ doc "Triggered when a key on the keyboard is released"
107
+ prop keyup , :event
108
+
109
+ @ doc "Triggered when a key on the keyboard is pressed (window-level)"
110
+ prop window_keydown , :event
111
+
112
+ @ doc "Triggered when a key on the keyboard is released (window-level)"
113
+ prop window_keyup , :event
114
+
115
+ @ doc "List values that will be sent as part of the payload triggered by an event"
116
+ prop values , :keyword , default: [ ]
117
+
64
118
@ doc """
65
119
The content of the generated `<a>` element. If no content is provided,
66
120
the value of property `label` is used instead.
@@ -101,4 +155,93 @@ defmodule Surface.Components.Link do
101
155
Keyword . merge ( opts , data: data , rel: "nofollow" )
102
156
end
103
157
end
158
+
159
+ def csrf_data ( to , opts ) do
160
+ case Keyword . pop ( opts , :csrf_token , true ) do
161
+ { csrf , opts } when is_binary ( csrf ) ->
162
+ { [ csrf: csrf ] , opts }
163
+
164
+ { true , opts } ->
165
+ { [ csrf: csrf_token ( to ) ] , opts }
166
+
167
+ { false , opts } ->
168
+ { [ ] , opts }
169
+ end
170
+ end
171
+
172
+ defp csrf_token ( to ) do
173
+ { mod , fun , args } = Application . fetch_env! ( :surface , :csrf_token_reader )
174
+ apply ( mod , fun , [ to | args ] )
175
+ end
176
+
177
+ def valid_destination! ( % URI { } = uri , context ) do
178
+ valid_destination! ( URI . to_string ( uri ) , context )
179
+ end
180
+
181
+ def valid_destination! ( { :safe , to } , context ) do
182
+ { :safe , valid_string_destination! ( IO . iodata_to_binary ( to ) , context ) }
183
+ end
184
+
185
+ def valid_destination! ( { other , to } , _context ) when is_atom ( other ) do
186
+ [ Atom . to_string ( other ) , ?: , to ]
187
+ end
188
+
189
+ def valid_destination! ( to , context ) do
190
+ valid_string_destination! ( IO . iodata_to_binary ( to ) , context )
191
+ end
192
+
193
+ for scheme <- @ valid_uri_schemes do
194
+ def valid_string_destination! ( unquote ( scheme ) <> _ = string , _context ) , do: string
195
+ end
196
+
197
+ def valid_string_destination! ( to , context ) do
198
+ if not match? ( "/" <> _ , to ) and String . contains? ( to , ":" ) do
199
+ raise ArgumentError , """
200
+ unsupported scheme given to #{ context } . In case you want to link to an
201
+ unknown or unsafe scheme, such as javascript, use a tuple: {:javascript, rest}
202
+ """
203
+ else
204
+ to
205
+ end
206
+ end
207
+
208
+ def events_to_opts ( assigns ) do
209
+ [
210
+ event_to_opts ( assigns . capture_click , :"phx-capture-click" ) ,
211
+ event_to_opts ( assigns . click , :"phx-click" ) ,
212
+ event_to_opts ( assigns . click_away , :"phx-click-away" ) ,
213
+ event_to_opts ( assigns . window_focus , :"phx-window-focus" ) ,
214
+ event_to_opts ( assigns . window_blur , :"phx-window-blur" ) ,
215
+ event_to_opts ( assigns . focus , :"phx-focus" ) ,
216
+ event_to_opts ( assigns . blur , :"phx-blur" ) ,
217
+ event_to_opts ( assigns . window_keyup , :"phx-window-keyup" ) ,
218
+ event_to_opts ( assigns . window_keydown , :"phx-window-keydown" ) ,
219
+ event_to_opts ( assigns . keyup , :"phx-keyup" ) ,
220
+ event_to_opts ( assigns . keydown , :"phx-keydown" ) ,
221
+ values_to_opts ( assigns . values )
222
+ ]
223
+ |> List . flatten ( )
224
+ end
225
+
226
+ defp values_to_opts ( [ ] ) do
227
+ [ ]
228
+ end
229
+
230
+ defp values_to_opts ( values ) when is_list ( values ) do
231
+ values_to_attrs ( values )
232
+ end
233
+
234
+ defp values_to_opts ( _values ) do
235
+ [ ]
236
+ end
237
+
238
+ defp values_to_attrs ( values ) when is_list ( values ) do
239
+ for { key , value } <- values do
240
+ { :"phx-value-#{ key } " , value }
241
+ end
242
+ end
243
+
244
+ def skip_csrf ( opts ) do
245
+ Keyword . delete ( opts , :csrf_token )
246
+ end
104
247
end
0 commit comments