You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

init.js 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. /**
  2. * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
  3. *
  4. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  5. * @author Jan-Christoph Borchardt <hey@jancborchardt.net>
  6. * @author John Molakvoæ <skjnldsv@protonmail.com>
  7. * @author nacho <nacho@ownyourbits.com>
  8. * @author Vincent Petry <vincent@nextcloud.com>
  9. *
  10. * @license AGPL-3.0-or-later
  11. *
  12. * This program is free software: you can redistribute it and/or modify
  13. * it under the terms of the GNU Affero General Public License as
  14. * published by the Free Software Foundation, either version 3 of the
  15. * License, or (at your option) any later version.
  16. *
  17. * This program is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. * GNU Affero General Public License for more details.
  21. *
  22. * You should have received a copy of the GNU Affero General Public License
  23. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  24. *
  25. */
  26. /* globals Snap */
  27. import _ from 'underscore'
  28. import $ from 'jquery'
  29. import moment from 'moment'
  30. import { initSessionHeartBeat } from './session-heartbeat.js'
  31. import OC from './OC/index.js'
  32. import { setUp as setUpContactsMenu } from './components/ContactsMenu.js'
  33. import { setUp as setUpMainMenu } from './components/MainMenu.js'
  34. import { setUp as setUpUserMenu } from './components/UserMenu.js'
  35. import { interceptRequests } from './utils/xhr-request.js'
  36. // keep in sync with core/css/variables.scss
  37. const breakpointMobileWidth = 1024
  38. const initLiveTimestamps = () => {
  39. // Update live timestamps every 30 seconds
  40. setInterval(() => {
  41. $('.live-relative-timestamp').each(function() {
  42. const timestamp = parseInt($(this).attr('data-timestamp'), 10)
  43. $(this).text(moment(timestamp).fromNow())
  44. })
  45. }, 30 * 1000)
  46. }
  47. /**
  48. * Moment doesn't have aliases for every locale and doesn't parse some locale IDs correctly so we need to alias them
  49. */
  50. const localeAliases = {
  51. zh: 'zh-cn',
  52. zh_Hans: 'zh-cn',
  53. zh_Hans_CN: 'zh-cn',
  54. zh_Hans_HK: 'zh-cn',
  55. zh_Hans_MO: 'zh-cn',
  56. zh_Hans_SG: 'zh-cn',
  57. zh_Hant: 'zh-hk',
  58. zh_Hant_HK: 'zh-hk',
  59. zh_Hant_MO: 'zh-mo',
  60. zh_Hant_TW: 'zh-tw',
  61. }
  62. let locale = OC.getLocale()
  63. if (Object.prototype.hasOwnProperty.call(localeAliases, locale)) {
  64. locale = localeAliases[locale]
  65. }
  66. /**
  67. * Set users locale to moment.js as soon as possible
  68. */
  69. moment.locale(locale)
  70. /**
  71. * Initializes core
  72. */
  73. export const initCore = () => {
  74. interceptRequests()
  75. $(window).on('unload.main', () => { OC._unloadCalled = true })
  76. $(window).on('beforeunload.main', () => {
  77. // super-trick thanks to http://stackoverflow.com/a/4651049
  78. // in case another handler displays a confirmation dialog (ex: navigating away
  79. // during an upload), there are two possible outcomes: user clicked "ok" or
  80. // "cancel"
  81. // first timeout handler is called after unload dialog is closed
  82. setTimeout(() => {
  83. OC._userIsNavigatingAway = true
  84. // second timeout event is only called if user cancelled (Chrome),
  85. // but in other browsers it might still be triggered, so need to
  86. // set a higher delay...
  87. setTimeout(() => {
  88. if (!OC._unloadCalled) {
  89. OC._userIsNavigatingAway = false
  90. }
  91. }, 10000)
  92. }, 1)
  93. })
  94. $(document).on('ajaxError.main', function(event, request, settings) {
  95. if (settings && settings.allowAuthErrors) {
  96. return
  97. }
  98. OC._processAjaxError(request)
  99. })
  100. initSessionHeartBeat()
  101. OC.registerMenu($('#expand'), $('#expanddiv'), false, true)
  102. // toggle for menus
  103. $(document).on('mouseup.closemenus', event => {
  104. const $el = $(event.target)
  105. if ($el.closest('.menu').length || $el.closest('.menutoggle').length) {
  106. // don't close when clicking on the menu directly or a menu toggle
  107. return false
  108. }
  109. OC.hideMenus()
  110. })
  111. setUpMainMenu()
  112. setUpUserMenu()
  113. setUpContactsMenu()
  114. // just add snapper for logged in users
  115. // and if the app doesn't handle the nav slider itself
  116. if ($('#app-navigation').length && !$('html').hasClass('lte9')
  117. && !$('#app-content').hasClass('no-snapper')) {
  118. // App sidebar on mobile
  119. const snapper = new Snap({
  120. element: document.getElementById('app-content'),
  121. disable: 'right',
  122. maxPosition: 300, // $navigation-width
  123. minDragDistance: 100,
  124. })
  125. $('#app-content').prepend('<div id="app-navigation-toggle" class="icon-menu" style="display:none" tabindex="0"></div>')
  126. // keep track whether snapper is currently animating, and
  127. // prevent to call open or close while that is the case
  128. // to avoid duplicating events (snap.js doesn't check this)
  129. let animating = false
  130. snapper.on('animating', () => {
  131. // we need this because the trigger button
  132. // is also implicitly wired to close by snapper
  133. animating = true
  134. })
  135. snapper.on('animated', () => {
  136. animating = false
  137. })
  138. snapper.on('start', () => {
  139. // we need this because dragging triggers that
  140. animating = true
  141. })
  142. snapper.on('end', () => {
  143. // we need this because dragging stop triggers that
  144. animating = false
  145. })
  146. snapper.on('open', () => {
  147. $appNavigation.attr('aria-hidden', 'false')
  148. })
  149. snapper.on('close', () => {
  150. $appNavigation.attr('aria-hidden', 'true')
  151. })
  152. // These are necessary because calling open or close
  153. // on snapper during an animation makes it trigger an
  154. // unfinishable animation, which itself will continue
  155. // triggering animating events and cause high CPU load,
  156. //
  157. // Ref https://github.com/jakiestfu/Snap.js/issues/216
  158. const oldSnapperOpen = snapper.open
  159. const oldSnapperClose = snapper.close
  160. const _snapperOpen = () => {
  161. if (animating || snapper.state().state !== 'closed') {
  162. return
  163. }
  164. oldSnapperOpen('left')
  165. }
  166. const _snapperClose = () => {
  167. if (animating || snapper.state().state === 'closed') {
  168. return
  169. }
  170. oldSnapperClose()
  171. }
  172. // Needs to be deferred to properly catch in-between
  173. // events that snap.js is triggering after dragging.
  174. //
  175. // Skipped when running unit tests as we are not testing
  176. // the snap.js workarounds...
  177. if (!window.TESTING) {
  178. snapper.open = () => {
  179. _.defer(_snapperOpen)
  180. }
  181. snapper.close = () => {
  182. _.defer(_snapperClose)
  183. }
  184. }
  185. $('#app-navigation-toggle').click((e) => {
  186. // close is implicit in the button by snap.js
  187. if (snapper.state().state !== 'left') {
  188. snapper.open()
  189. }
  190. })
  191. $('#app-navigation-toggle').keypress(e => {
  192. if (snapper.state().state === 'left') {
  193. snapper.close()
  194. } else {
  195. snapper.open()
  196. }
  197. })
  198. // close sidebar when switching navigation entry
  199. const $appNavigation = $('#app-navigation')
  200. $appNavigation.attr('aria-hidden', 'true')
  201. $appNavigation.delegate('a, :button', 'click', event => {
  202. const $target = $(event.target)
  203. // don't hide navigation when changing settings or adding things
  204. if ($target.is('.app-navigation-noclose')
  205. || $target.closest('.app-navigation-noclose').length) {
  206. return
  207. }
  208. if ($target.is('.app-navigation-entry-utils-menu-button')
  209. || $target.closest('.app-navigation-entry-utils-menu-button').length) {
  210. return
  211. }
  212. if ($target.is('.add-new')
  213. || $target.closest('.add-new').length) {
  214. return
  215. }
  216. if ($target.is('#app-settings')
  217. || $target.closest('#app-settings').length) {
  218. return
  219. }
  220. snapper.close()
  221. })
  222. let navigationBarSlideGestureEnabled = false
  223. let navigationBarSlideGestureAllowed = true
  224. let navigationBarSlideGestureEnablePending = false
  225. OC.allowNavigationBarSlideGesture = () => {
  226. navigationBarSlideGestureAllowed = true
  227. if (navigationBarSlideGestureEnablePending) {
  228. snapper.enable()
  229. navigationBarSlideGestureEnabled = true
  230. navigationBarSlideGestureEnablePending = false
  231. }
  232. }
  233. OC.disallowNavigationBarSlideGesture = () => {
  234. navigationBarSlideGestureAllowed = false
  235. if (navigationBarSlideGestureEnabled) {
  236. const endCurrentDrag = true
  237. snapper.disable(endCurrentDrag)
  238. navigationBarSlideGestureEnabled = false
  239. navigationBarSlideGestureEnablePending = true
  240. }
  241. }
  242. const toggleSnapperOnSize = () => {
  243. if ($(window).width() > breakpointMobileWidth) {
  244. $appNavigation.attr('aria-hidden', 'false')
  245. snapper.close()
  246. snapper.disable()
  247. navigationBarSlideGestureEnabled = false
  248. navigationBarSlideGestureEnablePending = false
  249. } else if (navigationBarSlideGestureAllowed) {
  250. snapper.enable()
  251. navigationBarSlideGestureEnabled = true
  252. navigationBarSlideGestureEnablePending = false
  253. } else {
  254. navigationBarSlideGestureEnablePending = true
  255. }
  256. }
  257. $(window).resize(_.debounce(toggleSnapperOnSize, 250))
  258. // initial call
  259. toggleSnapperOnSize()
  260. }
  261. initLiveTimestamps()
  262. }