forked from ppy/osu-web
/
osu_common.coffee
276 lines (190 loc) · 7.24 KB
/
osu_common.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
###
# Copyright 2015-2017 ppy Pty. Ltd.
#
# This file is part of osu!web. osu!web is distributed with the hope of
# attracting more community contributions to the core ecosystem of osu!.
#
# osu!web is free software: you can redistribute it and/or modify
# it under the terms of the Affero GNU General Public License version 3
# as published by the Free Software Foundation.
#
# osu!web is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with osu!web. If not, see <http://www.gnu.org/licenses/>.
###
@osu =
isIos: /iPad|iPhone|iPod/.test(navigator.platform)
urlRegex: /(https?:\/\/(?:(?:[a-z0-9]\.|[a-z0-9][a-z0-9-]*[a-z0-9]\.)*[a-z][a-z0-9-]*[a-z0-9](?::\d+)?)(?:(?:(?:\/+(?:[a-z0-9$_\.\+!\*',;:@&=-]|%[0-9a-f]{2})*)*(?:\?(?:[a-z0-9$_\.\+!\*',;:@&=-]|%[0-9a-f]{2})*)?)?(?:#(?:[a-z0-9$_\.\+!\*',;:@&=/?-]|%[0-9a-f]{2})*)?)?)/ig
bottomPage: ->
osu.bottomPageDistance() == 0
bottomPageDistance: ->
body = document.documentElement ? document.body.parent ? document.body
(body.scrollHeight - body.scrollTop) - body.clientHeight
currentUserIsFriendsWith: (user_id) ->
_.find currentUser.friends, target_id: user_id
executeAction: (element) =>
if !element?
osu.reloadPage()
return
if element.dataset.isFileupload == '1'
$(element).trigger 'fileuploadRetry'
else if element.submit
# plain javascript here doesn't trigger submit events
# which means jquery-ujs handler won't be triggered
# reference: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit
$(element).submit()
else if element.click
# inversely, using jquery here won't actually click the thing
# reference: https://github.com/jquery/jquery/blob/f5aa89af7029ae6b9203c2d3e551a8554a0b4b89/src/event.js#L586
element.click()
setHash: (newHash) ->
newUrl = location.href.replace /#.*/, ''
newUrl += newHash
return if newUrl == location.href
history.replaceState history.state, null, newUrl
ajaxError: (xhr) ->
osu.popup osu.xhrErrorMessage(xhr), 'danger'
emitAjaxError: (element = document.body) =>
(xhr, status, error) =>
$(element).trigger 'ajax:error', [xhr, status, error]
fileuploadFailCallback: ($elFunction) =>
(_e, data) =>
$el = $elFunction()
$el[0].dataset.isFileupload ?= '1'
$el
.off 'fileuploadRetry'
.one 'fileuploadRetry', =>
data.submit()
osu.emitAjaxError($el[0]) data.jqXHR
pageChange: ->
Timeout.set 0, osu.pageChangeImmediate
pageChangeImmediate: ->
$(document).trigger('osu:page:change')
parseJson: (id) ->
JSON.parse document.getElementById(id)?.text ? null
isInputElement: (el) ->
el.tagName in ['INPUT', 'SELECT', 'TEXTAREA'] || el.isContentEditable
isClickable: (el) ->
if osu.isInputElement(el) || el.tagName in ['A', 'BUTTON']
true
else if el.parentNode
osu.isClickable el.parentNode
else
false
isDesktop: ->
# sync with boostrap-variables @screen-sm-min
window.matchMedia('(min-width: 900px)').matches
isMobile: -> !osu.isDesktop()
# mobile safari zooms in on focus of input boxes with font-size < 16px, this works around that
focus: (el) =>
el = $(el)[0] # so we can handle both jquery'd and normal dom nodes
return el.focus() if !osu.isIos
prevSize = el.style.fontSize
el.style.fontSize = '16px'
el.focus()
el.style.fontSize = prevSize
src2x: (mainUrl) ->
src: mainUrl
srcSet: "#{mainUrl} 1x, #{mainUrl.replace(/(\.[^.]+)$/, '@2x$1')} 2x"
link: (url, text, options = {}) ->
el = document.createElement('a')
el.setAttribute 'href', url
el.setAttribute 'data-remote', true if options.isRemote
el.className = options.classNames.join(' ') if options.classNames
el.textContent = text
if options.props
_.each options.props, (val, prop) ->
el.setAttribute prop, val
el.outerHTML
linkify: (text) ->
return text.replace(osu.urlRegex, '<a href="$1" rel="nofollow">$1</a>')
timeago: (time) ->
el = document.createElement('time')
el.classList.add 'timeago'
el.setAttribute 'datetime', time
el.textContent = time
el.outerHTML
formatBytes: (bytes, decimals=2) ->
suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
k = 1000
return "#{bytes} B" if (bytes < k)
i = Math.floor(Math.log(bytes) / Math.log(k))
return "#{(bytes / Math.pow(k, i)).toFixed(decimals)} #{suffixes[i]}"
reloadPage: (keepScroll = true) ->
$(document).off '.ujsHideLoadingOverlay'
Turbolinks.clearCache()
url =
if !_.isEmpty window.reloadUrl
window.reloadUrl
else
location.href
window.reloadUrl = null
osu.navigate url, keepScroll, action: 'replace'
navigate: (url, keepScroll, {action = 'advance'} = {}) ->
osu.keepScrollOnLoad() if keepScroll
Turbolinks.visit url, action: action
keepScrollOnLoad: ->
position = [
window.pageXOffset
window.pageYOffset
]
$(document).one 'turbolinks:load', ->
window.scrollTo position[0], position[1]
popup: (message, type = 'info') ->
$popup = $('#popup-container')
$alert = $('.popup-clone').clone()
closeAlert = -> $alert.click()
# handle types of alerts by changing the colour
$alert
.addClass "alert-#{type} popup-active"
.removeClass 'popup-clone'
$alert.find('.popup-text').html message
# warning/danger messages stay forever until clicked
if type in ['warning', 'danger']
$('#overlay')
.off('click.close-alert')
.one('click.close-alert', closeAlert)
.fadeIn()
else
Timeout.set 5000, closeAlert
document.activeElement.blur?()
$alert.appendTo($popup).fadeIn()
popupShowing: ->
$('#overlay').is(':visible')
trans: (key, replacements) ->
Lang.get key, replacements
transArray: (array, key = 'common.array_and') ->
switch array.length
when 0
''
when 1
"#{array[0]}"
when 2
array.join(osu.trans("#{key}.two_words_connector"))
else
"#{array[...-1].join(osu.trans("#{key}.words_connector"))}#{osu.trans("#{key}.last_word_connector")}#{_.last(array)}"
transChoice: (key, count, replacements) ->
Lang.choice key, count, replacements
uuid: ->
Turbolinks.uuid() # no point rolling our own
updateQueryString: (url, params) ->
urlObj = new URL(url ? window.location.href, document.location.origin)
for own key, value of params
urlObj.searchParams.set(key, value)
return urlObj.href
xhrErrorMessage: (xhr) ->
validationMessage = xhr?.responseJSON?.validation_error
if validationMessage?
allErrors = []
for own _field, errors of validationMessage
allErrors = allErrors.concat(errors)
message = "#{allErrors.join(', ')}."
message ?= xhr?.responseJSON?.error
if !message? || message == ''
errorKey = "errors.codes.http-#{xhr?.status}"
message = osu.trans errorKey
message = osu.trans 'errors.unknown' if message == errorKey
message